Skip to content

[Bug] ft checkpoint fails on Qwen3-Coder-30B-A3B modelopt NVFP4: qwen3_moe loader lacks NVFP4 expert-source wiring (0-dim scale scalars crash shard_tensor) #425

Open

Description

@theanton0327-cpu

Environment

  • Windows 11, single RTX 5090 (32GB, sm_120)
  • FreeToken v0.1.2+g141c31a8d, torch 2.11 / CUDA 13

Goal

Serve Qwen3-Coder-30B-A3B-Instruct (arch Qwen3MoeForCausalLM) from a native modelopt NVFP4 checkpoint via the proven FTW route (ft checkpoint → ft serve, the same path that works for the Qwen3.6-35B-A3B qwen3_5_moe model on this box).

Source tried: NVFP4/Qwen3-Coder-30B-A3B-Instruct-FP4 (modelopt, producer confirmed in hf_quant_config.json).

Repro

ft checkpoint --model <modelopt-NVFP4-dir> --out <out> --dtype bfloat16 --moe-backend offload --gpu 0

Failure

File "freetoken/models/loader.py", line 122, in iter_merged_tensors
  File "freetoken/models/qwen3_moe/weight.py", line 57, in sharded_tensors
    tensor = shard_tensor(...)
RuntimeError: chunk expects at least a 1-dimensional tensor

(crashes inside the compiled loader.shard_tensor when fed a 0-dim tensor)

Root cause (diagnosed)

qwen3_moe (registered for Qwen3MoeForCausalLM, added in this build) is a generic dense-MoE loader. Its generic experts.<idx>.<name> pattern feeds every per-expert tensor — including the modelopt quant 0-dim scale scalars input_scale and weight_scale_2 — through iter_stacked_experts → shard_tensor, which cannot chunk() a scalar.

By contrast every other modelopt-NVFP4 model here (gemma4, glm5_next, minimax_m2/m3, qwen4_exp) declares an Nvfp4ExpertSourceSpec and routes routed-experts to the offload-bank loader (load_moe_expert_sources), skipping input_scale and carrying weight_scale_2 as the per-row global scale (see glm5_next/weight.py kind_map). qwen3_moe has none of that wiring (only a comment referencing load_moe_expert_sources), so it was evidently validated on bf16 only and cannot ingest a modelopt-NVFP4 checkpoint.

Per-expert tensor shapes from the source:

[]          down_proj.input_scale      # 0-dim scalar
[2048,384]  down_proj.weight
[2048,48]   down_proj.weight_scale
[]          down_proj.weight_scale_2   # 0-dim scalar

Ask

Wire NVFP4 expert-source support into qwen3_moe — qwen4_exp is the closest template (identical gate_proj/up_proj/down_proj + weight_scale/weight_scale_2/input_scale layout, and it also handles the shared_expert). Or confirm whether Qwen3MoeForCausalLM (Qwen3-Coder-30B-A3B / Qwen3-30B-A3B) NVFP4 is on the roadmap and which source layout it should take.

Happy to help test on sm_120/Windows once a nightly carries it.

Activity

  1. ljrirene commented on Sep 17, 2026

    @ljrirene

    Same gap on a much bigger export — nvidia/Qwen3-235B-A22B-Instruct-2507-NVFP4 (94 layers, 128 experts, top-8) — so it's not specific to the 30B Coder checkpoint. Dropping the layout here in case it's useful.

    Two loader bugs fire before you can even reach this one; patches for both are in #496. Worth having those first, otherwise loading dies at 0% and never reaches load_state_dict. One of them is the chunk expects at least a 1-dimensional tensor you already hit here — your diagnosis is right that the expert scalars shouldn't be going through shard_tensor at all, but note the attention-side scalars (o_proj.input_scale, o_proj.weight_scale_2) aren't expert tensors, so they'd keep crashing on the SPLIT_DIM_1 arm even after the expert-source wiring lands. Cheap to guard separately.

    What the failure looks like once you're past them (with the bare assert in python/freetoken/layers/base.py made informative):

    AssertionError: model.layers.0.self_attn.o_proj.weight:
    model expects (4096, 8192)/torch.bfloat16, checkpoint has (4096, 4096)/torch.bfloat16
    

    Why. Mixed-precision export. hf_quant_config.json carries quant_algo: NVFP4, kv_cache_quant_algo: FP8, group_size: 16, and an exclude_modules list that keeps lm_head, every layer's q_proj/k_proj/v_proj and the MoE router gate in BF16 — while o_proj and all routed experts are NVFP4-packed. Layer-0 inventory straight from the safetensors:

    self_attn.q_proj.weight                   (8192, 4096)  BF16     # excluded from quant
    self_attn.k_proj.weight / v_proj.weight   (512, 4096)   BF16     # excluded from quant
    self_attn.k_proj.k_scale / v_proj.v_scale ()            F32
    self_attn.o_proj.weight                   (4096, 4096)  U8       # NVFP4, 8192 4-bit values/row
    self_attn.o_proj.weight_scale             (4096, 512)   F8_E4M3
    self_attn.o_proj.weight_scale_2           ()            F32
    self_attn.o_proj.input_scale              ()            F32
    mlp.experts.*.gate_proj.weight            (1536, 2048)  U8
    mlp.experts.*.gate_proj.weight_scale      (1536, 256)   F8_E4M3
    mlp.experts.*.down_proj.weight            (4096, 768)   U8
    mlp.experts.*.down_proj.weight_scale      (4096, 96)    F8_E4M3
    

    The qwen3_moe model definition declares every parameter full-width BF16 (o_proj expects (4096, 8192)), so the mismatch is structural — it needs quant-aware module construction driven by hf_quant_config.json: packed-weight params, scale params, and honouring exclude_modules. Presumably along the lines of #438 for qwen3_5_moe and #426 for qwen4_exp, as you say.

    Environment: single RTX PRO 6000 Blackwell Max-Q 96 GB, driver 595.58.03, CUDA 13.0, torch 2.11.0+cu130, dual-socket EPYC / 1.1 TiB RAM, FreeToken 0.1.2, hybrid MoE backend. Happy to test a fix on the 235B export.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions