Skip to content

GLM 5.2 Indexer support - #25407

Merged
ngxson merged 13 commits into
ggml-org:masterfrom
pcuenca:glm-dsa-indexer
Jul 24, 2026
Merged

GLM 5.2 Indexer support#25407
ngxson merged 13 commits into
ggml-org:masterfrom
pcuenca:glm-dsa-indexer

Conversation

@pcuenca

@pcuenca pcuenca commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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:

  • Only "full" indexers compute top_k, "shared" ones reuse top_k from the previous full layer. In Deepseek 3.2, all indexers are "full".
  • The indexer uses interleaved rope, instead of the neox-style used by Deepseek 3.2.

Additional information

  • Existing unsloth checkpoints work without conversion. They contain indexer weights at every layer, but only those in "full" indexers are used.
  • Here's another checkpoint, quantized using this PR, that can be used for testing purposes. It uses unsloth's imatrix, and preserves indexer weights in Q8_0.
  • I verified that generations in greedy mode match the previous implementation for short sequences (indexers are essentially a no-op the first 2048 tokens). I have verified that long sequences remain coherent, but have not compared KLD against a reference implementation.
  • I was a bit surprised to find decoding speed to be much slower than with the previous (deepseek2-based) implementation. I haven't investigated the reason yet, if there are ways to make it faster, or if it happens across all sequence lengths.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES, as follows:
    • All code, debugging, and testing performed by a human 🙋‍♂️
    • PR description written by the same person
    • AI was used to ask about the codebase, navigate it, find patterns, identify idiomatic approaches.

pcuenca added 6 commits July 5, 2026 19:17
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.
@pcuenca
pcuenca requested review from CISC and ggerganov as code owners July 7, 2026 14:18
Comment thread src/models/glm-dsa.cpp Outdated
#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 = {

@pcuenca pcuenca Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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_arr with a single scalar (all DS indexers are "full").

Comment thread src/models/glm-dsa.cpp

switch (hparams.n_layer()) {
case 79: type = LLM_TYPE_744B_A40B; break;
case 78: type = LLM_TYPE_744B_A40B; break;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this is a previous inoffensive bug introduced during the n_layer refactor; n_layer() excludes MTP layers so this should be 78.

Comment thread src/models/glm-dsa.cpp
return std::make_unique<graph>(*this, params);
}

llama_model_glm_dsa::graph::graph(const llama_model & model, const llm_graph_params & params) :

@pcuenca pcuenca Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

@github-actions github-actions Bot added model Model specific conversion labels Jul 7, 2026
@am17an

am17an commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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

@pcuenca

pcuenca commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

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!

@am17an

am17an commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Yes, my comment is for your awareness w.r.t. to the lightning indexer and not anything else different for GLM5.2

@Dorijan10

Copy link
Copy Markdown

Tested this branch on 8× RTX 5090 (sm_120), GLM-5.2 UD-IQ2_M, all layers on
GPU, fa=1, q8_0 KV. This PR vs master (dense) on identical weights and flags
via llama-bench:

depth pp512 master pp512 PR Δ tg64 master tg64 PR
0 758.8 652.7 -14.0% 52.67 49.26
2048 704.4 566.0 -19.6% 51.05 44.99
8192 564.0 426.0 -24.5% 47.21 42.79

(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
gap widens monotonically with depth (14% → 24.5%), and there's a ~6.5% decode
hit even at depth 0, where top-k selects all tokens and the indexer should be a
near no-op. I believe we have two stacked costs: a fixed indexer overhead on the
full layers, and a depth-scaling cost from attending over the full sequence
(masked flash-attn) rather than gathering the selected keys.

I also confirmed correctness holds: greedy generations are byte-for-byte
identical to master below 2048 tokens, as expected.

The wiring looks right; from the perfomance side looks like it wants a fused
indexer/sparse-attention kernel doing an O(L·k) gather over the selected keys
instead of the generic masked path, which lines up with the dedicated-op
direction in #24231. I have the hardware to develop and validate that on
sm_120 and would like to take it on as a follow-up. Happy to share full logs /
op-placement traces, and to run KLD-vs-reference on the q3_k_m checkpoint.
cc @pcuenca @am17an

@pcuenca

pcuenca commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

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.

@fairydreaming

fairydreaming commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

@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:

  • I don't think it's a good idea to use GLM 5.2 indexer usage pattern as default. This code is also used for GLM 5 and GLM 5.1, AFAIK they used indexer in all layers and this should be the default setting. @ngxson please correct me if I'm wrong.
  • after merging lightning indexer PRs indexer mask is now F16 if ggml_lightning_indexer() is available for the used backend. Unfortunately it fails with your code as you can't add F16 mask to F32 indexer scores in GGML. This can be fixed with this patch (adds ggml_lightning_indexer() call if it's available and uses previous indexer implementation if it's not), I did the same changes to DeepSeek V3.2 code few days ago:
diff --git a/src/models/glm-dsa.cpp b/src/models/glm-dsa.cpp
index f7109cbd4..2687266bd 100644
--- a/src/models/glm-dsa.cpp
+++ b/src/models/glm-dsa.cpp
@@ -328,43 +328,50 @@ llama_model_glm_dsa::graph::graph(const llama_model & model, const llm_graph_par
                 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);
 
-                // calculate indexer kq
-                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);
-
-                // apply ReLU
-                ggml_tensor * indexer_score = ggml_relu(ctx0, indexer_kq);
-                cb(indexer_score, "indexer_score", il);
-
                 // 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);
 
-                // multiply scores by indexer weights
-                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);
-
-                // mask indexer scores
-                ggml_tensor * indexer_kq_mask = inp_attn_dsa->get_kq_mask_lid();
-                indexer_score = ggml_add(ctx0, indexer_score, indexer_kq_mask);
-                cb(indexer_score, "indexer_score", il);
+                ggml_tensor * indexer_score = nullptr;
+                if (cparams.fused_lid) {
+                    indexer_score = ggml_lightning_indexer(ctx0, indexer_q, indexer_k, indexer_weights, inp_attn_dsa->get_kq_mask_lid());
+                    cb(indexer_score, "indexer_score", il);
+                    res->add_fused_node({LLM_FUSED_OP_LIGHTNING_INDEXER, indexer_score, il});
+                } else {
+                    // calculate indexer kq
+                    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);
+
+                    // apply ReLU
+                    indexer_score = ggml_relu(ctx0, indexer_kq);
+                    cb(indexer_score, "indexer_score", il);
+
+                    // multiply scores by indexer weights
+                    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);
+
+                    // mask indexer scores
+                    ggml_tensor * indexer_kq_mask = inp_attn_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;

Edit: but update your branch to the current master first, otherwise it won't work correctly.

@pcuenca

pcuenca commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

I don't think it's a good idea to use GLM 5.2 indexer usage pattern as default

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.

@fairydreaming

Copy link
Copy Markdown
Collaborator

I don't think it's a good idea to use GLM 5.2 indexer usage pattern as default

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!

@pcuenca I see, indeed they miss this info. Maybe you could simply check context length, I think it's in hparams.n_ctx_train - if it's 202752 then it's GLM 5 or 5.1, if it's 1048576 then it's GLM 5.2 and provide two default arrays?

@Dorijan10

Copy link
Copy Markdown

Confirmed on the unsloth GLM-5.2 UD-IQ2_M GGUF: context_length = 1048576 and no attention.indexer.types key, so it does hit the default, e.g. the 1048576 -> GLM-5.2 checks out.

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 -c 8192). Ping me once you push the synced branch. @pcuenca

pcuenca and others added 2 commits July 17, 2026 17:05
@pcuenca

pcuenca commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

I synced with master and added the fused_lid path, thanks again @fairydreaming!

I'm still undecided about the 5.1 vs 5.2-without-metadata discrimination; looking at context_length (or glm-dsa.rope.freq_base) seems fragile to me. Would it be better to use the metadata general.version field? I'm not familiar enough with how similar situations have been resolved in the past.

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).

@fairydreaming

Copy link
Copy Markdown
Collaborator

I'm still undecided about the 5.1 vs 5.2-without-metadata discrimination; looking at context_length (or glm-dsa.rope.freq_base) seems fragile to me. Would it be better to use the metadata general.version field? I'm not familiar enough with how similar situations have been resolved in the past.

@pcuenca Not really, from my understanding the metadata fields have to be explicitly set by passing a metadata file during conversion (convert_hf_to_gguf.py --metadata) and usually most of them are missing in locally converted GGUF files. So it's the metadata fields that are the fragile source of information (frequently not present). I don't remember ever seeing them used in model implementations.

One example of how it has been done in the past is here:

// lite variants include DeepSeek-V2-Lite, GigaChat3-10B-A1.8B, Kanana-2-30B-A3B
const bool is_lite = (hparams.n_layer() == 27 || hparams.n_layer() == 26 || (hparams.n_layer() == 48 && n_vocab == 128256));

As you can see field values originating from config.json are used here to discriminate between model variants. So we have a precedent in case of a trial. ;-)

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).

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 glm-dsa.attention.indexer.types field present after this PR is merged, so it shouldn't be a problem.

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>
@pcuenca

pcuenca commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

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 glm-dsa.attention.indexer.types field present after this PR is merged, so it shouldn't be a problem.

Done in b6f2945, thanks again!

@fairydreaming

fairydreaming commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

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:

./bin/llama-batched-bench -m /mnt/md0/models/GLM-5.2-Q8_0.gguf -b 2048 -ub 2048 -npl 1 -npp 2048,4096,8192,16384,32768,65536,131072,262144,524288,1048064 -ntg 128 -fa 1

Edit: on my machine (Epyc 9374F + RTX PRO 6000 Max-Q, -cmoe -ctk q8_0 -ctv q8_0) I have:

this PR:

llama_batched_bench: n_kv_max = 1048576, n_batch = 2048, n_ubatch = 2048, flash_attn = 1, is_pp_shared = 0, is_tg_separate = 0, n_gpu_layers = -1, n_threads = 32, n_threads_batch = 32

|    PP |     TG |    B |   N_KV |   T_PP s | S_PP t/s |   T_TG s | S_TG t/s |      T s |    S t/s |
|-------|--------|------|--------|----------|----------|----------|----------|----------|----------|
|  2048 |    128 |    1 |   2176 |   34.144 |    59.98 |   12.699 |    10.08 |   46.842 |    46.45 |
|  4096 |    128 |    1 |   4224 |   56.870 |    72.02 |   12.704 |    10.08 |   69.574 |    60.71 |
|  8192 |    128 |    1 |   8320 |  114.826 |    71.34 |   12.908 |     9.92 |  127.734 |    65.14 |
| 16384 |    128 |    1 |  16512 |  233.358 |    70.21 |   13.174 |     9.72 |  246.532 |    66.98 |
| 32768 |    128 |    1 |  32896 |  485.915 |    67.44 |   13.573 |     9.43 |  499.489 |    65.86 |
| 65536 |    128 |    1 |  65664 | 1046.899 |    62.60 |   14.391 |     8.89 | 1061.290 |    61.87 |
|131072 |    128 |    1 | 131200 | 2422.271 |    54.11 |   16.481 |     7.77 | 2438.752 |    53.80 |

this PR + #25917

|    PP |     TG |    B |   N_KV |   T_PP s | S_PP t/s |   T_TG s | S_TG t/s |      T s |    S t/s |
|-------|--------|------|--------|----------|----------|----------|----------|----------|----------|
|  2048 |    128 |    1 |   2176 |   28.545 |    71.75 |   12.706 |    10.07 |   41.251 |    52.75 |
|  4096 |    128 |    1 |   4224 |   56.563 |    72.41 |   12.804 |    10.00 |   69.367 |    60.89 |
|  8192 |    128 |    1 |   8320 |  115.395 |    70.99 |   12.921 |     9.91 |  128.315 |    64.84 |
| 16384 |    128 |    1 |  16512 |  234.931 |    69.74 |   13.013 |     9.84 |  247.943 |    66.60 |
| 32768 |    128 |    1 |  32896 |  477.294 |    68.65 |   13.158 |     9.73 |  490.451 |    67.07 |
| 65536 |    128 |    1 |  65664 |  972.275 |    67.40 |   13.436 |     9.53 |  985.712 |    66.62 |
|131072 |    128 |    1 | 131200 | 1973.457 |    66.42 |   14.340 |     8.93 | 1987.797 |    66.00 |

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.

@Dorijan10

Copy link
Copy Markdown

Tested the synced branch (b6f2945) on 8x RTX 5090 (sm_120), GLM-5.2 unsloth
UD-IQ2_M, all layers on GPU, -fa 1 -ctk q8_0 -ctv q8_0. This includes the KLD
vs reference comparison mentioned in the PR description as not yet done @pcuenca.

The fused indexer path is active. Compute buffers at -c 8192:

                  dense master       this PR
CUDA0                  304 MiB       425 MiB
CUDA1-7            316-364 MiB   388-468 MiB
CUDA_Host               56 MiB        88 MiB
graph nodes               6063          7134

So roughly +100 MiB/card for the whole indexer. The old six-op chain would need
~512 MiB just for the materialized score tensor at these dims
(8192 x 512 x 32 x 4 bytes), so the F16 mask fix is clearly doing its job here.

Greedy generation is byte identical to dense master: 512 tokens from a short
prompt, so the sequence stays well under the 2048 top_k threshold where the
indexer is still a no-op.

KLD against a dense master reference (wikitext-2 test, same weights and flags):

                    c=4096 (13 ch)   c=8192 (35 ch)
PPL (this PR)               2.9375           3.2970
PPL (dense base)            2.9353           3.2875
PPL(Q)/PPL(base)          1.000758         1.002908
RMS delta p                 4.61 %           5.34 %
Same top p                 95.21 %          94.17 %

Cor(ln PPL) is 99.14% at c=8192. The divergence from dense grows with context,
which is what you would expect as more positions go past top_k=2048 and drop a
larger fraction of the keys, while PPL stays almost flat. Same model base vs
base on this setup measures ~0.001% RMS, so the deviation is real, but it looks
like the DSA approximation doing its thing rather than wrong key selection: a
mis-wired indexer would move PPL a lot more than 0.29%, and the tokens that flip
top-1 are evidently low margin ones. Could do more KLD comparisons at even larger context lengths if needed.

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 fairydreaming left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Comment thread src/models/glm-dsa.cpp
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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This indentation change doesn't seem to serve any purpose.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

indentation change

haha yes, sorry, I added a line that was later removed, will fix!

Comment thread src/models/glm-dsa.cpp Outdated
@ngxson

ngxson commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

@ngxson Since you created the initial GLM-DSA implementation can you take a look at this PR?

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 review

Code Review: PR #25407 — GLM 5.2 Indexer support

Overview

This 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 deepseek2::graph, i.e. plain MLA attention). The PR:

  • Adds a new per-layer GGUF KV {arch}.attention.indexer.types (a bool array: full=1 / shared=0) plus writer/conversion plumbing.
  • Adds hparams.is_indexer_full(il) and the is_indexer_full_impl array.
  • Gives GLM_DSA its own graph (src/models/glm-dsa.cpp), copied from deepseek32.cpp with two deltas: (1) indexer RoPE is interleaved (LLAMA_ROPE_TYPE_NORM) rather than NeoX, and (2) shared indexer layers reuse the previous full layer's top_k.
  • Routes GLM_DSA to the DSA KV cache (llama_kv_cache_dsa) and enables Hadamard rotation tensors.
  • Fixes the layer-count case 79 → 78 and an assert message.

I verified the supporting infrastructure against master: get_arr correctly handles a BOOL GGUF array → uint32_t (int8→transform path, same as is_recr_impl), the indexer RoPE type differs from deepseek32 as claimed, GLM_DSA's main-attention rope is already NORM, and GLM_DSA is not duplicated in the create_memory switch (previously handled by default).


Findings

1. (Medium) is_indexer_full_impl is never default-initialized, but the model-saver serializes it for every architecture → reads uninitialized memory (UB).

  • In llama-hparams.h:231 the new member has no initializer:
    std::array<uint32_t, LLAMA_MAX_LAYERS> is_indexer_full_impl;
  • It is only ever populated in glm-dsa.cpp::load_arch_hparams. For any other arch, it stays uninitialized.
  • llama-model-saver.cpp:283 adds it unconditionally with per_layer=true, which reads value[0..n_layer()-1]. llama_model_saver_supports_arch() is an allow-by-default blocklist, so this path runs for LLAMA, Qwen, DeepSeek, etc. → UB read and a garbage indexer.types KV written into re-saved GGUFs.
  • Contrast: is_recr_impl is safe only because it's globally std::fill-ed in llama-model.cpp for all archs; the new array has no equivalent.
  • Fix: std::array<uint32_t, LLAMA_MAX_LAYERS> is_indexer_full_impl = {}; (or gate the add_kv on the arch actually using an indexer).

2. (Low, verify) Load will throw if the converted indexer.types length ≠ n_layer().

  • get_key_or_arr(LLM_KV_ATTENTION_INDEXER_TYPES, …, hparams.n_layer(), false) throws "wrong array length" when the stored array length doesn't equal n_layer() (78, excluding the MTP/nextn layer). Conversion writes len(config["indexer_types"]). Worth confirming the HF config's indexer_types has exactly 78 entries (i.e. excludes the NextN layer); otherwise freshly converted GGUFs will fail to load. The in-code GLM_5_2_DEFAULT_INDEXER_TYPES has 78 non-default entries, which is consistent — but this coupling is implicit and load-time-fatal.

3. (Low) prev_top_k can be nullptr for a shared layer preceding any full layer.

  • In the shared branch (glm-dsa.cpp) top_k = prev_top_k; and is passed to build_attn. This is safe for real GLM 5.2 (layers 0–1 are full) and for both BC defaults, but a malformed/custom indexer.types with a leading shared layer silently passes a null tensor. A GGML_ASSERT(top_k != nullptr && "shared indexer layer before any full layer"); would fail loudly instead of downstream. Defensive nit.

4. (Low) is_pre_5_2 heuristic via n_ctx_train < 1048576 is a fragile magic number.

  • Backward-compat detection keyed on context length works for today's GLM 5/5.1 vs 5.2, but would misclassify a 5.1 checkpoint with extended context. It only matters for old checkpoints lacking the new KV (new conversions write it explicitly), so impact is limited — but consider a named constant + comment, or keying on something more robust.

5. (Info) Layer-count 79 → 78 is a real fix, display-only.

  • n_layer() = n_layer_all - n_layer_nextn = 78 for the 744B model, so the old case 79 never matched and type was always UNKNOWN. Correct fix; affects only the reported model-type label. The assert-message correction (n_layer_all) is also good.

6. (Info, author-acknowledged) Decode slowdown.

  • Expected direction: the old GLM_DSA path ran dense MLA (indexer ignored), so it did less work. The new path computes the indexer on every full layer even for sequences <2048 tokens where it's effectively a no-op. Not a regression against a correct baseline, but the "much slower" the author reports is worth profiling before merge — likely the non-fused (!cparams.fused_lid) branch with its several ggml_cont/permute ops, or the extra ggml_concats. Not blocking.

Style / conventions

  • The graph is a near-verbatim copy of deepseek32.cpp. This duplicates a lot of MLA code, but it matches the established project convention (deepseek2/32/4 each have their own full graph file), so it's acceptable. If the maintainers ever factor the shared MLA body out, this should follow.
  • GLM_5_2_DEFAULT_INDEXER_TYPES is clearly commented with a source link — good. Consider a static_assert/comment that the meaningful prefix length equals the expected n_layer() to catch drift.
  • Naming/comments are consistent with the codebase; cb(...) labels mirror deepseek32.

Correctness spot-checks that passed

  • Indexer RoPE LLAMA_ROPE_TYPE_NORM correctly implements the interleaved variant and differs from deepseek32's NeoX, matching the referenced transformers change.
  • q_pe/q_nope split strides and concat order match deepseek32.
  • BOOL→uint32 load path (get_arr) is correct; converter writes BOOL, saver writes UINT32, loader handles both.
  • GLM_DSA correctly added to the DSA KV-cache and Hadamard-rotation gates.

Recommendation

Solid, well-scoped PR. The one thing I'd fix before merge is finding #1 (trivial = {} initializer — genuine UB affecting unrelated architectures' save path). #2 is worth a quick confirmation, and #3 is a cheap defensive assert. The performance note (#6) can be a follow-up.

Comment thread src/llama-hparams.h

// 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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

llama-model.cpp run std::fill for all arrays like this

@fairydreaming

Copy link
Copy Markdown
Collaborator

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):

  • GLM-5.1 mainline: PPL = 2.5079 +/- 0.01268
  • GLM-5.1 this PR: PPL = 2.5038 +/- 0.01261
  • GLM-5.2 mainline: PPL = 2.4710 +/- 0.01198
  • GLM-5.2 this PR: PPL = 2.4728 +/- 0.01201

Additionally I added a small test change to allow override of indexer_top_k value and observed increased perplexity with smaller top_k values (the more indexer_top_k value is reduced the higher the perplexity is). I think we can be fairly certain that the indexer part works correctly.

Regarding the automated code review, I confirmed that in the converted model indexer_types array has correct length:

     43: [BOOL]     |       78 | glm-dsa.attention.indexer.types = [True, True, True, False, False, False, ...]

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.

@pcuenca

pcuenca commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review (nice Skill!) 🙌

  1. Addressed via a std::fill like in previous cases.
  2. Verified while debugging and independently by @fairydreaming.
  3. Added an assertion; didn't do it before because the shared indexer models we know about were verified to be correct in this regard.
  4. Yeah, already discussed in previous comments. We could use a named constant instead, not sure it's worth it.

@pcuenca
pcuenca requested a review from ngxson July 24, 2026 17:52

@ngxson ngxson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

looks good, thanks!! let's merge when the CI passes

@ngxson
ngxson merged commit 88bfee1 into ggml-org:master Jul 24, 2026
23 of 27 checks passed
@pcuenca
pcuenca deleted the glm-dsa-indexer branch July 24, 2026 19:24
@fairydreaming

Copy link
Copy Markdown
Collaborator

I just realized that llama_kv_cache_dsa still allocates indexer KV cache memory for layers with shared indexer. Probably a separate layer filter shall be added in this class for indexer cache based on indexer types array. That would probably save few hundred MB of our precious VRAM.

@pcuenca

pcuenca commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Yes, good point @fairydreaming, I can try to take a look.

ggerganov pushed a commit to am17an/llama.cpp that referenced this pull request Jul 28, 2026
* Start building graph - reuse deepseek32

* Enable kv cache and rotation for glm_dsa architecture

Just follow Deepseek 3.2 for now.

* Reuse prev_top_k for "shared" indexer layers

* GLM 5.2 uses LLAMA_ROPE_TYPE_NORM for the indexer.

This is transformers' `apply_rotary_pos_emb_interleave`

* Default indexer types to GLM pattern

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.

* Save indexer types to gguf, restore on load

* Use ggml_lightning_indexer when cparams.fused_lid

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>

* GLM 5 and 5.1 use full indexers

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>

* Fix indentation

* Ensure array is zero-filled

* Prefer explicit std::fill

* Assert prev_top_k exists for shared indexer

---------

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>
zgiles added a commit to zgiles/llama.cpp that referenced this pull request Jul 29, 2026
…2 indexer

Brings the fork up to current upstream (97 commits). Main reconciliation is
src/models/glm-dsa.cpp, where upstream ggml-org#25407 added the GLM-5.2 DSA lightning-
indexer (sparse attention) to the same file our MTP speculative-decode work
rewrote. They are complementary, so the merged file = upstream's indexer graph
(LLAMA_ROPE_TYPE_NORM interleaved indexer RoPE, ggml_lightning_indexer, shared-
layer prev_top_k, per-layer indexer.types schedule) + our MTP re-applied
(has_mtp loader probe, DECODER_MTP dispatch, graph_mtp draft head, t_h_nextn
tail ported into the indexer main graph).

Also:
- llama-model.cpp create_memory: GLM_DSA MTP context gets a plain llama_kv_cache
  (filter il>=n_layer) instead of the dsa cache, so build_attn_inp_k()'s cast is
  valid (else UB).
- llama-graph.cpp: add the TP wo all-reduce to the DSA build_attn overload
  (previously missing) — fixes glm-dsa AND deepseek3.2/V4-indexer under TP.
- Adapt to upstream's load_mode API refactor (use_mmap/mlock/direct_io -> enum).

Builds clean (llama-completion + llama-server). Not yet node-validated.
praneshgo pushed a commit to praneshgo/llama.cpp that referenced this pull request Jul 29, 2026
* Start building graph - reuse deepseek32

* Enable kv cache and rotation for glm_dsa architecture

Just follow Deepseek 3.2 for now.

* Reuse prev_top_k for "shared" indexer layers

* GLM 5.2 uses LLAMA_ROPE_TYPE_NORM for the indexer.

This is transformers' `apply_rotary_pos_emb_interleave`

* Default indexer types to GLM pattern

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.

* Save indexer types to gguf, restore on load

* Use ggml_lightning_indexer when cparams.fused_lid

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>

* GLM 5 and 5.1 use full indexers

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>

* Fix indentation

* Ensure array is zero-filled

* Prefer explicit std::fill

* Assert prev_top_k exists for shared indexer

---------

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>
turbo-tan added a commit to turbo-tan/llama.cpp-tq3 that referenced this pull request Aug 1, 2026
* mtmd : use align_corners for qwen3vl vision position embedding interpolation (#25781)

The Qwen3-VL learned position embedding is interpolated to the runtime patch
grid with the default bilinear+antialias (align_corners=False) sampling, while
the transformers reference uses align_corners=True (torch.linspace(0, side-1, T)).
The mismatch scales grounding coordinates about the image center, growing with
image size and per-axis for non-square images (see #16880).

* convert: fix handle HunyuanVL XD-RoPE config (#25514)

Signed-off-by: wendadawen <wendadawen@qq.com>

* Add support for Laguna XS.2 & M.1 (#25165)

* llama-arch: fix DeepSeek4 APE tensor op (#25945)

* cuda: GET_ROWS quants (#25962)

* cuda: add k-quant support to GET_ROWS

Device-side embedding lookups require GET_ROWS to handle the k-quants
used by common GGUF recipes (Q4_K_M stores token_embd as q6_K). Without
it the backend rejects the op and the scheduler falls back to the host,
copying the full embedding matrix back on every token in single-device
graphs.

Factor the super-block dequantizers out of the dequantize_block kernels
in convert.cu into shared device functions in dequantize.cuh and reuse
them from a new k_get_rows_kq kernel : one thread block dequantizes one
(dst row, super-block) pair with the existing thread layouts, 32 threads
for q4_K and 64 for the other k-quants.

Covers q2_K to q6_K in get_rows_cuda and supports_op. i-quants are left
as a TODO.

* cuda: add i-quant support to GET_ROWS

Extends the shared super-block dequantizers to the nine i-quants and
reuses them from k_get_rows_kq with the 32-thread layout of the matching
convert.cu kernels. supports_op gates the k-quant and i-quant path on
ne0 being a multiple of QK_K, which iq4_nl does not guarantee on its
own (QK4_NL sub-blocks). mxfp4 is left as a TODO.

* cuda: add mxfp4 support to GET_ROWS

Moves the mxfp4 dequantizer into the shared super-block helpers and
reuses it from k_get_rows_kq with the 32-thread layout of the matching
convert.cu kernel. mxfp4 joins the ne0 % QK_K gate in supports_op since
its 32-value sub-blocks do not guarantee QK_K-aligned rows on their own.
This closes GET_ROWS type coverage on CUDA: every quantized GGML type
now takes the direct device path.

* cuda: gate the GET_ROWS row size only for 32-value sub-block types

Address review from @pwilkin: the i-quant commit replaced the return
shared by the whole supported type cascade, so f16/f32/bf16/i32 and the
legacy quants also inherited the ne0 % QK_K == 0 gate and any row size
that is not a multiple of 256 fell back to the scheduler. Split the
cascade: unconditional support is restored everywhere, the gate stays
only on iq4_nl and mxfp4 whose 32-value sub-blocks do not guarantee the
QK_K super-blocks the kernel iterates on.

* webgpu : add CONV_2D_DW (depthwise conv2d) kernel (#25847)

* webgpu : add CONV_2D_DW (depthwise conv2d) kernel

Implement GGML_OP_CONV_2D_DW for the WebGPU backend,
ported from the Vulkan backend's conv2d_dw.comp.

Assisted-by: Claude Opus-4.8

* Remove unnecessary comments in webgpu support

* update supported ops tables, triggered by adding webgpu CONV_2D_DW

* ci : fix SYCL package shared library lookup (#25987)

* ggml: enable PowerPC backend variants on AIX (#25983)

* ggml: enable PowerPC backend variants on AIX

Allow the PowerPC CPU backend variants to be built on AIX by extending the platform check in the CMake configuration. This reuses the existing PowerPC backend implementations without changing their behavior.

Also fix a missing semicolon in the PowerPC Q0 matmul implementation.

* Fix missing semicolon in sgemm.cpp

* Fix DeepSeek4 crafted template (#25414)

* chat: fix DS4 template to explicitly follow reference behavior

* Support DeepSeekv4 flag (`drop_reasoning`).

* fix: hook DS3.2 parser for DS4 as well

* fix: add tool result reordering

* fix: post-merge

* common: infer the speculative type from the draft repo sidecars (#25989)

With -hfd pointing to a repo that ships mtp-/dflash-/eagle3- sidecars
and no --spec-type given, the draft resolved to a full model while the
sidecar was the intended draft.

When the speculative types are still at their default, discover the
sidecars of the draft repo, pick the first available following the
existing mtp > dflash > eagle3 priority, and set the corresponding
type, so this now works without any extra flag:

llama-server -hf repo:Q3_K_M -hfd repo:Q8_0

An explicit --spec-type disables the inference, and a draft repo
without sidecars keeps resolving to a full model as before.

* minor: fix reasoning preserve var for DS4 [no ci] (#25999)

* feat(ui): add symbolic math support to JS sandbox via nerdamer (#25948)

* feat(ui): add symbolic math support to JS sandbox via nerdamer

Preload nerdamer (with decimal.js) in the sandboxed worker,
exposing the `nerdamer` global for symbolic computation:
simplify, expand, factor, diff, integrate, solve, laplace,
ilt, limit, partfrac, gcd/lcm, roots, coefficients, and more.

Mirrors the math.js integration pattern from the
feature/sandbox-symbolic-math branch, but uses nerdamer
for a lighter, more focused symbolic math engine.

* Update sandbox-harness.ts

* docs(ui): update sandbox tool description with detailed nerdamer usage guide

* Clarify nerdamer usage in sandbox tool description

Updated the description of the sandbox tool to clarify usage of nerdamer.

* ui: build nerdamer sandbox prelude from vendored source

Replace the vendored all.min.js with the readable nerdamer-prime
source and its two bundled deps (big-integer, decimal.js), licenses
included. A vite plugin bundles and minifies them at build time with
the upstream esbuild flags, exposed as virtual:nerdamer and imported
lazily on first sandbox use. The vendors package.json pins commonjs
so the project level type: module does not break esbuild format
detection. The harness gains a CSP removing network egress from the
worker, and browser tests cover the prelude, exact arithmetic, the
fetch block and the timeout.

Upstream snapshot: together-science/nerdamer-prime@1936145

* feat(ui): make symbolic math (nerdamer) a user-toggleable setting

- Add SYMBOLIC_MATH_ENABLED setting key and registry entry (checkbox, default false)
- Convert SANDBOX_TOOL_DEFINITION to buildSandboxToolDefinition(includeSymbolicMath)
  so the tool description includes/excludes nerdamer API docs dynamically
- Cache sandbox harness per variant ('nerdamer' / 'plain') for instant toggle
- Deprecate SANDBOX_TOOL_DEFINITION constant alias for backward compatibility
- Update tools store to pass symbolic math config into tool definition

* docs(ui): tell LLM to list nerdamer functions first, do not guess

* test(ui): enable symbolic math in sandbox tests via settingsStore config

* style(ui): fix formatting for tools.svelte.ts

---------

Co-authored-by: Pascal <admin@serveurperso.com>

* mtmd: use RAII for setting and resetting non-causal attention (#25723)

* mtmd: use RAII for setting and resetting non-causal attention

* mtmd: drop dependency on <optional>

* mtmd: shorten class and variable names

* hexagon: activation ops update (#25974)

* hex-geglu: optimized all-in-one geglu microkernel

* hex-geglu: enable non-contiguous src and strided DMA

* hex-act: enable non-contiguous srs and strided DMA for rest of ACT ops

* hex-act: generalize GLU per-thread functions via DEFINE_GLU_PER_THREAD macro

* hexagon: move UNARY_SILU and UNARY_GELU to unary-ops

* hex-act: replace the generic ops_context scratchpad usage with a local htp_vtcm_layout computation per act op.

---------

Co-authored-by: Max Krasnyansky <maxk@qti.qualcomm.com>

* CUDA: Improve NVFP4 W4A4 activation quantization (#25730)

* Squash history before conflict-resolution during rebase on master

WIP commit

Add 32-byte loads, restore per-block amax

Use nvfp4x4 intrinsic when available

Fuse per-channel amax and quantization kernels

Do pointer arithmetic only once on x

Remove unnecessary ternary in the load

We assert on host side that ne00 is 64-aligned

Add back scale-search, but optimize it with intrinsics

Code cleanup

Make scale in MMQ-epilogue NVFP4-specific/restrictive for now

Remove unneeded include, add comment

Fix trailing whitespace

Guard __builtin_align__(32) struct to NVIDIA

Seems like HIP doesn't have this available, see https://github.com/ggml-org/llama.cpp/actions/runs/29438651734/job/87431623001

* compiler massaging to avoid unnecessary LDCs

* kvalues_mxfp4 -> kvalues_nvfp4 in quantize_mmq_nvfp4

* Always pass in src1_scale.ptr

* Extract ggml_cuda_is_aligned helper

* ui: Add a "Default" option for the reasoning selector (#25846)

* ui: add Default reasoning option that defers to the server

The webui always injected enable_thinking, overriding the chat template
default and the --reasoning flag, breaking models that reason
unconditionally (e.g. Gemma 4 E4B) on a fresh client.

Default sends nothing so the server decides, Off and effort levels
force the value as before. All choices are remembered.

Also remove the boolean thinking API from the conversations store and
drop ChatFormReasoningEffortSubmenu.svelte (dead code).

* ui: close the whole menu tree on reasoning level selection

The reasoning levels were raw buttons inside the SubContent, so
selecting one only closed the submenu via manual state while the root
dropdown stayed open. DropdownMenu.Item closes the full tree on select
like the sibling entries and brings native keyboard navigation.

* ui: prevent the add menu tooltip from flashing when the dropdown closes

* contrib: allow all AI-generated code in general (#26012)

* conversion: fix non-MoE NomicBert GGUF conversion error (#25996)

* metal : add f16 type support to leaky relu (#25981)

* contrib: fix leftovers from the AI usage policy update (#26030)

* args: refactor mlock/mmap/directio into load-mode (#20834)

* args: overhaul mmap/mlock/dio into single arg

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: update docs with llama-gen-docs

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* chore: satisfy code quality

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* args: make the `+` sign an actual modifier now

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* chore: general code clean up + comments

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: fix deprecated flags support

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: quick sanity check

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* bench: sync llama-bench argument parsing

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* fix: bugfix variable behaviour + llama-bench lm column size

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: inverse commands should do the opposite instead of doing nothing

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* bench: fix incorrect dash

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* bench: fix missing modifiers for deprecated flags

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* llama: switch back to thread_local

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: switch back to single enum

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: update arg docs

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* chore: fix missing `mlock` from llama_load_mode_from_str + cleanup llama-bench

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* llama: fix mlock not activating

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: add deprecation warning when old and new flags are combined

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: cont add comment for todo in the future

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: sync with upstream

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: re-sync with upstream again

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

---------

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* CUDA: fix external compilation of q1_0 MMQ (#25778)

* hexagon: fix Windows crash when op_poll is enabled (#26029)

* hexagon: further improved pipeline of the core bits (L2, DMA, MM, FA) (#26049)

* hex-l2: use dirty ranges for flushing

* hex-l2: simplify range based flush logic

* hex-l2: optimize dirty range scans

* hex-hvx: support for reduce_max_i32

* hex-mm: optimize fused MUL_MAT+ADD to use vtcm for bias when it fits

* hex-mmid: optimize mmid row-mapping generation

* hex-mmid: optimize mmid row-mapping generation

* hex-mmid: optimize mmid row-mapping generation (round2)

* hmx-mm: optimize output proc by tiling (col-chunking)

* hex-fa: start the next q dmas a bit earlier

* hex-fa: prefetch Q even earlier

* hvx-fa: optimize softmax to keep things in hvx registers

* hex-fa: hoist const register init in softmax loop

* hmx-fa: kick off next-qkv DMAs before o-proc

* hmx-fa: hoist various checks out of the inner loop

* hmx-fa: adjust the cost model to better balance softmax work across hvx threads

* hmx-fa: overlap diag rescale build with last HMX task

* hmx-fa: optimize idx update in output proc

* hmx-fa: unroll the softmax loops for improved perf

* hmx-fa: overlap qk-dot with softmax, double-buffer p and s tiles

* hex-trace: double the default number of trace entries

* hex-trace: add trace events for opbatch and buffer mgmt

* hex-trace: overhaul tracing to simplify runtime event handling and support opbatch stats

* hex-trace: replace ascii timeline diagram with pipeline bubbles detector

* hex-trace: handle missing start/stop events

* hex-dma: always log stop/start trace events even for dummy dmas

* hex-scripts: fix flake warnings

* vendor: update subprocess.h (#26061)

* cohere2 moe template parser: enforce JSON schema for text responses if a response schema is provided (#26018)

* UI: Fix settings precedence, Factory < Admin (--ui-config-file) < Users (Settings panel) (#26002)

* skill: create `add-new-model` and `code-review` (#26042)

* skill: add new model

* add common pitfalls

* add code review skill

* nits

* add codeowners

* add security review section

* mention about skills in agents.md

* add design review

* exclude ggml-gh-bot

* Apply suggestions from code review

Co-authored-by: Piotr Wilkin (ilintar) <piotr.wilkin@syndatis.com>
Co-authored-by: Xuan-Son Nguyen <thichthat@gmail.com>

* conversion-time weight modification

* nits

---------

Co-authored-by: Piotr Wilkin (ilintar) <piotr.wilkin@syndatis.com>

* opencl: do not treat NULL-mask flash attention as causal (#25771)

* opencl: cache compiled cl_program binaries on disk (#26050)

* HIP: remove rocWMMA FlashAttention (#26046)

* llama: various bug fixes (#26051)

* server: support "reasoning_effort": "none" in OAI API (#26045)

* support "reasoning_effort": "none" in OAI API

* handle reasoning.effort: "none" in OAI responses API

* clarify non-"none" values of reasoning_effort have no effect

* use json_value instead of body.at

* ui: fix MCP server display name conflicts in tools lists (#26011)

* ui: fix MCP server display name conflicts in tools lists

Tool groups were keyed by display label so two servers reporting
the same name broke the keyed each blocks and only one was visible.
Key rendering, expand state and toggles by the stable server id
instead, and suffix duplicate labels with a counter in config order.

* ui: customizable MCP server display name with autofill

Add a display name field to the MCP server form, add and edit alike.
The custom name takes precedence over the server-reported one, so two
servers reporting the same name can be told apart; clearing the field
returns to the automatic label. In the add dialog a debounced preview
handshake prefills the field with the server-reported name: a manual
edit freezes the autofill, stale responses are discarded, failures
stay silent, and an unedited prefill is not persisted so the label
keeps following the server.

* ui: fix recursive fetch passthrough in the client test setup

The original fetch was captured inside beforeEach, where it is the
previous test's spy since vi.spyOn returns the existing one, so the
default passthrough recursed on itself for any URL outside the
mocked set. Capture the real fetch once at module load.

* model: add GLM 5.2 Indexer support (#25407)

* Start building graph - reuse deepseek32

* Enable kv cache and rotation for glm_dsa architecture

Just follow Deepseek 3.2 for now.

* Reuse prev_top_k for "shared" indexer layers

* GLM 5.2 uses LLAMA_ROPE_TYPE_NORM for the indexer.

This is transformers' `apply_rotary_pos_emb_interleave`

* Default indexer types to GLM pattern

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.

* Save indexer types to gguf, restore on load

* Use ggml_lightning_indexer when cparams.fused_lid

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>

* GLM 5 and 5.1 use full indexers

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>

* Fix indentation

* Ensure array is zero-filled

* Prefer explicit std::fill

* Assert prev_top_k exists for shared indexer

---------

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>

* ui: remove render effects (#26083)

* ui: remove viewport fade in and smooth autoscroll bottom snap

fadeInView mounted every message and markdown block at opacity 0 and
relied on an IntersectionObserver to reveal it. When the observer never
fires (blocks mounted offscreen during long agentic loops) the content
stays invisible forever while still present in the DOM. Remove the
action, its orphaned isElementInViewport util and all call sites:
blocks now render visible immediately.

AutoScrollController.scrollToBottom defaulted to behavior smooth and is
invoked every 100 ms while streaming. Each tick restarts an easing
animation toward a moving scrollHeight, producing a random elastic bump
of a few pixels when the user reaches the bottom and autoscroll
reengages. Default to instant scrolling; the user facing scroll down
button keeps its smooth behavior.

* ui: skip rendering of offscreen chat messages via content-visibility

Apply content-visibility auto with contain-intrinsic-size to chat
messages so the browser skips layout and paint for messages outside
the viewport. The DOM stays complete: component state, find-in-page,
text selection, and the mutation based autoscroll are unaffected, and
browsers without support simply ignore the properties.

* ui: remove conversation switch fade

Switching conversations faded the message list out and in over 500 ms
plus a 300 ms route delay, deferring the message refresh behind two
requestAnimationFrame calls. Remove the fade, its navigation hooks and
dead state, and refresh messages directly so switching is only bound
by actual render time.

* ui: describe present behavior in comments and drop unused parameter

* ui: anchor the context gauge popup to the form with plain CSS

The stats card was portaled to body and repositioned in script on
every ancestor scroll event, trailing the page by one frame while
streaming. Render it as an absolutely positioned sibling of the input
box inside the already relative form, so nothing runs during scroll.

The card sits just above the dial, centered on it and overlapping the
textarea, from a single measurement of the dial center and top taken
when it opens; the dial and the card share the same positioning frame,
so the values stay exact while the card is open. Mouse pointers open
on hover with a grace delay to reach the card, touch pointers toggle
on tap, any press outside the card and the dial closes it, and Enter
and Space toggle from the keyboard. The card lives outside the input
box because its overflow-hidden and backdrop-filter would clip any
positioned descendant.

* ui: extract context gauge popup constants and relocate its state store

Move the placement values and the close grace delay to
lib/constants/context-gauge-popup.ts, matching the auto-scroll
constants layout, and move the popup state module from the component
folder to lib/stores where runes modules live in this codebase.

* ui: declare the context gauge popup card ref as $state

* ui: reduce per-token render cost when streaming  (#26053)

* performance harness - the empirical root

Assisted-by: Claude Opus 4.8

* 210.36ms -> 2.67ms per streamed token

Assisted-by: Claude Opus 4.8

* 11.58ms -> 0.62ms per streamed token

Assisted-by: Claude Opus 4.8

* 22.02ms -> 3.33ms per streamed token

Assisted-by: Claude Opus 4.8

* 3.07ms -> 1.36ms per streamed token at 40 messages

Assisted-by: Claude Opus 4.8

---------

Co-authored-by: Zach Winter <dmtommy@icloud.com>

* tests: synchronize save-load-state generation (#26056)

* common : add support for multiple end sequences in the reasoning budget sampler (#25544)

* common : extract trie/ac to a separate file

* common : support multiple token sequences in the reasoning budget sampler

* common/trie : return matched word index

* common/trie : rename "word" to "pattern"

* common/reasoning-budget : expose matched end sequence

* common/sampling : replay end sequence when reasoning budget is done

* cont : update to use multiple end sequences

* cont : clean up

* Update ggml/src/gguf.cpp : Defined virtual keyword for destructor of gguf_writer_base (#25867)

Without a virtual destructor, deleting a derived object through a
base-class pointer only invokes the base destructor, skipping the
derived one.

* vendor : update cpp-httplib to 0.51.0 (#26067)

Signed-off-by: Adrien Gallouët <angt@huggingface.co>

* server : add missing task parameters(adaptive_target, adaptive_decay) in generation_settings (#25830)

These two parameters were overlooked when task parameters were being JSONized
within `generation_settings` and have been added. A regression test has been
added to prevent the problem from recurring and it passes.

Fixes #25803

* server: add format arg to datetime tool (#26117)

* common : skip empty implicit default preset (#25643)

The INI parser creates an implicit default section for top-level metadata.
After reserved keys such as `version` are skipped, that section can have no
model options but was still added and exposed in router mode.

Skip only the empty implicit default while preserving real default presets,
named presets, and the global `[*]` settings.

Signed-off-by: JS van Dijk <267467744+hogeheer499-commits@users.noreply.github.com>
Co-authored-by: JS van Dijk <267467744+hogeheer499-commits@users.noreply.github.com>

* hexagon: partial im2col support (#26007)

* hexagon: add IM2COL op

Add Hexagon IM2COL support targeting only patch-embedding convolutions.

* hexagon: im2col refactor and cleanup

* hex-im2col: instrument and update im2col.

* hex-im2col: add local htp_vtcm_layout computation.

* server: support MCP stdio (#26062)

* move server_pipe to common

* init impl

* vendor: update subprocess.h

* add server_mcp_stdio

* stderr drain

* server_mcp_transport

* server_mcp_stdio is now framing-only, no json

* internal/mcp-stdio: integration + tests + fixes (#26075)

* server-mcp: harden transport and wire up the tool integration

Builds on the transport/manager architecture (server_mcp_transport + server_pipe)
with the hardening and integration the draft did not yet have.

Hardening:
* Reader and stderr pumps are polled (running-aware) instead of blocking on a read
  that only ends at EOF. subprocess_terminate() SIGKILLs only the direct child, so a
  grandchild the MCP server spawned that inherited the pipe would otherwise keep the
  write end open and hang teardown (both warmup shutdown at startup and process
  shutdown). The writer is likewise non-blocking + polled.
* Windows: resolve the command through PATHEXT so "npx" (npm ships npx.cmd, never
  npx.exe) spawns, matching POSIX's PATH search; and enumerate the parent environment
  as UTF-8 (GetEnvironmentStringsW) instead of the active code page.
* server_pipe gains an opt-in max_size (default unbounded, so the router's streaming
  use is unchanged); the MCP reply queue uses it so a server that streams unsolicited
  notifications between requests cannot grow it without bound.

Integration:
* --mcp-servers-config / --mcp-servers-json flags; enabling MCP restricts default CORS
  to localhost, same as --tools.
* MCP tools are exposed through /tools (and chat-completions) as <server>_<tool>,
  skipping names that collide with a built-in or another MCP tool.
* Manager lifecycle wired into llama_server(): warmup at start, shutdown() from the
  signal handler before the HTTP server drains, blocking teardown in clean_up().
* SIGPIPE ignored so a child dying mid-write yields EPIPE rather than killing us.

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>

* server-mcp: add MCP test suite with grandchild deadlock regression test

21 tests over the /tools endpoint: tool discovery/invocation, timeouts, crash
recovery and respawn cooldown, warmup partial failure, malformed and batched
notification+response output, tool-definition shape, and prompt shutdown during a
slow call.

The last test spawns an MCP server that leaves a grandchild inheriting its
stdout/stderr and asserts the server both starts and stops promptly. Verified it
fails (5s SIGKILL fallback on a deadlocked reader-join) when the pump is made to
ignore the running flag, and passes with the polled reader.

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>

* clean up

* clean up 2

* even stricter life cycle

* nits

* nits 2

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>

* fix some edge cases

* fix last_error data race

* fix response schema + docs

* server: fix MCP zombie leak and timeout-induced transport teardown

join_pumps() never reaped the child, leaking one zombie per spawn:
call subprocess_join() before subprocess_destroy().

A per-call timeout permanently closed from_server and got a healthy
transport evicted: add close_on_stop to server_pipe::read() and pass
false from send_rpc(), where should_stop is a per-request deadline
and a late reply is already skipped on id mismatch.

Also drop the unreachable disconnect cancellation in
server_mcp_tool::invoke(): support_stream is false, st is always null.

(cherry picked from commit e6de1ec043174fd0570b1e60d47f06c7c19d620d)

Assisted-by: Claude Opus 4.8

* server: make MCP test fixtures JSON-RPC 2.0 compliant

Add the missing notification guard to mcp_malformed_server.py and
mcp_burst_server.py (the latter treated id 0 as a notification and
replied to unknown ones; its notification table is now unused).

Return -32602 instead of -32601 for unknown tools: tools/call is a
valid method, the tool name is the invalid parameter.

Also fix the test module docstring: tools are named <server>_<tool>.

(cherry picked from commit 74a08e8c311dabf3b49d06cc6d754b0097ae7a38)

Assisted-by: Claude Opus 4.8

---------

Co-authored-by: Piotr Wilkin (ilintar) <piotr.wilkin@syndatis.com>
Co-authored-by: Pascal <admin@serveurperso.com>

* common : use-after-free when loading LoRA adapter fails (#25611)

* ggml-webgpu: Fix WASM compilation with OpenMP (#25943)

* Fix emscripten compilation with openmp

* Separate wasm job to its own workflow

* Add flags necessary for newer emsdk

* Just disable openmp

* Update triggers

* ui: fix context gauge card regressions and land at the conversation end (#26099)

The context gauge card starts monitoring like the dial does, because
its own processing state instance only follows the live stream while
its monitoring flag is set. It also gets back the text-sm and ring
classes the removed hover card wrapper used to inject, which restores
its layout. Routing to a conversation now lands at the bottom
instantly and keeps the pin one frame at a time until the page height
settles, since content-visibility size realizations and syntax
highlight passes grow the page without DOM mutations.

* opencl: fix fused RMS norm mul view offset (#26085)

* model: Add MiniMax-M3 (MSA: MiniMax Sparse Attention) support (#24908)

* Add preliminary MiniMax-M3 support

Text-only port that re-uses existing components: MiniMax-M2 style GQA with
per-head QK-norm and partial rotary, DeepSeek-V3 style leading-dense and
routed/shared experts, and swigluoai activation. Sparse attention is not
yet supported (dense fallback); vision tower and MTP heads are dropped.

* MiniMax-M3 vision tower (mmproj + clip graph)

* Delete m3_vision_ref.py

* Update clip.cpp

* MSA

* Update constants.py

* Update minimax.py

* Cache creation. Working withotu flash attention

* Added flash attention for sparse layers

* Decomposed slow cpu OP into GPU + CPU ops. Massive speedup over long ctx

* Rewrote indexer op to be cuda native. Modified flash attention to match per group block picking

* Implement sparse attention calc out of stock ops.

* Fix a cache allocation and cont issue

* Fixed -fa auto crash, flagged debug spots

* Delete vocab.json

* Delete model.safetensors.index.json

* Delete generation_config.json

* Delete Minimax directory

* Handled multi stream case to fall back on Dense Attention

* Development scaffolding cleanup. No functional change to the decode or
4-way paths. Full debug harness remains at <8136a9c68ed7a5eb009aa67bba3fda8062f4648f> for reproducing the
selection-parity validation.

* Remove redundant comment from minimax-m3.cpp

* Changed 3 Gelu Ops for vision into Gelu_erf ops

* Assert that n_kv is multiple of 128

* Rename MSA index tensors to indexer convention

Note: All GGUFs generated before this change will need to be regenerated.

* Fix incorrect Assert

* Review driven changes (#3)

* Remove comment from conversion minimax.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Remove whitespaces from constants.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Tighten comment in minimax.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* inherit MiniMax-M3 from MiniMax-M2

* drop dead text_config fallbacks

* Add indexer writer methods

* Reuse LLM_FFN_SWIGLU_OAI_MOE

* Remove duplicate  indexer setters, add only block_size/local_blocks, follow value naming convention

* Fix conversion error /gguf_writer.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Update gguf-py/gguf/gguf_writer.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Update gguf-py/gguf/tensor_mapping.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Update conversion/minimax.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Update conversion/minimax.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Remove whitespace in src/llama-kv-cache.cpp

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Remove Whitespace in Update src/llama-model.h

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Remove whitespace in src/llama-hparams.h

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* remove multimodal code upon maintainer request. Will be made as a separate PR

* Whitespace clean in tensor_mapping.py

* Log cache size on launch, block ctx shift, support prompt caching

Log indexer cache size on launch

Disallow ctx shift

Support prompt caching

* Update minimax-m3.cpp

* Optimize implementation, add multi stream support. 

Fully rewrote minimax-m3.cpp for speed and buffer size gains:

Unified the 4-way + decode, 1 FA call per layer instead of 4, with the groups mapped onto ne[3]

Custom CPU op now emits block-level mask, expanded on GPU, which causes CPU to GPU transfer to shrinks at prefill

Decode: ~25 nodes/layer vs ~50, no per-group concats/conts

Unified selection semantics, so both regimes rank bs + local bias (position-anchored local force), which means prefill/decode can no longer disagree on selection

can_reuse on the MSA bias input. Graph reuse at decode restored (was rebuilding the full graph every token)

In-place mask adds, shrinking compute buffer ~6.8 to ~4.2 GiB at ub2048/62k

Multi-stream: MSA now runs with -np N when kv_unified=false. Decode stays batched across streams (still 1 FA call), prefill loops per stream. dense fallback only for --kv-unified + multi-seq

Measured effect on expert offload bound setup: decode 6.2(4WAY)–7.15(MSA_decode) -> 7.7~7.8 t/s, flat from 5k to 60k+. prefill around 10% faster. buffer about 20% smaller, multi-user support.

* set default cache type to F32

* Fix potential DSA double indexer cache  allocation bug, only allocate in-cache k_idx for archs that opt in

* remove F16 downcasts in MSA attention, force F32 indexer score accum

* Add Minimax eos to llama vocab

* Guard edge case where idx cache can become stale after a tail trim

* Update llama-kv-cache.h

* Update llama-kv-cache.cpp

* Update llama-kv-cache.cpp

* Update llama-kv-cache.h

* Update llama-kv-cache.cpp

* Review driven changes

* style fix

* indexer hparams are required

* fix tests

* fix lint

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>
Co-authored-by: Xuan Son Nguyen <son@huggingface.co>

* mtmd: add GLM-5.2-Vision (#26126)

Co-authored-by: Eric Hartford <eric@quixi.ai>

* common: add `subproc.h` wrapper, disabled on android/ios (#26102)

* add common/subproc.h|cpp

* add compile flag LLAMA_SUBPROCESS

* disabled by default on android and ios

* test-jinja: use common subproc

* mtmd: disable video if subproc is not set

* disable subproc on wasm

* make is_created atomic

* migrate server-mcp

* ui: detect the conversation import format from file contents (#26121)

* ui: detect the conversation import format from file contents

iOS resolves every accept entry to a UTI and has none for ".jsonl", so the
picker greyed out exported conversations. Drop the accept filter and pick the
parser from the file contents: ZIP magic bytes, then a first "session" record
for JSONL, otherwise the legacy JSON format.

Also remove the unused importConversations() picker and an orphan doc comment,
and cover each format with unit tests.

* ui: report what a conversation import actually wrote

The import summary echoed the selection back, so re-importing conversations
already in the database claimed success while nothing was written and only a
console warning said otherwise.

Return the imported and skipped conversations from the database layer, list the
written ones in the summary, and count the rest in a toast.

* ui: name the literals of the JSONL conversation format

Introduce SessionRecordType and SESSION_HARNESS, and reuse the existing
NEWLINE constant, so the record format lives in one place.

This also covers the writer side, which predates the import path under
review and carried the same literals: an enum stated by the reader alone
lets the two sides drift.

Values are unchanged, so an export stays byte identical.

* Keep Minimax's indexer tensors at F32 for speed and accuracy (#26144)

* Keep Minimax's indexer tensors at F32 for speed and accuracy

* name -> new_name

* ui: fix system message edit box not expanding to fit content (#26006)

The in-conversation system-message edit textarea had a fixed min-height and no auto-resize, so long messages were crammed to 2 lines. Reused existing autoResizeTextarea helper on input and when the editor opens, and added max-height to cap growth.

* mtmd: fix android build (#26150)

* mtmd: Add Vision Support for Minimax-M3 (#25113)

* Add preliminary MiniMax-M3 support

Text-only port that re-uses existing components: MiniMax-M2 style GQA with
per-head QK-norm and partial rotary, DeepSeek-V3 style leading-dense and
routed/shared experts, and swigluoai activation. Sparse attention is not
yet supported (dense fallback); vision tower and MTP heads are dropped.

* MiniMax-M3 vision tower (mmproj + clip graph)

* Delete m3_vision_ref.py

* Update clip.cpp

* MSA

* Update constants.py

* Update minimax.py

* Cache creation. Working withotu flash attention

* Added flash attention for sparse layers

* Decomposed slow cpu OP into GPU + CPU ops. Massive speedup over long ctx

* Rewrote indexer op to be cuda native. Modified flash attention to match per group block picking

* Implement sparse attention calc out of stock ops.

* Fix a cache allocation and cont issue

* Fixed -fa auto crash, flagged debug spots

* Delete vocab.json

* Delete model.safetensors.index.json

* Delete generation_config.json

* Delete Minimax directory

* Handled multi stream case to fall back on Dense Attention

* Development scaffolding cleanup. No functional change to the decode or
4-way paths. Full debug harness remains at <8136a9c68ed7a5eb009aa67bba3fda8062f4648f> for reproducing the
selection-parity validation.

* Remove redundant comment from minimax-m3.cpp

* Changed 3 Gelu Ops for vision into Gelu_erf ops

* Assert that n_kv is multiple of 128

* Rename MSA index tensors to indexer convention

Note: All GGUFs generated before this change will need to be regenerated.

* Fix incorrect Assert

* Review driven changes (#3)

* Remove comment from conversion minimax.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Remove whitespaces from constants.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Tighten comment in minimax.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* inherit MiniMax-M3 from MiniMax-M2

* drop dead text_config fallbacks

* Add indexer writer methods

* Reuse LLM_FFN_SWIGLU_OAI_MOE

* Remove duplicate  indexer setters, add only block_size/local_blocks, follow value naming convention

* Fix conversion error /gguf_writer.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Update gguf-py/gguf/gguf_writer.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Update gguf-py/gguf/tensor_mapping.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Update conversion/minimax.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Update conversion/minimax.py

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Remove whitespace in src/llama-kv-cache.cpp

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Remove Whitespace in Update src/llama-model.h

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Remove whitespace in src/llama-hparams.h

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>

* Update minimax_m3.cpp

Rewrite code comment based on feedback and to better reflect the actual architecture, and reuse existing build_vit

* Rename minimax_m3.cpp to minimax-m3.cpp

* Update CMakeLists.txt

* Remove debug code from clip.cpp

* Update clip.cpp

* Update comments in tools/mtmd/models/minimax-m3.cpp

* Permute Q/K at conversion, drop precomputed sin/cos

* Log cache size on launch, block ctx shift, support prompt caching

Log indexer cache size on launch

Disallow ctx shift

Support prompt caching

* Update minimax-m3.cpp

* Optimize implementation, add multi stream support. 

Fully rewrote minimax-m3.cpp for speed and buffer size gains:

Unified the 4-way + decode, 1 FA call per layer instead of 4, with the groups mapped onto ne[3]

Custom CPU op now emits block-level mask, expanded on GPU, which causes CPU to GPU transfer to shrinks at prefill

Decode: ~25 nodes/layer vs ~50, no per-group concats/conts

Unified selection semantics, so both regimes rank bs + local bias (position-anchored local force), which means prefill/decode can no longer disagree on selection

can_reuse on the MSA bias input. Graph reuse at decode restored (was rebuilding the full graph every token)

In-place mask adds, shrinking compute buffer ~6.8 to ~4.2 GiB at ub2048/62k

Multi-stream: MSA now runs with -np N when kv_unified=false. Decode stays batched across streams (still 1 FA call), prefill loops per stream. dense fallback only for --kv-unified + multi-seq

Measured effect on expert offload bound setup: decode 6.2(4WAY)–7.15(MSA_decode) -> 7.7~7.8 t/s, flat from 5k to 60k+. prefill around 10% faster. buffer about 20% smaller, multi-user support.

* set default cache type to F32

* Fix potential DSA double indexer cache  allocation bug, only allocate in-cache k_idx for archs that opt in

* remove F16 downcasts in MSA attention, force F32 indexer score accum

* Add Minimax eos to llama vocab

* Guard edge case where idx cache can become stale after a tail trim

* Update llama-kv-cache.h

* Update llama-kv-cache.cpp

* Update llama-kv-cache.cpp

* Update llama-kv-cache.h

* Change resize Pad to none, resize alg to Bicubic Pillow

* Review driven changes

* Update llama-kv-cache.cpp

* rm unrotated pos_t

* fused rope w + pad

* rename merge --> merger for consistency

* add review skill for mtmd

* graph should use hparams n_merge

* fix lint

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>
Co-authored-by: Xuan Son Nguyen <son@huggingface.co>

* ui: Fix symbolic math tool JS sandbox prompt (#26131)

* Update sandbox.ts

* Update sandbox.ts

* Update sandbox.ts

* Update sandbox.ts

* Revise nerdamer description in sandbox constants

Updated NERDAMER_DESCRIPTION to clarify usage and warnings.

* server + ui: fix stream routes for model names containing a slash (#26137)

* server + ui: refactor resumable stream routes to query string conv_id

The conversation id can embed a model name containing slashes
(ggml-org/...) in router mode, which the decoded path splits before the
:conv_id param is captured, so stop and resume never matched the
session. Move the id to the conv_id query string on the public routes
and on the internal router -> child hop, where slashes survive
encoding. Handlers are unchanged since query and path params land in
the same map. Add a regression test with a slashed model name.

* server: move stream route docs to server-stream.h

Address review: ngxson wants the main server.cpp registration code kept
clean and simple, with route-level explanations living in the header.
Move the query string rationale and the lookup ownership note next to
the handler declarations in server-stream.h, and shorten the wiring
comment to a pointer.

* server: cancel a pending request when its stream is stopped during model load

The conversation was registered in the conv map only after the blocking
autoload wait, so a stop issued while the model loaded found nothing to
cancel and the request went on to generate an orphan once the load
ended. Register the conversation before the wait and give the entry a
ticket: a stop erases the entry, and the parked request checks its
ticket after the wait and aborts with 400 instead of starting. A newer
request on the same conversation replaces the entry, so only the
stopped request is cancelled. Add a regression test that stops during
the load window.

* server + ui: resume a stream after a page reload during model load

A pending request died with the client socket when the page was
reloaded while its model was loading, so no session ever existed and
the conversation had nothing to recover. A session request that waited
for a load now detaches from the client socket and reaches the child
regardless, the session buffer receives the generation, and the resume
route answers 503 while the owner is loading so the client retries
instead of dropping its state. The WebUI persists the pending stream at
send time, quietly polls on 503, and attaches once the session exists.
Add a regression test that drops the client during the load window.

* ui: show the model load progress again after a page refresh

The resume wait was invisible, so a conversation refreshed while its
model was loading showed nothing until the first byte. On a 503 from
the resume probe, mark the conversation as loading again so the
assistant row persisted at send time renders the processing info, and
target the model frozen in the persisted stream state for the
progress, since the row has no model yet and the dropdown may not be
restored.

* fix CI

* fix CI bis

* args: add `-lm mlock` where it mlocks but doesnt mmap (#26135)

* arg: add `-lm mlock` where it mlocks but doesnt mmap

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: rm unwanted docs changes

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: revert auto-formatting

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* bench: fix automated review point 3

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: revert the meaning of --mlock to non-mmap'ed mlock

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: update docs

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: remove extra changes from `llama-gen-docs`

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

---------

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* ggml-cpu: Enable BF16 tiled gemm optimization on PowerPC (#26068)

* docs: add exception about weight folding (#26168)

* docs: add exception about weight folding

* add example

* common: fix explicit -md precedence over draft sidecar resolution (#26165)

* common: fix explicit -md precedence over draft sidecar resolution

Follow-up of #25955, an explicit --model-draft file given with -hfd
was silently overridden by the sidecar resolution of the draft repo,
and its path was never resolved to a local file.

An explicit draft file selection now disables the sidecar resolution,
so the manual CLI configuration wins over the automatic one.

* common: apply the -hfd tag to the sidecar resolution

The sidecar selection was anchored on the primary of the draft plan,
so a tag without a matching full model aborted the whole plan, and
the sidecar quant silently followed the default model pick.

The tag now anchors the sidecar directly: exact tag match first, then
closest quant to the tag, and a requested sidecar resolves even when
no full model matches the tag. A wired draft sidecar also counts as
an explicit draft, so the main plan no longer downloads a second one.

* common: promote speculative load logs from trace to info

Show the loaded draft model and the MTP draft context at the default
verbosity, for consistency with the mmproj and primary logs.

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* tests : remove unnecessary sync in test-save-load-state (#26166)

* ggml : adjust logic for offloading ops to weight's backend (#25832)

* ggml : adjust logic for offloading ops to weight's backend

* llama : dsv4 graph fixes

* sycl(build): parallelize ocloc invocations (#25903)

* fit : count nextn (MTP) blocks in n_gpu_layers so front layers stay on GPU (#26177)

* model: Add support for Nanbeige4.2 (#25994)

* support nanbeige4.2 model

* fix

* fix flake8 Lint check

* fix loop bound check and drop redundant head_dim

---------

Co-authored-by: root <lizongqiang@kanzhun.com>

* common : add common_print_available_devices() (#26170)

Signed-off-by: Adrien Gallouët <angt@huggingface.co>

* mtmd: support MiMo-V2.5 audio input (RVQ-based model) (#26190)

* gguf converter for mimo audio

* fix conv

* cpp impl

* nits

* nits 2

* Disable -ffast-math on HIP (#25495)

* contrib : add guideline about the "merge ready" label (#26178)

* contrib : add guideline about the "merge ready" label

* cont : add ref

[no ci]

* spec: add eagle3-v3 support for gpt-oss model (#25794)

* ggml-metal: FWHT kernel for metal backend (#25924)

* metal fwht wip

* shape guard and formatting

* formatting

* Formatting and typos

Co-authored-by: YiChen Lv <63285796+forforever73@users.noreply.github.com>

* fix narrowing issue

Co-authored-by: YiChen Lv <63285796+forforever73@users.noreply.github.com>

* cont : minor style

---------

Co-authored-by: YiChen Lv <63285796+forforever73@users.noreply.github.com>
Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* server : add extra trace log for prompt similarity (#26218)

* sycl: fix use-after-return of the SDPA scale in the oneDNN flash-attention path (#25880)

* sycl: fix use-after-return of the SDPA scale in the oneDNN flash-attention path

The scale was uploaded with an async memcpy sourced from a stack local. On the
in-order queue that copy is ordered behind the K/V staging kernels; once n_kv is
large enough (>= ~26k observed on Arc Pro B70) the staging outlives the host
stack frame and the copy reads recycled memory, feeding the SDPA a garbage scale.
Output then collapses to a single repeated token and the KV cache is poisoned
for the rest of the session.

Short contexts win the race by accident, and test-backend-ops caps
FLASH_ATTN_EXT at kv=1024, which is why CI never caught it. The previous
device_count > 1 wait_and_throw() gate (and reverting it, PR #25741) fixes the
symptom only by keeping the frame alive across the copy at the cost of a host
sync on every FA call.

Fix: cache one device scalar per (device, value) -- the scale is constant per
model -- and upload it synchronously once. The single-device fast path (no
per-call host sync) is then safe: every device-side hazard already serializes
on the in-order queue. The multi-GPU conservative wait is kept unchanged.

Also:
- GGML_SYCL_FA_ONEDNN_MAX_KV env (0 = unlimited): optional n_kv ceiling that
  routes very long sequences to the native FA kernel.
- test-backend-ops: FLASH_ATTN_EXT F16 cases up to kv=65536 (Qwen3.6-27B
  geometry hsk=hsv=256 GQA 6, and hsk=128 GQA 4), closing the kv=1024 blind
  spot. Note the race itself needs a live multi-op pipeline to reproduce;
  single-op runs pass even on broken builds.

Verified on Arc Pro B70 (bmg_g31), Qwen3.6-27B Q4_K, -c 131072: output
byte-identical at temp 0 to the native FA path through 32k-deep prefill, with
prefill depth-flat at 820-840 t/s (vs 340-350 native at 32k depth).

Assisted-by: Claude Fable 5

* sycl: handle GGML_SYCL_FA_ONEDNN_MAX_KV like the other runtime env vars and document it

Review feedback on #25880:
- read the variable once at backend init into g_ggml_sycl_fa_onednn_max_kv via
  ggml_sycl_get_env, and print it in the startup env listing (-lv 4 shows it)
- document GGML_SYCL_FA_ONEDNN and GGML_SYCL_FA_ONEDNN_MAX_KV in the SYCL.md
  runtime table

Also trim the added FLASH_ATTN_EXT cases to kv={4096,16384}: the 32768/65536
shapes exceed the legacy NMSE threshold on both the oneDNN and native kernels
(long-sequence fp16 accumulation drift, present before this PR) and would fail
CI for an unrelated reason.

Assisted-by: Claude Fable 5

* sycl: clarify GGML_SYCL_FA_ONEDNN_MAX_KV default is disabled

Assisted-by: Claude Fable 5

* sycl: state default behavior of GGML_SYCL_FA_ONEDNN_MAX_KV explicitly

Assisted-by: Claude Fable 5

* Update ggml/src/ggml-sycl/fattn-onednn.cpp

Co-authored-by: Neo Zhang <zhang.jianyu@outlook.com>

* sycl: write the SDPA scale from a kernel instead of caching it

The per-(device, value) scale cache was a function-local static
unordered_map with no synchronization, so concurrent backend instances
could access and rehash it at the same time.

Write the scalar with a single_task instead. The value is captured into
the command, so no host memory has to outlive the call -- which is what
the use-after-return fix needed in the first place. That removes the
shared container, the leaked device allocation and the string key, and
it also closes the remaining async-memcpy-from-a-stack-local on the
first flash-attention call.

Ordering does not rely on timing: the queue is created with
sycl::property::queue::in_order and the dnnl stream wraps that same
queue, so the write completes before the SDPA reads the scalar. The
multi-GPU wait_and_throw() branch is unchanged.

Also drop the <cstdlib> include, which is unused.

Assisted-by: Claude Opus 5

---------

Co-authored-by: Neo Zhang <zhang.jianyu@outlook.com>

* common/chat: add specialized minimax m3 parser (#26210)

* spec: add DSpark speculative decoding (#25173)

* spec: add DSpark speculative decoding

DSpark (DeepSpec, 2026) on top of the merged DFlash drafter. It reuses the
DFlash encoder/decoder graph, target feature extraction and KV-cache injection,
and the verify/accept path unchanged; the draft model is a new "dspark" arch
adding a low-rank Markov head (markov_w1/w2) and an optional (unused here)
confidence head. No new public APIs.

The proposal is the only change: the block is anchor-first (position 0 already
predicts the first draft) and the decoder graph applies a semi-autoregressive,
previous-token conditioned logit bias in-graph, chained per block position:

  logits'(i) = logits(i) + markov_w2 . markov_w1[prev(i)]
  prev(0)    = the block's anchor token, prev(i>0) = argmax(logits'(i-1))

vectorized across all blocks in the batch; the anchors are fed through a
dedicated graph input (token 0 of every block). Greedy stays lossless
(verify unchanged, same as DFlash).

- new arch "dspark" (llama_model_dspark : llama_model_dflash, reuses the graph,
  loads the markov/confidence tensors; shares the target's embed/lm_head).
- Qwen3DSparkModel converter.
- new spec type "draft-dspark" (common_speculative_impl_draft_dspark :
  common_speculative_impl_draft_dflash, overrides draft() only: submits whole
  anchor-first blocks and greedily reads back the biased logits).

* spec: read draft block size in the dflash impl

* docs: add DSpark section to speculative.md

* spec: keep dspark block size read in the dspark impl

* dspark : add TODOs for incomplete parts

- confidence head is loaded but not used yet
- confidence-scheduled prefix pruning is not implemented
- the in-graph Markov chain is greedy-only
- only Qwen3 backbones are supported for now (also noted in docs)

* spec: fold DSpark into the DFlash arch

Address review: drop LLM_ARCH_DSPARK and the dspark.block_size /
markov_rank GGUF keys. A DSpark draft now converts to a DFlash GGUF;
the Markov head tensors are detected by presence (like eagle3 d2t),
block_size is read from the existing dflash.block_size key, and the
block anchors are taken as a strided view of the decoder's token
input instead of a separate graph input.

* spec: add confidence-based draft pruning for DSpark

The DSpark confidence head predicts per-position acceptance of the
drafted block. --spec-draft-conf-min truncates the block at the first
position below the threshold (default 0 = disabled).

* fold the dspark impl into dflash, selected by spec type

* address review comments

* dspark: clean up and improve naming

* update readme

* remove trailing whitespace

* dflash: draft full n_max blocks, defer dp.n_max to the central truncation

The DSpark markov head views the draft batch as a uniform [n_seqs x block]
grid, but the per-seq dp.n_max clamp could produce blocks of different
sizes, silently corrupting the strided views and the resulting logits.

Drop the clamp and always draft the full n_max block for every sequence:
dp.n_max is already enforced by the central truncation in
common_speculative_draft(), the same way eagle3 handles it.

Co-authored-by: Zaire404 <3147879462@qq.com>

* dflash: assert the markov head block-uniformity invariant, require the conf head

With the draft batch always submitting equal-size n_max blocks, a
non-divisible token count can only mean the batch was split across
ubatches or a caller broke the layout - fail loudly instead of silently
dropping the markov bias. The block_drafts > block_size early return
stays: worst-case graph reserve passes legitimately build with
n_seq_tokens > block_size.

Also make conf_proj required when the markov head is present: the
confidence head is part of the DSpark checkpoint format, and a missing
head would otherwise leave --spec-draft-conf-min silently reading stale
embeddings instead of confidences.

Co-authored-by: Zaire404 <3147879462@qq.com>

* dspark: fold conf_min into p_min

p_min and conf_min express the same thing - the minimum predicted
survival probability for a drafted position - differing only in how the
estimate is obtained: token probability for regular drafters, the
trained confidence head for DSpark. The DSpark readback never used
p_min, so reuse it for the confidence threshold and drop the separate
--spec-draft-conf-min flag. Both defaulted to 0 (disabled), so behavior
is unchanged.

Co-authored-by: Zaire404 <3147879462@qq.com>

* dflash: note the confidence broadcast workaround

Requested in review: the ggml_repeat only adapts the [1, n_tok]
confidences to the n_embd-wide embd_nextn transport so that
llama_get_embeddings_nextn can be reused - not a placeholder.

Co-authored-by: Zaire404 <3147879462@qq.com>

* cont : clarify

[no ci]

---------

Co-authored-by: Ruixiang Wang <wangruixiang07@outlook.com>
Co-authored-by: Zaire404 <3147879462@qq.com>
Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* ggml-cuda: add chunked SSD matmul for Mamba-2 prefill acceleration (#22675)

* ggml-cuda: add chunked SSD matmul for Mamba-2 prefill acceleration

* cuda: added SSD CICD fixes for CUDA / HIP / MUSA / MSVC.

* ggml-cuda: review comments fixed.

* ggml-cuda: Fuse M matrix materialization into pre_matmul kernel and enabled test.

* ggml-cuda: test updates and fixes

* ggml-cuda: test updates to remove hardcoding of tensor initialise data limits.

* ggml-cuda: ssd minor review comment fixed.

* ggml-cuda: ssd minor CICD fixed.

* CUDA SSD: Fixes correctness by promoting s0_stride_seq to int64_t, improves memory coalescing in ssm_ssd_prepare_dt_kernel, and boosts efficiency by merging B_weighted and C_scaled; also addresses prior review comments.

* cuda: fix sdata read-write race in prepare_dt fallback scan loop

* vulkan: add iq4_nl support back to FA (#24585)

* vulkan: add iq4_nl support back to FA

I was originally concerned about wasting shared memory on the LUT, but it's small
and unlikely to matter in practice.

Also support q1_0 for non-coopmat2.

Fixes #23681

* remove q1_0 FA support

* ggml : set output of view src (#25729)

* llama-graph: set_outputs to t->view_src

* change set_output to GGML_ASSERT about views not being outputs

* sampler : avoid views in outputs

* cont : fix dist sampler

* cont : consistent logits handling

* ggml : set output of view src

* graph : simplify set_outputs()

* cont : cleanup

Co-authored-by: Gaurav Garg <gaugarg@nvidia.com>

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
Co-authored-by: Gaurav Garg <gaugarg@nvidia.com>

* server: abstract llama_memory calls to common_memory (#26221)

* docs: Adapt conda-forge package name (#26229)

Co-authored-by: dev-tinker <dev-tinker@users.noreply.github.com>

* ui: rendering performance follow-up (#26097)

* mtmd : add Nemotron 3 Nano Omni support (parakeet) (#22520)

* mtmd : add Nemotron 3 Nano Omni support (parakeet)

This commit adds support for the subsampling and encoder part of
Nemotron Nemo 3 omni model.

The Parakeet subsampling/encoder were taken from parakeet.cpp which
is currently a pull request against whisper.cpp. I've tried to copy the
code a close as possible to hopefully enable easy patching between the
these two project later.

Refs: https://github.com/ggml-org/whisper.cpp/pull/3735

* mtmd : generate rel pos tensor in graph instead of in conversion [no ci]

This commit removes the generation of the relative positional tensor in
the model conversion script and instead computes it in the encoder
graph. This is only done for the window of positions required for the
current audio sample.

* mtmd : add clip_get_model to clip API [no ci]

This commit adds a function to get access to the clip_model. It also
removes the two functions clip_get_mel_filter_tensor, and
clip_get_window_tensor(const struct clip_ctx * ctx) which can now use
clip_get_model to access the model tensors that it needs.

* mtmd : read mel_filters and window into hparams

* mtmd : use set_input_f32 lambda [no ci]

* mtmd : add better asserts for mel_filters and hann window [no ci]

* mtmd : add missing size_t cast

* mtmd : change type of pad to size_t

* mtmd : zero initialize samples_padded

* mtmd : remove unsued ctx member from parakeet preprocessor

* mtmd : make log_mel_spectrogram_parakeet_worker_thread private static

* mtmd : sync/update parakeeet impl with latest whisper.cpp

This commit updates the parakeet code in mtmd to reflect the latest
updates to parakeet.cpp in whisper.cpp.

A follow up commit will address the currently hardcoded dw_pad and see
if we can add n_conv_kernel as a model metadata field.

* mtmd : add audio_conv_kernel_size to model conversion

This commit updates the model conversion to read the conv_kernel_size
field from the sound_config section of the models config.json file.
It then uses this field instead of the hardcoded values in parakeet.cpp.

* mtmd : cleanup [no ci]

* conversion : call super().filter_tensors [no ci]

* do not discard result of super filter_tensors

* mtmd : use build_mm instead of ggml_mul_mat

* mtmd : use build_ffn

* mtmd : move and reuse get_vector lambda

* mtmd : use build_inp_raw for parakeet

* mtmd : throw exception in get_scalar instead of assert

* mtmd : fix std::min call

* mtmt : use .c_str in throw clause in get_vector

* mtmd : check for F32 type and non-empty tensor in get_vector

The get_vector lambda is used by get_scalar but also standalone to read
in the mel_filters and the window data. Therefor we are not checking
for 1D tensors but allowing multiple dimensions. We do have a check in
get_scalar to verify the size of the vector.

* mtmd : replace hardcoded 1101 for n_tokens_real

* mtmd : assert subsampling_factor is 8

This commit adds an assert of the parakeet subsampling factor to check
that it is 8.

The motivation for this is that this model currently has three
convolutions with a stride of 2. If the underlying model updates the
subsampling factor these convolution operations will need to be updated
and this will produce and error if this occurs.

* mtmd : remove unused ggml_tensors attn_pos_w and mm_norm_w

* mtmd : remove single thread path

This commit removes the single thread path which was a left over from
the original parakeet.cpp where n_threads is configurable.

* fix some security issues

---------

Co-authored-by: Sigbjørn Skjæret <1629204+CISC@users.noreply.github.com>
Co-authored-by: Xuan Son Nguyen <son@huggingface.co>

* opencl: skip the Adreno KQ/KQV image kernels for multi-stream batches (#26189)

The Adreno KQ/KQV image1d kernels (ggml_cl_mul_mat_kq_kqv_adreno) ignore
dim 3 entirely: the sub-buffer covers only nb02*ne02 bytes and the kernel
receives no ne03/ne13/nb03/nb13 arguments. With the unified KV cache,
multi-sequence batches (e.g. llama-perplexity with its default -b 2048,
n_seq=4, or a multi-slot llama-server) present KQ/KQV as 4D tensors with
ne3 = n_stream, so every stream past the first reads the first stream's
K/V and produces garbage. Flash attention masks the bug where it is
enabled; devices where FA is declined (e.g. Adreno 740) hit it with
default settings.

Route ne03/ne13 > 1 to the general path, which handles dim 3, and honor
view_offs when creating the sub-buffers (currently always 0 for tensors
reaching this function, but the function would silently misread any
future view).

Llama-3.2-1B-Instruct Q4_0, wiki.test.raw, 8 chunks, -ngl 99:
- Adreno 740, default:            PPL 1817.64 -> 15.61
- Adreno 740, -fa 0:              PPL 1941.64 -> 15.61
- Adreno 840, -fa 0:              PPL 1943.90 -> 15.50
- single-stream (-b 512) results unchanged (15.6090)
- test-backend-ops -o MUL_MAT on 740: identical before/after (909 OK,
  12 pre-existing q6_K failures)

* ggml-webgpu: Fix some binding alias issues to support all archs, fix recurrent-state-rollback test (#25931)

* Add overlap glu variant to support all archs, fix recurrent-state-rollback test

* format

* Fix all arch overlapped ranges

* format

* diagnose bus error on apple ci

* More testing

* more testing

* more targeted testing

* Fix bug in alignment for > 4gb buffer offsets

* Fix bug in view offsets

* Try avoiding multi_buffers

* not fixed yet, more logging :(

* Handle edge case in set_rows

* Try looking at view source

* Skip deepseek32 for now and clean up trace infrastructure

* simplify skipping

* last cleanup

* actually final cleanup

* update handling of overlap

* format

* try skipping other failing model

* model: Add Laguna-S-2.1 LLM_TYPE (#26233)

* 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)

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) (sam…
@fairydreaming

Copy link
Copy Markdown
Collaborator

I just realized that llama_kv_cache_dsa still allocates indexer KV cache memory for layers with shared indexer. Probably a separate layer filter shall be added in this class for indexer cache based on indexer types array. That would probably save few hundred MB of our precious VRAM.

Implemented this in #26474, needs more testing.

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

Labels

conversion model Model specific

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants