Skip to content

feat(gguf): serve Qwen3.5 dense and MoE GGUF checkpoints - #701

Merged
jason-fxz merged 1 commit into
mainfrom
feat/gguf-qwen35
Oct 16, 2026
Merged

jason-fxz merged 1 commit into
mainfrom
feat/gguf-qwen35

Conversation

@jason-fxz

Copy link
Copy Markdown
Collaborator

Summary

Serves llama.cpp qwen35 (dense) and qwen35moe (MoE) GGUF files, which cover Qwen3.5, Qwen3.6 and Qwen3.8 (27B and 2.4T-A95B), on the existing qwen3_5_moe model classes. Text only; MTP blocks in the file are skipped.

Changes

  • models/qwen3_5_moe/gguf.py (new):
    • Builds the config from the GGUF metadata.
    • Maps checkpoint names to GGUF tensors, including the fused ffn_gate_up_exps.
    • Undoes the converter's transforms: GDN value heads go from tiled back to grouped order by reordering whole packed rows; A_log is restored from -exp(A_log).
    • Refuses prism (Hadamard-rotated) files and NVFP4 GGUF files with per-tensor .scale tensors.
  • gdn.py, model.py:
    • ssm_out keeps its tiled input columns, because a K-quant superblock spans two heads. Under GGUF the GDN layer permutes its input to that order instead.
    • embed_tokens takes the quant config, so a quantized token_embd is served packed.
  • gguf/config.py, register.py: register the two archs.

Tested models

RTX PRO 6000 Blackwell (sm_120), ft serve --model-path <file>, AIME25 #1 run greedy to EOS, plus 48 staggered requests at concurrency 16.

Model Quant bs=1 tok/s AIME #1
unsloth/Qwen3.6-35B-A3B-GGUF UD-Q4_K_M, fused / offload 260 / 160 70 / 70
bartowski/Qwen_Qwen3.6-35B-A3B-GGUF IQ3_XXS 264 70
unsloth/Qwen3.8-27B-GGUF UD-Q4_K_M 79 70
unsloth/Qwen3.5-9B-GGUF Q4_K_M 216 70
DavidAU/Qwen3.5-9B-…-NEO-IMATRIX-MAX-MTP-GGUF NEO-MAX-MTP-Q4_K_M 185 70
unsloth/Qwen3.5-4B-GGUF UD-Q4_K_XL 317 70
ggml-org/Qwen3.5-0.8B-GGUF BF16 550 wrong (0.8B)

Staggered requests: no corrupted output on any file.

Results

  • Weights: after ggml.dequantize, every float tensor is bf16-exact against the HF checkpoint, and Q8_0 tensors reach cos 0.99998. Dry load against the meta-built model matches 493/493 params (35B-A3B) and 738/738 (27B).
  • vs llama.cpp f2cc728: same file, greedy, 6 prompts × 128 tokens. Identical outputs: 0.8B 4/6, 9B 5/6, 35B 3/6, 27B 1/6. The divergences checked are near-ties in llama.cpp's own probabilities (e.g. 0.509 vs 0.488).
  • Coverage: header check (dry load, kernel coverage, tokenizer, chat template) of 1204 files from 73 HF repos: 1100 pass. Every dense size passes, and so does every Q2_K–Q8_0, IQ2–IQ4, BF16 and MXFP4_MOE quant.
  • Unit tests: tests/moe, tests/kernels/test_ggml.py, and the tests/models tests for qwen3_5_moe, the registry, the loader and gemma4 GGUF pass.

Not covered

  • IQ1_M weights (no MMQ kernel).
  • Expert layers that mix float and quantized types (follow-up PR).
  • Experts whose gate and up types differ.
  • ik_llama.cpp types, NVFP4 GGUF, prism.

@jason-fxz jason-fxz added the gguf label Oct 16, 2026
@jason-fxz
jason-fxz added this pull request to stack #703 October 16, 2026 05:11
@jason-fxz
jason-fxz merged commit 18448d2 into main Oct 16, 2026
1 check passed
void-mckenzie pushed a commit to void-mckenzie/FreeToken that referenced this pull request Oct 16, 2026
Adds the llama.cpp glm5-next GGUF reader (unsloth GLM-5.3-Flash-GGUF, both
the glm5-next and the older glm5next arch spellings) on the GGUF quant
kind from FlashML-org#690:

- parse_gguf_config derives the text config from the GGUF KV metadata,
  with per-block attention.head_count_kv laying out the KDA/DSA hybrid
  (tensor-name census fallback for the pre-list writer), the trailing
  MTP block dropped like the HF loader drops it, and the MLA head widths
  from the *_length_mla keys (q_b/v_b rows fallback).
- iter_gguf_weights maps every tensor to the glm5_next state dict and
  undoes the converter transforms: A_log stored as -exp(A_log) is
  inverted, the per-head attn_k_b/attn_v_b pair (k transposed) fuses
  into the dense kv_b_proj layout the MLA absorption reads, and the
  q|k|v conv kernels concatenate on the channel axis. The mixed-type
  fused KDA in_proj (Q8_0 q|k|v + BF16 b|f_a|g_a) rides FlashML-org#702 segments;
  the routed experts load as GGUF expert banks at their shipped types
  (unsloth dynamic quants: IQ3_S gate/up + IQ4_XS down, Q8_0 outlier
  layers pass through natively).
- passes quant_config to embed_tokens so the Q8_0 token_embd serves
  packed; registers Glm5NextGGUFForCausalLM and claims it in the AOT
  table (also claims the two qwen3.5 GGUF archs FlashML-org#701 left unclaimed,
  which broke the claim test on main).
- expert banks: load_gguf_expert_banks gains the dense-MLP prefix
  offset (first_k_dense_replace) glm5-next needs -- banks for MoE
  layers mid-stack read blk.<first + moe_layer> now.

Verified end to end against unsloth GLM-5.3-Flash-IQ4_XS (5 split
shards): full 45-layer engine on a 32GB GPU serves coherent output with
43 layers of IQ3_S/IQ4_XS experts off host banks; unit tests cover the
config parse, the state-dict mapping and each converter transform on a
synthetic miniature glm5-next GGUF. Text-only, TP=1.

Part of FlashML-org#671.
void-mckenzie pushed a commit to void-mckenzie/FreeToken that referenced this pull request Oct 20, 2026
Adds the llama.cpp glm5-next GGUF reader (unsloth GLM-5.3-Flash-GGUF, both
the glm5-next and the older glm5next arch spellings) on the GGUF quant
kind from FlashML-org#690:

- parse_gguf_config derives the text config from the GGUF KV metadata,
  with per-block attention.head_count_kv laying out the KDA/DSA hybrid
  (tensor-name census fallback for the pre-list writer), the trailing
  MTP block dropped like the HF loader drops it, and the MLA head widths
  from the *_length_mla keys (q_b/v_b rows fallback).
- iter_gguf_weights maps every tensor to the glm5_next state dict and
  undoes the converter transforms: A_log stored as -exp(A_log) is
  inverted, the per-head attn_k_b/attn_v_b pair (k transposed) fuses
  into the dense kv_b_proj layout the MLA absorption reads, and the
  q|k|v conv kernels concatenate on the channel axis. The mixed-type
  fused KDA in_proj (Q8_0 q|k|v + BF16 b|f_a|g_a) rides FlashML-org#702 segments;
  the routed experts load as GGUF expert banks at their shipped types
  (unsloth dynamic quants: IQ3_S gate/up + IQ4_XS down, Q8_0 outlier
  layers pass through natively).
- passes quant_config to embed_tokens so the Q8_0 token_embd serves
  packed; registers Glm5NextGGUFForCausalLM and claims it in the AOT
  table (also claims the two qwen3.5 GGUF archs FlashML-org#701 left unclaimed,
  which broke the claim test on main).
- expert banks: load_gguf_expert_banks gains the dense-MLP prefix
  offset (first_k_dense_replace) glm5-next needs -- banks for MoE
  layers mid-stack read blk.<first + moe_layer> now.

Verified end to end against unsloth GLM-5.3-Flash-IQ4_XS (5 split
shards): full 45-layer engine on a 32GB GPU serves coherent output with
43 layers of IQ3_S/IQ4_XS experts off host banks; unit tests cover the
config parse, the state-dict mapping and each converter transform on a
synthetic miniature glm5-next GGUF. Text-only, TP=1.

Part of FlashML-org#671.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant