Skip to content

glm5_next: support GLM-5.3-Flash (hybrid KDA + DSA, mHC, NVFP4 expert offload) - #270

Open
Cerynitius wants to merge 5 commits into
FlashML-org:mainfrom
Cerynitius:feat/glm5-flash
Open

glm5_next: support GLM-5.3-Flash (hybrid KDA + DSA, mHC, NVFP4 expert offload)#270
Cerynitius wants to merge 5 commits into
FlashML-org:mainfrom
Cerynitius:feat/glm5-flash

Conversation

@Cerynitius

@Cerynitius Cerynitius commented Aug 29, 2026

Copy link
Copy Markdown

Summary

Adds serving support for zai-org/GLM-5.3-Flash (model_type: glm5_next) — Z.ai's 320B / 18B-active MoE and the first GLM to combine sparse and linear attention — targeting single-GPU hosts with expert offload:

  • new models/glm5_next package: 34 KDA linear-attention layers (fla-core chunk_kda / fused_recurrent_kda, decode passes cu_seqlens so batched single-token requests keep per-request state), 11 MLA/DSA sparse-attention layers, Manifold-Constrained Hyper-Connections (the DSV4 kernels, plus a weightless mean hc_head), 288-expert top-8 routing, and the MTP head skipped at load (FREETOKEN_GLM5_MTP=1 scaffolding is inert without engine hooks)
  • NoPE end to end: rotary_dim=0 guards in the shared GLM/DSA attention and D_R=0 constexpr branches in the sparse kernels
  • DSA key-pooling (index_kpool_* weights): pooled keys reuse the decode-logits kernel over strided rows and score bitwise-identical to the HF reference
  • hybrid KV pools: dense layer_ids remapping so only the 11 MLA/DSA layers allocate latent slabs (without it the 34 KDA layers each claimed one)
  • clamped swiglu (swiglu_limit: 10.0): configs carrying the limit route to the Triton NVFP4 expert backend (marlin/b12x hard-code plain silu and are rejected), and the CPU MoE epilogue applies silu(min(gate, lim)) * clamp(up, ±lim)lim == +inf leaves every other model bitwise unchanged
  • hotness-driven resident/offload expert split: FREETOKEN_GLM5_RESIDENT_LAYERS=3-6,8-11 keeps the named layers' experts on the GPU as model weights (no host bank, no cache slots), cutting the host pin from ~163 GiB to ~129 GiB so a 157 GiB host fits; num_moe_layers excludes them for every bank/cache consumer
  • three fused Triton kernels used by the model code (KDA gate math, mHC pre-norm, router epilogue); each matches its eager chain to 1 ulp, and the fused router's expert selection is id-identical to torch.topk on 200 random batches
  • optional per-row FP8 for the non-expert projections (FREETOKEN_GLM5_ATTN_FP8 / _MLP_FP8 / _KDA_FP8, all default off)

Validation

Validated on a single RTX PRO 6000 Blackwell Workstation Edition (96 GB), 157 GiB host RAM, PCIe Gen5 x16; torch 2.11.0+cu130, triton 3.6.0, CUDA 13.0:

  • LibertAIDAI/GLM-5.3-Flash-NVFP4 (181 GiB experts) served with FREETOKEN_GLM5_RESIDENT_LAYERS=3-6,8-11, --moe-cache-auto, 256K --max-seq-len-override: greedy arithmetic and Chinese-QA probes returned the expected answers; an 8K-token needle prompt recalled the planted passphrase; single-stream decode at this branch's defaults (BF16 non-expert weights) ran 23.5–23.7 tok/s
  • during development the port was teacher-forced against a pure-PyTorch HF-reference implementation (real weights, dequantized experts): 48/48 steps across 3 prompts matched the reference argmax token for token; the DSA pooled keys and the clamped-swiglu kernel were verified bitwise against the HF formulas
  • python -m compileall -q python passes; every patch applies cleanly onto current main

Scope

Serving-throughput work from the same effort is intentionally left out to keep this reviewable: speculative expert prefetch, short-prompt on-demand prefill, decode-GEMV retuning, and the MTP speculative-decoding engine hooks (measured net-negative on PCIe-bound offload) are follow-up material. The vision tower is not loaded (text-only). Defaults are conservative: no resident split, no FP8, no MTP unless the environment variables opt in.

Tuned companion

The defaults here are deliberately conservative. The full tuning layer from the same effort is maintained separately as a patch overlay in Cerynitius/freetoken-ox-boost; on the same single-GPU box it takes single-stream decode from this branch's 23.7 tok/s (BF16 defaults) to ~35 tok/s, 2-way concurrent aggregate to 44.9, and 10-token TTFT to 0.59 s.

What it adds on top of this PR: the non-expert FP8 switches enabled by default, speculative expert prefetch (layer L+1's gate scored a layer early, +8%), short-prompt on-demand prefill (TTFT 3.5x), decode-GEMV retuning (marlin tile config, +4.5%), an M-tiled FP8 GEMV for decode micro-batches (2-way aggregate +13%), and a 2-slot 256K KV pool that frees ~10 GiB for the expert cache (~84% hit rate).

What it trades for that: FP8 numerics on the non-expert projections by default (MMLU-100 spot-check held at 94 vs 89 for BF16, but the numerics are not bitwise), epsilon-level reduction-order changes in the retuned GEMVs, decode-GEMV tile defaults retuned for one card (RTX PRO 6000) rather than swept across GPUs, prefetch/prefill hooks in the shared offload serving path, and a KV-capacity-for-cache default that assumes the 2x256K working set. Those trade-offs are why this PR keeps upstream defaults untouched.

…FP4 experts)

320B/A18B natively-multimodal MoE (text tower only): 34 KDA linear-attention
layers + 11 MLA/DSA sparse-attention layers (NoPE throughout), Manifold-
Constrained Hyper-Connections, 288-expert top-8 routing with clamped swiglu.
Includes the hotness-driven resident/offload expert split
(FREETOKEN_GLM5_RESIDENT_LAYERS keeps chosen layers' experts on the GPU as
model weights so a 157 GiB host can pin the rest), optional per-row FP8 for
the non-expert projections, and inert MTP scaffolding (off by default).
The three fused Triton kernels (KDA gate math, mHC pre-norm, router epilogue)
match the eager chains to 1 ulp; router expert selection is id-identical to
torch.topk on 200 random batches.
…nd selection

Plumbing GLM-5.3-Flash needs from the shared GLM/DSA stack: rotary_dim=0
guards (NoPE) in attention and the sparse kernels, dense layer_ids remapping
so hybrid models only allocate latent slabs for their MLA/DSA layers, the
DSA k-pool sparsity path (pooled keys score bitwise-identical to HF), and
routing configs carrying swiglu_limit to the Triton NVFP4 backend
(marlin/b12x hard-code plain silu and are correctly rejected).
silu(min(gate, lim)) * clamp(up, +-lim) when the limit is finite (GLM-5.3
swiglu_clamp); lim == +inf leaves every other model's path bitwise unchanged.
Registers swiglu_clamp as a silu-family activation id.
…safe prefix caching)

Loads the checkpoint's Glm5NextVisionModel tower behind FREETOKEN_GLM5_VISION
(default off; text-only behavior is byte-identical when unset) and wires image
and video input through the online serving stack.

Model side:
- models/glm5_next/vision.py: eager port of the 0.6B ViT (Conv3d patch embed,
  24 blocks, 2x2 spatial-merge downsample, clamped-swiglu merger). Attention
  runs one segment per temporal unit (qwen2_vl convention). Validated
  stage-by-stage against the HF reference on real weights: patch embed and
  rotary tables bitwise-equal; per-layer drift within the bf16 kernel-noise
  envelope measured between HF's own sdpa and eager backends (t=1 and t=2).
- models/glm5_next/image_process.py: vendored preprocessing. Images: bitwise-
  equal pixel_values vs Glm5NextImageProcessorPil (needs pillow). Video: PyAV
  decode, fps-2 sampling with even padding, paired-frame temporal patches,
  HF-faithful smart_resize budgets (FREETOKEN_GLM5_VIDEO_FPS /
  MAX_VIDEO_TOKENS / MAX_VIDEO_FRAMES / MAX_IMAGE_TOKENS knobs).
- weight.py streams model.visual.* through verbatim (BF16) when enabled.

Serving side:
- OpenAI chat: image_url (data URI / http) and video_url content parts;
  Anthropic messages: base64/url image blocks. The chat template's own
  image/video branches emit the placeholders; the tokenize worker expands
  them per grid (videos expand to per-unit frame blocks with timestamps,
  mirroring Glm5NextProcessor.replace_video_token).
- Pixels ride the ZMQ msgpack codec as raw fp32 bytes; the scheduler process
  (which owns the GPU) encodes them at admission and reuses the existing
  mm_embeds scatter rails.
- Prefix caching is made media-safe instead of skipped: image-span placeholder
  ids are replaced by a per-image pixel blake2b hash (negative id range) in
  the radix cache keys, so identical text+media prefixes hit while different
  media diverges at the span's first token; covered leading spans slice the
  scatter rows, and generated tokens splice back into the key stream. The
  offline mm path (no key ids) keeps the previous cache skip.

Verified end-to-end on an RTX PRO 6000 Blackwell 96 GB serving
GLM-5.3-Flash-NVFP4: image shapes/colors/positions, counting, two-image
comparison, text-in-image reading on both APIs; synthetic-clip motion
direction, start/end positions and colors; same-image repeat TTFT 3.8 -> 1.0 s
and image-conversation turn-2 2.8 -> 1.1 s under --cache-type radix with no
false hits across different images and no page-accounting drift; text-only
paths regression-free.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant