Repository navigation
Conversation
Adds the text model of zai-org/GLM-5.3-Flash, building on the closed #3439: - KDA linear attention (FLA chunk_kda, safe lower-bounded gate) in 3 of every 4 layers; NoPE sparse MLA in the rest. - The DSA indexer with k-pool compression: per-document pools of 4 keys, softmax(gate + ape)-weighted and Hadamard-rotated as in vLLM, top-512 pools scored with the existing FP8 indexer, plus the query's incomplete pool. - Manifold-constrained hyper-connections via DeepseekV4HyperConnection (same math, fused Sinkhorn and write-back kernels); clamped-SwiGLU MoE via ClampedSilu(10). - Sparse MLA kernels accept a 512-wide q/k without a RoPE tail (TileLang and FlashMLA/cuDNN); the indexer pads index rows to FlashMLA's 128 multiple. - Registers glm5_next / glm5_next_text, the VLM layer prefix, and lets the pinned transformers (trainer and vLLM plugin) accept the deepseek_sparse_attention layer type. Co-authored-by: samsja <55492238+samsja@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A micro-batch holding one document gives a 2-entry cu_seqlens, which the compiled decoder layers specialized on and failed the dynamic-size constraint (found by an SFT smoke run with EP=8 and compile). The indexer's pool layout is now built once per forward outside the compiled layers (PoolLayout), and FLA's causal_conv1d runs compile-disabled on both the CP and non-CP paths. Adds GLM-5.3 Flash to the supported-model tables. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- maybe_mark_dynamic instead of mark_dynamic on cu_seqlens: a one-document row (2-entry cu_seqlens) gets its own graph instead of failing the dynamic-size constraint. FLA's non-CP convolution is traced again, as in Qwen3.5. - FLA's CP context is built eagerly (same as #4011 for Qwen3.5). - KDA takes a per-layer cu_seqlens copy: FLA memoizes cu_seqlens metadata by tensor identity, so with one shared tensor a layer's forward and its activation-checkpoint recompute ran different op counts, and checkpoint replay handed a cached int CPU copy to the wrong op under CP. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
hallerite
force-pushed
the
feat/glm53-flash
branch
from
October 12, 2026 15:06
ed0294d to
7c65b7c
Compare
The per-block cast of expert weights addressed elements with int32 offsets. With all experts on one rank the stacked weights pass 2**31 elements (GLM-5.3 Flash: 288 experts of 2048 x 4096 at ep=1), so DeepGEMM FP8 MoE compute hit an illegal memory access. The other FP8 cast kernels already compute offsets in int64. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
convert_dcp_to_bf16 crashed after writing the weights for models without GenerationMixin (GLM-5.3 Flash, Qwen3.8 Flash Next, Qwen3.5, gpt-oss, Nemotron-H), so no config, tokenizer or processor files were saved next to them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n vLLM The configs dropped keys the trainer does not model, so an exported config.json lost topk_method (vLLM then creates no selection-bias parameter and fails to load the export), moe_router_dtype, indexer_rope_interleave and the vision keys. Pass them through and drop only dtype and head_dim (0 in the checkpoint). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
vLLM serves GLM-5.3 Flash's attention in BF16 (KDA, sparse MLA and its indexer) and quantizes only the MLPs and experts, for the FP8 checkpoint and online FP8 alike. The default FP8 ignore list would quantize the attention projections on the trainer side only. Models can now add ignore patterns through PreTrainedModelPrimeRL.quantization_ignore_patterns; GLM-5.3 Flash keeps every self_attn linear in BF16. Also freeze the k-pool indexer through freeze_sparse_indexer like the other sparse indexers, instead of inside the module. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
vLLM computes GLM-5.3 Flash's hyper-connections (fn, base, scale) and KDA's A_log / dt_bias in fp32, and the checkpoint stores all of them except hc fn in fp32, but FSDP cast them to bf16 for the trainer's forward. Models can now list such modules in PreTrainedModelPrimeRL.fp32_modules: each becomes its own fp32 FSDP unit (fp32 compute dtype under full offload) that does not cast its inputs and stays unsharded until the backward, so its forward may return its parameters. KDA's decay parameters move to a submodule for this (self_attn.decay.A_log, converted from the checkpoint's self_attn.A_log), and hc fn now goes to vLLM in fp32 with the rest of the hyper-connection. PrimeRL conversion caches from earlier versions of this branch use the old key layout and must be regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
samsja
pushed a commit
that referenced
this pull request
Oct 26, 2026
## Summary Adds the text model of [`zai-org/GLM-5.3-Flash`](https://huggingface.co/zai-org/GLM-5.3-Flash-BF16) (`glm5_next`, 313B params / ~18B active). Based on #4036, which this supersedes. - **KDA linear attention** (3 of every 4 layers) on FLA's `chunk_kda` with GLM's lower-bounded forget gate. - **NoPE sparse MLA** in the remaining layers (absorbed form) on the existing `sparse_mla` kernels, which now accept q/k without a RoPE tail. - **DSA indexer with k-pool compression**: keys pooled per document in groups of 4, FP8 scoring after a Hadamard rotation (as vLLM), top 512 pools plus the query's own incomplete pool. Frozen, as in GLM-5. - **mHC** via `DeepseekV4HyperConnection`; **MoE** with `ClampedSilu(10)`, built inline in the decoder layer like the other models. - Compile- and AC-safe `cu_seqlens` handling (one-document rows, FLA's CP context, per-layer `cu_seqlens` copy for checkpoint replay). - Composite `glm5_next` / text `glm5_next_text` configs; checkpoint-only keys (`topk_method`, `moe_router_dtype`, vision keys, ...) pass through so an HF export still loads in vLLM. Conversion stacks the experts, moves the flat `hc_*` tensors onto the hyper-connection modules, and drops the vision tower and the MTP layer. - Merged with `main` (vLLM 0.32, transformers 5.19): transformers now accepts GLM-5.3 Flash's layer types, so the compat shim is gone. It renames the checkpoint's `deepseek_sparse_attention` to `indexed_attention` on load, which the decoder layer now matches. Context parallelism uses the Ulysses-only `cp_blocker` API (no head constraint: KDA, the indexer and sparse MLA all-gather over the CP group). - **vLLM patch for KDA conv weight updates**: vLLM caches a `torch.cat` copy of the q/k/v short-conv weights on the first forward, so weight updates never reached inference. `monkey_patch_glm5_next_kda_conv_weights` stores the three weights as slices of one buffer and points the cache at a view of it; reload copies in place, so every update lands. Remove when vLLM keeps them in one merged parameter. - **Example SWE RL config** (`examples/advanced/glm-5.3-flash/swe.toml`): SWE-rebench-V2 at 128K context on 2 trainer nodes (EP 8, CP 8) + 6 rollout nodes (TP 8). Text only: the vision tower is not modelled. Multimodal support follows in a separate PR. ## Validation 100+ RL steps on SWE-rebench-V2. mismatch_kl is stable, reward goes up. Wandb run: [link](https://forge.coreweave.com/wandb/primeintellect/glm53-flash/runs/7ce7b27d533a45d593f475c56bd2b486) <img width="963" height="617" alt="Screenshot 2026-10-22 at 7 11 53 PM" src="https://github.com/user-attachments/assets/47ec258f-bf31-4a56-99ad-54e7a996ddbd" /> <img width="1075" height="309" alt="Screenshot 2026-10-22 at 7 11 21 PM" src="https://github.com/user-attachments/assets/ab4905f9-6037-420c-a1a0-b427ee7bbbb4" /> Also, initial tests of 20 RL steps on `math-env` (Hendrycks-Math train). Two runs at `cp = 1` and at `cp = 2` (Ulysses). All steps have `mismatch_kl` below 0.015: Wandb run for cp = 1: [link](https://forge.coreweave.com/wandb/primeintellect/glm53-flash/runs/00023e7a572141449318d5453de56e1a) Wandb run for cp = 2: [link](https://forge.coreweave.com/wandb/primeintellect/glm53-flash/runs/a17dba66002a4e99a28c7b2be54c22f7) **Unit test**: config and checkpoint round trip for the text and composite configs, including the pass-through keys. ## Follow-ups Now that we have a baseline RL run, the additional changes from #4036 can be validated against the KL numbers above: - fp32 hyper-connections and KDA decay (FSDP casts them to bf16 while vLLM keeps them in fp32). - FP8 trainer layout matching vLLM (attention stays bf16). - Export tool without a generation config; int64 offsets in the grouped FP8 weight cast (both model-independent fixes). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the text model of
zai-org/GLM-5.3-Flash(glm5_next, 313B text params / ~18B active), building on the closed #3439 by @samsja. Based onmainwith #4037.chunk_kdawith GLM's safe, lower-bounded forget gate (lower_bound * sigmoid(exp(A_log) * (f + dt_bias)), computed in-kernel). The checkpoint's three depthwise convs run as one conv over concatenated q/k/v.sparse_mlakernels. Both DSA backends now accept a q/k without a RoPE tail: TileLang skips the tail tile when it is zero-width (from Add GLM-5.3 training and stack configs #3439), FlashMLA/cuDNN take the 512 shape natively.softmax(gate + ape)-weighted mean; a query scores the complete pools ending at or before it, keeps the top 512, and also attends to its own incomplete pool. Following vLLM, queries and pooled keys are Hadamard-rotated (same fp32 butterfly order) before FP8 scoring, which reuses the existingfp8_indexerover pools. Index rows are padded to a multiple of 128 for FlashMLA. The indexer is frozen throughfreeze_sparse_indexer, like the other sparse indexers. Its per-document pool layout (PoolLayout) is built once per forward, outside the compiled decoder layers.DeepseekV4HyperConnection(identical math; fused Sinkhorn and write-back kernels), final stream mean before the norm. MoE withClampedSilu(10)(GLM's clamped SwiGLU) on routed and shared experts and the dense MLPs.cu_seqlens:maybe_mark_dynamic(a one-document row gets its own graph instead of failing the dynamic-size constraint), FLA's CP context built eagerly (as fix(trainer): keep Qwen3.5 CP context setup eager #4011 does for Qwen3.5), and a per-layercu_seqlenscopy in KDA so FLA's identity-keyed metadata cache cannot desynchronize activation-checkpoint replay (see below).PreTrainedModelPrimeRL.quantization_ignore_patterns; GLM-5.3 Flash keeps everyself_attnlinear in BF16.fn,base,scale) and KDA'sA_log/dt_biasin fp32, and the checkpoint stores all butfnin fp32, but FSDP cast them to bf16 for the trainer's forward. Models can now list such modules inPreTrainedModelPrimeRL.fp32_modules: each becomes its own fp32 FSDP unit (fp32 compute dtype under full offload) that does not cast its inputs and stays unsharded until the backward. KDA's decay parameters live on aself_attn.decaysubmodule for this (converted from the checkpoint'sself_attn.A_log), and hcfngoes to vLLM in fp32 with the rest of the hyper-connection.glm5_next/ textglm5_next_textconfigs registered like Qwen3.8 Flash Next (model.language_model.*layout, VLM layer prefix). Checkpoint keys the trainer does not model pass through, so an exportedconfig.jsonstill loads in vLLM. The conversion stacks experts, moves the flathc_attn_fn-style tensors onto the hyper-connection modules, and drops the vision tower and MTP layer.deepseek_sparse_attentionis not a layer type the pinned transformers accepts, which broke both our config and vLLM'sGlm5NextConfig; the transformers compat shim now takes a generic list, applied in the trainer and the vLLM plugin.tools/convert_dcp_to_bf16.pycrashed on models without a generation config (also Qwen3.8 Flash Next, Qwen3.5, gpt-oss, Nemotron-H).vLLM 0.31.0 already serves the model; no inference bump.
Validation
Scripts and logs:
/home/konstantin/glm53-flash-validation(H200).Checkpoint mapping: every text tensor of the real checkpoint maps HF→prime→HF with matching shapes (1372 tensors, 313.3B params), only vision/MTP dropped.
Against the transformers 5.19 reference (tiny random 8-layer model, bf16 vs fp32 reference, routing replayed): dense-regime logits KL 9.0e-4 vs the reference's own bf16 error of 1.35e-3; per-layer error grows smoothly to ~1%; gradients at a median 0.89x the reference's own bf16 error. On the reference's exact layer inputs with an exact fp32 scorer, the indexer reproduces transformers' selection on 100% of queries (pooling, tail, packing, causal ranges); the FP8 scorer agrees on 78–83% of exact sets (Jaccard 0.97–0.98) in this random model. Packed vs separate documents, compiled vs eager (including one-document rows), TileLang vs FlashMLA/cuDNN: all within bf16 noise.
Expert and context parallelism on real weights (8 real layers of GLM-5.3-Flash-BF16, the trainer's own
setup_modelon 8 GPUs, 8 packed rows of 8192 real tokens, routing replayed, full per-parameter gradients against an FSDP-only reference):EP, compile, AC and offloading sit exactly on the noise floor. CP sits ~2x above it because of the smaller per-rank shape, not CP logic: the same rows cut to 4096 tokens without CP deviate exactly as much (1.54e-4 vs 1.52e-4) and match cp=2 at the floor.
Context parallelism, tiny model (real 2-rank NCCL, three row shapes including a one-document row, each twice): eager, compile, compile + full AC, eager + full AC and compile + selective AC all match an eager cp=1 reference (dense logits ≤0.8% rel, gradients ≤1.3% median rel).
Trainer vs vLLM on real weights (first 8 layers, 1k–30k-token prompts, prefill and decode, fp32 router and lm_head on both sides): mismatch KL ≈ 5e-4 below 2048 tokens, 6.5e-4 above (k-pool sparse path), ≈ 9e-4 for decoded tokens; identical on both DSA backends and packed. No query in ~52k chose among zero-score pools, so trainer/vLLM tie-breaking differences cannot contribute there.
SFT on real weights (4 layers, EP=8, compile, fp32 router, packed 8k rows, 20 steps on
mainwith revert(trainer): compile around the checkpoint again for MoE blocks with an fp32 router #4037): full AC and selective AC, both with activation offloading (on by default with AC), finite and matching step for step (step 20 loss 14.8000 vs 14.7998), peak 30.6 vs 38.2 GiB per GPU, ~1.2–2.8 s per warm step.Checkpoint save/resume (same setup, lr 5e-5 so 20 steps move the weights): forking a run from its own step-10 checkpoint reproduces it (step 11 loss differs by 3.8e-6, i.e. forward-pass noise; later steps stay within run-to-run noise), and the data stream resumes at the same samples.
HF export (
tools/convert_dcp_to_bf16.py): an lr=0 export is bit-identical to the original checkpoint on all 972 text tensors, including the fp32hc_*,A_log,dt_biasand selection-bias tensors. vLLM loads a trained export (text-only, see below) at trainer-vs-vLLM KL 1.0e-3 prefill / 3.4e-4 decode, versus 2.6e-4 on the original weights.RL smoke run (8-layer cut, 4 trainer GPUs with EP=4 + 4 vLLM GPUs, NCCL weight broadcast,
glm-5.3renderer, reverse-text with thedebugalgorithm since the cut only writes gibberish): vLLM serves the model, the startup weight broadcast reloads every language-model layer (only the unmodelled vision tower is skipped), rollouts flow, and 3 full RL steps complete, each followed by a weight broadcast into vLLM. Live mismatch KL 7e-4 – 8e-4 per step.FP8 (same RL smoke run with the FP8 trainer, DeepGEMM dense linears +
deepgemm_fp8MoE, against vLLM quantizing the BF16 weights online withfp8_per_blockas in the GLM-5.3 recipe): 5 steps with weight broadcasts into the FP8 server, mismatch KL flat at 3.7e-3 – 4.1e-3 with vLLM's defaults, and 2.3e-3 – 2.4e-3 withVLLM_USE_DEEP_GEMM_E8M0=0(exact DeepGEMM scales, as the trainer uses on SM90, instead of power-of-two scales). Offline on the 8-layer cut (52k tokens): trainer BF16 vs vLLM FP8 2.0e-3 (default) / 1.4e-3 (exact scales); trainer FP8 vs vLLM FP8 with exact scales 1.8e-3.Full model in vLLM (all 45 layers, BF16, one node at TP=8): loads in ~20 min, answers chat prompts correctly with reasoning, and prompt logprobs on 1k–32k tokens of real text average −1.3 to −1.8.
Full model in the RL loop (all 313B params; 3 trainer nodes with EP=8, compile and full AC, plus one vLLM node at TP=8; reverse-text with real rewards, before the fp32 change above): 50 steps with SignSGD and
offload = "full"complete in 66 min, with an NCCL weight broadcast into vLLM after every step, 63–73 GiB peak per trainer GPU and 33–45 s per typical step. Reward stays at 0.98–0.997 (the task starts near its ceiling), but mismatch KL drifts from 2.1e-3 (mean of steps 1–5) to 4.7e-3 (steps 46–50) at flat entropy (0.09–0.13). AdamW does not fit on 3 trainer nodes:offload = "optimizer"moves its state back to the GPU for the step (~208 GB per GPU), andoffload = "full"needs 16 bytes per param of CPU RAM (~1.52 TiB per node, 1.48 TiB available), so AdamW on this model needs at least 4 trainer nodes.fp32 hyper-connections and KDA decay (8 real layers, the parallelism harness above): EP=8 + compile + full AC + offloading and EP=8 + CP=2 + compile + selective AC agree with an FSDP-only run of the same code at the earlier floors (mismatch KL 8.3e-5 and 1.6e-4), and all three move away from the bf16-parameter reference identically (KL 2.6e-4), so the fp32 units are correct under EP, CP, compile, AC and offloading. 20 SFT steps track the bf16 run (step 20 loss 14.7903 vs 14.7998) at the same step time and memory. In the 8-layer RL smoke run (full offload) mismatch KL goes from 7.4e-4 to 7.0e-4.
Renderer: the existing
glm-5.3renderer reproduces GLM-5.3 Flash's own chat template token for token (plain, multi-turn with reasoning, tool call).Coverage
torch.compile, incl. one-document rows🚧 Still missing
offload = "full", ~1.14 TiB CPU RAM per node), should show whether the drift remains. Trainer-vs-vLLM KL at 64k–128k context on the full model is still unmeasured.CUDA error: invalid argument) on the full model; not investigated yet. Trainer FP8 and vLLM FP8 still round independently (trainer FP8 raises the mismatch against FP8 vLLM instead of lowering it), so FP8 buys speed and memory, not accuracy.top_p = 0.95, which turns sampling masks on).🩹 Patches and workarounds
Needed but not in this PR:
num_seqs × headson CUDA grid axis z (max 65535), so at TP=1 / data-parallel serving the server dies in warmup unlessmax_num_seqs < 1024. Workaround: setinference.vllm.max_num_seqs(e.g. 512).index_kpool × 32tokens, but DeepGEMM's paged-MQA kernel takes 32-pool pages only on SM100 and needs 64 on SM90. At TP=8 the block lands on 640 tokens and engine startup fails withblock_kv == 64 or (arch_major == 10 and block_kv == 32). Workaround:inference.vllm.block_size = 768(a multiple of 256). TP=1 happens to get 4352 and works.VLLM_USE_DEEP_GEMM_E8M0=0ininference.env_varson Hopper; it cut FP8 mismatch KL from ~4e-3 to ~2.4e-3.GLM-5.3-Flash-BF16on our cluster carries aprime/cache from Add GLM-5.3 training and stack configs #3439's run in the old key layout. Pointmodel.conversion_direlsewhere until it is removed.No longer needed on
main:aten.empty_like ... not found in storage; with both cherry-picked it passed. Onmainwith revert(trainer): compile around the checkpoint again for MoE blocks with an fp32 router #4037 the same run passes without them, and CP + AC passes too. With revert(trainer): compile around the checkpoint again for MoE blocks with an fp32 router #4037, checkpointed blocks run eagerly under compile; this model's blocks would anyway, since FLA's compile-disabled KDA calls break the graph inside the compiled checkpoint, so compile gives no speedup while AC is on.Included in this PR:
deepseek_sparse_attentionlayer type in both our config and vLLM'sGlm5NextConfig. The compat shim now takes a generic list and is applied in the trainer and the vLLM plugin.maybe_mark_dynamiconcu_seqlens(a 2-entrycu_seqlensotherwise fails the hardmark_dynamicconstraint withConstraintViolationError), plus the indexer layout built outside the compiled layers.cu_seqlenscopy. FLA memoizescu_seqlensmetadata by tensor identity; with one tensor shared by every layer, a layer's forward and its recompute ran different op counts, torch's checkpoint replay (matched by op invocation order) handed a cached int CPU copy to the wrong op, and every CP + AC backward failed withAutograd not support dtype: Int.Found along the way, not addressed here:
mainfor the other FLA models. It is confirmed on Qwen3.8 Flash Next: CP=2 with full, selective or compiled AC crashes with the same error, and either a per-layercu_seqlens.clone()orFLA_DISABLE_TENSOR_CACHE=1fixes it (feat(models): add native Qwen3.8 Flash Next #3434's validation run set the latter, which likely masked it). Qwen3.5 and Nemotron-H are untested.mark_dynamiccu_seqlens(qwen3, qwen3_5, gpt_oss, nemotron_h; fix(qwen3.8): allow sequence-boundary shape specialization #4028 fixes Qwen3.8 only).processor_config.jsonorgeneration_config.json, so vLLM's default multimodal setup cannot load them; only text-only serving (limit_mm_per_prompt = {image: 0, video: 0}) works. Exports ofmodel.debug.num_layersruns keep the fulllayer_typeslist.progress/num_tokensstays one step behind:sft/train.pysaves interval checkpoints before updatingprogress.total_tokens.length = "variable"andinput_ids = "random": it always drawsseq_len + 1ids.Notes
fp32_modules. PrimeRL conversion caches written by earlier versions of this branch use the old KDA key layout and must be regenerated.debugalgorithm (constant advantage), because the 8-layer cut only writes gibberish and GRPO filters every zero-variance group, and full CPU offload, because AdamW state did not fit on 4 GPUs.🤖 Generated with Claude Code