model: add dots3-note (#27060)

* text: conversion

* init impl

* address review comments

* fix rope

* move to a new llama_kv_cache_dsa_iswa
This commit is contained in:
Xuan-Son Nguyen
2026-08-21 19:52:34 +02:00
committed by GitHub
parent 873e5d8e39
commit 5a32f7b66e
20 changed files with 1412 additions and 9 deletions
+3
View File
@@ -64,6 +64,9 @@ TEXT_MODEL_MAP: dict[str, str] = {
"DistilBertForSequenceClassification": "bert", "DistilBertForSequenceClassification": "bert",
"DistilBertModel": "bert", "DistilBertModel": "bert",
"Dots1ForCausalLM": "dots1", "Dots1ForCausalLM": "dots1",
"Dots3NoteForCausalLM": "dots3",
"Dots3NoteForConditionalGeneration": "dots3",
"Dots3NoteTextForCausalLM": "dots3",
"DotsOCRForCausalLM": "qwen", "DotsOCRForCausalLM": "qwen",
"DreamModel": "dream", "DreamModel": "dream",
"Ernie4_5ForCausalLM": "ernie", "Ernie4_5ForCausalLM": "ernie",
+195
View File
@@ -0,0 +1,195 @@
from __future__ import annotations
import math
import re
from typing import TYPE_CHECKING, Callable, Iterable
if TYPE_CHECKING:
from torch import Tensor
from .base import ModelBase, gguf
from .deepseek import DeepseekV2Model
@ModelBase.register("Dots3NoteForCausalLM", "Dots3NoteForConditionalGeneration", "Dots3NoteTextForCausalLM")
class Dots3NoteModel(DeepseekV2Model):
model_arch = gguf.MODEL_ARCH.DOTS3NOTE
skip_mtp = False
supports_mtp_export = True
# trunk layer count, stashed before indexing for filter_tensors (mirrors DeepseekV32Model)
_n_main_layers: int | None = None
def index_tensors(self, remote_hf_model_id: str | None = None):
type(self)._n_main_layers = self.hparams["num_hidden_layers"]
return super().index_tensors(remote_hf_model_id=remote_hf_model_id)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
hparams = self.hparams
# config file doesn't specify MTP block, detect it from model weight
self.n_nextn = 1 if "model.mtp.embed_tokens.weight" in self.model_tensors else 0
if self.n_nextn:
self.block_count += self.n_nextn
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count)
self.layer_types = hparams["layer_types"]
if len(self.layer_types) < hparams["num_hidden_layers"]:
raise ValueError("layer_types is shorter than num_hidden_layers")
if hparams.get("use_dsa", True) is not True:
raise ValueError("dots3-note conversion requires use_dsa=true")
if hparams.get("normalization", "RMSNorm") != "RMSNorm" or hparams.get("final_norm", "RMSNorm") != "RMSNorm":
raise ValueError("dots3-note conversion only supports RMSNorm")
if hparams.get("k_rope_only_layernorm", True) is not True:
raise ValueError("dots3-note conversion requires k_rope_only_layernorm=true")
if hparams.get("topk_method", "noaux_tc") != "noaux_tc" or hparams.get("scoring_func") != "sigmoid":
raise ValueError("dots3-note conversion only supports noaux_tc/sigmoid expert gating")
if hparams.get("n_group", 1) != 1 or hparams.get("topk_group", 1) != 1:
raise ValueError("dots3-note conversion does not support grouped expert routing")
if hparams.get("use_dynamic_rsf", False) or hparams.get("moe_gating_fp32", False):
raise ValueError("dots3-note conversion does not support use_dynamic_rsf/moe_gating_fp32")
for key in ("attention_gate_type", "swa_attention_gate_type"):
if hparams.get(key, "headwise") != "headwise":
raise ValueError(f"dots3-note conversion only supports headwise attention gate, got {key}={hparams.get(key)!r}")
if hparams["swa_qk_nope_head_dim"] + hparams["swa_qk_rope_head_dim"] != hparams.get("swa_head_dim", 256):
raise ValueError("swa_head_dim must equal swa_qk_nope_head_dim + swa_qk_rope_head_dim")
if hparams["swa_qk_rope_head_dim"] != hparams["qk_rope_head_dim"]:
# both layer kinds share a single rope_dimension_count
raise ValueError("swa_qk_rope_head_dim must match qk_rope_head_dim")
self.apply_lora_rescale = hparams.get("apply_mla_qkv_lora_rescale", False)
def _is_swa_layer(self, bid: int) -> bool:
if bid >= self.hparams["num_hidden_layers"]:
# note: the NextN/MTP block uses the sliding-attention MLA
return True
return self.layer_types[bid] == "sliding_attention"
def set_vocab(self):
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(self.dir_model)
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
tokens, toktypes, tokpre = self.get_vocab_base()
self.gguf_writer.add_tokenizer_model("gpt2")
self.gguf_writer.add_tokenizer_pre(tokpre)
self.gguf_writer.add_token_list(tokens)
self.gguf_writer.add_token_types(toktypes)
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|endofassistant|>"]) # ty: ignore[unresolved-attribute]
special_vocab.add_to_gguf(self.gguf_writer)
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
if (titem := super().filter_tensors(item)) is None:
return None
name, gen = titem
if name.startswith(("vision_encoder.", "audio_encoder.")):
return None
assert cls._n_main_layers is not None
is_mtp = name.startswith("model.mtp.") or \
((m := re.match(r"model\.layers\.(\d+)\.", name)) is not None and int(m.group(1)) >= cls._n_main_layers)
# --no-mtp: drop the NextN/MTP block; --mtp: keep only that block plus the shared embeddings/norm/lm_head
if is_mtp and cls.no_mtp:
return None
if cls.mtp_only and not is_mtp and name not in (
"model.embed_tokens.weight", "model.norm.weight", "lm_head.weight",
):
return None
return name, gen
def set_gguf_parameters(self):
hparams = self.hparams
# head_count is a per-layer array because the two layer kinds have different head counts
n_layer = hparams["num_hidden_layers"]
hparams["num_attention_heads"] = [
hparams["swa_num_attention_heads"] if self._is_swa_layer(il) else hparams["num_attention_heads"]
for il in range(self.block_count)
]
# prevent the base class from emitting key/value_length from the unused head_dim
hparams.pop("head_dim", None)
super().set_gguf_parameters()
# MLA geometry of the sliding-window layers (rope.freq_base_swa is emitted by the base class)
swa_kv_lora_rank = hparams["swa_kv_lora_rank"]
self.gguf_writer.add_sliding_window(hparams["sliding_window_size"])
self.gguf_writer.add_sliding_window_pattern([self._is_swa_layer(il) for il in range(n_layer)])
self.gguf_writer.add_kv_lora_rank_swa(swa_kv_lora_rank)
self.gguf_writer.add_key_length_swa(swa_kv_lora_rank + hparams["swa_qk_rope_head_dim"])
self.gguf_writer.add_value_length_swa(swa_kv_lora_rank)
self.gguf_writer.add_key_length_mla_swa(hparams["swa_qk_nope_head_dim"] + hparams["swa_qk_rope_head_dim"])
self.gguf_writer.add_value_length_mla_swa(hparams["swa_v_head_dim"])
if hparams["swa_q_lora_rank"] != hparams["q_lora_rank"]:
raise ValueError("dots3-note conversion assumes a shared q_lora_rank for both layer kinds")
if self.n_nextn:
self.gguf_writer.add_nextn_predict_layers(self.n_nextn)
# DSA indexer (full-attention layers only)
self.gguf_writer.add_indexer_head_count(hparams["index_n_heads"])
self.gguf_writer.add_indexer_key_length(hparams["index_head_dim"])
self.gguf_writer.add_indexer_top_k(hparams["index_topk"])
self.gguf_writer.add_indexer_types([not self._is_swa_layer(il) for il in range(n_layer)])
def prepare_metadata(self, vocab_only: bool):
from_dir = self.fname_out.is_dir()
super().prepare_metadata(vocab_only=vocab_only)
if not self.mtp_only or not from_dir:
return
output_type: str = self.ftype.name.partition("_")[2]
fname_default: str = gguf.naming_convention(
self.metadata.name, self.metadata.basename, self.metadata.finetune,
self.metadata.version, size_label=None, output_type=output_type, model_type=None)
self.fname_out = self.fname_out.parent / f"mtp-{fname_default}.gguf"
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
# move the MTP token embedding into the NextN block so the standard nextn mapping picks it up
if name == "model.mtp.embed_tokens.weight":
name = f"model.layers.{self.hparams['num_hidden_layers']}.embed_tokens.weight"
bid = self.hparams["num_hidden_layers"]
# fold the activation rescale sqrt(n_embd/lora_rank) into the preceding RMSNorm weight
# this also covers the indexer wq_b, which reads the same rescaled q_lora activation
if self.apply_lora_rescale and bid is not None:
if name.endswith("q_a_layernorm.weight"):
data_torch = data_torch * math.sqrt(self.hparams["hidden_size"] / self.hparams["q_lora_rank"])
elif name.endswith("kv_a_layernorm.weight"):
rank = self.hparams["swa_kv_lora_rank"] if self._is_swa_layer(bid) else self.hparams["kv_lora_rank"]
data_torch = data_torch * math.sqrt(self.hparams["hidden_size"] / rank)
# MLA absorption: split kv_b_proj into k_b (transposed) and v_b, per-layer-kind geometry
if name.endswith("kv_b_proj.weight"):
assert bid is not None
if self._is_swa_layer(bid):
n_head = self.hparams["swa_num_attention_heads"]
qk_nope_head_dim = self.hparams["swa_qk_nope_head_dim"]
v_head_dim = self.hparams["swa_v_head_dim"]
else:
n_head = self.hparams["num_attention_heads"]
qk_nope_head_dim = self.hparams["qk_nope_head_dim"]
v_head_dim = self.hparams["v_head_dim"]
if isinstance(n_head, list): # set_gguf_parameters turns this into a per-layer array
n_head = n_head[bid]
assert data_torch.shape[0] == n_head * (qk_nope_head_dim + v_head_dim)
kv_b = data_torch.view(n_head, qk_nope_head_dim + v_head_dim, data_torch.shape[-1])
k_b, v_b = kv_b.split([qk_nope_head_dim, v_head_dim], dim=1)
k_b = k_b.transpose(1, 2)
yield from ModelBase.modify_tensors(self, k_b, name.replace("kv_b_proj", "k_b_proj"), bid)
yield from ModelBase.modify_tensors(self, v_b, name.replace("kv_b_proj", "v_b_proj"), bid)
return
yield from super().modify_tensors(data_torch, name, bid)
+43
View File
@@ -205,6 +205,9 @@ class Keys:
VALUE_LENGTH_MLA = "{arch}.attention.value_length_mla" VALUE_LENGTH_MLA = "{arch}.attention.value_length_mla"
KEY_LENGTH_SWA = "{arch}.attention.key_length_swa" KEY_LENGTH_SWA = "{arch}.attention.key_length_swa"
VALUE_LENGTH_SWA = "{arch}.attention.value_length_swa" VALUE_LENGTH_SWA = "{arch}.attention.value_length_swa"
KEY_LENGTH_MLA_SWA = "{arch}.attention.key_length_mla_swa"
VALUE_LENGTH_MLA_SWA = "{arch}.attention.value_length_mla_swa"
KV_LORA_RANK_SWA = "{arch}.attention.kv_lora_rank_swa"
SHARED_KV_LAYERS = "{arch}.attention.shared_kv_layers" SHARED_KV_LAYERS = "{arch}.attention.shared_kv_layers"
SLIDING_WINDOW_PATTERN = "{arch}.attention.sliding_window_pattern" SLIDING_WINDOW_PATTERN = "{arch}.attention.sliding_window_pattern"
TEMPERATURE_SCALE = "{arch}.attention.temperature_scale" TEMPERATURE_SCALE = "{arch}.attention.temperature_scale"
@@ -558,6 +561,7 @@ class MODEL_ARCH(IntEnum):
BAILINGMOE2 = auto() BAILINGMOE2 = auto()
BAILINGMOE3 = auto() BAILINGMOE3 = auto()
DOTS1 = auto() DOTS1 = auto()
DOTS3NOTE = auto()
ARCEE = auto() ARCEE = auto()
AFMOE = auto() AFMOE = auto()
LAGUNA = auto() LAGUNA = auto()
@@ -1275,6 +1279,7 @@ MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = {
MODEL_ARCH.BAILINGMOE2: "bailingmoe2", MODEL_ARCH.BAILINGMOE2: "bailingmoe2",
MODEL_ARCH.BAILINGMOE3: "bailingmoe3", MODEL_ARCH.BAILINGMOE3: "bailingmoe3",
MODEL_ARCH.DOTS1: "dots1", MODEL_ARCH.DOTS1: "dots1",
MODEL_ARCH.DOTS3NOTE: "dots3note",
MODEL_ARCH.ARCEE: "arcee", MODEL_ARCH.ARCEE: "arcee",
MODEL_ARCH.AFMOE: "afmoe", MODEL_ARCH.AFMOE: "afmoe",
MODEL_ARCH.LAGUNA: "laguna", MODEL_ARCH.LAGUNA: "laguna",
@@ -4334,6 +4339,44 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
MODEL_TENSOR.FFN_UP_EXP, MODEL_TENSOR.FFN_UP_EXP,
MODEL_TENSOR.FFN_UP_SHEXP, MODEL_TENSOR.FFN_UP_SHEXP,
], ],
MODEL_ARCH.DOTS3NOTE: [
MODEL_TENSOR.TOKEN_EMBD,
MODEL_TENSOR.OUTPUT_NORM,
MODEL_TENSOR.OUTPUT,
MODEL_TENSOR.ATTN_NORM,
MODEL_TENSOR.ATTN_Q_A,
MODEL_TENSOR.ATTN_Q_B,
MODEL_TENSOR.ATTN_KV_A_MQA,
MODEL_TENSOR.ATTN_K_B,
MODEL_TENSOR.ATTN_V_B,
MODEL_TENSOR.ATTN_Q_A_NORM,
MODEL_TENSOR.ATTN_KV_A_NORM,
MODEL_TENSOR.ATTN_K_NORM,
MODEL_TENSOR.ATTN_GATE,
MODEL_TENSOR.ATTN_OUT,
MODEL_TENSOR.FFN_NORM,
MODEL_TENSOR.FFN_GATE,
MODEL_TENSOR.FFN_DOWN,
MODEL_TENSOR.FFN_UP,
MODEL_TENSOR.FFN_GATE_INP,
MODEL_TENSOR.FFN_EXP_PROBS_B,
MODEL_TENSOR.FFN_GATE_EXP,
MODEL_TENSOR.FFN_DOWN_EXP,
MODEL_TENSOR.FFN_UP_EXP,
MODEL_TENSOR.FFN_GATE_SHEXP,
MODEL_TENSOR.FFN_DOWN_SHEXP,
MODEL_TENSOR.FFN_UP_SHEXP,
MODEL_TENSOR.INDEXER_K_NORM,
MODEL_TENSOR.INDEXER_PROJ,
MODEL_TENSOR.INDEXER_ATTN_K,
MODEL_TENSOR.INDEXER_ATTN_Q_B,
# NextN/MTP tensors - preserved but unused
MODEL_TENSOR.NEXTN_EH_PROJ,
MODEL_TENSOR.NEXTN_EMBED_TOKENS,
MODEL_TENSOR.NEXTN_ENORM,
MODEL_TENSOR.NEXTN_HNORM,
MODEL_TENSOR.NEXTN_SHARED_HEAD_NORM,
],
MODEL_ARCH.ARCEE: [ MODEL_ARCH.ARCEE: [
MODEL_TENSOR.TOKEN_EMBD, MODEL_TENSOR.TOKEN_EMBD,
MODEL_TENSOR.OUTPUT_NORM, MODEL_TENSOR.OUTPUT_NORM,
+9
View File
@@ -785,6 +785,15 @@ class GGUFWriter:
def add_key_length_swa(self, length: int) -> None: def add_key_length_swa(self, length: int) -> None:
self.add_uint32(Keys.Attention.KEY_LENGTH_SWA.format(arch=self.arch), length) self.add_uint32(Keys.Attention.KEY_LENGTH_SWA.format(arch=self.arch), length)
def add_key_length_mla_swa(self, length: int) -> None:
self.add_uint32(Keys.Attention.KEY_LENGTH_MLA_SWA.format(arch=self.arch), length)
def add_value_length_mla_swa(self, length: int) -> None:
self.add_uint32(Keys.Attention.VALUE_LENGTH_MLA_SWA.format(arch=self.arch), length)
def add_kv_lora_rank_swa(self, length: int) -> None:
self.add_uint32(Keys.Attention.KV_LORA_RANK_SWA.format(arch=self.arch), length)
def add_value_length_swa(self, length: int) -> None: def add_value_length_swa(self, length: int) -> None:
self.add_uint32(Keys.Attention.VALUE_LENGTH_SWA.format(arch=self.arch), length) self.add_uint32(Keys.Attention.VALUE_LENGTH_SWA.format(arch=self.arch), length)
+1
View File
@@ -723,6 +723,7 @@ class TensorNameMap:
"model.layers.layers.{bid}.mixer.k", # plamo2 "model.layers.layers.{bid}.mixer.k", # plamo2
"model.layers.layers.{bid}.mixer.k_norm", # plamo3 "model.layers.layers.{bid}.mixer.k_norm", # plamo3
"layers.{bid}.self_attn.k_norm", # qwen3-embedding "layers.{bid}.self_attn.k_norm", # qwen3-embedding
"model.layers.{bid}.self_attn.k_rope_only_layernorm", # dots3note
"model.layers.{bid}.attention.key_layernorm", # apertus "model.layers.{bid}.attention.key_layernorm", # apertus
), ),
+1
View File
@@ -25,6 +25,7 @@ add_library(llama
llama-kv-cache.cpp llama-kv-cache.cpp
llama-kv-cache-iswa.cpp llama-kv-cache-iswa.cpp
llama-kv-cache-dsa.cpp llama-kv-cache-dsa.cpp
llama-kv-cache-dsa-iswa.cpp
llama-kv-cache-msa.cpp llama-kv-cache-msa.cpp
llama-kv-cache-dsv4.cpp llama-kv-cache-dsv4.cpp
llama-memory.cpp llama-memory.cpp
+5
View File
@@ -110,6 +110,7 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {
{ LLM_ARCH_BAILINGMOE2, "bailingmoe2" }, { LLM_ARCH_BAILINGMOE2, "bailingmoe2" },
{ LLM_ARCH_BAILINGMOE3, "bailingmoe3" }, { LLM_ARCH_BAILINGMOE3, "bailingmoe3" },
{ LLM_ARCH_DOTS1, "dots1" }, { LLM_ARCH_DOTS1, "dots1" },
{ LLM_ARCH_DOTS3NOTE, "dots3note" },
{ LLM_ARCH_ARCEE, "arcee" }, { LLM_ARCH_ARCEE, "arcee" },
{ LLM_ARCH_AFMOE, "afmoe" }, { LLM_ARCH_AFMOE, "afmoe" },
{ LLM_ARCH_LAGUNA, "laguna" }, { LLM_ARCH_LAGUNA, "laguna" },
@@ -273,6 +274,9 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
{ LLM_KV_ATTENTION_VALUE_LENGTH_MLA, "%s.attention.value_length_mla" }, { LLM_KV_ATTENTION_VALUE_LENGTH_MLA, "%s.attention.value_length_mla" },
{ LLM_KV_ATTENTION_KEY_LENGTH_SWA, "%s.attention.key_length_swa" }, { LLM_KV_ATTENTION_KEY_LENGTH_SWA, "%s.attention.key_length_swa" },
{ LLM_KV_ATTENTION_VALUE_LENGTH_SWA, "%s.attention.value_length_swa" }, { LLM_KV_ATTENTION_VALUE_LENGTH_SWA, "%s.attention.value_length_swa" },
{ LLM_KV_ATTENTION_KEY_LENGTH_MLA_SWA, "%s.attention.key_length_mla_swa" },
{ LLM_KV_ATTENTION_VALUE_LENGTH_MLA_SWA, "%s.attention.value_length_mla_swa" },
{ LLM_KV_ATTENTION_KV_LORA_RANK_SWA, "%s.attention.kv_lora_rank_swa" },
{ LLM_KV_ATTENTION_INDEXER_HEAD_COUNT, "%s.attention.indexer.head_count" }, { LLM_KV_ATTENTION_INDEXER_HEAD_COUNT, "%s.attention.indexer.head_count" },
{ LLM_KV_ATTENTION_INDEXER_KEY_LENGTH, "%s.attention.indexer.key_length" }, { LLM_KV_ATTENTION_INDEXER_KEY_LENGTH, "%s.attention.indexer.key_length" },
{ LLM_KV_ATTENTION_INDEXER_TOP_K, "%s.attention.indexer.top_k" }, { LLM_KV_ATTENTION_INDEXER_TOP_K, "%s.attention.indexer.top_k" },
@@ -1056,6 +1060,7 @@ bool llm_arch_supports_sm_tensor(const llm_arch & arch) {
case LLM_ARCH_DEEPSEEK2: case LLM_ARCH_DEEPSEEK2:
case LLM_ARCH_DEEPSEEK32: case LLM_ARCH_DEEPSEEK32:
case LLM_ARCH_DEEPSEEK4: case LLM_ARCH_DEEPSEEK4:
case LLM_ARCH_DOTS3NOTE:
case LLM_ARCH_GLM_DSA: case LLM_ARCH_GLM_DSA:
case LLM_ARCH_BITNET: case LLM_ARCH_BITNET:
case LLM_ARCH_T5: case LLM_ARCH_T5:
+4
View File
@@ -115,6 +115,7 @@ enum llm_arch {
LLM_ARCH_BAILINGMOE2, LLM_ARCH_BAILINGMOE2,
LLM_ARCH_BAILINGMOE3, LLM_ARCH_BAILINGMOE3,
LLM_ARCH_DOTS1, LLM_ARCH_DOTS1,
LLM_ARCH_DOTS3NOTE,
LLM_ARCH_ARCEE, LLM_ARCH_ARCEE,
LLM_ARCH_AFMOE, LLM_ARCH_AFMOE,
LLM_ARCH_LAGUNA, LLM_ARCH_LAGUNA,
@@ -278,6 +279,9 @@ enum llm_kv {
LLM_KV_ATTENTION_VALUE_LENGTH_MLA, LLM_KV_ATTENTION_VALUE_LENGTH_MLA,
LLM_KV_ATTENTION_KEY_LENGTH_SWA, LLM_KV_ATTENTION_KEY_LENGTH_SWA,
LLM_KV_ATTENTION_VALUE_LENGTH_SWA, LLM_KV_ATTENTION_VALUE_LENGTH_SWA,
LLM_KV_ATTENTION_KEY_LENGTH_MLA_SWA,
LLM_KV_ATTENTION_VALUE_LENGTH_MLA_SWA,
LLM_KV_ATTENTION_KV_LORA_RANK_SWA,
LLM_KV_ATTENTION_INDEXER_HEAD_COUNT, LLM_KV_ATTENTION_INDEXER_HEAD_COUNT,
LLM_KV_ATTENTION_INDEXER_KEY_LENGTH, LLM_KV_ATTENTION_INDEXER_KEY_LENGTH,
LLM_KV_ATTENTION_INDEXER_TOP_K, LLM_KV_ATTENTION_INDEXER_TOP_K,
+60 -6
View File
@@ -9,6 +9,7 @@
#include "llama-kv-cache.h" #include "llama-kv-cache.h"
#include "llama-kv-cache-iswa.h" #include "llama-kv-cache-iswa.h"
#include "llama-kv-cache-dsa.h" #include "llama-kv-cache-dsa.h"
#include "llama-kv-cache-dsa-iswa.h"
#include "llama-kv-cache-msa.h" #include "llama-kv-cache-msa.h"
#include "llama-kv-cache-dsv4.h" #include "llama-kv-cache-dsv4.h"
#include "llama-memory-hybrid.h" #include "llama-memory-hybrid.h"
@@ -507,10 +508,12 @@ void llm_graph_input_attn_k::set_input(const llama_ubatch * ubatch) {
} }
bool llm_graph_input_attn_k::can_reuse(const llm_graph_params & params) { bool llm_graph_input_attn_k::can_reuse(const llm_graph_params & params) {
const auto * mctx = static_cast<const llama_kv_cache_context *>(params.mctx); mctx = static_cast<const llama_kv_cache_context *>(params.mctx);
this->mctx = mctx; return can_reuse_impl(params);
}
bool llm_graph_input_attn_k::can_reuse_impl(const llm_graph_params & params) {
bool res = true; bool res = true;
res &= self_k_idxs->ne[0] == params.ubatch.n_tokens; res &= self_k_idxs->ne[0] == params.ubatch.n_tokens;
@@ -567,10 +570,12 @@ void llm_graph_input_attn_k_dsa::set_input(const llama_ubatch * ubatch) {
} }
bool llm_graph_input_attn_k_dsa::can_reuse(const llm_graph_params & params) { bool llm_graph_input_attn_k_dsa::can_reuse(const llm_graph_params & params) {
const auto * mctx = static_cast<const llama_kv_cache_dsa_context *>(params.mctx); mctx = static_cast<const llama_kv_cache_dsa_context *>(params.mctx);
this->mctx = mctx; return can_reuse_impl(params);
}
bool llm_graph_input_attn_k_dsa::can_reuse_impl(const llm_graph_params & params) {
bool res = true; bool res = true;
res &= self_k_idxs_mla->ne[0] == params.ubatch.n_tokens; res &= self_k_idxs_mla->ne[0] == params.ubatch.n_tokens;
@@ -582,6 +587,25 @@ bool llm_graph_input_attn_k_dsa::can_reuse(const llm_graph_params & params) {
return res; return res;
} }
void llm_graph_input_attn_k_dsa_iswa::set_input(const llama_ubatch * ubatch) {
inp_dsa->set_input(ubatch);
inp_swa->set_input(ubatch);
}
bool llm_graph_input_attn_k_dsa_iswa::can_reuse(const llm_graph_params & params) {
mctx = static_cast<const llama_kv_cache_dsa_iswa_context *>(params.mctx);
inp_dsa->mctx = mctx->get_dsa();
inp_swa->mctx = mctx->get_swa();
bool res = true;
res &= inp_dsa->can_reuse_impl(params);
res &= inp_swa->can_reuse_impl(params);
return res;
}
void llm_graph_input_attn_kv_iswa::set_input(const llama_ubatch * ubatch) { void llm_graph_input_attn_kv_iswa::set_input(const llama_ubatch * ubatch) {
// base tensors may not be allocated if there are no non-SWA attention layers // base tensors may not be allocated if there are no non-SWA attention layers
if (self_k_idxs && self_k_idxs->buffer) { if (self_k_idxs && self_k_idxs->buffer) {
@@ -3210,8 +3234,12 @@ ggml_tensor * llm_graph_context::build_attn(
return cur; return cur;
} }
llm_graph_input_attn_k_dsa * llm_graph_context::build_attn_inp_k_dsa() const { static std::unique_ptr<llm_graph_input_attn_k_dsa> build_attn_inp_k_dsa_impl(
const auto * mctx_cur = static_cast<const llama_kv_cache_dsa_context *>(mctx); ggml_context * ctx0,
const llama_ubatch & ubatch,
const llama_hparams & hparams,
const llama_cparams & cparams,
const llama_kv_cache_dsa_context * mctx_cur) {
auto inp = std::make_unique<llm_graph_input_attn_k_dsa>(hparams, cparams, mctx_cur); auto inp = std::make_unique<llm_graph_input_attn_k_dsa>(hparams, cparams, mctx_cur);
@@ -3235,9 +3263,35 @@ llm_graph_input_attn_k_dsa * llm_graph_context::build_attn_inp_k_dsa() const {
inp->self_k_rot_lid = mctx_cur->get_lid()->build_input_k_rot(ctx0); inp->self_k_rot_lid = mctx_cur->get_lid()->build_input_k_rot(ctx0);
} }
return inp;
}
llm_graph_input_attn_k_dsa * llm_graph_context::build_attn_inp_k_dsa() const {
const auto * mctx_cur = static_cast<const llama_kv_cache_dsa_context *>(mctx);
auto inp = build_attn_inp_k_dsa_impl(ctx0, ubatch, hparams, cparams, mctx_cur);
return (llm_graph_input_attn_k_dsa *) res->add_input(std::move(inp)); return (llm_graph_input_attn_k_dsa *) res->add_input(std::move(inp));
} }
llm_graph_input_attn_k_dsa_iswa * llm_graph_context::build_attn_inp_k_dsa_iswa() const {
const auto * mctx_cur = static_cast<const llama_kv_cache_dsa_iswa_context *>(mctx);
auto inp_dsa = build_attn_inp_k_dsa_impl(ctx0, ubatch, hparams, cparams, mctx_cur->get_dsa());
// build_attn_inp_k_impl rejects SWA caches, so construct the input directly
auto inp_swa = std::make_unique<llm_graph_input_attn_k>(hparams, cparams, mctx_cur->get_swa());
inp_swa->self_k_idxs = mctx_cur->get_swa()->build_input_k_idxs(ctx0, ubatch);
inp_swa->self_kq_mask = build_attn_inp_kq_mask(ctx0, mctx_cur->get_swa(), ubatch, cparams);
inp_swa->self_kq_mask_cnv = inp_swa->self_kq_mask;
auto inp = std::make_unique<llm_graph_input_attn_k_dsa_iswa>(std::move(inp_dsa), std::move(inp_swa), mctx_cur);
return (llm_graph_input_attn_k_dsa_iswa *) res->add_input(std::move(inp));
}
llm_graph_input_attn_kv_msa * llm_graph_context::build_attn_inp_kv_msa(bool msa_enabled) const { llm_graph_input_attn_kv_msa * llm_graph_context::build_attn_inp_kv_msa(bool msa_enabled) const {
const auto * mctx_cur = static_cast<const llama_kv_cache_msa_context *>(mctx); const auto * mctx_cur = static_cast<const llama_kv_cache_msa_context *>(mctx);
+35
View File
@@ -23,6 +23,7 @@ struct llama_memory_context_i;
class llama_kv_cache_context; class llama_kv_cache_context;
class llama_kv_cache_dsa_context; class llama_kv_cache_dsa_context;
class llama_kv_cache_dsa_iswa_context;
class llama_kv_cache_msa_context; class llama_kv_cache_msa_context;
class llama_kv_cache_dsv4_raw_context; class llama_kv_cache_dsv4_raw_context;
class llama_kv_cache_dsv4_context; class llama_kv_cache_dsv4_context;
@@ -374,6 +375,9 @@ public:
bool can_reuse(const llm_graph_params & params) override; bool can_reuse(const llm_graph_params & params) override;
// like can_reuse, but does not re-bind mctx
bool can_reuse_impl(const llm_graph_params & params);
ggml_tensor * get_k_idxs() const { return self_k_idxs; } ggml_tensor * get_k_idxs() const { return self_k_idxs; }
ggml_tensor * get_kq_mask() const { return self_kq_mask_cnv; } ggml_tensor * get_kq_mask() const { return self_kq_mask_cnv; }
@@ -405,6 +409,9 @@ public:
bool can_reuse(const llm_graph_params & params) override; bool can_reuse(const llm_graph_params & params) override;
// like can_reuse, but does not re-bind mctx
bool can_reuse_impl(const llm_graph_params & params);
ggml_tensor * get_k_idxs_mla() const { return self_k_idxs_mla; } ggml_tensor * get_k_idxs_mla() const { return self_k_idxs_mla; }
ggml_tensor * get_k_idxs_lid() const { return self_k_idxs_lid; } ggml_tensor * get_k_idxs_lid() const { return self_k_idxs_lid; }
@@ -427,6 +434,32 @@ public:
const llama_kv_cache_dsa_context * mctx; const llama_kv_cache_dsa_context * mctx;
}; };
// DSA input (full-attention layers + indexer) with K-only input for the SWA layers
class llm_graph_input_attn_k_dsa_iswa : public llm_graph_input_i {
public:
llm_graph_input_attn_k_dsa_iswa(
std::unique_ptr<llm_graph_input_attn_k_dsa> inp_dsa,
std::unique_ptr<llm_graph_input_attn_k> inp_swa,
const llama_kv_cache_dsa_iswa_context * mctx) :
inp_dsa(std::move(inp_dsa)),
inp_swa(std::move(inp_swa)),
mctx(mctx) {
}
~llm_graph_input_attn_k_dsa_iswa() = default;
void set_input(const llama_ubatch * ubatch) override;
bool can_reuse(const llm_graph_params & params) override;
llm_graph_input_attn_k_dsa * get_dsa() const { return inp_dsa.get(); }
llm_graph_input_attn_k * get_swa() const { return inp_swa.get(); }
std::unique_ptr<llm_graph_input_attn_k_dsa> inp_dsa;
std::unique_ptr<llm_graph_input_attn_k> inp_swa;
const llama_kv_cache_dsa_iswa_context * mctx;
};
// standard K/V attention input against the base cache, plus destination indices for the indexer key cache // standard K/V attention input against the base cache, plus destination indices for the indexer key cache
class llm_graph_input_attn_kv_msa : public llm_graph_input_attn_kv { class llm_graph_input_attn_kv_msa : public llm_graph_input_attn_kv {
public: public:
@@ -1191,6 +1224,8 @@ struct llm_graph_context {
llm_graph_input_attn_k_dsa * build_attn_inp_k_dsa() const; llm_graph_input_attn_k_dsa * build_attn_inp_k_dsa() const;
llm_graph_input_attn_k_dsa_iswa * build_attn_inp_k_dsa_iswa() const;
llm_graph_input_attn_kv_msa * build_attn_inp_kv_msa(bool msa_enabled) const; llm_graph_input_attn_kv_msa * build_attn_inp_kv_msa(bool msa_enabled) const;
ggml_tensor * build_attn( ggml_tensor * build_attn(
+5
View File
@@ -101,6 +101,11 @@ struct llama_hparams {
uint32_t n_group_used = 0; uint32_t n_group_used = 0;
uint32_t n_group_experts = 0; uint32_t n_group_experts = 0;
// MLA + SWA (i.e. dots3note)
uint32_t n_lora_kv_swa = 0;
uint32_t n_embd_head_k_mla_swa = 0;
uint32_t n_embd_head_v_mla_swa = 0;
float expert_group_scale = 0.05f; float expert_group_scale = 0.05f;
float expert_weights_scale = 0.0f; float expert_weights_scale = 0.0f;
bool expert_weights_norm = false; bool expert_weights_norm = false;
+341
View File
@@ -0,0 +1,341 @@
#include "llama-kv-cache-dsa-iswa.h"
#include "llama-impl.h"
#include "llama-batch.h"
#include "llama-model.h"
#include <algorithm>
#include <cassert>
//
// llama_kv_cache_dsa_iswa
//
llama_kv_cache_dsa_iswa::llama_kv_cache_dsa_iswa(
const llama_model & model,
ggml_type type_k,
ggml_type type_v,
bool v_trans,
bool offload,
bool swa_full,
bool unified,
uint32_t kv_size,
uint32_t n_seq_max,
uint32_t n_ubatch,
uint32_t n_pad,
const layer_filter_cb & filter_mla,
const layer_filter_cb & filter_lid,
const layer_reuse_cb & reuse) : unified(unified) {
const auto & hparams = model.hparams;
// chain filters
const layer_filter_cb filter_dsa = [&](int32_t il) {
if (filter_mla && !filter_mla(il)) {
return false;
}
return !hparams.is_swa(il);
};
const layer_filter_cb filter_swa = [&](int32_t il) {
if (filter_mla && !filter_mla(il)) {
return false;
}
return hparams.is_swa(il);
};
const uint32_t size_dsa = kv_size;
// note: the SWA cache is always padded to 256 for performance
// https://github.com/ggml-org/llama.cpp/issues/17037
uint32_t size_swa = GGML_PAD(std::min(size_dsa, hparams.n_swa*(unified ? n_seq_max : 1) + n_ubatch), 256);
// when using full-size SWA cache, we set the SWA cache size to be equal to the base cache size
if (swa_full) {
LLAMA_LOG_WARN("%s: using full-size SWA cache (ref: %s)\n",
__func__, "https://github.com/ggml-org/llama.cpp/pull/13194#issuecomment-2868343055");
size_swa = size_dsa;
}
LLAMA_LOG_INFO("%s: creating DSA KV cache, size = %u cells\n", __func__, size_dsa);
kv_dsa = std::make_unique<llama_kv_cache_dsa>(
model, type_k, type_v,
v_trans, offload, unified, size_dsa, n_seq_max, n_pad,
0, LLAMA_SWA_TYPE_NONE, filter_dsa, filter_lid, reuse);
LLAMA_LOG_INFO("%s: creating SWA KV cache, size = %u cells\n", __func__, size_swa);
kv_swa = std::make_unique<llama_kv_cache>(
model, hparams, type_k, type_v,
v_trans, offload, unified, size_swa, n_seq_max, n_pad,
hparams.n_swa, hparams.swa_type, nullptr, filter_swa, reuse, nullptr);
}
void llama_kv_cache_dsa_iswa::clear(bool data) {
kv_dsa->clear(data);
kv_swa->clear(data);
}
bool llama_kv_cache_dsa_iswa::seq_rm(llama_seq_id seq_id, llama_pos p0, llama_pos p1) {
bool res = true;
res = res & kv_dsa->seq_rm(seq_id, p0, p1);
res = res & kv_swa->seq_rm(seq_id, p0, p1);
return res;
}
void llama_kv_cache_dsa_iswa::seq_cp(llama_seq_id seq_id_src, llama_seq_id seq_id_dst, llama_pos p0, llama_pos p1) {
kv_dsa->seq_cp(seq_id_src, seq_id_dst, p0, p1);
kv_swa->seq_cp(seq_id_src, seq_id_dst, p0, p1);
}
void llama_kv_cache_dsa_iswa::seq_keep(llama_seq_id seq_id) {
kv_dsa->seq_keep(seq_id);
kv_swa->seq_keep(seq_id);
}
void llama_kv_cache_dsa_iswa::seq_add(llama_seq_id seq_id, llama_pos p0, llama_pos p1, llama_pos shift) {
kv_dsa->seq_add(seq_id, p0, p1, shift);
kv_swa->seq_add(seq_id, p0, p1, shift);
}
void llama_kv_cache_dsa_iswa::seq_div(llama_seq_id seq_id, llama_pos p0, llama_pos p1, int d) {
kv_dsa->seq_div(seq_id, p0, p1, d);
kv_swa->seq_div(seq_id, p0, p1, d);
}
llama_pos llama_kv_cache_dsa_iswa::seq_pos_min(llama_seq_id seq_id) const {
// the DSA cache is a superset of the SWA cache, so we can just check the SWA cache
return kv_swa->seq_pos_min(seq_id);
}
llama_pos llama_kv_cache_dsa_iswa::seq_pos_max(llama_seq_id seq_id) const {
return kv_swa->seq_pos_max(seq_id);
}
std::map<ggml_backend_buffer_type_t, size_t> llama_kv_cache_dsa_iswa::memory_breakdown() const {
std::map<ggml_backend_buffer_type_t, size_t> mb = kv_dsa->memory_breakdown();
for (const auto & buft_size : kv_swa->memory_breakdown()) {
mb[buft_size.first] += buft_size.second;
}
return mb;
}
llama_memory_context_ptr llama_kv_cache_dsa_iswa::init_batch(llama_batch_allocr & balloc, uint32_t n_ubatch, bool embd_all) {
GGML_UNUSED(embd_all);
// first try simple split
do {
if (!unified) {
// requires equal splits, so we skip the simple split
break;
}
balloc.split_reset();
std::vector<llama_ubatch> ubatches;
while (true) {
auto ubatch = balloc.split_simple(n_ubatch);
if (ubatch.n_tokens == 0) {
break;
}
ubatches.push_back(std::move(ubatch)); // NOLINT
}
if (balloc.get_n_used() < balloc.get_n_tokens()) {
// failed to find a suitable split
break;
}
auto sinfos_mla = kv_dsa->get_mla()->prepare(ubatches);
if (sinfos_mla.empty()) {
break;
}
auto sinfos_lid = kv_dsa->get_lid()->prepare(ubatches);
if (sinfos_lid.empty()) {
break;
}
auto sinfos_swa = kv_swa->prepare(ubatches);
if (sinfos_swa.empty()) {
break;
}
assert(sinfos_mla.size() == sinfos_swa.size());
return std::make_unique<llama_kv_cache_dsa_iswa_context>(
this, std::move(sinfos_mla), std::move(sinfos_lid), std::move(sinfos_swa), std::move(ubatches));
} while (false);
// if it fails, try equal split
do {
balloc.split_reset();
std::vector<llama_ubatch> ubatches;
while (true) {
auto ubatch = balloc.split_equal(n_ubatch, !unified, 0);
if (ubatch.n_tokens == 0) {
break;
}
ubatches.push_back(std::move(ubatch)); // NOLINT
}
if (balloc.get_n_used() < balloc.get_n_tokens()) {
// failed to find a suitable split
break;
}
auto sinfos_mla = kv_dsa->get_mla()->prepare(ubatches);
if (sinfos_mla.empty()) {
break;
}
auto sinfos_lid = kv_dsa->get_lid()->prepare(ubatches);
if (sinfos_lid.empty()) {
break;
}
auto sinfos_swa = kv_swa->prepare(ubatches);
if (sinfos_swa.empty()) {
break;
}
assert(sinfos_mla.size() == sinfos_swa.size());
return std::make_unique<llama_kv_cache_dsa_iswa_context>(
this, std::move(sinfos_mla), std::move(sinfos_lid), std::move(sinfos_swa), std::move(ubatches));
} while (false);
return std::make_unique<llama_kv_cache_dsa_iswa_context>(LLAMA_MEMORY_STATUS_FAILED_PREPARE);
}
llama_memory_context_ptr llama_kv_cache_dsa_iswa::init_full() {
return std::make_unique<llama_kv_cache_dsa_iswa_context>(this);
}
llama_memory_context_ptr llama_kv_cache_dsa_iswa::init_update(llama_context * lctx, bool optimize) {
return std::make_unique<llama_kv_cache_dsa_iswa_context>(this, lctx, optimize);
}
bool llama_kv_cache_dsa_iswa::get_can_shift() const {
return kv_dsa->get_can_shift() &&
kv_swa->get_can_shift() &&
kv_dsa->get_mla()->get_size() == kv_swa->get_size();
}
void llama_kv_cache_dsa_iswa::state_write(llama_io_write_i & io, llama_seq_id seq_id, llama_state_seq_flags flags) const {
if ((flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY) == 0) {
kv_dsa->state_write(io, seq_id, flags);
}
kv_swa->state_write(io, seq_id, flags);
}
void llama_kv_cache_dsa_iswa::state_read(llama_io_read_i & io, llama_seq_id seq_id, llama_state_seq_flags flags) {
if ((flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY) == 0) {
kv_dsa->state_read(io, seq_id, flags);
}
kv_swa->state_read(io, seq_id, flags);
}
llama_kv_cache_dsa * llama_kv_cache_dsa_iswa::get_dsa() const {
return kv_dsa.get();
}
llama_kv_cache * llama_kv_cache_dsa_iswa::get_swa() const {
return kv_swa.get();
}
//
// llama_kv_cache_dsa_iswa_context
//
llama_kv_cache_dsa_iswa_context::llama_kv_cache_dsa_iswa_context(llama_memory_status status) : status(status) {}
llama_kv_cache_dsa_iswa_context::llama_kv_cache_dsa_iswa_context(
llama_kv_cache_dsa_iswa * kv) :
ctx_dsa(kv->get_dsa()->init_full()),
ctx_swa(kv->get_swa()->init_full()),
status(llama_memory_status_combine(ctx_dsa->get_status(), ctx_swa->get_status())) {
}
llama_kv_cache_dsa_iswa_context::llama_kv_cache_dsa_iswa_context(
llama_kv_cache_dsa_iswa * kv,
llama_context * lctx,
bool optimize) :
ctx_dsa(kv->get_dsa()->init_update(lctx, optimize)),
ctx_swa(kv->get_swa()->init_update(lctx, optimize)),
status(llama_memory_status_combine(ctx_dsa->get_status(), ctx_swa->get_status())) {
}
llama_kv_cache_dsa_iswa_context::llama_kv_cache_dsa_iswa_context(
llama_kv_cache_dsa_iswa * kv,
slot_info_vec_t sinfos_mla,
slot_info_vec_t sinfos_lid,
slot_info_vec_t sinfos_swa,
std::vector<llama_ubatch> ubatches) :
ubatches(std::move(ubatches)),
// note: here we copy the ubatches. not sure if this is ideal
ctx_dsa(new llama_kv_cache_dsa_context(kv->get_dsa(), std::move(sinfos_mla), std::move(sinfos_lid), this->ubatches)),
ctx_swa(new llama_kv_cache_context(kv->get_swa(), std::move(sinfos_swa), this->ubatches)),
status(llama_memory_status_combine(ctx_dsa->get_status(), ctx_swa->get_status())) {
}
llama_kv_cache_dsa_iswa_context:: ~llama_kv_cache_dsa_iswa_context() = default;
bool llama_kv_cache_dsa_iswa_context::next() {
assert(status == LLAMA_MEMORY_STATUS_SUCCESS);
ctx_dsa->next();
ctx_swa->next();
if (++i_next >= ubatches.size()) {
return false;
}
return true;
}
bool llama_kv_cache_dsa_iswa_context::apply() {
assert(!llama_memory_status_is_fail(status));
bool res = true;
res = res & ctx_dsa->apply();
res = res & ctx_swa->apply();
return res;
}
llama_memory_status llama_kv_cache_dsa_iswa_context::get_status() const {
return status;
}
const llama_ubatch & llama_kv_cache_dsa_iswa_context::get_ubatch() const {
assert(status == LLAMA_MEMORY_STATUS_SUCCESS);
return ubatches[i_next];
}
const llama_kv_cache_dsa_context * llama_kv_cache_dsa_iswa_context::get_dsa() const {
assert(status == LLAMA_MEMORY_STATUS_SUCCESS);
return static_cast<const llama_kv_cache_dsa_context *>(ctx_dsa.get());
}
const llama_kv_cache_context * llama_kv_cache_dsa_iswa_context::get_swa() const {
assert(status == LLAMA_MEMORY_STATUS_SUCCESS);
return static_cast<const llama_kv_cache_context *>(ctx_swa.get());
}
+134
View File
@@ -0,0 +1,134 @@
#pragma once
#include "llama-kv-cache-dsa.h"
#include <vector>
//
// llama_kv_cache_dsa_iswa
//
// utilizes two child memories: llama_kv_cache_dsa for the full-attention (DSA) layers and llama_kv_cache for the SWA layers
class llama_kv_cache_dsa_iswa : public llama_memory_i {
public:
llama_kv_cache_dsa_iswa(
const llama_model & model,
ggml_type type_k,
ggml_type type_v,
bool v_trans,
bool offload,
bool swa_full,
bool unified,
uint32_t kv_size,
uint32_t n_seq_max,
uint32_t n_ubatch,
uint32_t n_pad,
const layer_filter_cb & filter_mla,
const layer_filter_cb & filter_lid,
const layer_reuse_cb & reuse);
~llama_kv_cache_dsa_iswa() = default;
//
// llama_memory_i
//
llama_memory_context_ptr init_batch(
llama_batch_allocr & balloc,
uint32_t n_ubatch,
bool embd_all) override;
llama_memory_context_ptr init_full() override;
llama_memory_context_ptr init_update(llama_context * lctx, bool optimize) override;
bool get_can_shift() const override;
void clear(bool data) override;
bool seq_rm (llama_seq_id seq_id, llama_pos p0, llama_pos p1) override;
void seq_cp (llama_seq_id seq_id_src, llama_seq_id seq_id_dst, llama_pos p0, llama_pos p1) override;
void seq_keep(llama_seq_id seq_id) override;
void seq_add (llama_seq_id seq_id, llama_pos p0, llama_pos p1, llama_pos shift) override;
void seq_div (llama_seq_id seq_id, llama_pos p0, llama_pos p1, int d) override;
llama_pos seq_pos_min(llama_seq_id seq_id) const override;
llama_pos seq_pos_max(llama_seq_id seq_id) const override;
std::map<ggml_backend_buffer_type_t, size_t> memory_breakdown() const override;
// state write/load
void state_write(llama_io_write_i & io, llama_seq_id seq_id = -1, llama_state_seq_flags flags = 0) const override;
void state_read (llama_io_read_i & io, llama_seq_id seq_id = -1, llama_state_seq_flags flags = 0) override;
//
// llama_kv_cache_dsa_iswa specific API
//
llama_kv_cache_dsa * get_dsa() const;
llama_kv_cache * get_swa() const;
private:
const bool unified;
std::unique_ptr<llama_kv_cache_dsa> kv_dsa;
std::unique_ptr<llama_kv_cache> kv_swa;
};
class llama_kv_cache_dsa_iswa_context : public llama_memory_context_i {
public:
using slot_info_vec_t = llama_kv_cache::slot_info_vec_t;
// used for errors
llama_kv_cache_dsa_iswa_context(llama_memory_status status);
// used to create a full-cache context
llama_kv_cache_dsa_iswa_context(
llama_kv_cache_dsa_iswa * kv);
// used to create an update context
llama_kv_cache_dsa_iswa_context(
llama_kv_cache_dsa_iswa * kv,
llama_context * lctx,
bool optimize);
// used to create a batch processing context from a batch
llama_kv_cache_dsa_iswa_context(
llama_kv_cache_dsa_iswa * kv,
slot_info_vec_t sinfos_mla,
slot_info_vec_t sinfos_lid,
slot_info_vec_t sinfos_swa,
std::vector<llama_ubatch> ubatches);
virtual ~llama_kv_cache_dsa_iswa_context();
//
// llama_memory_context_i
//
bool next() override;
bool apply() override;
llama_memory_status get_status() const override;
const llama_ubatch & get_ubatch() const override;
//
// llama_kv_cache_dsa_iswa_context specific API
//
const llama_kv_cache_dsa_context * get_dsa() const;
const llama_kv_cache_context * get_swa() const;
private:
// the index of the next ubatch to process
size_t i_next = 0;
std::vector<llama_ubatch> ubatches;
const llama_memory_context_ptr ctx_dsa;
const llama_memory_context_ptr ctx_swa;
const llama_memory_status status;
};
+2 -1
View File
@@ -323,7 +323,8 @@ llama_kv_cache::llama_kv_cache(
hparams.n_embd_head_k() % 64 == 0; hparams.n_embd_head_k() % 64 == 0;
// always create Hadamard rotation tensors for DeepSeek lightning indexers // always create Hadamard rotation tensors for DeepSeek lightning indexers
if ((model.arch == LLM_ARCH_DEEPSEEK32 || model.arch == LLM_ARCH_DEEPSEEK4 || model.arch == LLM_ARCH_GLM_DSA) && if ((model.arch == LLM_ARCH_DEEPSEEK32 || model.arch == LLM_ARCH_DEEPSEEK4 ||
model.arch == LLM_ARCH_GLM_DSA || model.arch == LLM_ARCH_DOTS3NOTE) &&
hparams.n_embd_head_k_full == hparams.indexer_head_size) { hparams.n_embd_head_k_full == hparams.indexer_head_size) {
attn_rot_k = true; attn_rot_k = true;
} }
+1
View File
@@ -31,6 +31,7 @@ bool llama_model_saver_supports_arch(llm_arch arch) {
case LLM_ARCH_MELLUM: case LLM_ARCH_MELLUM:
case LLM_ARCH_LAGUNA: case LLM_ARCH_LAGUNA:
case LLM_ARCH_GRANITE_SWA: case LLM_ARCH_GRANITE_SWA:
case LLM_ARCH_DOTS3NOTE: // TODO: need to handle SWA pattern and MLA+SWA config
return false; return false;
default: default:
return true; return true;
+59 -1
View File
@@ -11,6 +11,7 @@
#include "llama-kv-cache.h" #include "llama-kv-cache.h"
#include "llama-kv-cache-iswa.h" #include "llama-kv-cache-iswa.h"
#include "llama-kv-cache-dsa.h" #include "llama-kv-cache-dsa.h"
#include "llama-kv-cache-dsa-iswa.h"
#include "llama-kv-cache-msa.h" #include "llama-kv-cache-msa.h"
#include "llama-kv-cache-dsv4.h" #include "llama-kv-cache-dsv4.h"
#include "llama-memory-hybrid.h" #include "llama-memory-hybrid.h"
@@ -194,6 +195,8 @@ static llama_model * llama_model_mapping(llm_arch arch, const llama_model_params
return new llama_model_deepseek2ocr(params); return new llama_model_deepseek2ocr(params);
case LLM_ARCH_DEEPSEEK32: case LLM_ARCH_DEEPSEEK32:
return new llama_model_deepseek32(params); return new llama_model_deepseek32(params);
case LLM_ARCH_DOTS3NOTE:
return new llama_model_dots3note(params);
case LLM_ARCH_DEEPSEEK4: case LLM_ARCH_DEEPSEEK4:
return new llama_model_deepseek4(params); return new llama_model_deepseek4(params);
case LLM_ARCH_GLM_DSA: case LLM_ARCH_GLM_DSA:
@@ -851,6 +854,7 @@ const char * llm_type_name(llm_type type) {
case LLM_TYPE_230B_A10B: return "230B.A10B"; case LLM_TYPE_230B_A10B: return "230B.A10B";
case LLM_TYPE_428B_A23B: return "428B.A23B"; case LLM_TYPE_428B_A23B: return "428B.A23B";
case LLM_TYPE_235B_A22B: return "235B.A22B"; case LLM_TYPE_235B_A22B: return "235B.A22B";
case LLM_TYPE_288B_A19B: return "288B.A19B";
case LLM_TYPE_300B_A47B: return "300B.A47B"; case LLM_TYPE_300B_A47B: return "300B.A47B";
case LLM_TYPE_310B_A15B: return "310B.A15B"; case LLM_TYPE_310B_A15B: return "310B.A15B";
case LLM_TYPE_355B_A32B: return "355B.A32B"; case LLM_TYPE_355B_A32B: return "355B.A32B";
@@ -1924,7 +1928,9 @@ void llama_model::print_info() const {
LLAMA_LOG_INFO("%s: expert_weights_scale = %.1f\n", __func__, hparams.expert_weights_scale); LLAMA_LOG_INFO("%s: expert_weights_scale = %.1f\n", __func__, hparams.expert_weights_scale);
} }
if (arch == LLM_ARCH_DEEPSEEK2 || arch == LLM_ARCH_DEEPSEEK2OCR || arch == LLM_ARCH_DEEPSEEK32 || arch == LLM_ARCH_GLM_DSA || arch == LLM_ARCH_MISTRAL4) { if (arch == LLM_ARCH_DEEPSEEK2 || arch == LLM_ARCH_DEEPSEEK2OCR ||
arch == LLM_ARCH_DEEPSEEK32 || arch == LLM_ARCH_GLM_DSA ||
arch == LLM_ARCH_DOTS3NOTE || arch == LLM_ARCH_MISTRAL4) {
LLAMA_LOG_INFO("%s: n_layer_dense_lead = %d\n", __func__, hparams.n_layer_dense_lead); LLAMA_LOG_INFO("%s: n_layer_dense_lead = %d\n", __func__, hparams.n_layer_dense_lead);
LLAMA_LOG_INFO("%s: n_lora_q = %d\n", __func__, hparams.n_lora_q); LLAMA_LOG_INFO("%s: n_lora_q = %d\n", __func__, hparams.n_lora_q);
LLAMA_LOG_INFO("%s: n_lora_kv = %d\n", __func__, hparams.n_lora_kv); LLAMA_LOG_INFO("%s: n_lora_kv = %d\n", __func__, hparams.n_lora_kv);
@@ -2193,6 +2199,57 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
nullptr); nullptr);
} }
} break; } break;
case LLM_ARCH_DOTS3NOTE:
{
GGML_ASSERT(hparams.swa_type != LLAMA_SWA_TYPE_NONE);
if (params.ctx_type == LLAMA_CONTEXT_TYPE_MTP && hparams.n_layer_nextn > 0) {
// MTP draft context: plain attention KV cache holding only the nextn layer
llama_kv_cache::layer_filter_cb filter =
[&](uint32_t il) { return il >= hparams.n_layer(); };
res = new llama_kv_cache(
*this,
hparams,
params.type_k,
params.type_v,
!cparams.flash_attn,
cparams.offload_kqv,
cparams.kv_unified,
cparams.n_ctx_seq,
cparams.n_seq_max,
1,
hparams.n_swa,
hparams.swa_type,
nullptr,
filter,
nullptr,
nullptr);
} else {
// main context: DSA cache for the trunk full-attention layers plus a window-sized SWA cache
llama_kv_cache::layer_filter_cb filter_mla = nullptr;
if (hparams.n_layer_nextn > 0) {
filter_mla = [&](uint32_t il) { return il < hparams.n_layer(); };
}
llama_kv_cache::layer_filter_cb filter_lid = [&](uint32_t il) { return il < hparams.n_layer() && hparams.is_indexer_full(il); };
res = new llama_kv_cache_dsa_iswa(
*this,
params.type_k,
params.type_v,
!cparams.flash_attn,
cparams.offload_kqv,
params.swa_full,
cparams.kv_unified,
cparams.n_ctx_seq,
cparams.n_seq_max,
cparams.n_ubatch,
1,
filter_mla,
filter_lid,
nullptr);
}
} break;
case LLM_ARCH_DEEPSEEK4: case LLM_ARCH_DEEPSEEK4:
{ {
GGML_ASSERT(hparams.swa_type != LLAMA_SWA_TYPE_NONE); GGML_ASSERT(hparams.swa_type != LLAMA_SWA_TYPE_NONE);
@@ -2661,6 +2718,7 @@ llama_rope_type llama_model_rope_type(const llama_model * model) {
case LLM_ARCH_LLAMA_EMBED: case LLM_ARCH_LLAMA_EMBED:
case LLM_ARCH_MAINCODER: case LLM_ARCH_MAINCODER:
case LLM_ARCH_GLM_DSA: case LLM_ARCH_GLM_DSA:
case LLM_ARCH_DOTS3NOTE:
case LLM_ARCH_NANBEIGE: case LLM_ARCH_NANBEIGE:
case LLM_ARCH_POCKETTTS: case LLM_ARCH_POCKETTTS:
return LLAMA_ROPE_TYPE_NORM; return LLAMA_ROPE_TYPE_NORM;
+1
View File
@@ -140,6 +140,7 @@ enum llm_type {
LLM_TYPE_230B_A10B, // Minimax M2 LLM_TYPE_230B_A10B, // Minimax M2
LLM_TYPE_428B_A23B, // Minimax M3 LLM_TYPE_428B_A23B, // Minimax M3
LLM_TYPE_235B_A22B, LLM_TYPE_235B_A22B,
LLM_TYPE_288B_A19B, // dots3-note
LLM_TYPE_300B_A47B, // Ernie MoE big LLM_TYPE_300B_A47B, // Ernie MoE big
LLM_TYPE_310B_A15B, // /MiMo-V2-Flash LLM_TYPE_310B_A15B, // /MiMo-V2-Flash
LLM_TYPE_355B_A32B, // GLM-4.5 LLM_TYPE_355B_A32B, // GLM-4.5
+480
View File
@@ -0,0 +1,480 @@
#include "models.h"
#include "llama-kv-cache.h"
#include "llama-kv-cache-dsa.h"
// note: code adapted from deepseek32.cpp (DSA indexer + absorbed MLA) and step35.cpp (head-wise output gate)
void llama_model_dots3note::load_arch_hparams(llama_model_loader & ml) {
ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps);
hparams.f_norm_eps = 1e-6; // eps for the indexer k_norm layer norm
// TODO: use MTP layer
ml.get_key(LLM_KV_NEXTN_PREDICT_LAYERS, hparams.n_layer_nextn, false);
GGML_ASSERT(hparams.n_layer_nextn < hparams.n_layer_all && "n_layer_nextn must be < n_layer_all");
// MoE parameters
ml.get_key(LLM_KV_EXPERT_SHARED_COUNT, hparams.n_expert_shared);
ml.get_key(LLM_KV_EXPERT_FEED_FORWARD_LENGTH, hparams.n_ff_exp);
ml.get_key(LLM_KV_LEADING_DENSE_BLOCK_COUNT, hparams.n_layer_dense_lead);
ml.get_key(LLM_KV_EXPERT_WEIGHTS_SCALE, hparams.expert_weights_scale, false);
ml.get_key(LLM_KV_EXPERT_WEIGHTS_NORM, hparams.expert_weights_norm, false);
ml.get_key(LLM_KV_EXPERT_GATING_FUNC, hparams.expert_gating_func);
// MLA parameters of the full-attention layers
ml.get_key(LLM_KV_ATTENTION_Q_LORA_RANK, hparams.n_lora_q);
ml.get_key(LLM_KV_ATTENTION_KV_LORA_RANK, hparams.n_lora_kv);
ml.get_key(LLM_KV_ATTENTION_KEY_LENGTH_MLA, hparams.n_embd_head_k_mla_impl);
ml.get_key(LLM_KV_ATTENTION_VALUE_LENGTH_MLA, hparams.n_embd_head_v_mla_impl);
// MLA parameters of the sliding-window layers
ml.get_key(LLM_KV_ATTENTION_KV_LORA_RANK_SWA, hparams.n_lora_kv_swa);
ml.get_key(LLM_KV_ATTENTION_KEY_LENGTH_MLA_SWA, hparams.n_embd_head_k_mla_swa);
ml.get_key(LLM_KV_ATTENTION_VALUE_LENGTH_MLA_SWA, hparams.n_embd_head_v_mla_swa);
hparams.swa_type = LLAMA_SWA_TYPE_STANDARD;
ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa);
ml.get_key(LLM_KV_ROPE_FREQ_BASE_SWA, hparams.rope_freq_base_train_swa);
ml.get_arr(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, hparams.is_swa_impl);
// DSA parameters
ml.get_key(LLM_KV_ATTENTION_INDEXER_HEAD_COUNT, hparams.indexer_n_head);
ml.get_key(LLM_KV_ATTENTION_INDEXER_KEY_LENGTH, hparams.indexer_head_size);
ml.get_key(LLM_KV_ATTENTION_INDEXER_TOP_K, hparams.indexer_top_k);
ml.get_arr(LLM_KV_ATTENTION_INDEXER_TYPES, hparams.is_indexer_full_impl);
switch (hparams.n_layer()) {
case 46: type = LLM_TYPE_288B_A19B; break;
default: type = LLM_TYPE_UNKNOWN;
}
}
void llama_model_dots3note::load_arch_tensors(llama_model_loader & ml) {
LLAMA_LOAD_LOCALS;
GGML_UNUSED(ml);
if (!hparams.is_mla()) {
throw std::runtime_error("DOTS3NOTE architecture requires MLA");
}
const int64_t n_embd_head_qk_rope = hparams.n_rot();
const int64_t q_lora_rank = hparams.n_lora_q;
const int64_t n_ff_exp = hparams.n_ff_exp;
const int64_t n_expert_shared = hparams.n_expert_shared;
tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, 0);
output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd}, 0);
output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), {n_embd, n_vocab}, TENSOR_NOT_REQUIRED);
if (!output) {
output = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, TENSOR_DUPLICATED);
}
for (int i = 0; i < n_layer_all; ++i) {
auto & layer = layers[i];
const bool is_mtp = i >= n_layer;
// the NextN/MTP block uses the sliding-attention geometry
const bool is_swa = is_mtp || hparams.is_swa(i);
// MTP tensors are preserved in the GGUF but there is no MTP graph yet
const int flags = is_mtp ? TENSOR_SKIP | TENSOR_NOT_REQUIRED : 0;
const int64_t n_head_l = hparams.n_head(i);
const int64_t kv_lora_rank = is_swa ? hparams.n_lora_kv_swa : hparams.n_lora_kv;
const int64_t n_embd_head_k_mla = is_swa ? hparams.n_embd_head_k_mla_swa : hparams.n_embd_head_k_mla();
const int64_t n_embd_head_v_mla = is_swa ? hparams.n_embd_head_v_mla_swa : hparams.n_embd_head_v_mla();
const int64_t n_embd_head_qk_nope = n_embd_head_k_mla - n_embd_head_qk_rope;
layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, flags);
layer.attn_q_a_norm = create_tensor(tn(LLM_TENSOR_ATTN_Q_A_NORM, "weight", i), {q_lora_rank}, flags);
layer.attn_kv_a_norm = create_tensor(tn(LLM_TENSOR_ATTN_KV_A_NORM, "weight", i), {kv_lora_rank}, flags);
// norm applied on the shared rope key before rope
layer.attn_k_norm = create_tensor(tn(LLM_TENSOR_ATTN_K_NORM, "weight", i), {n_embd_head_qk_rope}, flags);
layer.wq_a = create_tensor(tn(LLM_TENSOR_ATTN_Q_A, "weight", i), {n_embd, q_lora_rank}, flags);
layer.wq_b = create_tensor(tn(LLM_TENSOR_ATTN_Q_B, "weight", i), {q_lora_rank, n_head_l * n_embd_head_k_mla}, flags);
layer.wkv_a_mqa = create_tensor(tn(LLM_TENSOR_ATTN_KV_A_MQA, "weight", i), {n_embd, kv_lora_rank + n_embd_head_qk_rope}, flags);
layer.wk_b = create_tensor(tn(LLM_TENSOR_ATTN_K_B, "weight", i), {n_embd_head_qk_nope, kv_lora_rank, n_head_l}, flags);
layer.wv_b = create_tensor(tn(LLM_TENSOR_ATTN_V_B, "weight", i), {kv_lora_rank, n_embd_head_v_mla, n_head_l}, flags);
layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_head_l * n_embd_head_v_mla, n_embd}, flags);
// head-wise sigmoid output gate
layer.wqkv_gate = create_tensor(tn(LLM_TENSOR_ATTN_GATE, "weight", i), {n_embd, n_head_l}, flags);
layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, flags);
// DSA indexer
if (!is_mtp && hparams.is_indexer_full(i)) {
layer.indexer_k_norm = create_tensor(tn(LLM_TENSOR_INDEXER_K_NORM, "weight", i), {hparams.indexer_head_size}, flags);
layer.indexer_k_norm_b = create_tensor(tn(LLM_TENSOR_INDEXER_K_NORM, "bias", i), {hparams.indexer_head_size}, flags);
layer.indexer_proj = create_tensor(tn(LLM_TENSOR_INDEXER_PROJ, "weight", i), {n_embd, hparams.indexer_n_head}, flags);
layer.indexer_attn_k = create_tensor(tn(LLM_TENSOR_INDEXER_ATTN_K, "weight", i), {n_embd, hparams.indexer_head_size}, flags);
layer.indexer_attn_q_b = create_tensor(tn(LLM_TENSOR_INDEXER_ATTN_Q_B, "weight", i), {q_lora_rank, hparams.indexer_n_head * hparams.indexer_head_size}, flags);
}
if (is_mtp || i < (int) hparams.n_layer_dense_lead) {
layer.ffn_gate = create_tensor(tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd, n_ff}, flags);
layer.ffn_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd}, flags);
layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), {n_embd, n_ff}, flags);
} else {
if (n_expert == 0 || n_expert_used == 0) {
throw std::runtime_error("n_expert and n_expert_used must be > 0");
}
layer.ffn_gate_inp = create_tensor(tn(LLM_TENSOR_FFN_GATE_INP, "weight", i), {n_embd, n_expert}, flags);
layer.ffn_exp_probs_b = create_tensor(tn(LLM_TENSOR_FFN_EXP_PROBS_B, "bias", i), {n_expert}, flags);
layer.ffn_gate_exps = create_tensor(tn(LLM_TENSOR_FFN_GATE_EXPS, "weight", i), { n_embd, n_ff_exp, n_expert}, flags);
layer.ffn_down_exps = create_tensor(tn(LLM_TENSOR_FFN_DOWN_EXPS, "weight", i), {n_ff_exp, n_embd, n_expert}, flags);
layer.ffn_up_exps = create_tensor(tn(LLM_TENSOR_FFN_UP_EXPS, "weight", i), { n_embd, n_ff_exp, n_expert}, flags);
layer.ffn_gate_shexp = create_tensor(tn(LLM_TENSOR_FFN_GATE_SHEXP, "weight", i), {n_embd, n_ff_exp * n_expert_shared}, flags);
layer.ffn_down_shexp = create_tensor(tn(LLM_TENSOR_FFN_DOWN_SHEXP, "weight", i), { n_ff_exp * n_expert_shared, n_embd}, flags);
layer.ffn_up_shexp = create_tensor(tn(LLM_TENSOR_FFN_UP_SHEXP, "weight", i), {n_embd, n_ff_exp * n_expert_shared}, flags);
}
if (is_mtp) {
layer.nextn.eh_proj = create_tensor(tn(LLM_TENSOR_NEXTN_EH_PROJ, "weight", i), { 2 * n_embd, n_embd }, flags);
layer.nextn.enorm = create_tensor(tn(LLM_TENSOR_NEXTN_ENORM, "weight", i), { n_embd }, flags);
layer.nextn.hnorm = create_tensor(tn(LLM_TENSOR_NEXTN_HNORM, "weight", i), { n_embd }, flags);
layer.nextn.embed_tokens = create_tensor(tn(LLM_TENSOR_NEXTN_EMBED_TOKENS, "weight", i), { n_embd, n_vocab }, flags);
layer.nextn.shared_head_norm = create_tensor(tn(LLM_TENSOR_NEXTN_SHARED_HEAD_NORM, "weight", i), { n_embd }, flags);
}
}
}
std::unique_ptr<llm_graph_context> llama_model_dots3note::build_arch_graph(const llm_graph_params & params) const {
return std::make_unique<graph>(*this, params);
}
llama_model_dots3note::graph::graph(const llama_model & model, const llm_graph_params & params) :
llm_graph_context(params) {
GGML_ASSERT(hparams.is_mla());
const int64_t n_embd_head_qk_rope = hparams.n_rot();
const int64_t n_indexer_head = hparams.indexer_n_head;
const int64_t n_embd_indexer_head = hparams.indexer_head_size;
const uint32_t n_indexer_top_k = hparams.indexer_top_k;
// the indexer head layout is [rope | nope]
GGML_ASSERT(hparams.n_rot() <= n_embd_indexer_head);
ggml_tensor * cur;
ggml_tensor * inpL;
inpL = build_inp_embd(model.tok_embd);
ggml_tensor * inp_pos = build_inp_pos();
llm_graph_input_attn_k_dsa_iswa * inp_attn = build_attn_inp_k_dsa_iswa();
ggml_tensor * inp_out_ids = build_inp_out_ids();
for (int il = 0; il < n_layer; ++il) {
ggml_tensor * inpSA = inpL;
const bool is_swa = hparams.is_swa(il);
const int64_t n_head_l = hparams.n_head(il);
const int64_t kv_lora_rank = is_swa ? hparams.n_lora_kv_swa : hparams.n_lora_kv;
const int64_t n_embd_head_k_mla = is_swa ? hparams.n_embd_head_k_mla_swa : hparams.n_embd_head_k_mla();
const int64_t n_embd_head_v_mla = is_swa ? hparams.n_embd_head_v_mla_swa : hparams.n_embd_head_v_mla();
const int64_t n_embd_head_qk_nope = n_embd_head_k_mla - n_embd_head_qk_rope;
const float kq_scale = 1.0f/sqrtf(float(n_embd_head_k_mla));
const float freq_base_l = model.get_rope_freq_base(cparams, il);
// norm
cur = build_norm(inpL, model.layers[il].attn_norm, NULL, LLM_NORM_RMS, il);
cb(cur, "attn_norm", il);
// self_attention
{
ggml_tensor * attn_inp = cur;
ggml_tensor * qr = ggml_mul_mat(ctx0, model.layers[il].wq_a, cur);
cb(qr, "qr", il);
qr = build_norm(qr, model.layers[il].attn_q_a_norm, nullptr, LLM_NORM_RMS, il);
cb(qr, "qr", il);
ggml_tensor * top_k = nullptr;
// lightning indexer (full-attention layers only)
if (!is_swa) {
ggml_tensor * indexer_q = ggml_mul_mat(ctx0, model.layers[il].indexer_attn_q_b, qr);
cb(indexer_q, "indexer_q", il);
// {n_embd_indexer_head, n_indexer_head, n_tokens}
indexer_q = ggml_reshape_3d(ctx0, indexer_q, n_embd_indexer_head, n_indexer_head, n_tokens);
indexer_q = ggml_rope_ext(ctx0, indexer_q, inp_pos, nullptr, n_rot,
LLAMA_ROPE_TYPE_NEOX, n_ctx_orig, freq_base, freq_scale,
ext_factor, attn_factor, beta_fast, beta_slow);
cb(indexer_q, "indexer_q", il);
ggml_tensor * indexer_k = ggml_mul_mat(ctx0, model.layers[il].indexer_attn_k, cur);
cb(indexer_k, "indexer_k", il);
indexer_k = build_norm(indexer_k, model.layers[il].indexer_k_norm, model.layers[il].indexer_k_norm_b, LLM_NORM, il);
cb(indexer_k, "indexer_k", il);
// {n_embd_indexer_head, 1, n_tokens}
indexer_k = ggml_reshape_3d(ctx0, indexer_k, n_embd_indexer_head, 1, n_tokens);
indexer_k = ggml_rope_ext(ctx0, indexer_k, inp_pos, nullptr, n_rot,
LLAMA_ROPE_TYPE_NEOX, n_ctx_orig, freq_base, freq_scale,
ext_factor, attn_factor, beta_fast, beta_slow);
cb(indexer_k, "indexer_k", il);
// perform Hadamard transform on indexer q and k
indexer_q = ggml_mul_mat(ctx0, inp_attn->get_dsa()->self_k_rot_lid, indexer_q);
cb(indexer_q, "indexer_q", il);
indexer_k = ggml_mul_mat(ctx0, inp_attn->get_dsa()->self_k_rot_lid, indexer_k);
cb(indexer_k, "indexer_k", il);
// store indexer keys to KV cache
const auto * mctx_lid = inp_attn->get_dsa()->mctx->get_lid();
const auto & k_idxs_lid = inp_attn->get_dsa()->get_k_idxs_lid();
ggml_build_forward_expand(gf, mctx_lid->cpy_k(ctx0, indexer_k, k_idxs_lid, il));
ggml_tensor * indexer_weights = ggml_mul_mat(ctx0, model.layers[il].indexer_proj, cur);
cb(indexer_weights, "indexer_weights", il);
indexer_k = mctx_lid->get_k(ctx0, il);
// split the batch into streams if needed
const auto n_stream = indexer_k->ne[3];
indexer_q = ggml_view_4d(ctx0, indexer_q, indexer_q->ne[0], indexer_q->ne[1], indexer_q->ne[2]/n_stream, n_stream, indexer_q->nb[1], indexer_q->nb[2], indexer_q->nb[3]/n_stream, 0);
indexer_weights = ggml_view_4d(ctx0, indexer_weights, indexer_weights->ne[0], indexer_weights->ne[1]/n_stream, indexer_weights->ne[2], n_stream, indexer_weights->nb[1], indexer_weights->nb[2]/n_stream, indexer_weights->nb[3]/n_stream, 0);
// pre-scale weights to avoid scaling operations on huge indexer_score tensor
indexer_weights = ggml_scale(ctx0, indexer_weights, 1.0f / sqrtf(float(n_embd_indexer_head * n_indexer_head)));
cb(indexer_weights, "indexer_weights", il);
ggml_tensor * indexer_score = nullptr;
if (cparams.fused_lid) {
indexer_score = ggml_lightning_indexer(ctx0, indexer_q, indexer_k, indexer_weights, inp_attn->get_dsa()->get_kq_mask_lid());
cb(indexer_score, "indexer_score", il);
res->add_fused_node({LLM_FUSED_OP_LIGHTNING_INDEXER, indexer_score, il});
} else {
indexer_q = ggml_permute(ctx0, indexer_q, 0, 2, 1, 3);
cb(indexer_q, "indexer_q", il);
indexer_k = ggml_permute(ctx0, indexer_k, 0, 2, 1, 3);
cb(indexer_k, "indexer_k", il);
ggml_tensor * indexer_kq = ggml_mul_mat(ctx0, indexer_k, indexer_q);
cb(indexer_kq, "indexer_kq", il);
// ReLU requires contiguous tensors
indexer_kq = ggml_cont(ctx0, ggml_permute(ctx0, indexer_kq, 2, 1, 0, 3));
cb(indexer_kq, "indexer_kq", il);
indexer_score = ggml_relu(ctx0, indexer_kq);
cb(indexer_score, "indexer_score", il);
indexer_score = ggml_mul(ctx0, indexer_score, indexer_weights);
cb(indexer_score, "indexer_score", il);
// sum by q n_indexer_head dimension
indexer_score = ggml_sum_rows(ctx0, indexer_score);
cb(indexer_score, "indexer_score", il);
// permute result to match KQ mask
indexer_score = ggml_cont(ctx0, ggml_permute(ctx0, indexer_score, 2, 1, 0, 3));
cb(indexer_score, "indexer_score", il);
ggml_tensor * indexer_kq_mask = inp_attn->get_dsa()->get_kq_mask_lid();
indexer_score = ggml_add(ctx0, indexer_score, indexer_kq_mask);
cb(indexer_score, "indexer_score", il);
}
// get indices of top k indexer scores
uint32_t n_top_k = indexer_score->ne[0] < n_indexer_top_k ? indexer_score->ne[0] : n_indexer_top_k;
top_k = ggml_cont(ctx0, ggml_top_k(ctx0, indexer_score, n_top_k));
cb(top_k, "top_k", il);
}
ggml_tensor * q = ggml_mul_mat(ctx0, model.layers[il].wq_b, qr);
cb(q, "q", il);
// split into {n_embd_head_qk_nope, n_head_l, n_tokens}
ggml_tensor * q_nope =
ggml_view_3d(ctx0, q, n_embd_head_qk_nope, n_head_l, n_tokens, ggml_row_size(q->type, n_embd_head_k_mla),
ggml_row_size(q->type, n_embd_head_k_mla) * n_head_l, 0);
cb(q_nope, "q_nope", il);
// and {n_embd_head_qk_rope, n_head_l, n_tokens}
ggml_tensor * q_pe = ggml_view_3d(
ctx0, q, n_embd_head_qk_rope, n_head_l, n_tokens, ggml_row_size(q->type, n_embd_head_k_mla),
ggml_row_size(q->type, n_embd_head_k_mla) * n_head_l, ggml_row_size(q->type, n_embd_head_qk_nope));
cb(q_pe, "q_pe", il);
ggml_tensor * kv_cmpr_pe = ggml_mul_mat(ctx0, model.layers[il].wkv_a_mqa, cur);
cb(kv_cmpr_pe, "kv_cmpr_pe", il);
// split into {kv_lora_rank, n_tokens}
ggml_tensor * kv_cmpr =
ggml_view_2d(ctx0, kv_cmpr_pe, kv_lora_rank, n_tokens,
ggml_row_size(kv_cmpr_pe->type, kv_lora_rank + n_embd_head_qk_rope), 0);
cb(kv_cmpr, "kv_cmpr", il);
// and {n_embd_head_qk_rope, 1, n_tokens}
ggml_tensor * k_pe = ggml_view_3d(ctx0, kv_cmpr_pe, n_embd_head_qk_rope, 1, n_tokens,
ggml_row_size(kv_cmpr_pe->type, kv_lora_rank + n_embd_head_qk_rope),
ggml_row_size(kv_cmpr_pe->type, kv_lora_rank + n_embd_head_qk_rope),
ggml_row_size(kv_cmpr_pe->type, kv_lora_rank));
cb(k_pe, "k_pe", il);
// norm on the shared rope key, applied before rope
k_pe = build_norm(k_pe, model.layers[il].attn_k_norm, nullptr, LLM_NORM_RMS, il);
cb(k_pe, "k_pe", il);
q_pe = ggml_rope_ext(ctx0, q_pe, inp_pos, nullptr, n_rot, rope_type, n_ctx_orig, freq_base_l, freq_scale,
ext_factor, attn_factor, beta_fast, beta_slow);
cb(q_pe, "q_pe", il);
k_pe = ggml_rope_ext(ctx0, k_pe, inp_pos, nullptr, n_rot, rope_type, n_ctx_orig, freq_base_l, freq_scale,
ext_factor, attn_factor, beta_fast, beta_slow);
cb(k_pe, "k_pe", il);
kv_cmpr = build_norm(kv_cmpr, model.layers[il].attn_kv_a_norm, nullptr, LLM_NORM_RMS, il);
cb(kv_cmpr, "kv_cmpr", il);
// MLA attention with the absorption optimization
{
// {n_embd_head_qk_nope, n_tokens, n_head_l}
q_nope = ggml_permute(ctx0, q_nope, 0, 2, 1, 3);
cb(q_nope, "q_nope_perm", il);
// {n_embd_head_qk_nope, kv_lora_rank, n_head_l} x {n_embd_head_qk_nope, n_tokens, n_head_l}
ggml_tensor * q_nope_absorbed = ggml_mul_mat(ctx0, model.layers[il].wk_b, q_nope);
cb(q_nope_absorbed, "q_nope_absorbed", il);
// {kv_lora_rank, n_head_l, n_tokens}
q_nope_absorbed = ggml_permute(ctx0, q_nope_absorbed, 0, 2, 1, 3);
cb(q_nope_absorbed, "q_nope_absorbed_perm", il);
// {n_embd_head_qk_rope + kv_lora_rank, n_head_l, n_tokens}
ggml_tensor * Qcur = ggml_concat(ctx0, q_nope_absorbed, q_pe, 0);
cb(Qcur, "Qcur", il);
kv_cmpr = ggml_reshape_3d(ctx0, kv_cmpr, kv_lora_rank, 1, n_tokens);
cb(kv_cmpr, "kv_cmpr_reshape", il);
// {n_embd_head_qk_rope + kv_lora_rank, 1, n_tokens}
ggml_tensor * Kcur = ggml_concat(ctx0, kv_cmpr, k_pe, 0);
cb(Kcur, "Kcur", il);
// {kv_lora_rank, 1, n_tokens}
ggml_tensor * Vcur = kv_cmpr;
cb(Vcur, "Vcur", il);
// apply the head-wise output gate before o_proj, so wo stays out of build_attn
if (is_swa) {
cur = build_attn(inp_attn->get_swa(),
nullptr, nullptr, nullptr,
Qcur, Kcur, Vcur, nullptr, nullptr, model.layers[il].wv_b, kq_scale, il);
} else {
cur = build_attn(inp_attn->get_dsa(),
nullptr, nullptr, nullptr,
Qcur, Kcur, Vcur, nullptr, nullptr, model.layers[il].wv_b, top_k, kq_scale, il);
}
cb(cur, "attn_out", il);
ggml_tensor * gate = build_lora_mm(model.layers[il].wqkv_gate, attn_inp);
cb(gate, "attn_gate", il);
gate = ggml_sigmoid(ctx0, gate);
cb(gate, "attn_gate_sigmoid", il);
// broadcast the per-head gate over the head dimension
ggml_tensor * attn_3d = ggml_reshape_3d(ctx0, cur, n_embd_head_v_mla, n_head_l, n_tokens);
ggml_tensor * gate_3d = ggml_reshape_3d(ctx0, gate, 1, n_head_l, n_tokens);
attn_3d = ggml_mul(ctx0, attn_3d, gate_3d);
cb(attn_3d, "attn_gated", il);
cur = ggml_reshape_2d(ctx0, attn_3d, n_embd_head_v_mla * n_head_l, n_tokens);
cur = build_lora_mm(model.layers[il].wo, cur, model.layers[il].wo_s);
cb(cur, "attn_output", il);
}
}
if (il == n_layer - 1 && inp_out_ids) {
cur = ggml_get_rows(ctx0, cur, inp_out_ids);
inpSA = ggml_get_rows(ctx0, inpSA, inp_out_ids);
}
ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpSA);
cb(ffn_inp, "ffn_inp", il);
cur = build_norm(ffn_inp, model.layers[il].ffn_norm, NULL, LLM_NORM_RMS, il);
cb(cur, "ffn_norm", il);
if ((uint32_t) il < hparams.n_layer_dense_lead) {
cur = build_ffn(cur,
model.layers[il].ffn_up, NULL, model.layers[il].ffn_up_s,
model.layers[il].ffn_gate, NULL, model.layers[il].ffn_gate_s,
model.layers[il].ffn_down, NULL, model.layers[il].ffn_down_s,
NULL, LLM_FFN_SILU, LLM_FFN_PAR, il);
cb(cur, "ffn_out", il);
} else {
ggml_tensor * moe_out = build_moe_ffn(cur,
model.layers[il].ffn_gate_inp,
model.layers[il].ffn_up_exps,
model.layers[il].ffn_gate_exps,
model.layers[il].ffn_down_exps,
model.layers[il].ffn_exp_probs_b,
n_expert, n_expert_used,
LLM_FFN_SILU, hparams.expert_weights_norm,
hparams.expert_weights_scale,
(llama_expert_gating_func_type) hparams.expert_gating_func,
il,
nullptr,
model.layers[il].ffn_gate_up_exps,
model.layers[il].ffn_up_exps_s,
model.layers[il].ffn_gate_exps_s,
model.layers[il].ffn_down_exps_s);
cb(moe_out, "ffn_moe_out", il);
ggml_tensor * ffn_shexp =
build_ffn(cur,
model.layers[il].ffn_up_shexp, NULL, model.layers[il].ffn_up_shexp_s,
model.layers[il].ffn_gate_shexp, NULL, model.layers[il].ffn_gate_shexp_s,
model.layers[il].ffn_down_shexp, NULL, model.layers[il].ffn_down_shexp_s,
NULL, LLM_FFN_SILU, LLM_FFN_PAR, il);
cb(ffn_shexp, "ffn_shexp", il);
cur = ggml_add(ctx0, moe_out, ffn_shexp);
cb(cur, "ffn_out", il);
}
cur = ggml_add(ctx0, cur, ffn_inp);
cur = build_cvec(cur, il);
cb(cur, "l_out", il);
inpL = cur;
}
cur = inpL;
cur = build_norm(cur, model.output_norm, NULL, LLM_NORM_RMS, -1);
cb(cur, "result_norm", -1);
res->t_embd = cur;
cur = ggml_mul_mat(ctx0, model.output, cur);
cb(cur, "result_output", -1);
res->t_logits = cur;
ggml_build_forward_expand(gf, cur);
}
+12
View File
@@ -1156,6 +1156,18 @@ struct llama_model_deepseek32 : public llama_model_base {
}; };
struct llama_model_dots3note : public llama_model_base {
llama_model_dots3note(const struct llama_model_params & params) : llama_model_base(params) {}
void load_arch_hparams(llama_model_loader & ml) override;
void load_arch_tensors(llama_model_loader & ml) override;
struct graph : public llm_graph_context {
graph(const llama_model & model, const llm_graph_params & params);
};
std::unique_ptr<llm_graph_context> build_arch_graph(const llm_graph_params & params) const override;
};
struct llama_model_deepseek4 : public llama_model_base { struct llama_model_deepseek4 : public llama_model_base {
llama_model_deepseek4(const struct llama_model_params & params) : llama_model_base(params) {} llama_model_deepseek4(const struct llama_model_params & params) : llama_model_base(params) {}
void load_arch_hparams(llama_model_loader & ml) override; void load_arch_hparams(llama_model_loader & ml) override;
+21 -1
View File
@@ -104,6 +104,7 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {
} else if (arch == LLM_ARCH_DEEPSEEK2 } else if (arch == LLM_ARCH_DEEPSEEK2
|| arch == LLM_ARCH_DEEPSEEK32 || arch == LLM_ARCH_DEEPSEEK32
|| arch == LLM_ARCH_GLM_DSA || arch == LLM_ARCH_GLM_DSA
|| arch == LLM_ARCH_DOTS3NOTE
|| arch == LLM_ARCH_KIMI_LINEAR || arch == LLM_ARCH_KIMI_LINEAR
|| arch == LLM_ARCH_BAILINGMOE3 || arch == LLM_ARCH_BAILINGMOE3
|| arch == LLM_ARCH_KIMI_K3 || arch == LLM_ARCH_KIMI_K3
@@ -166,6 +167,7 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {
if (arch == LLM_ARCH_DEEPSEEK2 if (arch == LLM_ARCH_DEEPSEEK2
|| arch == LLM_ARCH_DEEPSEEK32 || arch == LLM_ARCH_DEEPSEEK32
|| arch == LLM_ARCH_GLM_DSA || arch == LLM_ARCH_GLM_DSA
|| arch == LLM_ARCH_DOTS3NOTE
|| arch == LLM_ARCH_KIMI_LINEAR || arch == LLM_ARCH_KIMI_LINEAR
|| arch == LLM_ARCH_BAILINGMOE3 || arch == LLM_ARCH_BAILINGMOE3
|| arch == LLM_ARCH_KIMI_K3 || arch == LLM_ARCH_KIMI_K3
@@ -175,6 +177,22 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {
ms.add_kv(LLM_KV_ROPE_DIMENSION_COUNT, uint32_t(64)); ms.add_kv(LLM_KV_ROPE_DIMENSION_COUNT, uint32_t(64));
ms.add_kv(LLM_KV_ATTENTION_KEY_LENGTH_MLA, uint32_t(192)); ms.add_kv(LLM_KV_ATTENTION_KEY_LENGTH_MLA, uint32_t(192));
ms.add_kv(LLM_KV_ATTENTION_VALUE_LENGTH_MLA, uint32_t(128)); ms.add_kv(LLM_KV_ATTENTION_VALUE_LENGTH_MLA, uint32_t(128));
if (arch == LLM_ARCH_DOTS3NOTE) {
// SWA layers reuse the same MLA geometry as the full layers in this fixture
ms.add_kv(LLM_KV_ATTENTION_KV_LORA_RANK_SWA, uint32_t(512));
ms.add_kv(LLM_KV_ATTENTION_KEY_LENGTH_SWA, uint32_t(576));
ms.add_kv(LLM_KV_ATTENTION_VALUE_LENGTH_SWA, uint32_t(512));
ms.add_kv(LLM_KV_ATTENTION_KEY_LENGTH_MLA_SWA, uint32_t(192));
ms.add_kv(LLM_KV_ATTENTION_VALUE_LENGTH_MLA_SWA, uint32_t(128));
ms.add_kv(LLM_KV_ROPE_FREQ_BASE_SWA, 10000.0f);
// indexer on the full-attention layers (inverse of the swa pattern)
std::vector<uint32_t> indexer_types;
indexer_types.reserve(n_layer);
for (uint32_t il = 0; il < n_layer; il++) {
indexer_types.push_back(il % 2 ? 0 : 1);
}
ms.add_kv(LLM_KV_ATTENTION_INDEXER_TYPES, indexer_types);
}
} else if (arch == LLM_ARCH_MINIMAX_M3) { } else if (arch == LLM_ARCH_MINIMAX_M3) {
// partial rotary: n_rot must not exceed the indexer key length (64) // partial rotary: n_rot must not exceed the indexer key length (64)
ms.add_kv(LLM_KV_ROPE_DIMENSION_COUNT, uint32_t(64)); ms.add_kv(LLM_KV_ROPE_DIMENSION_COUNT, uint32_t(64));
@@ -197,7 +215,8 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {
ms.add_kv(LLM_KV_ROPE_FREQ_BASE_SWA, 10000.0f); ms.add_kv(LLM_KV_ROPE_FREQ_BASE_SWA, 10000.0f);
// SWA pattern: every 5th layer is full attention (matches E2B layer_types) // SWA pattern: every 5th layer is full attention (matches E2B layer_types)
ms.add_kv(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, uint32_t(5)); ms.add_kv(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, uint32_t(5));
} else if (arch == LLM_ARCH_COHERE2MOE || arch == LLM_ARCH_MIMO2 || arch == LLM_ARCH_STEP35 || arch == LLM_ARCH_MUSE_GLIMMER || arch == LLM_ARCH_GRANITE_SWA) { } else if (arch == LLM_ARCH_COHERE2MOE || arch == LLM_ARCH_MIMO2 || arch == LLM_ARCH_STEP35 ||
arch == LLM_ARCH_MUSE_GLIMMER || arch == LLM_ARCH_GRANITE_SWA || arch == LLM_ARCH_DOTS3NOTE) {
std::vector<uint32_t> pattern; std::vector<uint32_t> pattern;
pattern.reserve(n_layer); pattern.reserve(n_layer);
for (uint32_t il = 0; il < n_layer; il++) { for (uint32_t il = 0; il < n_layer; il++) {
@@ -365,6 +384,7 @@ static bool moe_mandatory(const llm_arch arch) {
case LLM_ARCH_DEEPSEEK: case LLM_ARCH_DEEPSEEK:
case LLM_ARCH_DEEPSEEK2: case LLM_ARCH_DEEPSEEK2:
case LLM_ARCH_DEEPSEEK32: case LLM_ARCH_DEEPSEEK32:
case LLM_ARCH_DOTS3NOTE:
case LLM_ARCH_GLM4_MOE: case LLM_ARCH_GLM4_MOE:
case LLM_ARCH_GLM_DSA: case LLM_ARCH_GLM_DSA:
case LLM_ARCH_EXAONE_MOE: case LLM_ARCH_EXAONE_MOE: