feat(neuron): qwen3_next config + MoE FFN block, single-GPU path with exact HF parity (#92) #108
Reference in New Issue
Block a user
Delete Branch "feat/92-qwen3_5-moe"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of #92 (F1, epic #84) — slices 1+2 of the MoE integration: everything except TP expert sharding (slice 3) and the fused CUDA grouped-GEMM (slice 4), which follow as separate PRs.
Slice 1 — config + normalization
TextConfiggains the MoE hyperparameters (num_experts,num_experts_per_tok,moe_intermediate_size,shared_expert_intermediate_size,decoder_sparse_step,mlp_only_layers,norm_topk_prob), all defaulting to the dense case, pluslayer_uses_moe()implementing the upstream sparse-layer selection.Config::from_config_jsonnormalises the flatqwen3_nextconfig layout (Qwen3-Next-80B-A3B family) into the nested qwen3_5 shape: flat rope fields →rope_parameters,attn_output_gateforced on (unconditional upstream),layer_typesderived fromfull_attention_intervalvia(i+1) % interval.qwen3_nextadded to the supported-type gates and dispatch arms.Slice 2 — the MoE block, single-GPU
Qwen3_5MoeBlock: top-k router (upstream softmax-then-topk order; renorm iffnorm_topk_prob), per-expert SwiGLU atmoe_intermediate_size, and the always-on shared expert mixed viasigmoid(shared_expert_gate(x))— a path candle-transformers' qwen3_moe lacks entirely. Correctness-first scatter dispatch; grouped-GEMM lands in slice 4 behind the same forward signature.MlpKind { Dense, Moe }dispatch in the decoder layer, mirroringAttentionKind.in_proj_qkvz/in_proj_bainterleaved per key-head group (upstreamfix_query_key_value_orderinglayout);split_fused_qkvz/split_fused_bade-interleave once at load into the contiguous layout the forward path already uses. Auto-detected viacontains_tensor— Qwen3.6 checkpoints untouched.text_weight_prefix(): qwen3_next checkpoints keep the text core atmodel.*vs Qwen3.6'smodel.language_model.*.Validation
qwen3_next_paritytest replays a committed tiny random-weightQwen3NextForCausalLMcheckpoint (generated byscript/dump_qwen3_next_tiny.pyon beast — transformers 5.9.0, torch 2.9.1) through neuron's full load path: max_abs 0.000000, cosine 1.00000000 at f32. Pins config normalisation, weight prefix, qkvz/ba de-interleave, hybrid layer interleaving, and the whole MoE block against upstream, in every CI run.Inert for the served 27B (dense path unchanged, parity fixtures still pass); a TP guard fails any premature 80B TP load with a clear "#92 slice 3" message instead of a missing-tensor error.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TczcGF7JSjJs8r15RSSGpx
F1 slice 2 — the MoE block itself, CPU/single-GPU: - arch/qwen3_5/moe.rs: Qwen3_5MoeBlock — top-k router (upstream softmax-then-topk order, renorm iff norm_topk_prob), per-expert SwiGLU (reusing Qwen3_5MLP at moe_intermediate_size), and the always-on shared expert mixed via sigmoid(shared_expert_gate). Correctness-first host-side scatter dispatch; the fused grouped-GEMM path is slice 4 behind the same forward signature. - decoder.rs: MlpKind { Dense, Moe } dispatch on layer_uses_moe, mirroring AttentionKind. - linear_attn.rs: fused-checkpoint support — qwen3_next stores in_proj_qkvz / in_proj_ba interleaved per key-head group (upstream fix_query_key_value_ordering layout); split_fused_qkvz/ba de-interleave once at load into the contiguous [Q|K|V] + Z / B + A layout the forward path (incl. the conv channels) already uses. Auto-detected via contains_tensor, so Qwen3.6 checkpoints are untouched. - mod.rs: text_weight_prefix() — qwen3_next checkpoints put the text core at `model.*`, Qwen3.6 at `model.language_model.*`; the slice-1 single-GPU MoE guard is removed (TP guard stays until slice 3). Validation: - qwen3_next_parity integration test replays a committed tiny random-weight HF Qwen3NextForCausalLM checkpoint (generated by script/dump_qwen3_next_tiny.py on beast: transformers 5.9.0, torch 2.9.1) through neuron's full load path: max_abs 0.000000, cosine 1.00000000 at f32 — exact parity, pinning the config normalisation, weight prefix, qkvz/ba de-interleave, hybrid layer interleaving, and the whole MoE block against upstream. - Unit tests: scatter forward vs per-token dense reference, no-shared-expert/no-renorm behaviour, fused-split round-trip, and a flat-layout end-to-end structural load. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TczcGF7JSjJs8r15RSSGpx