Skip to content

GLM-DSA: improve TG performance even more - #2068

Merged
ikawrakow merged 3 commits into
mainfrom
ik/glm_dsa_opt3
Jul 6, 2026
Merged

ikawrakow merged 3 commits into
mainfrom
ik/glm_dsa_opt3

Conversation

@ikawrakow

@ikawrakow ikawrakow commented Jul 2, 2026

Copy link
Copy Markdown
Owner

This PR follows in the footsteps of #2066 and #2067 and further improves GLM TG performance when using DSA. TG performance is now better than no DSA for context length greater than 8k tokens or so.

For TG the trick to take advantage of sparse attention is relatively simple: we just need to take out from the MLA cache the n_top_k keys selected by the indexer, and then do FA with just those. Hence, past 2k tokens (n_top_k = 2048 for GLM-5), FA is constant time per generated token. TG performance still slightly degrades with context length as the cost of the indexer increases linearly with context, but overall the performance decrease is very slow (see table below).

Below are some sweep-bench results with this PR compared to no-DSA. GLM-5.2-Q4_K_M, 13x3090 (limited to 200W each), Ryzen-3995WX CPU. Command line is

GGML_CUDA_NO_PINNED=1 ./bin/llama-sweep-bench \
    -m /zfsdata/data/GLM-5.2-GGUF/UD-Q4_K_M/GLM-5.2-UD-Q4_K_M-00001-of-00011.gguf \
    -t 64 -ngl 100 -ub 2048 -c 65536 -n 64 -amb 256 -wgt 1 \
    --fit --fit-margin 2048 --gpu-fit-margin 0,6144 [-dsa]

which results in routed experts in 37 layers being left in RAM.

No DSA

PP TG N_KV T_PP s S_PP t/s T_TG s S_TG t/s
2048 64 0 17.296 118.41 5.755 11.12
2048 64 2048 17.964 114.01 5.919 10.81
2048 64 4096 18.838 108.72 5.955 10.75
2048 64 6144 19.827 103.29 5.983 10.70
2048 64 8192 20.845 98.25 6.087 10.51
2048 64 10240 21.607 94.78 6.153 10.40
2048 64 12288 22.630 90.50 6.223 10.28
2048 64 14336 23.640 86.63 6.274 10.20
2048 64 16384 24.656 83.06 6.386 10.02
2048 64 18432 24.930 82.15 6.441 9.94
2048 64 20480 25.760 79.50 6.510 9.83
2048 64 22528 26.875 76.21 6.558 9.76
2048 64 24576 27.895 73.42 6.677 9.59
2048 64 26624 28.774 71.18 6.785 9.43
2048 64 28672 29.821 68.68 6.798 9.42
2048 64 30720 30.708 66.69 6.882 9.30
2048 64 32768 31.620 64.77 6.992 9.15
2048 64 34816 32.508 63.00 7.111 9.00
2048 64 36864 39.366 52.02 7.076 9.05
2048 64 38912 40.443 50.64 7.176 8.92
2048 64 40960 41.706 49.11 7.273 8.80
2048 64 43008 42.938 47.70 7.274 8.80
2048 64 45056 44.136 46.40 7.416 8.63
2048 64 47104 45.519 44.99 7.444 8.60
2048 64 49152 46.851 43.71 7.474 8.56
2048 64 51200 48.149 42.53 7.531 8.50
2048 64 53248 49.261 41.57 7.615 8.40
2048 64 55296 50.569 40.50 7.651 8.37
2048 64 57344 51.710 39.61 7.765 8.24
2048 64 59392 52.900 38.71 7.802 8.20
2048 64 61440 54.233 37.76 7.875 8.13
2048 64 63488 55.434 36.94 7.918 8.08

DSA (this PR)

PP TG N_KV T_PP s S_PP t/s T_TG s S_TG t/s
2048 64 0 17.767 115.27 5.865 10.91
2048 64 2048 18.530 110.53 6.027 10.62
2048 64 4096 19.720 103.85 6.062 10.56
2048 64 6144 21.038 97.35 6.078 10.53
2048 64 8192 22.202 92.24 6.093 10.50
2048 64 10240 23.409 87.49 6.115 10.47
2048 64 12288 24.580 83.32 6.139 10.42
2048 64 14336 25.767 79.48 6.132 10.44
2048 64 16384 27.159 75.41 6.143 10.42
2048 64 18432 27.805 73.66 6.153 10.40
2048 64 20480 29.058 70.48 6.161 10.39
2048 64 22528 30.311 67.57 6.214 10.30
2048 64 24576 31.310 65.41 6.196 10.33
2048 64 26624 32.364 63.28 6.230 10.27
2048 64 28672 33.615 60.93 6.221 10.29
2048 64 30720 34.585 59.22 6.271 10.21
2048 64 32768 35.748 57.29 6.236 10.26
2048 64 34816 36.888 55.52 6.250 10.24
2048 64 36864 43.920 46.63 6.258 10.23
2048 64 38912 45.225 45.28 6.293 10.17
2048 64 40960 46.860 43.70 6.305 10.15

The DSA run aborts with OOM on device 12 at a context of 43008 tokens, so I guess I need a larger margin there as well (so one less MoE layer offloaded). But we see that at a context of 40k tokens DSA TG is ~15% better than no DSA, with the gap likely increasing even further at longer context. PP is of course still worse by 10-15%, but improving this will be a separate effort.

@ikawrakow

Copy link
Copy Markdown
Owner Author

OK, with

GGML_CUDA_NO_PINNED=1 ./bin/llama-sweep-bench \
    -m /zfsdata/data/GLM-5.2-GGUF/UD-Q4_K_M/GLM-5.2-UD-Q4_K_M-00001-of-00011.gguf \
    -t 64 -ngl 100 -ub 2048 -c 65536 -n 64 -amb 256 -wgt 1 \
    --fit --fit-margin 2048 --gpu-fit-margin 0,6144,12,6144 -dsa

I can go to a context of 55k tokens, but then get OOM in device 2. But here the sweep bench result in this case

PP TG N_KV T_PP s S_PP t/s T_TG s S_TG t/s
2048 64 0 17.891 114.47 5.942 10.77
2048 64 2048 18.741 109.28 6.116 10.47
2048 64 4096 20.064 102.08 6.098 10.50
2048 64 6144 21.314 96.09 6.150 10.41
2048 64 8192 22.572 90.73 6.166 10.38
2048 64 10240 23.768 86.16 6.178 10.36
2048 64 12288 24.845 82.43 6.228 10.28
2048 64 14336 25.981 78.83 6.251 10.24
2048 64 16384 27.287 75.05 6.209 10.31
2048 64 18432 27.971 73.22 6.228 10.28
2048 64 20480 29.138 70.29 6.267 10.21
2048 64 22528 30.380 67.41 6.279 10.19
2048 64 24576 31.470 65.08 6.308 10.15
2048 64 26624 32.532 62.95 6.291 10.17
2048 64 28672 33.628 60.90 6.291 10.17
2048 64 30720 34.756 58.93 6.362 10.06
2048 64 32768 35.974 56.93 6.300 10.16
2048 64 34816 37.088 55.22 6.325 10.12
2048 64 36864 44.061 46.48 6.329 10.11
2048 64 38912 45.387 45.12 6.354 10.07
2048 64 40960 46.926 43.64 6.371 10.05
2048 64 43008 48.350 42.36 6.406 9.99
2048 64 45056 49.893 41.05 6.412 9.98
2048 64 47104 51.358 39.88 6.399 10.00
2048 64 49152 52.960 38.67 6.403 10.00
2048 64 51200 54.274 37.73 6.457 9.91
2048 64 53248 55.613 36.83 6.426 9.96
2048 64 55296 57.192 35.81 6.447 9.93

@saood06

saood06 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

TG performance is now better than no DSA for context length greater than 8k tokens or so.
but overall the performance decrease is very slow (see table below).

Nice, the performance drop for TG looks really flat. Any chance you have the patience to run the sweep bench comparison with pure CPU to see how flat it is and where the performance cutoff is without any GPUs.

@ikawrakow

Copy link
Copy Markdown
Owner Author

Running CPU only is sluggish - <4 t/s TG and <30 t/s PP (for short context), so getting up to 64k tokens will take quite some time.

For CPU-only I'm getting somewhat more usable performance with MTP - about 5.5 t/s for a query with not particularly high acceptance rate. But we don't have something like sweep-bench with MTP setup. Oh, there is also the fact that DSA is not (yet) implemented for the MTP layer, so my guess is MTP performance will suffer badly at very long context.

I'm also interested to see what happens with full GPU offload. In these benchmarks TG performance is strongly dominated by computing the MoE FFN for 37 layers on the CPU, so DSA performance improvement for long context is limited by that. But I'm curious to know how this implementation compares to what z.AI have reported (i.e., do we get 6X better TG compared to MLA at very long context), so if someone has the hardware to run with full GPU offload, please let us know.

@saood06

saood06 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

I'm also interested to see what happens with full GPU offload.

I was going to suggest something but then realized that because of the fact that the indexer weights exist for every block means that unless someone splits their GGUF in a way that intentionally makes it possible to just download the indexer weights you can't download them separately (and as far as I can find no one has done that yet), and bolt them onto an existing GGUF.

@ikawrakow

Copy link
Copy Markdown
Owner Author

@saood06

OK, here the sweep-bench CPU-only on a Ryzen-3995WX using GLM-5.2-Q4_K_M from Unsloth:

MLA

PP TG N_KV T_PP s S_PP t/s T_TG s S_TG t/s
4096 32 0 111.375 36.78 8.268 3.87
4096 32 4096 157.545 26.00 8.846 3.62
4096 32 8192 211.841 19.34 10.406 3.08
4096 32 12288 247.579 16.54 10.980 2.91
4096 32 16384 314.688 13.02 11.484 2.79
4096 32 20480 353.252 11.60 11.711 2.73
4096 32 24576 390.263 10.50 12.177 2.63
4096 32 28672 431.292 9.50 12.701 2.52
4096 32 32768 468.686 8.74 13.213 2.42
4096 32 36864 558.137 7.34 13.776 2.32
4096 32 40960 602.965 6.79 14.224 2.25
4096 32 45056 633.496 6.47 14.706 2.18
4096 32 49152 677.268 6.05 15.274 2.10
4096 32 53248 712.104 5.75 15.717 2.04
4096 32 57344 743.216 5.51 16.289 1.96
4096 32 61440 789.242 5.19 16.809 1.90

DSA

PP TG N_KV T_PP s S_PP t/s T_TG s S_TG t/s
4096 32 0 122.268 33.50 8.230 3.89
4096 32 4096 181.537 22.56 9.874 3.24
4096 32 8192 244.238 16.77 10.058 3.18
4096 32 12288 289.793 14.13 10.192 3.14
4096 32 16384 371.025 11.04 10.376 3.08
4096 32 20480 426.061 9.61 10.542 3.04
4096 32 24576 469.453 8.73 10.688 2.99
4096 32 28672 515.636 7.94 10.816 2.96
4096 32 32768 557.992 7.34 10.848 2.95
4096 32 36864 662.354 6.18 10.906 2.93
4096 32 40960 713.191 5.74 11.008 2.91
4096 32 45056 748.069 5.48 11.178 2.86
4096 32 49152 801.007 5.11 11.244 2.85
4096 32 53248 848.963 4.82 11.311 2.83
4096 32 57344 887.810 4.61 11.443 2.80
4096 32 61440 934.813 4.38 11.513 2.78

So, nearly 1.5X for TG ar 64k tokens. As stated earlier, PP needs more work.

Btw, existing GGUFs do contain the indexer tensors, at least those from Unsloth do. That's where I took the GLM-5.2 model that I'm using for testing.

@saood06

saood06 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@saood06

OK, here the sweep-bench CPU-only on a Ryzen-3995WX using GLM-5.2-Q4_K_M from Unsloth:
So, nearly 1.5X for TG ar 64k tokens. As stated earlier, PP needs more work.

Thank you! The curve did flatten significantly, and the crossover point for performance stays around 8K. This plus MTP (assuming my workload gets good enough acceptance rates) will make V3.2 look a lot more attractive than the older versions of Deepseek (where I started at 3.5 t/s at 0 context and dropped fast). Performance with DSA on V3.2 might not be as nice given no IndexShare/IndexCache on that model.

Btw, existing GGUFs do contain the indexer tensors, at least those from Unsloth do. That's where I took the GLM-5.2 model that I'm using for testing.

Yes for GLM-5/5.1/5.2 that seems to be the case but not for Speciale which I thought the box you use had a pure GPU quant for.

@ikawrakow

Copy link
Copy Markdown
Owner Author

I haven't tried any of this with DeepSeek Speciale. Has anyone else tried?

@saood06

saood06 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

I haven't tried any of this with DeepSeek Speciale. Has anyone else tried?

Just in case anyone wants links of premade GGUFs that have the indexer for DSA, here is Speciale, V3.2, and V3.2-Exp. They come in Q8_0 and Q4_K_M, but no imatrix was used for either quant.

As reference, "DeepSeek-V3.2-Speciale variant is designed exclusively for deep reasoning tasks and does not support the tool-calling functionality", V3.2 is the "normal" variant, V3.2-Exp is an earlier checkpoint with weaker benchmarks than V3.2.

@usrlocalben

Copy link
Copy Markdown
Contributor

@ikawrakow I've been testing this PR w/MTP, no crash - what conditions cause crash?

@ikawrakow

Copy link
Copy Markdown
Owner Author

@ikawrakow I've been testing this PR w/MTP, no crash - what conditions cause crash?

Running CPU-only. Possibly the crash is not here but in the next PR. As I wasn't sure, I decided to push the change to the base of the currently opened performance improvement PR.

@ikawrakow

Copy link
Copy Markdown
Owner Author

@usrlocalben

Btw, I saw you discussing elsewhere that even with DSA the model becomes incoherent after 100k tokens. On the current branches the matrix multiplication between the indexer cache and the query is done using f16 precision. Perhaps one should go to f32? Or, if you checkout #2075, you could use bf16 for the indexer cache to see if this improves the situation.

From past experience, some models may also need adjustment of the FA offset for sufficiently long context. You could try adding -cuda fa-offset=2 or -cuda fa-offset=1.38 to see if that helps.

@usrlocalben

Copy link
Copy Markdown
Contributor

@usrlocalben

Btw, I saw you discussing elsewhere that even with DSA the model becomes incoherent after 100k tokens. On the current branches the matrix multiplication between the indexer cache and the query is done using f16 precision. Perhaps one should go to f32? Or, if you checkout #2075, you could use bf16 for the indexer cache to see if this improves the situation.

From past experience, some models may also need adjustment of the FA offset for sufficiently long context. You could try adding -cuda fa-offset=2 or -cuda fa-offset=1.38 to see if that helps.

My plan for today was to work on a clean, minimal reproduction, prompt, etc. for an Issue but on a re-test earlier my 100K test gave answer-correct. 🤷 I'll keep these two ideas in mind as I continue.

@usrlocalben

usrlocalben commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

segfault as soon as tg begins w/mtp enabled @ 0249a69

edit: disregard.

Comment thread src/graphs/build_deepseek2.cpp Outdated
}

last_sparse_mask = last_sparse_mask_fa = nullptr;
last_sparse_mask = last_sparse_mask_fa = dsa_last_full_sorted = dsa_tg_fast_mask = nullptr;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this breaks non-MTP

@ikawrakow
ikawrakow merged commit 4130ecc into main Jul 6, 2026
@saood06

saood06 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

I haven't tried any of this with DeepSeek Speciale. Has anyone else tried?

I tried V3.2 (Q8_0 which is the same shape as Speciale)

DSA:
--numa distribute -t 40 -tb 80 -mla 1 -fidx --dsa -c 64000 -b 4096 -ub 4096 -amb 1024

PP TG N_KV T_PP s S_PP t/s T_TG s S_TG t/s
4096 1024 0 201.319 20.35 360.588 2.84
4096 1024 4096 206.257 19.86 362.766 2.82
4096 1024 8192 209.281 19.57 370.261 2.77
4096 1024 12288 214.693 19.08 377.433 2.71
4096 1024 16384 226.428 18.09 396.338 2.58
4096 1024 20480 230.769 17.75 407.045 2.52
4096 1024 24576 234.997 17.43 402.326 2.55
4096 1024 28672 239.270 17.12 395.039 2.59
4096 1024 32768 240.542 17.03 402.395 2.54

MLA:
--numa distribute -t 40 -tb 80 -mla 3 -c 64000 -b 4096 -ub 4096 -amb 1024

PP TG N_KV T_PP s S_PP t/s T_TG s S_TG t/s
4096 1024 0 126.806 32.30 328.039 3.12
4096 1024 4096 176.028 23.27 382.591 2.68
4096 1024 8192 221.271 18.51 423.430 2.42
4096 1024 12288 267.497 15.31 454.968 2.25
4096 1024 16384 314.216 13.04 494.416 2.07
4096 1024 20480 361.262 11.34 528.965 1.94
4096 1024 24576 407.718 10.05 555.807 1.84
4096 1024 28672 456.113 8.98 590.914 1.73
4096 1024 32768 500.126 8.19 629.808 1.63
4096 1024 36864 552.535 7.41 664.462 1.54

I don't know if anyone else has tried but if anyone does try with the quant above (or something similar to it) the patch below can be used to make them load.

git diff
diff --git a/src/llama-arch.cpp b/src/llama-arch.cpp
index bf07387f..9d94b7b2 100644
--- a/src/llama-arch.cpp
+++ b/src/llama-arch.cpp
@@ -81,7 +81,7 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {
     { LLM_ARCH_SEED_OSS,        "seed_oss"     },
     { LLM_ARCH_STEP35,          "step35"       },
     { LLM_ARCH_LAGUNA,          "laguna"       },
-    { LLM_ARCH_GLM_DSA,         "glm-dsa"      },
+    { LLM_ARCH_GLM_DSA,         "deepseek32"   },
     { LLM_ARCH_MISTRAL4,        "mistral4"     },
     { LLM_ARCH_GEMMA4,          "gemma4"       },
     { LLM_ARCH_GEMMA4_MTP,      "gemma4_mtp"   },
diff --git a/src/llama-load-tensors.cpp b/src/llama-load-tensors.cpp
index 3ece0f96..9989fa9d 100644
--- a/src/llama-load-tensors.cpp
+++ b/src/llama-load-tensors.cpp
@@ -2942,8 +2942,8 @@ bool create_tensors_helper::create_glm_dsa_tensors(const LLM_TN & tn) {
         }

             // Incompatible mainline model. Let's see if we can still load it
-        layer.wk_b = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_K_B, "weight", i), {n_embd_head_qk_nope, kv_lora_rank, n_head}, 0);
-        layer.wv_b = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_V_B, "weight", i), {kv_lora_rank, n_embd_head_v, n_head}, 0);
+        layer.wk_b = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_K_B, "weight", i), {n_embd_head_qk_nope, kv_lora_rank, n_head}, flags);
+        layer.wv_b = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_V_B, "weight", i), {kv_lora_rank, n_embd_head_v, n_head}, flags);
         layer.wo   = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_head * n_embd_head_v, n_embd}, flags);

                        // DSA indexer

I'm actually working on refining my own quants of this while testing to see if I like this model. Once I get something I like I plan to put it up on huggingface. It will be made with ik_llama.cpp in mind so I'll use the architecture name from here, and include the MTP tensors which should eliminate the need for the above patch.

My current mix:

--custom-q "token_embd\.weight=q4_K,indexer.proj.weight=f32,indexer*=q8_k_r8,attn*=q8_k_r8,output\.weight=q6_K,.*=iq4_k_r4"`

llm_load_print_meta: model size       = 357.733 GiB (4.574 BPW)
llm_load_print_meta: repeating layers = 356.540 GiB (4.571 BPW, 670.025 B parameters)
llama_model_loader: - type  f32:  544 tensors
llama_model_loader: - type q8_0:   61 tensors
llama_model_loader: - type q4_K:    1 tensors
llama_model_loader: - type q6_K:    1 tensors
llama_model_loader: - type iq4_k_r4:  357 tensors
llama_model_loader: - type q8_k_r8:  427 tensors

--numa mirror -t 40 -tb 80 -mla 1 -fidx --dsa -c 64000 -b 4096 -ub 4096 -amb 1024

PP TG N_KV T_PP s S_PP t/s T_TG s S_TG t/s
4096 1024 0 227.420 18.01 226.285 4.53
4096 1024 4096 244.664 16.74 264.343 3.87
4096 1024 8192 239.134 17.13 268.480 3.81
4096 1024 12288 244.327 16.76 271.745 3.77
4096 1024 16384 247.081 16.58 273.762 3.74
4096 1024 20480 266.570 15.37 275.348 3.72
4096 1024 24576 258.111 15.87 279.483 3.66
4096 1024 28672 279.137 14.67 281.671 3.64
4096 1024 32768 282.790 14.48 281.493 3.64
4096 1024 36864 285.670 14.34 284.726 3.60
4096 1024 40960 303.297 13.50 286.052 3.58

I know it can be faster given I did extensive testing on ~4.5BPW Deepseek models with this new machine and certain quants got 5.35 TG at N_KV 0 with MLA ( and hopefully even faster once I add MTP to the model if it is beneficial with my workloads and system). I'm going to pare this down closer to IQ4_K. Also I did find mirror to be the best performing in my tests holding it's advantage even at high context values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants