GLM 5.2 Indexer support#25407
Conversation
Just follow Deepseek 3.2 for now.
This is transformers' `apply_rotary_pos_emb_interleave`
Previous converted GGUFs like https://huggingface.co/unsloth/GLM-5.2-GGUF write indexer weights to _all_ layers, even if they are only required for "full" types. This PR relies on a new key "%s.attention.indexer.types"; if absent, it will use the default GLM 5.2 schedule as defined in https://huggingface.co/zai-org/GLM-5.2/blob/main/config.json#L26. Note that conversion is not saving this key yet.
| #include "llama-kv-cache-dsa.h" | ||
|
|
||
| // https://huggingface.co/zai-org/GLM-5.2/blob/main/config.json#L26 | ||
| const std::array<uint32_t, LLAMA_MAX_LAYERS> GLM_DSA_DEFAULT_INDEXER_TYPES = { |
There was a problem hiding this comment.
I considered the alternative to store just two numbers to represent the frequency of full indexers, and the number of prefix full indexers before the regular pattern. I decided towards the array because:
- There are also precedents in the codebase for irregular patterns.
- Deepseek 3.2 could be included in this pattern via
get_key_or_arrwith a single scalar (all DS indexers are "full").
|
|
||
| switch (hparams.n_layer()) { | ||
| case 79: type = LLM_TYPE_744B_A40B; break; | ||
| case 78: type = LLM_TYPE_744B_A40B; break; |
There was a problem hiding this comment.
I think this is a previous inoffensive bug introduced during the n_layer refactor; n_layer() excludes MTP layers so this should be 78.
| return std::make_unique<graph>(*this, params); | ||
| } | ||
|
|
||
| llama_model_glm_dsa::graph::graph(const llama_model & model, const llm_graph_params & params) : |
There was a problem hiding this comment.
Same graph as Deepseek v3.2, except for the two differences noted in the PR description.
I didn't want to touch other modules, but we can generalize these changes into Deepseek's and reuse the graph here.
|
As I understand GLM 5.2 uses the same lighting indexer as DS3.2 but the top-k is shared across 4 layers. If this is correct then #24231 should be used |
Yes, indeed, thanks for linking to it, I had missed the recent activity there. My understanding is that #24231 is about creating a new op that we can use from both the Deepseek and gml-dsa modeling files when it's merged, and when cuda / metal implementations are added. This PR (mine) is about contributing the remaining aspects of the architecture that were deferred from the initial GLM 5.2 implementation, and that are mostly available in the deepseek 3.2 one. But I'm new to the project, happy to coordinate with the team as necessary! |
|
Yes, my comment is for your awareness w.r.t. to the lightning indexer and not anything else different for GLM5.2 |
|
Tested this branch on 8× RTX 5090 (sm_120), GLM-5.2 UD-IQ2_M, all layers on
(depth-0 decode averaged over 5 repeats; gap stable and well outside the error bars.) So I reproduce the slowdown you flagged, and it has a clear shape: the prefill I also confirmed correctness holds: greedy generations are byte-for-byte The wiring looks right; from the perfomance side looks like it wants a fused |
|
Thanks @Dorijan10 for the tests 🙌 Yes, this PR is about fidelity to the original modeling code, and as explained it follows exactly the same pattern Deepseek v3.2 does. I think performance updates would have to be adopted separately, as @am17an pointed out. We could also do everything at once, but I thought it'd be easier for review this way. |
|
@pcuenca I'm testing this PR on my machine with GLM-5.1 as I have it on disk. Overall it looks good, but I found the following issues so far:
Edit: but update your branch to the current master first, otherwise it won't work correctly. |
Ah, I introduced the default to support the existing unsloth GLM 5.2 checkpoints without conversion, must have forgotten about GLM 5 and 5.1 when I did it 🤦 I'll revisit this process. Sorry! Thanks a lot for testing and pointing out these issues @fairydreaming, I'll sync with master and adapt. |
@pcuenca I see, indeed they miss this info. Maybe you could simply check context length, I think it's in |
|
Confirmed on the unsloth GLM-5.2 UD-IQ2_M GGUF: I am also set up on GLM-5.2 & 8×RTX 5090 to run the KLD vs reference you mentioned you have not done yet (dense-master ref staged over wikitext-2 at |
Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>
|
I synced with I'm still undecided about the 5.1 vs 5.2-without-metadata discrimination; looking at context_length (or Another solution would be for me to open PRs to the unsloth 5.2 repos to update the metadata of converted models, once this PR is directionally approved so we exactly know what fields to update. This would potentially incur in re-downloads from existing users (they would be cheap with xet, but not without). |
@pcuenca Not really, from my understanding the metadata fields have to be explicitly set by passing a metadata file during conversion ( One example of how it has been done in the past is here: llama.cpp/src/models/deepseek2.cpp Lines 7 to 8 in 86d86ed As you can see field values originating from
I think they keep all metadata separate from the weights in a very small file, so it wouldn't be a large redownload. But generally if we can avoid reconversion/redownload by using some default settings I'd say let's do it. All future GGUF files that need specific indexer type patterns will have |
Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>
Done in b6f2945, thanks again! |
|
I extracted sparse attention changes from my old DeepSeek V3.2 implementation and created PR #25917. I think it should work without any changes with this GLM PR, will be testing it later today. Would be nice if someone could benchmark the current dense attention implementation vs sparse one (with applied PR #25917) for large context sizes, so for example: Edit: on my machine (Epyc 9374F + RTX PRO 6000 Max-Q, this PR: this PR + #25917 so it is faster (or rather it doesn't get slow so quickly as the prompt length increases), but with expert offloading the speedup is less pronounced. |
|
Tested the synced branch (b6f2945) on 8x RTX 5090 (sm_120), GLM-5.2 unsloth The fused indexer path is active. Compute buffers at So roughly +100 MiB/card for the whole indexer. The old six-op chain would need Greedy generation is byte identical to dense master: 512 tokens from a short KLD against a dense master reference (wikitext-2 test, same weights and flags): Cor(ln PPL) is 99.14% at c=8192. The divergence from dense grows with context, Modeling looks correct to me on this model and hardware. I also stacked #25917 on top of this branch for perf testing and hit a separate issue at longer contexts, which I might follow up on separately @fairydreaming. It does not affect this PR. |
fairydreaming
left a comment
There was a problem hiding this comment.
From my (somewhat limited) testing this is good to merge.
@ngxson Since you created the initial GLM-DSA implementation can you take a look at this PR?
| 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); | ||
|
|
There was a problem hiding this comment.
This indentation change doesn't seem to serve any purpose.
There was a problem hiding this comment.
indentation change
haha yes, sorry, I added a line that was later removed, will fix!
I didn't actually added the GLM-DSA support, but rather I added a placeholder in the code. That being said, my knowledge about llama.cpp's indexer impl is very limited, so I'm running code-review skill #26042 just for an extra review. Items number 1 and 2 are worth addressing before merging (roadblock), but the rest is optional: Automated code reviewCode Review: PR #25407 — GLM 5.2 Indexer supportOverviewThis PR completes the GLM 5.2 modeling implementation by making the lightning indexer (DeepSeek sparse attention) functional. Previously the indexer tensors were loaded but ignored (GLM_DSA reused
I verified the supporting infrastructure against master: Findings1. (Medium)
2. (Low, verify) Load will throw if the converted
3. (Low)
4. (Low)
5. (Info) Layer-count
6. (Info, author-acknowledged) Decode slowdown.
Style / conventions
Correctness spot-checks that passed
RecommendationSolid, well-scoped PR. The one thing I'd fix before merge is finding #1 (trivial |
|
|
||
| // Indexer is "full" (1) or "shared" (0) | ||
| // Shared indexers reuse top-k from previous full layer | ||
| std::array<uint32_t, LLAMA_MAX_LAYERS> is_indexer_full_impl; |
There was a problem hiding this comment.
llama-model.cpp run std::fill for all arrays like this
|
I checked perplexity of GLM-5.1 and GLM-5.2 in mainline and this PR with wiki.test.raw and large 8k chunk size (so that indexer does some work):
Additionally I added a small test change to allow override of Regarding the automated code review, I confirmed that in the converted model So that covers point 2 from the automated review, I agree with point 1, this array shall be zero-initialized. Number 3 is easy to add. @pcuenca IMHO you can ignore the rest. |
|
Thanks for the review (nice Skill!) 🙌
|
Overview
Adds indexer support to GLM 5.2. Currently, indexer tensors are loaded when present, but ignored. If accepted, this PR would in my understanding complete the implementation of GLM 5.2 in terms of modeling.
The implementation just copies the graph from Deepseek 3.2 and applies the GLM 5.2 changes:
"full"indexers compute top_k,"shared"ones reuse top_k from the previous full layer. In Deepseek 3.2, all indexers are"full".Additional information
"full"indexers are used.Requirements