Skip to content

Qwen3.5/3.8 text-only checkpoints cannot load: Qwen3_5MoeForCausalLM is unregistered and the loader hardcodes the VL model.language_model. prefix #490

Description

@localai-bot

Qwen3.8 (Qwen/Qwen3.8-2.4T-A95B, 2.4T total / A95B active) is not a new architecture. Its config declares Qwen3_5MoeForCausalLM / model_type: qwen3_5_moe_text — the same GDN-hybrid MoE backbone we already run token-exact (315/315 on Qwen3.6-35B-A3B). Every structural knob matches: head_dim 256, linear_key/value_head_dim 128, linear_num_key_heads 16, full_attention_interval 4, attn_output_gate true, partial_rotary_factor 0.25, rope_theta 1e7, mtp_num_hidden_layers 1, and the same tokenizer (vocab_size 248320). The deltas are pure scale (hidden 2048->8192, layers 40->92, experts 256->512, top-k 8->10, moe/shared intermediate 512->2048, linear V-heads 32->128), all config-driven.

Two things block loading it:

1. The architecture string is unregistered. ModelRegistry::Resolve is an exact string match with no aliasing (src/vllm/model_executor/models/model_registry.cpp:217-231); we register only Qwen3_5MoeForConditionalGeneration (qwen3_5_moe.cpp:215) and Qwen3_5ForConditionalGeneration (qwen3_5_dense.cpp:243). A text-only config falls through to RaiseForUnsupported.

2. The loader hardcodes the VL weight prefix. qwen3_5_weights.cpp:560,632,633,659 (and qwen3_5_dense_weights.cpp, 3 sites) look up model.language_model.*. Verified against the real checkpoints:

Qwen3.6-35B-A3B Qwen3.8-2.4T-A95B
embed model.language_model.embed_tokens.weight model.embed_tokens.weight
layer model.language_model.layers.0.linear_attn.* model.layers.0.linear_attn.*
experts ...mlp.experts.gate_up_proj (3D stacked) ...mlp.experts.gate_up_proj (3D stacked)
head lm_head.weight lm_head.weight

The tensor names are identical modulo the prefix — same 3D-stacked experts, same shared_expert_gate, same top-level lm_head. So the existing loader is correct once the prefix is tolerant.

Upstream solves exactly this with a mapper on the text-only base class:

# vllm/model_executor/models/qwen3_5.py:296-300 @ origin/main
# Some community text-only checkpoints keep the extraneous
# `model.language_model.` prefix inherited from the VL training stack.
hf_to_vllm_mapper = WeightsMapper(orig_to_new_prefix={"model.language_model.": "model."})

Qwen3_5MoeForCausalLM is a thin text-only class over the same backbone (qwen3_5.py:443-449), and Qwen3_5ForCausalLM is pass over the shared base (:439).

Ahead-of-pin, and stated as such. Our parity pin is 555967922, whose registry has only the ForConditionalGeneration entries. The text-only arms were added upstream by PR #50210 / ad5d29db7 ("[Model] Support Qwen3.5 text-only dense and MoE models"), which is post-pin. This is therefore a deliberate forward port, not a mirror of the pin.

We cannot gate it on our hardware, and that is not hidden. 2.4T bf16 is ~4.8 TB; the only other released variant is Qwen/Qwen3.8-2.4T-A95B-FP8 at ~2.4 TB. GB10 has 128 GB unified, and no smaller Qwen3.8 sibling exists. So there is no token-exact oracle run for the 2.4T checkpoint. What IS gateable: config resolution, architecture dispatch, weight-name mapping, and byte-identical inertness of the existing 27B/35B/Coder gates. The run gate stays explicitly OWED and the row must not claim DONE on the 3.8 checkpoint.

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