fix(runtime): validate routed-expert tensor shapes in Qwen GGUF loader#292
Conversation
e62cc24 to
bff3d84
Compare
load_gguf validates the shape of every attention, router, shared-expert,
and norm tensor with expect_dims, but loads the three routed-expert
tensors (ffn_gate_exps / ffn_up_exps / ffn_down_exps) with only a
null-check. These are the largest, fully file-controlled weights, and
their per-expert stride (hidden x moe_ffn) is assumed — never
re-derived — by launch_moe_expert_ffn_q4k.
A GGUF whose expert_count / expert_feed_forward_length metadata disagrees
with the actual tensor extent therefore loads cleanly, then the expert
FFN computes expert base offsets expert_id * hidden * moe_ffn with the
wrong stride and reads out of bounds of the quantized blob — corrupt
routed output or an illegal-address crash, all from a file-controlled
dimension.
Add the missing expect_dims checks (ggml ne order: gate/up
{H, moe_ffn, n_experts}, down {moe_ffn, H, n_experts}) so a mismatched
GGUF is rejected up front with a clear error, mirroring the existing
ffn_*_shexp validation. Valid Qwen3-MoE and Qwen3.6 GGUFs are unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
⏸ Merge conflict — rebase before evalThis branch conflicts with Please rebase onto |
|
The round's |
3 similar comments
|
The round's |
|
The round's |
|
The round's |
Fixes #291.
Root cause
Qwen35Model::load_ggufvalidates the shape of every tensor it loads withexpect_dims— attention, router (ffn_gate_inp), shared experts (ffn_*_shexp), and norms — except the three routed-expert tensors, which were loaded with only a null-check:These are the largest, fully file-controlled weights, and their per-expert stride (
hidden × moe_ffn) is assumed — never re-derived — bylaunch_moe_expert_ffn_q4k(called withc.hidden/c.moe_ffnfrom the GGUF metadata). A GGUF whoseexpert_count/expert_feed_forward_lengthmetadata disagrees with the actual tensor extent therefore loads cleanly, then the expert FFN computes offsetsexpert_id * hidden * moe_ffnwith the wrong stride and reads out of bounds of the quantized blob → corrupt routed output or an illegal-address crash.Fix
Add the missing
expect_dimschecks before thedev_quantloads, mirroring the existingffn_*_shexpvalidation with the expert axis appended (ggmlneorder):ffn_gate_exps.weight{H, moe_ffn, n_experts}ffn_up_exps.weight{H, moe_ffn, n_experts}ffn_down_exps.weight{moe_ffn, H, n_experts}A shape-mismatched GGUF is now rejected up front with a clear
[gguf] bad shape …error, before any device pointer arithmetic runs.Impact / risk
{2048, 768, 128}) and Qwen3.6-35B-A3B ({2048, 512, 256}) GGUFs load unchanged — the expert tensors already carry exactly these extents (the runtime relies on them today).ffn_*_shexpchecks in the same function, the transposes inruntime/tools/convert_gguf.py, and the standard llama.cpp MoEnelayout.Verification
No GPU/CUDA build was available in this environment, so the dimension-ordering logic was verified by re-implementing
expect_dimsagainst the real tensor layouts:expert_count, wrongmoe_ffn, transposed down axes, and 2-D rank → rejected before the kernel runs.