model: add NextN/MTP speculative decoding support for GLM_DSA (GLM-5.2)#25980
model: add NextN/MTP speculative decoding support for GLM_DSA (GLM-5.2)#25980satindergrewal wants to merge 2 commits into
Conversation
am17an
left a comment
There was a problem hiding this comment.
C++ part looks okay to me.
PS - @satindergrewal while your work is reasonable your comments/descriptions are not, you should write your messages yourself rather than routing everything through Claude. I'm not sure you understand any of this code but I take responsibility for maintaining it
|
@am17an You are right. Really appreciate you taking responsibility of this code. Much appreciate it. You are free to reject it if it still doesn't meet your standard of quality. However I'll be happy to amend and fix if notified about anything needs change or fixing. I understand as maintainers it's hard to keep up with a flood of AI code pushes to the projects you maintain, that's fair to expect to have human worded communication. I go through PR, issues, mainter's and other contributer's forks and their branches and git commits and make sure I am not pushing anything duplicate, and also try to offer help in existing PR and issues with my measurements, without even expecting credit in back. |
|
Yes as I said your work is good, however it would be much better if you wrote your own words as it would deepen your understanding of the codebase. Not to mention the contributing guidelines explicitly forbid comments written by AI. While I understand your stance is take or leave it, I'm suggesting a middle ground which would help you as well as the project. In the end there is no compulsion for a maintainer to accept a PR even if it is not AI written. |
|
Absolutely, and I have seen maintainers closing other PRs without merge 😅 But I has to take help from AI, because to be honest, I am not as competent at this as this work needs to communicate that level of detail. I will catch up, but too slow, and it's much faster if I give control to AI, and make sure it did not mess up. That's why I'm also holding back from pushing more things to project as I suspect folks here don't fully like AI and specially the communication back and forth. So, I'm sort of mixed mind if I push more PRs next time or not here. |
|
If you plan to invest time long-term in this project, read the following - otherwise ignore. My suggestion would be not to push anything you don't understand, as it doesn't really benefit you or the project longer term. You would be unable to make progress on higher levels of abstractions in the code-base without really understanding how it all works, even if line by line understanding of the code is not important.
There is no harm in going slower and taking time to understand. In fact, that will allow you to go much faster in the future PRs. |
|
thank you for your kind words @am17an. I'll try my best 🙏 |
|
Thank you, @satindergrewal !!!! I ran this PR with SixVolts/GLM-5.2-Q3_K_M in a old Dual Xeon with a lot of tinkering (:-)) and I got a TG speed increase from ~6 T/s to ~7.1 T/s (in code generation). |
|
@satindergrewal Lightning indexer changes were merged recently, it looks like there are some conflicts now with the master to resolve. Also please add llama.cpp/src/models/glm-dsa.cpp Lines 482 to 522 in 720d7fa AFAIK it's needed for NVFP4 quant to work. I tried this PR and it does seem to speed up code generation a bit (~10 t/s -> ~12 t/s). For English prose only Edit: In addition to GLM-5.2 I tested this with GLM-5.1 and it also works without issues. |
Adds GLM-5.2 NextN/MTP as a --spec-type draft-mtp target: nextn tensor loading via the qwen35moe/step35-style presence probe, a graph_mtp builder (enorm/hnorm/eh_proj + dense MLA + sigmoid-gated MoE with shared expert + shared head with fallbacks, _s scale tensors passed for NVFP4), t_h_nextn extraction in the trunk graph, and MTP-context KV setup: the draft head runs dense MLA, so the MTP context uses a plain attention KV cache holding only the nextn layer(s) (same pattern as the hybrid Qwen3.5 MTP context) while the main context keeps the DSA cache, now filtered to trunk layers only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-5.2) Opt GLM-5.2 into the supports_mtp_export contract (post-ggml-org#25641 shape, mirroring HYV3Model/Step35Model): --no-mtp drops the appended NextN block (blk.78) and its nextn_predict_layers KV; --mtp keeps only the NextN block plus shared embeddings/norm/lm_head. Default (bundled) output is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e2ac771 to
8cd7811
Compare
|
Thanks for testing, and for the GLM-5.1 confirmation @fairydreaming. Update pushed: rebased past the indexer merge (which restructured GLM_DSA's graph and KV cache, so the MTP wiring needed re-plumbing on the new structure, not just conflict fixes), and added the Re-verified after the changes: greedy acceptance 0.6623 (per-position 0.838/0.650/0.489), same as before the rebase. |
Overview
Adds GLM-5.2's multi-token-prediction (NextN) head as a
draft-mtpspeculative target for the existingGLM_DSAarchitecture. This is the fifth in-treedraft-mtpimplementation (after qwen35, qwen35moe, step35, cohere2moe). It complements #25407 (GLM 5.2 Indexer); see "Relationship to #24868" below for the GLM-family code-sharing question.src/models/glm-dsa.cpp: nextn tensor loading is un-skipped via the qwen35moe/step35-style presence probe (mtp_only/trunk_onlydetection, so trunk-only GGUFs keep skipping). Adds the fullgraph_mtpbuilder: the qwen35moe frame (embd_h input, enorm/hnorm, concat, eh_proj, shared_head fallbacks), deepseek2's MLA attention (absorbed wk_b/wv_b, exact YaRN mscale^2 kq_scale), and deepseek2's sigmoid-gated MoE plus shared-expert FFN. The MTP layer runs dense MLA; the DSA lightning indexer is not used there, matching the GLM_DSA trunk graph. Also fixes the 744B_A40B type string (previously "?B").src/models/models.h:graph_mtpdeclaration.src/models/deepseek2.cpp: setsres->t_h_nextnafteroutput_normin the trunk graph (shared by DEEPSEEK2/MISTRAL4, guarded so MTP-off graphs stay bit-identical to master).src/llama-model.cpp: KV filter clause extended to GLM_DSA, so the MTP context allocates KV only for the nextn layer.conversion/glm.py):GlmMoeDsaModelopts intosupports_mtp_export(post-convert_hf_to_gguf.py: Refactor how--mtp/--no-mtpis enabled for a model #25641 class-flag shape, mirroring HYV3Model and Step35Model).--no-mtpstrips the NextN block and itsnextn_predict_layersKV;--mtpemits a draft-only GGUF (NextN block plus shared embeddings/norm/lm_head). Default bundled output is unchanged.--spec-type draft-mtpmachinery runs it unchanged.GLM-5.2's GGUF
block_count=79includes the nextn layer, son_layer()=78and the nextn tensors live atblk.78. Existing community GGUFs already carry these tensors and load unmodified.Validation
Hardware: 2x RTX PRO 6000 Blackwell (96 GB each), CUDA 12.9, SM120a,
-sm layer. Model: GLM-5.2 744B-A40B, IQ1_S (the quant that fits fully in VRAM on this rig). Benchmark format: am17an'smtp-bench.py.Full audit, benchmark scripts, raw outputs, charts, and the determinism control run are published here: https://huggingface.co/datasets/satgeze/glm-dsa-mtp-audit
mtp-bench.py house format, baseline vs MTP (server
llama-server -m GLM-5.2-IQ1_S.gguf -ngl 99 -sm layer -c 32768 -np 1 -fa on --jinja [--spec-type draft-mtp]):Per-position acceptance (greedy, ungated,
common_speculative_print_stats):0.834 / 0.653 / 0.494, mean accepted length 2.98. Acceptance is healthy at all three draft positions, so the framework defaults (n_max=3, p_min=0) are the correct configuration for this model and no per-arch override is proposed. (Contrast hy_v3 #25395, whose single-depth head needed p_min=0.75.)Real-workload field evidence: a 2-hour agentic coding session (320K ctx configured, q4_0 KV,
-np 1) reached 264K context with thousands of draft/verify cycles and zero server-side failures, including clean cancel-on-client-disconnect. Final-task server log:draft acceptance = 0.86207 (150 accepted / 174 generated), mean len = 3.59.Determinism note. Greedy MTP-on and MTP-off outputs are quality-equivalent but not byte-identical: batched speculative verification evaluates N tokens' logits in one forward pass, whose floating-point reduction order differs from sequential decode and flips argmax at near-tie tokens. This is framework-level behavior, not specific to this port. The same A/B on an already-merged draft-mtp architecture (hy_v3, greedy top_k=1 temp=0 seed=42) diverges identically, with that arch's own MTP genuinely engaged. Raw control logs are in the linked dataset.
Relationship to #24868 (GLM 4.x MTP)
#24868 adds
llama_model_deepseek2::graph_mtpand plugs GLM-4.7-Flash (Glm4MoeLite) into draft-mtp. This PR is a different architecture (GLM_DSA: MLA with absorbed wk_b/wv_b plus DSA; the MTP layer runs dense MLA with the exact YaRN mscale^2 kq_scale) with a standaloneglm_dsa::graph_mtp. It is not a duplicate, but there is a real code-sharing question: if maintainers prefer consolidating GLM-family MTP builders on deepseek2-reuse (the route #24868 and out-of-tree implementations took), I am glad to restructure onto whichever lands first.Additional information
Reviewer notes, honestly flagged:
exceed_context_size_error; generation pastn_ctxcontext-shifts and finishes withfinish_reason=length; a context too large for VRAM fails the KV allocation and exits cleanly with an error message. The null-context path is guarded by server : properly handle null llama_context #25868, which this branch includes.Bad layer 78abort for GLM-5.2 nextn was already fixed upstream by quant : fix quantizing moe with mtp #24986 (n_layer_allFFN-counter seeding); blk.78 nextn tensors quantize through the standard path. quant: include MTP/NextN block when counting FFN layers (GLM-5.2) #24832 was a duplicate of that one-liner, closed after unrelated changes.Wiring was verified operation-by-operation against vLLM's GLM MTP reference (which routes
glm_moe_dsathroughdeepseek_mtp.py): concat order [embed, hidden], enorm/hnorm placement, eh_proj, attention dims and ranks and scale, MoE gating, shared-head order, and next-step hidden recycle. 8 of 9 items are exact matches. The one flagged divergence: the MTP layer runs dense MLA, matching this project's own GLM_DSA trunk, where vLLM uses the sparse DSA lightning indexer. This is a pre-existing whole-model design choice in llama.cpp, not an MTP wiring decision. The full audit is in the linked dataset.Credits: ik_llama.cpp pioneered out-of-tree GLM-DSA MTP (
build_deepseek2_mtp); this port validates against its behavior and follows mainline's draft-mtp framework (#18886 / #22673 / #23269 / #23287 / #23643). Landscape also surveyed: xj85770's reference patch and the Mesh-LLM sidecar implementation.Regression:
--spec-type draft-mtpis not used. Mitigations shipped here:--no-mtpat conversion strips the block, and--mtpproduces a draft-only split. This matches the merged hy_v3 behavior; if maintainers prefer a load-time gate instead, happy to discuss.--spec-typeis unchanged (graph identical; nextn tensors are loaded but not executed).test-llama-archs: 436 OK / 0 failures, glm_dsa included.AI usage disclosure
Yes. Full transparency: this implementation was written predominantly by an AI system (Anthropic Claude) working under the direction of @satindergrewal, who commissioned, reviewed, and hardware-tested the resulting builds over multi-hour real workloads, and who takes responsibility for the contribution. The port maps an existing implementation (ik_llama.cpp's glm-dsa MTP) onto mainline's existing draft-mtp framework and verifies wiring against vLLM's reference semantics rather than inventing new design. We understand this project restricts predominantly AI-generated contributions and we defer to the maintainers' judgment: if this cannot be accepted under policy, the PR still serves as a documented, validated reference for whoever implements it by hand, and we are glad to assist them.