add GLM-5.3-Flash (GLM5-Next) support - #27773
timkhronos wants to merge 57 commits into
Conversation
106ece6 to
9370c82
Compare
|
Hey @timkhronos great work on the PR! A few requests if possible:
Tagging @ngxson for visibility as well. I re-checked and if (1) + (2) is applied, the quants we uploaded work fine (+ the small shard-1 rewrite) and KLD / PPL are correct under this PR. Seems like a simple alias isn't possible actually :( It breaks the quants made with this PR |
|
Hmmm https://github.com/timkhronos/llama.cpp/pull/9/changes would alias the tensors but it looks a bit problematic hmmm |
|
Throwing up some performance numbers here from the lower end of consumer hardware (128GB DDR5 + 24GB VRAM (4090)). avar6 has some freshly converted imatrix quants from this PR up as of now if anyone else wants to give them a go: https://huggingface.co/avar6/GLM-5.3-Flash-BF16-gguf For the IQ3_S, I am getting roughly 300t/s prefill at 256K context and 2048 b/ub size. Generation speed starts off at around 9t/s and drops down considerably by mid window (~128K) to around 6t/s. This seems to track with the 'pooled indexer keys' issue. The model is fully coherent and seems to be working fine. I don't have PPL/KL numbers at the moment as I still need to generate a logit dump. I have noticed an interesting memory quirk, which I haven't seen before. This is the only model I have ever seen have inconsistent checkpoint sizes. As the context fills the checkpoints grow alarmingly fast in size. At ~90K they are already up to nearly 1.6GB. I don't know if this is an expected behavior for this model arch, or if this is a something which needs to be looked into. Also, something of note for you @danielhanchen which I found last night while looking over the three PRs for this arch. The vision towers between this PR and yours differ as well. This PR reuses the name clip.vision.projector_type = "glm4v" while you built a new one clip.vision.projector_type = "glm5next". Likely not much of an issue given how easy it is to regenerate mmproj files, but it will need to be delt with as well. |
|
Yes I'll re-do the vision! This is fine! @timkhronos I confirmed timkhronos#9 works fine and does not break your GGUFs. We will however need to do a cheap shard-1 update so that should be fine |
…ed up long context decode, fla, and slight MTP improvements.
|
@timkhronos I saw you changed the tensor naming - but my solution I provided was to allow everyone's quants to work - now your own ones you uploaded don't work haha. We still need to provide the shard rewrite for the naming (glm5-next) which we're fine with, but now the DeepSeek convention means you yourself have to reupload all shards or do a tensor rename inplace with a script - was this your intention? |
|
@danielhanchen Hey! I ended up going with the the indexer_compressor naming scheme, as it is closer to what's already there, and I was meaning to ask Avar to reconvert anyways, as his ggufs were made when we were missing quantization protection for some crucial tensors so they are not ideal. Your vision projectors will need reconverting though most likely, and your main model ggufs might be missing the |
|
@timkhronos Hey! I made some shard rewrites to https://huggingface.co/unsloth/GLM-5.3-Flash-GGUF/tree/main/Shard_Rewrite for in preparation! |
am17an
left a comment
There was a problem hiding this comment.
can we add a test for this memory type? or maybe enroll it in test-recurrent-state-rollback once seq_rm works without repooling the entire kv-cache
| // The pooled keys persist in the idx cache across batches. | ||
| // Sequence edits shift the pool grid and stale the cached values. | ||
| bool mem_idx_is_stale() const { return mem_idx_stale; } | ||
| void mem_idx_stale_clear () { mem_idx_stale = false; } |
There was a problem hiding this comment.
| void mem_idx_stale_clear () { mem_idx_stale = false; } | |
| void mem_idx_stale_clear() { mem_idx_stale = false; } |
| if (hparams.has_swiglu_clamp()) { | ||
| cur = ggml_clamp(ctx0, cur, hparams.swiglu_clamp_gate.first, hparams.swiglu_clamp_gate.second); | ||
| tmp = ggml_clamp(ctx0, tmp, hparams.swiglu_clamp_up.first, hparams.swiglu_clamp_up.second); | ||
| cb(cur, "ffn_gate_clamped", il); | ||
| } | ||
| cur = ggml_swiglu_split(ctx0, cur, tmp); |
There was a problem hiding this comment.
we already have ggml_swiglu_clamp, please re-use that
|
|
||
| ggml_tensor * pooled_new = nullptr; | ||
| // Pool only entries completed by this ubatch. | ||
| if (n_new > 0) { |
There was a problem hiding this comment.
this is going to have a graph thrash every k_pool tokens, it's better to write a dummy value regardless of n_new
| pooled_new = ggml_reshape_2d(ctx0, pooled_new, n_embd_indexer, n_new); | ||
| cb(pooled_new, "indexer_pool_k_new", il); | ||
|
|
||
| if (inp_kpool->cache_safe) { |
There was a problem hiding this comment.
we should try to eliminate the concept of cache_safe as it will change graph topology and generally have quite a performance cost relative to the "safe" option where no two seqs share a cell. ATM it seems like the only way to really avoid this to not use the kv-unified cache. cc: @ggerganov
| graph(const llm_graph_params & params) : llm_graph_context(params) {} | ||
| graph(const llama_model & model, const llm_graph_params & params); | ||
| // manifold-constrained hyper-connections (mHC), shared by deepseek4 and derived model graphs like glm5-next. | ||
| template <typename Base = llm_graph_context> |
There was a problem hiding this comment.
I think this abstraction maybe too early, as every model right now is creating it's own slightly different version of these ops. It may be better to keep these separate for now and refactor later
| new llama_kv_cache_context(mem->get_mem_idx(), std::move(sinfos_idx), ubatches)) {} | ||
| new llama_kv_cache_context(mem->get_mem_idx(), std::move(sinfos_idx), ubatches)) { | ||
| // Sequence edits require a full re-pool. | ||
| mem_idx_stale_batch = mem->mem_idx_is_stale(); |
There was a problem hiding this comment.
right now any edit to the mem_idx is going to cause a full re-pool. Since we're keeping the entire kv cache along with the pool entries, why do we need to do this? We can just re-do the pool from that point? This will allow MTP to roll-back effectively. seq_rm from tail should be free in this setup
| uint32_t indexer_top_k = 0; | ||
| uint32_t indexer_kpool = 0; // k-pool size | ||
| bool indexer_kpool_select_tail = true; | ||
| bool indexer_index_share_mtp = false; // MTP iterations reuse one indexer selection |
There was a problem hiding this comment.
looks like this is not used at the moment
| // Pools start at the first valid token | ||
| for (size_t j = 0; j + kpool <= sq.cells.size(); ) { | ||
| const llama_pos p0 = sq.cells[j].first; | ||
| if ((p0 - sq.pos_min) % (llama_pos) kpool != 0) { | ||
| ++j; | ||
| continue; | ||
| } | ||
| bool ok = true; | ||
| for (uint32_t k = 1; k < kpool; ++k) { | ||
| if (sq.cells[j + k].first != p0 + (llama_pos) k) { | ||
| ok = false; | ||
| break; | ||
| } | ||
| } | ||
| if (ok) { | ||
| sq.pools.push_back((uint32_t) j); | ||
| j += kpool; | ||
| } else { | ||
| ++j; | ||
| } |
There was a problem hiding this comment.
I feel like this machinery exists in case there are holes in the sequence. This usually is only required for context shift iirc, maybe switching it off will simplify this code a lot. Not required in this PR though
|
Also |
|
|
||
| // Fix the pool layout of this ubatch. | ||
| if (res && kpool_track()) { | ||
| kpool_st = std::make_unique<kpool_state>(kpool_build_state(get_ubatch())); |
There was a problem hiding this comment.
this is going to repack the entire pool state for every ubatch, we should fix this
The two clamps around swiglu_split are what ggml_swiglu_clamp already does, so the clamp bounds collapse back to one value. GLM5V also never called set_limit_image_tokens(), so --image-max-tokens had no effect. Assisted-by: Claude Opus 5 (cherry picked from commit 46d18e1)
The layout was rebuilt from a full cell scan on every ubatch. Pools are fixed by the positions relative to the sequence's first one, so the layout now lives on the memory and a ubatch only appends to it. A sequence edit no longer stales every pooled key either, only the ones at or after the edited position, which makes a tail seq_rm free. The pooling subgraph is built unconditionally so the graph shape no longer changes every kpool tokens, and the pool axis is folded into rows before soft_max, which otherwise exceeds the CUDA gridDim.y limit past n_kv 262144. Assisted-by: Claude Opus 5 (cherry picked from commit 5d1c40b)
The conv state and the delta net state were only written to the live row, so a rollback restored whatever the checkpoint rows happened to hold. Take the same route as kimi-k3: build_recurrent_attn for the state, and write all K_rs conv groups. That also drops a state view that assumed contiguous rows. Enroll the arch in test-recurrent-state-rollback, which catches this under its garbage-filled cache pass. Assisted-by: Claude Opus 5 (cherry picked from commit 5ace37e)
…flicts Merge ggml-org/llama.cpp master (a97cce8) into the GLM-5.3-Flash (GLM5-Next) branch of PR ggml-org#27773 so the branch becomes mergeable again (GitHub reported mergeable=false, mergeable_state=dirty). Exactly one file conflicted; all other files merged automatically: * src/llama-graph.cpp (build_moe_ffn swiglu-clamp path): both sides added a new architecture to the same OR-chain that selects the fused ggml_swiglu_clamp path. master added LLM_ARCH_MAPLE; this branch added LLM_ARCH_GLM5_NEXT. Resolved by keeping BOTH arches in the condition: if (arch == LLM_ARCH_MAPLE || arch == LLM_ARCH_DEEPSEEK4 || arch == LLM_ARCH_GLM5_NEXT || (arch == LLM_ARCH_DFLASH && hparams.dsv4_hc_mult > 0) || arch == LLM_ARCH_HY_V4) No other change to the file; the branch's separate GLM5_NEXT addition in build_ffn (swiglu_clamp_shexp) auto-merged untouched. Assisted-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clear the attention and indexer cache data after a failed hybrid state restore so restored NaNs cannot affect a later sequence. Assisted-by: Codex
Reserve the full GLM5-Next pool capacity and dirty pool count. The gpu-rocm Test step aborts when n_new grows while the graph node count stays fixed; CUDA, Vulkan, Metal, and WebGPU checks report the same error. Assisted-by: Codex
|
CI is failing |
|
Yeah I'm on it. |
|
No idea what's with the Windows checks but I fixed the other ones. |
|
Probably you need to rebase as well. Not sure if this failure is caused by this PR https://github.com/ggml-org/llama.cpp/actions/runs/36382726074/job/108808733541?pr=27773 |
…teardown Two defects in the cross-ubatch k-pool layout added by the k-pool commit: 1. Wrong results. An edited sequence only rebuilt its pool layout when its cell count changed, so if the first ubatch after an edit added back exactly as many cells as were removed, the stale position-to-cell list survived. With a unified cache and more than one sequence, where another sequence takes the freed cells, the reused layout points at the wrong cells (CPU: large logit drift, CUDA: NaN). Rebuild whenever the sequence is stale, not only on a size mismatch. 2. Slowdown. "shared" mode was assumed to end only with an edit that forces a rebuild, but sharing also ends when the other sequence is removed. The survivor kept shared = true, pinning cache_safe off and re-pooling every pool on every ubatch (server trigger: n>1 completions with -kvu, via the seq_cp in copy_state_to). In seq_rm, if the layout has shared cells, stale every sequence so one rebuild re-derives sharing and cache_safe returns to 1. Assisted-by: Claude Opus 5
am17an
left a comment
There was a problem hiding this comment.
We can support -sm tensor and MTP in follow-up PRs but this looks good to me. Ran a couple of tests locally.
|
Could you rebase/merge master? |
build_attn_mha split the batch into streams with a stream stride of q->nb[3]/n_stream. That only equals one stream's span, (ne[2]/n_stream)*nb[2], when q is contiguous. GLM5-Next is nope-only, so it does not concat a rope part and passes the permuted q_absorbed straight in, where nb[3] != ne[2]*nb[2]; the stride was then n_head times too large and every stream s >= 1 read another head's queries. Split-KV (-np N without --kv-unified) multi-stream prefill was wrong for every stream past the first. Unified KV and decode were unaffected (n_stream == 1, and decode takes the gather path). Other MLA models concat rope so q is contiguous and the computed value is unchanged for them. Compute the stride from the token dimension, which is identical for a contiguous q. Assisted-by: Claude Opus 5
The shared-cell teardown added to seq_rm (stale every sequence when the layout has shared cells, so a survivor does not keep shared = true and pin cache_safe off) was missing from the other paths that can free shared cells: state_read and state_drop staled only the one sequence. Apply the same re-derivation there and correct the comment that claimed sharing ends only via an edit or seq_rm. Assisted-by: Claude Opus 5
The hc_ name filter was listed twice in the GLM5_NEXT protection block. Assisted-by: Claude Opus 5
Bring the PR current with ggml-org master (f00a64c). The only conflict was in tests/CMakeLists.txt: master's ggml-org#29426 refactored test-recurrent-state-rollback to run across every generated model via --models, which already covers glm5-next (it is generated by test-llama-archs and is recurrent, so not skipped, and the runner fails if any model fails). The PR's separate per-arch glm5-next invocation was therefore redundant and dropped; glm5-next rollback is still exercised by the all-archs run.
|
Done :) |
Overview
Add support for GLM -5.3-flash a 320B hybrid model, supporting both text and vision.
Additional information
Architecture
GLM 5.3 flash mixes 34 KDA linear layers with 11 DSA laters, with mHC and Deepseek style Moe. Most of the parts are already in llama.cpp so I reused whatever I could:
What I implemented new:
llama_memory_hybrid_dsa: recurrent state + DSA cache, cloned from hybrid ISWA.Rebased onto llama_memory_hybrid_idx instead of the earlier ISWA clone.the encoder is the same family as glmv4 with per head qk-norm, clamped Swiglu and no post conv norm. It reuses glm4v projector with a swiglu_limit key and an optional image token budget.Added as glm5v as GLM 5.3 Flash requires a different pre processing method than what glm4v uses.Tests
Limitations
Quantized GGUFs converted with this PR are available here.
Requirements