Skip to content

Commit 3658d2d

Browse files
Patrick Hoffmannclaude
authored andcommitted
llama: add the glm5next NextN/MTP draft head
GLM-5.3-Flash ships a NextN block that neither upstream draft implements: ggml-org#27752 has no MTP graph at all, ggml-org#27754 asserts "glm5next NextN graph not implemented yet". The tensors were already declared by the loader but carried TENSOR_SKIP unconditionally, so the head sat unused in every GGUF. Model it on the working GLM-5.2 head in glm-dsa.cpp: enorm(embed) + hnorm(prev_hidden) -> concat -> eh_proj -> one dense DSA decoder block -> shared_head_norm -> shared LM head. graph_mtp derives from graph through a no-trunk tag constructor so it calls the trunk's own build_mla_layer and build_ffn_layer instead of duplicating them; attention and FFN semantics therefore cannot drift from the trunk. Two deliberate differences: no mHC mixer, because the loader creates no hc_* tensors for the NextN block, and inp_kpool = nullptr, so build_dsa_top_k is skipped and the block runs dense MLA — the same choice the GLM-5.2 head makes. Loader now follows the glm-dsa pattern: trunk and NextN may live in separate GGUFs in either direction, and TENSOR_SKIP is applied only when the loader was not asked for MTP. load_mtp defaults to false, so ordinary loading is byte-for-byte unchanged. An MTP context now allocates a plain KV cache filtered to the NextN layers. Without that it built a second full hybrid memory for the entire model, which cannot fit at production context sizes. NOT VALIDATED: this has never been executed. test-llama-archs covers no MTP head for any architecture, so there is no harness to extend, and no ROCm or GLM-5.3 checkpoint was available. Acceptance rate and correctness must be measured on hardware before relying on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 40f557a commit 3658d2d

3 files changed

Lines changed: 181 additions & 3 deletions

File tree

src/llama-model.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,6 +2403,34 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
24032403
} break;
24042404
case LLM_ARCH_GLM5NEXT:
24052405
{
2406+
// an MTP context holds only the NextN block, which is a dense attention
2407+
// layer with plain residuals - no recurrent state and no indexer cache.
2408+
// Without this it would allocate a second full hybrid memory for the
2409+
// whole model, which does not fit at production context sizes.
2410+
if (params.ctx_type == LLAMA_CONTEXT_TYPE_MTP && hparams.n_layer_nextn > 0) {
2411+
llama_kv_cache::layer_filter_cb filter_mtp =
2412+
[&](uint32_t il) { return il >= hparams.n_layer(); };
2413+
2414+
res = new llama_kv_cache(
2415+
*this,
2416+
hparams,
2417+
params.type_k,
2418+
params.type_v,
2419+
!cparams.flash_attn,
2420+
cparams.offload_kqv,
2421+
cparams.kv_unified,
2422+
cparams.n_ctx_seq,
2423+
cparams.n_seq_max,
2424+
1,
2425+
hparams.n_swa,
2426+
hparams.swa_type,
2427+
nullptr,
2428+
filter_mtp,
2429+
nullptr,
2430+
nullptr);
2431+
break;
2432+
}
2433+
24062434
// KDA layers recur, MLA layers cache, and the DSA indexer shadows the MLA layers
24072435
llama_memory_hybrid_kpool::layer_filter_cb filter_recr =
24082436
[&](int32_t il) { return (uint32_t) il < hparams.n_layer() && hparams.is_recr(il); };

src/models/glm5next.cpp

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,20 @@ void llama_model_glm5next::load_arch_tensors(llama_model_loader &) {
163163
const int64_t d_conv = hparams.ssm_d_conv;
164164
const int64_t d_inner = head_dim * n_head;
165165

166-
// the NextN/MTP block is loaded but ignored - there is no MTP graph yet
167-
const int mtp_flags = TENSOR_NOT_REQUIRED | TENSOR_SKIP;
166+
// the NextN/MTP block backs the DECODER_MTP draft head. Follow the glm-dsa
167+
// pattern: trunk and NextN may live in separate GGUFs in either direction, and
168+
// the block is only materialised when the loader was asked for it.
169+
const bool mtp_only = (hparams.n_layer_nextn > 0) &&
170+
(ml->get_weight("blk.0.attn_norm.weight") == nullptr);
171+
const std::string mtp_probe = "blk." + std::to_string(n_layer) + ".nextn.eh_proj.weight";
172+
const bool trunk_only = (hparams.n_layer_nextn > 0) &&
173+
(ml->get_weight(mtp_probe.c_str()) == nullptr);
174+
175+
const int trunk_flags = mtp_only ? TENSOR_NOT_REQUIRED : 0;
176+
int mtp_flags = trunk_only ? TENSOR_NOT_REQUIRED : 0;
177+
if (!ml->load_mtp) {
178+
mtp_flags |= TENSOR_SKIP;
179+
}
168180

169181
tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, 0);
170182
output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd}, 0);
@@ -179,7 +191,7 @@ void llama_model_glm5next::load_arch_tensors(llama_model_loader &) {
179191

180192
const bool is_mtp = i >= n_layer;
181193
const bool is_recr = !is_mtp && hparams.is_recr(i);
182-
const int flags = is_mtp ? mtp_flags : 0;
194+
const int flags = is_mtp ? mtp_flags : trunk_flags;
183195

184196
layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, flags);
185197
layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, flags);
@@ -279,6 +291,9 @@ void llama_model_glm5next::load_arch_tensors(llama_model_loader &) {
279291
}
280292

281293
std::unique_ptr<llm_graph_context> llama_model_glm5next::build_arch_graph(const llm_graph_params & params) const {
294+
if (params.gtype == LLM_GRAPH_TYPE_DECODER_MTP) {
295+
return std::make_unique<graph_mtp>(*this, params);
296+
}
282297
return std::make_unique<graph>(*this, params);
283298
}
284299

@@ -963,3 +978,127 @@ llama_model_glm5next::graph::graph(const llama_model & model, const llm_graph_pa
963978

964979
ggml_build_forward_expand(gf, cur);
965980
}
981+
982+
// no-trunk tag ctor: prepares the graph context and its helpers without emitting the
983+
// trunk. graph_mtp uses it so it can call build_mla_layer/build_ffn_layer directly.
984+
llama_model_glm5next::graph::graph(const llama_model & model, const llm_graph_params & params,
985+
no_trunk_t) :
986+
llm_build_delta_net_base(params), model(model) {
987+
}
988+
989+
// LLM_GRAPH_TYPE_DECODER_MTP draft head for GLM-5.3-Flash (glm5next).
990+
//
991+
// Mirrors the deepseek-family NextN layout, as glm-dsa does for GLM-5.2:
992+
// enorm(embed) + hnorm(prev_hidden) -> concat(e, h) -> eh_proj ->
993+
// one dense DSA decoder block (MLA attention + MoE FFN, exactly the trunk helpers) ->
994+
// shared_head_norm (fallback output_norm) -> shared LM head.
995+
//
996+
// Two things differ from a trunk layer and are deliberate:
997+
// - no mHC mixer. The NextN block keeps plain residuals, and the loader does not
998+
// create hc_* tensors for it, so build_hc_pre/build_hc_post must not run here.
999+
// - no DSA indexer. build_mla_layer takes inp_kpool = nullptr, which makes it skip
1000+
// build_dsa_top_k and run dense MLA, the same choice glm-dsa's MTP head makes.
1001+
llama_model_glm5next::graph_mtp::graph_mtp(const llama_model & model, const llm_graph_params & params) :
1002+
graph(model, params, no_trunk_t{}) {
1003+
GGML_ASSERT(hparams.n_layer_nextn > 0 && "glm5next MTP requires n_layer_nextn > 0");
1004+
GGML_ASSERT(cparams.nextn_layer_offset >= 0 &&
1005+
cparams.nextn_layer_offset < (int) hparams.n_layer_nextn &&
1006+
"nextn_layer_offset out of range [0, n_layer_nextn)");
1007+
1008+
const int il = hparams.n_layer() + cparams.nextn_layer_offset;
1009+
const auto & layer = model.layers[il];
1010+
1011+
GGML_ASSERT(layer.nextn.eh_proj && "glm5next MTP block missing nextn.eh_proj");
1012+
GGML_ASSERT(layer.nextn.enorm && "glm5next MTP block missing nextn.enorm");
1013+
GGML_ASSERT(layer.nextn.hnorm && "glm5next MTP block missing nextn.hnorm");
1014+
GGML_ASSERT(layer.attn_norm && "glm5next MTP block missing attn_norm");
1015+
GGML_ASSERT(layer.ffn_norm && "glm5next MTP block missing ffn_norm");
1016+
1017+
// nope-only, so no YaRN mscale correction - identical to the trunk graph
1018+
const float kq_scale = 1.0f / sqrtf(float(hparams.n_embd_head_k_mla()));
1019+
1020+
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
1021+
1022+
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
1023+
ggml_set_input(inp->tokens);
1024+
1025+
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_inp(), n_tokens);
1026+
ggml_set_input(inp->embd);
1027+
1028+
ggml_tensor * tok_embd;
1029+
if (ubatch.token) {
1030+
ggml_tensor * tok_embd_w = layer.nextn.embed_tokens ? layer.nextn.embed_tokens : model.tok_embd;
1031+
tok_embd = ggml_get_rows(ctx0, tok_embd_w, inp->tokens);
1032+
} else {
1033+
tok_embd = inp->embd;
1034+
}
1035+
cb(tok_embd, "mtp_tok_embd", il);
1036+
1037+
inp->h = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
1038+
ggml_set_input(inp->h);
1039+
ggml_set_name(inp->h, "mtp_h_input");
1040+
1041+
ggml_tensor * h_embd = inp->h;
1042+
1043+
res->add_input(std::move(inp));
1044+
1045+
ggml_tensor * inp_out_ids = build_inp_out_ids();
1046+
1047+
// MLA with absorption uses a K-only cache (V is a view of K)
1048+
auto * inp_attn = build_attn_inp_k();
1049+
1050+
ggml_tensor * h_norm = build_norm(h_embd, layer.nextn.hnorm, nullptr, LLM_NORM_RMS, il);
1051+
cb(h_norm, "mtp_hnorm", il);
1052+
1053+
ggml_tensor * e_norm = build_norm(tok_embd, layer.nextn.enorm, nullptr, LLM_NORM_RMS, il);
1054+
cb(e_norm, "mtp_enorm", il);
1055+
1056+
ggml_tensor * concat = ggml_concat(ctx0, e_norm, h_norm, /*dim=*/ 0);
1057+
cb(concat, "mtp_concat", il);
1058+
1059+
ggml_tensor * cur = build_lora_mm(layer.nextn.eh_proj, concat, layer.nextn.eh_proj_s);
1060+
cb(cur, "mtp_eh_proj", il);
1061+
1062+
// dense DSA decoder block, plain residuals
1063+
ggml_tensor * inpSA = cur;
1064+
1065+
cur = build_norm(cur, layer.attn_norm, nullptr, LLM_NORM_RMS, il);
1066+
cb(cur, "mtp_attn_norm", il);
1067+
1068+
cur = build_mla_layer(cur, layer, inp_attn, /*inp_kpool =*/ nullptr, kq_scale, il);
1069+
cb(cur, "mtp_attn_out", il);
1070+
1071+
ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpSA);
1072+
cb(ffn_inp, "mtp_ffn_inp", il);
1073+
1074+
cur = build_norm(ffn_inp, layer.ffn_norm, nullptr, LLM_NORM_RMS, il);
1075+
cb(cur, "mtp_ffn_norm", il);
1076+
1077+
cur = build_ffn_layer(cur, layer, il);
1078+
cb(cur, "mtp_ffn_out", il);
1079+
1080+
cur = ggml_add(ctx0, cur, ffn_inp);
1081+
cb(cur, "mtp_post_ffn", il);
1082+
1083+
// the post-norm hidden state seeds the next MTP step
1084+
ggml_tensor * head_norm_w = layer.nextn.shared_head_norm
1085+
? layer.nextn.shared_head_norm
1086+
: model.output_norm;
1087+
GGML_ASSERT(head_norm_w && "glm5next MTP: missing nextn.shared_head_norm and output_norm");
1088+
cur = build_norm(cur, head_norm_w, nullptr, LLM_NORM_RMS, -1);
1089+
1090+
cb(cur, "h_nextn", -1);
1091+
res->t_h_nextn = cur;
1092+
1093+
cur = ggml_get_rows(ctx0, cur, inp_out_ids);
1094+
cb(cur, "mtp_shared_head_norm", -1);
1095+
1096+
ggml_tensor * head_w = layer.nextn.shared_head_head ? layer.nextn.shared_head_head : model.output;
1097+
ggml_tensor * head_s = layer.nextn.shared_head_head ? layer.nextn.shared_head_head_s : model.output_s;
1098+
GGML_ASSERT(head_w && "glm5next MTP: missing LM head (nextn.shared_head_head or model.output)");
1099+
cur = build_lora_mm(head_w, cur, head_s);
1100+
cb(cur, "result_output", -1);
1101+
1102+
res->t_logits = cur;
1103+
ggml_build_forward_expand(gf, cur);
1104+
}

src/models/models.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,11 @@ struct llama_model_glm5next : public llama_model_base {
13451345
struct graph : public llm_build_delta_net_base {
13461346
graph(const llama_model & model, const llm_graph_params & params);
13471347

1348+
// tag ctor: initialise the context and helpers without building the trunk,
1349+
// so graph_mtp can reuse build_mla_layer/build_ffn_layer on the NextN block
1350+
struct no_trunk_t {};
1351+
graph(const llama_model & model, const llm_graph_params & params, no_trunk_t);
1352+
13481353
const llama_model & model;
13491354

13501355
// manifold-constrained hyper-connections (mHC), same formulation as deepseek4
@@ -1382,6 +1387,12 @@ struct llama_model_glm5next : public llama_model_base {
13821387
ggml_tensor * build_ffn_layer(ggml_tensor * cur, const llama_layer & layer, int il);
13831388
};
13841389

1390+
// LLM_GRAPH_TYPE_DECODER_MTP draft head: the NextN block is a dense DSA decoder
1391+
// layer with plain residuals (no mHC mixer) and its own shared LM head
1392+
struct graph_mtp : public graph {
1393+
graph_mtp(const llama_model & model, const llm_graph_params & params);
1394+
};
1395+
13851396
std::unique_ptr<llm_graph_context> build_arch_graph(const llm_graph_params & params) const override;
13861397
};
13871398

0 commit comments

Comments
 (0)