Repository navigation
[6/8] feat(models): port Qwen3.5 / Qwen3-Next GDN to the head-sharded layer - #3610
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
93a34d6 to
a01e25b
Compare
a01e25b to
dab59ed
Compare
dab59ed to
b83822e
Compare
0d33e13 to
fb2f243
Compare
302355d to
e64c544
Compare
a01c5ad to
11031e5
Compare
11031e5 to
4e62901
Compare
4e62901 to
b9620fb
Compare
b9620fb to
ff7ec02
Compare
ff7ec02 to
123b7c8
Compare
123b7c8 to
7b05f67
Compare
| dtype = config.params_dtype | ||
|
|
||
| self._sharded_params: dict[str, int] = {} | ||
| self.register_buffer( |
There was a problem hiding this comment.
[P2] Exclude the layout marker from offline HF export
This persistent buffer is included in distributed checkpoints, but tools/convert_torch_dist_to_hf.py passes every loaded model tensor to convert_to_hf(). The Qwen converters reject this key with ValueError: Unknown parameter name: module.module.decoder.layers.0.self_attention.linear_attn.weight_layout_version, so checkpoints produced with this layer can no longer be exported through that tool. The bridge's filtering does not cover the offline exporter. Please exclude this metadata from HF export while retaining it in distributed checkpoints.
7b05f67 to
480f735
Compare
Qwen3.5 / 3.6 / 3.8 and Qwen3-Next ran GDN replicated on every TP rank
through the HF-attention shim. They now use the shared
LinearAttentionLayer: GatedDeltaNet joins linear_attn.py as the second
family (softplus-gated per-head decay, fla or FlashQLA), and
Qwen3_5GatedDeltaNet (in_proj_qkv, z, b, a) and Qwen3NextGatedDeltaNet
(HF-fused in_proj_qkvz / ba) declare only their projections.
- Only the q/k/v projection and the conv change layout (group-major
rows); bridges and exporters permute exactly those two tensors, the FP8
export is unchanged.
- Checkpoints in the old layout are rejected instead of loading scrambled:
Megatron's default dist-checkpoint load keeps a tensor's init value when
the checkpoint lacks it, and old checkpoints carry the same names and
shapes. The layer saves a weight_layout_version buffer and
load_checkpoint raises when a checkpoint lacks it ("re-convert from
HF"). The mbridge bridges keep the marker out of HF weights
(LinearAttentionBridgeMixin). CONVERSION_VERSION bumps to 4 so cached
conversions from another checkout are redone.
- Qwen3.8-Next still runs the replicated GDN (moved to
qwen3_8_next/gdn.py, its only user) but delegates weight conversion to
Qwen3.5's, so it keeps HF's flat q/k/v and conv: its bridge empties the
permuted names and its exporter returns them first. Without that its
rollout weights are permuted and its training weights are not, which the
R3 replay check catches (283 of 384 tokens mismatched in the 4-layer
e2e).
- tools/convert_hf_to_torch_dist.py registers --linear-attention-backend /
--allgather-cp, which the spec reads; convert_torch_dist_to_hf.py reads
the head layout from --origin-hf-dir when given.
- Tests gain the GDN cases next to KDA; tests/manual/bench_linear_attn.py
covers Qwen3.8-Flash, GLM-5.3-Flash and Kimi-K3 at 4k-128k and 10 TP x CP
layouts.
What
Qwen3.5 / 3.6 / 3.8 and Qwen3-Next GDN move from the replicated HF shim onto the shared layer.
GatedDeltaNetjoinslinear_attn.pyas the second family.Qwen3_5GatedDeltaNetandQwen3NextGatedDeltaNetonly declare their projections.weight_layout_versionbuffer,load_checkpointchecks for it, and the mbridge bridges keep it out of HF weights.CONVERSION_VERSIONgoes to 4.qwen3_8_next/gdn.py) and HF's flat layout. Its bridge and exporter skip the permutation; without that, R3 replay mismatches 283 of 384 tokens.vs current main (GB300, 2026-10-08)
The Qwen3.5 GDN
self_attentionfrom main (replicated HF shim) vs this PR, forward + backward per layer, SP when TP > 1, real model configs:At 4k tokens the layer is launch-bound (~7 ms either way) and roughly even with main.
Summary of an earlier run (H200, before the rebase)
Precision against the replicated layer:
Qwen3.8-Flash GDN (2560 hidden, 16 k-heads, 48 v-heads), forward + backward per layer. Speedup and memory saving are against the replicated layer:
RL end to end, Qwen3.5-4B with TP=2 + SP: train / rollout logprob absolute difference is 0.007–0.010, and step time matches the replicated baseline.
Note on CP. GDN scales less well with CP (3.05x at CP=8, 128k) than KDA does. fla dispatches GDN to FlashQLA without CP and to its Triton kernel with CP, so prefer TP over CP for GDN.
This branch (
0d33e13a1)test_linear_attn_layout.py,test_linear_attn_short_conv.pytest_qwen3_5_mtp_bridge_mapping.py,test_fp32_param_utils.pymain)CI's GLM-5 end-to-end jobs on this PR stop before training because the runner's wandb key is rejected. Both were run on H200 at this stack (
dd2cc6f6b) without wandb and pass:test_glm5_744b_a40b_4layer_r3.py(train/rollout logprob abs diff 0.047, 0.049) andtest_glm5_744b_a40b_4layer_ci.py(0.009). They caught two GLM-5 call-site bugs in #3607, fixed there; see its description.H200 results
8×H200 on the current
radixark/miles:devimage (pulled 2026-09-23): torch 2.13 + CUDA 13.0, sglang 0.5.21.dev55, tilelang 0.1.14, fla 0.5.2, FlashQLA 0.1.2, FlashMLA; Megatron-LM7a1f7fe3f(the image's); this stack atdd2cc6f6b. Sequence parallel on; CP uses the zigzag relayout as in training; error is against the replicated layer at CP=1 and against no CP otherwise.Speedups are against the replicated layer this stack replaces. In multi-GPU runs that reference repeatedly measured 30–900× slow while the head-sharded timings stayed put (they match the earlier runs to a few percent), so the TP=2/4/8 rows carry no speedup here. Clean measurements of the same code: TP=4 at 32k, 3.37× (Qwen3.8 GDN), 3.39× (GLM-5.3 KDA), 3.58× (Kimi-K3 KDA), from two separate reruns on two H200 boxes; TP=8, 1.45–5.32× (GDN), 2.2–5.5× (GLM-5.3 KDA), 3.8–6.2× (Kimi-K3 KDA) with 2.9–5.6× lower activation memory, from the first full run.
qwen3.8-flash, forward + backward ms per layer (speedup vs replicated at CP=1)
GB300 results
8×GB300 (sm_103) on two 4-GPU nodes, GPUs otherwise idle; torch 2.13 + CUDA 13.0, tilelang 0.1.12, fla 0.5.2, FlashMLA
b7643bdbuilt for sm_100f; commits as pushed to this stack. Sequence parallel on; CP runs use the zigzag relayout as in training. FlashQLA is not installed on GB300, so fla runs GDN on its Triton kernel with and without CP; that is why TP=1 differs from the replicated layer by 5e-3 here (native GVA vs repeated q/k take different kernels) while it is bitwise on H200.qwen3.8-flash, forward + backward ms per layer (speedup vs replicated at CP=1)
Local end to end, tier 2 (8×H200, stack tip
dd2cc6f6b, Megatron8a5dbe5ff, no wandb)These are the non-model-specific megatron e2e tests CI runs, rerun locally because the CI wandb key is rejected on PR runs. All 19 pass:
test_qwen3_0.6B_sft_snapshot_evaltest_mimo_7B_mtp_only_gradtest_qwen3_4B_muon_offload_disktest_qwen3_4B_offload_disktest_qwen3_4B_offload_disk_streamtest_qwen3_30B_A3B/test_baselinetest_qwen3_30B_A3B/test_dp_attentiontest_qwen3_30B_A3B/test_r3_baselinetest_qwen3_4B_variable_gbstest_qwen3_4B_ppotest_gpt_oss_20b_moe_lora_citest_qwen3_4B_disk_deltatest_qwen3_30B_A3B_p2ptest_glm47_flash/test_r3_mtptest_qwen3_30B_A3B/test_disagg_broadcasttest_qwen3_30B_A3B/test_fully_asynctest_qwen3_4B_offload_disaggregatedtest_nemotron_3_ultra_4layer_citest_qwen3_4b_fully_async_evalCI's H100 timeout on
test_mtp1_spec_v2_r3.pyCI's
stage-c-8-gpu-h100 (1)timed out this test at its 2000 s budget while it was still making progress (R3 replay checks passing), and its siblingtest_mtp0_spec_v2.pypassed at 2000 s in the same run. Rerun on one 8×H200 box, main (a6b72d31f) and this stack (6893c4e88) both pass: 2111 s on main, 2030 s here. The timeout is the test's budget, not a slowdown from this PR.Local end to end, tier 1b (8×H200, this stack
6893c4e88, Megatron8a5dbe5ff, no wandb)test_glm5_744b_a40b_4layer_r3test_glm5_2_744b_a40b_5layer_citest_deepseek_v32_5layer_fp8test_deepseek_v4_flash_4layer_citest_deepseek_v4_flash_4layer_megatron_impl_citest_dsv4_bshd_thd_rollout_paritytest_glm5_1_744b_a40b_6layer_lora_cimaintootest_glm5_1_744b_a40b_6layer_lora_cistops at actor init withdsa_kernel_backend=tilelang requires fused DSA kernels ... TileLang DSA kernels. Megatron's check importsmegatron.core.transformer.experimental_attention_variant.ops.tilelang_dsa, which is not in the image's Megatron (7a1f7fe3f), in8a5dbe5ff, or in the currentmiles-main. The same test onmain(a6b72d31f) on the same box fails with the same error, so it is not from this stack.Stack
miles/kernelsmiles/kernels/attentionmiles_plugins/models/linear_attn.py)Rules for
miles/kernelsare in its README: only kernels we write, one directory per op, names say what is computed (never the backend), callers import the entry, a torch-reference test per op.