Onboard Qwen3.5 and Qwen3-Next to explicit sharding and ZeRO-1 gradient accumulation - #5119
Open
NuojCheng wants to merge 2 commits into
Open
Onboard Qwen3.5 and Qwen3-Next to explicit sharding and ZeRO-1 gradient accumulation#5119NuojCheng wants to merge 2 commits into
NuojCheng wants to merge 2 commits into
Conversation
Qwen3_5DecoderLayer still expressed its activation layouts with nn.with_logical_constraint, so `shard_mode=explicit` rejected the qwen3_5 decoder block. Switch it over to the same pattern the Qwen3 and DeepSeek layers already use: cache the physical NamedShardings in __init__, pin every sublayer output through maybe_shard_with_logical, and thread out_sharding / intermediate_sharding into the norms, both attention variants and the MoE block. Under ShardMode.AUTO the callees ignore those arguments and the constraints become no-ops, matching the pattern cdfade2 established for Qwen3 so XLA keeps the fusions it picks today. Qwen3.5 is a hybrid decoder, so the Qwen3-Next sublayers it subclasses had to learn explicit sharding too. Qwen3NextGatedDeltaNet is the interesting one: explicit sharding cannot infer a layout across its reshapes, its head repeat or the jax.shard_map boundary, so pin each intermediate to the logical axes the auto path already asks GSPMD for, reshard A_log and dt_bias onto the head axis before they broadcast against sharded activations, and hand shard_map operands that already match its in_specs, since it will not insert a reshard for them. Qwen3NextRMSNorm was dropping the shard_mode, kernel_axes and parameter_memory_host_offload it was handed, and Qwen3NextRMSNormGated had no way to take an out_sharding; both now forward them. Qwen3-Next itself is deliberately left out of the allowlist: it has its own decoder layer and scanned block, which still express their layouts with nn.with_logical_constraint. Two config guards reject combinations that have not been onboarded. sparse_matmul=False is rejected because RoutedMoE.dense_matmul is still not onboarded, a gap shared with qwen3_moe and kimi. Context parallelism is rejected because the gated-delta-net short convolution left-pads the sequence by gdn_conv_kernel_dim - 1 and slices the result back, which explicit sharding cannot express on a sharded sequence axis. For coverage, add AOT tests for qwen3.5-397b-a17b on v5p-512 with FSDP 32 x expert 8, and for a four-layer qwen3.5-35b-a3b on v5p-256 with ZeRO-1 and gradient accumulation, plus TPU integration tests comparing explicit against auto under expert and tensor parallelism and explicit + ZeRO-1 + gradient accumulation against the auto baseline. Unlike mistral and qwen3, qwen3.5 explicit is not bit-for-bit with auto: activation_batch carries the expert axis, so pinning it reassociates the backward reductions. The forward pass is bit-for-bit and the drift stays below 3e-5 relative over 20 steps while changing sign, so the parity tests use rtol=1e-4.
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Qwen3-Next builds on the same gated-delta-net, full-attention and MoE sublayers Qwen3.5 uses, all of which were onboarded in the previous commit, so what is left is its own plumbing: Qwen3NextDecoderLayer and Qwen3NextScannableBlock still expressed their activation layouts with nn.with_logical_constraint. Move both to the pattern the rest of the Qwen3 family uses - cache the physical NamedShardings in __init__, pin through maybe_shard_with_logical, thread out_sharding and intermediate_sharding into the norms, both attention variants and the MoE - and drop qwen3_next into the explicit-sharding allowlist. The scannable block is the part that is new relative to Qwen3.5, which runs a cycle as a plain Python loop. Qwen3-Next puts its linear-attention layers inside nnx_scan.apply_scanned_layers and its lone full-attention layer inside a trip-count-one jax.lax.scan, and both scans require the carry's layout to be invariant across iterations. That holds because the decoder layer now returns the layout it was handed, so no extra machinery is needed inside the scans themselves. The two guards added for qwen3_5 - sparse_matmul=True, and no context parallelism, both of which come from the shared gated-delta-net path - now cover qwen3_next as well, and their tests are parameterized over both decoders rather than duplicated. Tests: AOT for qwen3-next-80b-a3b on v5p-512 with FSDP 32 x expert 8, and for a four-layer variant on v5p-256 with ZeRO-1 and gradient accumulation. The TPU integration tests are the qwen3_5 ones extended to run over both hybrid decoders: explicit vs auto, and explicit + ZeRO-1 + gradient accumulation against the auto baseline, all with scan_layers on. Following test_tpu_qwen3_explicit_sharding_matches_auto, the parity test pairs each decoder with the one parallelism that stresses a different half of the shared layer rather than running the cross-product: qwen3_5 under expert parallelism, which shards the MoE dispatch, and qwen3_next under tensor parallelism, which shards the gated-delta-net head axis. That keeps the two hybrid integration tests at 124s on a v4-8 instead of 346s. Qwen3-Next behaves like Qwen3.5 numerically - explicit and auto agree to 3.1e-5 relative over 20 steps with the difference changing sign - so the parity tests keep rtol=1e-4.
NuojCheng
force-pushed
the
chengnuojin-explicit-qwen35
branch
from
September 3, 2026 05:22
6d33a43 to
0796715
Compare
NuojCheng
marked this pull request as ready for review
September 4, 2026 16:24
NuojCheng
requested review from
A9isha,
RissyRan,
SurbhiJainUSC,
abhinavclemson,
aireenmei,
bvandermoon,
darisoy,
dipannita08,
gagika,
gobbleturk,
hengtaoguo,
huytransformer,
igorts-git,
jiangjy1982,
khatwanimohit,
parambole,
richjames0,
shralex,
shuningjin,
vipannalla and
xibinliu
as code owners
September 4, 2026 16:24
gobbleturk
approved these changes
Sep 4, 2026
igorts-git
approved these changes
Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Onboards the
qwen3_5andqwen3_nextdecoder blocks toshard_mode=explicit, andadds ZeRO-1 + efficient gradient accumulation coverage on top of them.
The two are the hybrid members of the Qwen3 family: both interleave GatedDeltaNet
linear attention with a full-attention layer every
inhomogeneous_layer_cycle_intervallayers, on top of a fully-MoE block with a sharedexpert. Qwen3.5 subclasses the Qwen3-Next sublayers outright, so almost all of the work
is shared. Two commits:
Onboard Qwen3.5 to explicit sharding and ZeRO-1 gradient accumulation— theshared sublayers plus
Qwen3_5DecoderLayer.Onboard Qwen3-Next to explicit sharding—Qwen3NextDecoderLayerandQwen3NextScannableBlock, which is the only structural difference between the two.The decoder layers
Qwen3_5DecoderLayer,Qwen3NextDecoderLayerandQwen3NextScannableBlockall stillexpressed their activation layouts with
nn.with_logical_constraint, soshard_mode=explicitrejected both blocks at config validation time. They now use thesame pattern the Qwen3, Qwen2 and DeepSeek layers already use: cache the physical
NamedShardings in__init__, pin every sublayer output throughmaybe_shard_with_logical, and threadout_sharding/intermediate_shardingintothe norms, both attention variants and the MoE block. Under
ShardMode.AUTOthecallees ignore those arguments and the constraints become no-ops, matching the pattern
cdfade207established for Qwen3 so XLA keeps the fusions it picks today.Qwen3NextScannableBlockis the part that is new relative to Qwen3.5, which runs onecycle as a plain Python loop. Qwen3-Next puts its linear-attention layers inside
nnx_scan.apply_scanned_layersand its lone full-attention layer inside atrip-count-one
jax.lax.scan, and both scans require the carry's layout to beinvariant across iterations. That holds because the decoder layer now returns the
layout it was handed, so nothing extra is needed inside the scans themselves.
The shared sublayers
Qwen3NextGatedDeltaNetis the interesting one. Explicit sharding cannot infer alayout across its reshapes, its head repeat or the
jax.shard_mapboundary, so eachintermediate is pinned to the logical axes the auto path already asks GSPMD for (a
new
_explicit_activation_shardingshelper returns the flat / head / state layouts,all
Noneunder AUTO).A_loganddt_biasare stored replicated but broadcastagainst
(B, S, H_v)activations whose head axis is sharded, so they are reshardedonto the head axis first — the same fix
_align_scale_with_normalized_axisappliesto the norm scales. Finally,
shard_mapmanualises the mesh axes it is given andwill not insert a reshard for an operand whose layout differs from
in_specs, so itssix operands are resharded to match before the call. The existing
with_sharding_constraintonmixed_qkvzis routed throughmaybe_shard_with_name,since under EXPLICIT
with_sharding_constraintis an assertion rather than a hintand rejects any layout it would have to change.
Qwen3NextRMSNormwas acceptingshard_mode,kernel_axesandparameter_memory_host_offloadand then silently dropping them on the floor; it nowforwards them to the inner
RMSNorm. No caller passes the latter two today, so thatpart is a no-op fix.
Qwen3NextRMSNormGatedhad no way to take anout_sharding; it now accepts oneand forwards it.
Attentionforwardsconfig.shard_modeto the hybrid q/k norms.Qwen3NextGatedDeltaNet,Qwen3NextFullAttentionandQwen3NextSparseMoeBlockallgained an
out_shardingkeyword (plusintermediate_shardingon the MoE block).Nothing inside the
jax.shard_mapbody changed.Deliberate gaps
Two new config guards reject combinations that have not been onboarded. Both come
from the shared gated-delta-net path, so both apply to
qwen3_5andqwen3_next:sparse_matmul=False—RoutedMoE.dense_matmulis still not onboarded to explicitsharding. This gap is shared with
qwen3_moeandkimiand is left for a follow-up.ici/dcn_context_parallelism,ici/dcn_context_usp_ulysses_parallelism) — the gated-delta-net short convolutionleft-pads the sequence by
gdn_conv_kernel_dim - 1and slices the result back, whichexplicit sharding cannot express on a sharded sequence axis.
shard_mode=autostillworks there.
The Qwen3-VL / Qwen3-Omni encoders remain unsupported; they are covered by the existing
use_multimodalguard.Known issue, not fixed here
shard_mode=explicit+shard_optimizer_over_data=True+ici_expert_parallelism>1fails for every MoE model, not just the two here:
add_data_to_shardingprependsdatato the optimizer mu/nu spec while the gradientskeep the plain params spec. This reproduces on unmodified
mainwithmixtral-8x7batDP2 x EP2 with ZeRO-1 and 4 accumulation steps (it passes under
shard_mode=auto). TheZeRO-1 coverage added here is therefore data-parallel only, which is the same coverage
every other onboarded MoE model has. A fix is left for a follow-up.
Tests
All commands below were run on a v4-8 TPU VM.
New AOT coverage (
tests/unit/train_compile_test.py), one pair per decoder:test_qwen3_5_explicit_sharding—qwen3.5-397b-a17bonv5p-512, FSDP 32 x expert8,
sparse_matmul/megablox/flash/tokamax-splash.test_qwen3_next_explicit_sharding—qwen3-next-80b-a3bonv5p-512, same mesh.test_qwen3_5_explicit_sharding_zero1andtest_qwen3_next_explicit_sharding_zero1—four-layer variants on
v5p-256, DP 128,gradient_accumulation_steps=4,shard_optimizer_over_data=True.These are the large-scale checks: explicit sharding type-checks every operation's
layout instead of letting GSPMD infer one, so a missing
out_shardingfails the tracehere rather than silently costing a collective at a scale a real test cannot reach.
Four layers is one full
inhomogeneous_layer_cycle_interval, so both attentionvariants are still covered while the model stays small enough to hold data-parallel
replicas of the parameters.
Each new case costs about what the AUTO sibling next to it already costs.
New TPU integration coverage (
tests/integration/train_tests.py), parameterizedover both hybrid decoders rather than duplicated:
test_tpu_qwen3_hybrid_explicit_sharding_matches_auto— explicit vs auto. Followingthe convention
test_tpu_qwen3_explicit_sharding_matches_autoalready uses, eachdecoder is paired with the one parallelism that stresses a different half of the
shared layer rather than running the cross-product: Qwen3.5 under expert parallelism,
which shards the MoE dispatch, and Qwen3-Next under tensor parallelism, which shards
the gated-delta-net head axis — the one the layer has to carry by hand across its
reshapes, its head repeat and the
shard_mapboundary.test_tpu_qwen3_hybrid_zero1_gradient_accumulation— explicit + ZeRO-1 + 8accumulation steps against the auto, non-ZeRO-1 baseline.
Both run with
scan_layerson, so the Qwen3-Next subtests exercise the nested scans.Qwen3-Next was additionally spot-checked with
scan_layers=False(the unscannedQwen3NextDecoderLayerpath) under both shard modes.The existing
_qwen3_losses/_mistral_losseshelpers were near-identical, so theyare now thin delegates over a shared
_losses(run_name, model_overrides, extra_args)rather than more copies being added.
Numerical note. Unlike mistral and qwen3, the hybrid decoders are not
bit-for-bit between explicit and auto.
activation_batchcarries the expert axis, sopinning it reassociates the backward reductions rather than leaving the layout
untouched — the forward pass is bit-for-bit and the drift only appears once gradients
flow. Measured over 20 steps it stays below
3.1e-5relative and changes sign, i.e. itis float noise rather than the two runs pulling apart, so the parity assertions use
rtol=1e-4. For the same reason each decoder's AUTO path itself moves by up to2.8e-5relative againstmain(itswith_logical_constraintcalls become AUTOno-ops), which is the tradeoff
cdfade207deliberately made for Qwen3 to restore XLAfusion.
New config-validation coverage (
tests/unit/pyconfig_test.py):qwen3_5andqwen3_nextmoved into the accepted-decoder loop, andtest_explicit_sharding_gated_delta_net_unsupported_combinationscovers the two newguards for both decoders.
Regressions checked:
attention_test.pyfailures that show up when the CPU device count israised above 1 are present unchanged on
main.tests/integration/hlo_diff_test.pyneeds no reference-HLO regeneration: it coversdeepseek3, llama3-8b and qwen3-1.7b, and no hunk in this PR is reachable from any of
them (every
qwen3.pychange is inside aQwen3Next*class, and theattentions.pychange is inside the
is_qwen3_hybridbranch).pylintreports 9.96/10 on the changed files, with all four remaining messagespresent unchanged on
main.Checklist
Before submitting this PR, please make sure (put X in square brackets):
gemini-reviewlabel.