Repository navigation
[megatron][vllm] Qwen3.8-Flash-Next (qwen4_exp) support: Megatron bridge, QSA sparse attention, GSM8K + DAPO recipes - #2513
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
…r type; test health timeout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
…ider Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
…output gate Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
Qwen4Exp's ngram_context_offsets lives in the weights pool and came back as zeros after a level-2 sleep, corrupting PLE n-gram contexts after every weight sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
…LE in recipe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
…memory Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
…memory_utilization 0.6 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
NVLS buffers stay resident through offload/sleep (~24 -> ~12.5 GiB/GPU trainer residual at EP16), which blocked the adapter load at step 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hp5LxobgMKPQJkiDxEXcef
…rity row on 4xH100 (EP4, vLLM TP4) make_slice.py replaces the ~51B-parameter PLE n-gram table with a small random one (ngram_vocab_size_base 2048, head sizes/offsets recomputed with the HF prime formula), so the 4-layer slice is 23 GB and fits the 4xH100 CI shape. Parity on 4xH100: forward |dlogp| 0.051, post-sync common prefix 0.021. The gsm8k recipe is renamed to the _lora_2node variant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
Zero adapter 0.052, stale sampler 0.239, updated adapter 0.056 (threshold 0.1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
…, FlexAttention fast path Megatron previously ran the QSA indexer only in its exactly-dense regime (<= indexer_budget + compress_ratio - 1 = 2051 tokens) and raised beyond. Qwen4ExpQSAIndexer now mirrors HF's selection: per query, the top indexer_budget/r of the complete r-token blocks (fp32-mean pooled keys, k_layernorm, RoPE at the block start, sum_h relu(q_h.k)/sqrt(d)) plus the incomplete tail, per packed sequence. qsa_sparse_attention attends over the selection: FlexAttention on CUDA (~3x faster than SDPA's math kernel, the only one it has for head_dim 256 with a bool mask), chunked SDPA otherwise. Short sequences keep the TE dense kernel. Tests: CPU selection/attention/grad parity vs HF (26 pass); GPU vLLM-vs-Megatron logprobs on ~7.7K-token prompts (test_qwen38_qsa_long_context.py): 0.056 mean |dlogp| (short dense row: 0.051). 8K core attention fwd+bwd on one H100: TE dense 6.7 ms, flex 44 ms, SDPA math 138 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
…he host With optimizer_cpu_offload and offload_fraction 1.0, megatron-core still builds FP32 main-param shards on the GPU (DistributedOptimizer._build_model_and_main_param_groups) and the HybridDeviceOptimizer only copies them to the host; at expert-DP 1 (Qwen3.8-Flash-Next, EP16) that is ~29 GiB/GPU and init OOMed. patch_hybrid_optimizer_cpu_masters routes full-FT runs to megatron-core's precision-aware CPU-offload path (no GPU masters, BF16 decoupled grads), builds each master as one pinned host tensor, and skips the throwaway HDO over the full model. LoRA keeps the default (the adapter store reads the FP32 shards). Also: QSA entries in the patches README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
…sync_lora_targets_only) merge_lora=true re-sent the whole model each sync. With megatron_config.lora_config.sync_lora_targets_only=true, MegatronWeightSource gets a param_filter keeping only the blocks that hold a LoRA-wrapped (or trainable) parameter, as whole blocks because vLLM's layerwise reload rebuilds a module from all of its tensors. vLLM's copies of the frozen rest must stay the checkpoint's, so vLLM sleeps at level 1 for weights in this mode. Rejected with dummy load format, FP8 weight sync, and the sharded_rdt/delta backends. Off by default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
chunked_mlp (mlp_chunks_for_training on hyper-connection layers: qwen4_exp, glm5_next) calls the MoE router once per sequence chunk, but megatron-core's RouterReplay holds one [tokens, topk] tensor per call: replay handed every chunk the whole micro-batch's indices (Qwen3.8 DAPO + R3: 'expected index [10634, 10] to be no larger than self [1330, 512]'), and recording kept only the last chunk. Each chunk now gets its slice of the forward target and of the recompute FIFO entry, and a recording is reassembled from the chunks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
chunked_mlp ran min(num_chunks, tokens) MoE calls via torch.chunk, which also returns fewer chunks than asked for short inputs. Each MoE call runs expert-parallel collectives, so a rank on a short micro-batch (e.g. the one-token padding micro-batches TokenBasedBatchIterator adds to even out micro-batch counts across DP ranks) made fewer calls than its EP peers and the group deadlocked: Qwen3.8 DAPO at EP16 hung in the forward pass until the 30 min NCCL watchdog fired (15 ranks in the MoE dispatch all-gather, one in the PLE lookup). Always make exactly num_chunks non-empty calls (tensor_split), padding short sequences with masked dummy tokens whose outputs (and router-replay rows) are dropped. Affects glm5_next too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
…hon ints mask_mod captured the sequence length and block count as ints, which torch.compile guards on, recompiling per distinct packed length. Pass them as 0-d tensors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
…ting test rows The recipe runs the QSA sparse regime and rollout router replay at full size (step 1: 966 s, rollout/train |dlogp| 0.011). test_trainer_full_checkpointing gains qwen3.8 slice rows for LoRA and full fine-tuning (EP4, optimizer on the host for full FT). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QepVYWMiu9bi2gEh5omjVi
…checks; qwen3.8 full-FT recipes compute_advantage_split_logratio_metrics logs the mean logp - old_logp over positive- and negative-advantage tokens (policy/adv_pos_logratio, policy/adv_neg_logratio) from the Megatron and FSDP policy losses. With update_epochs_per_batch > 1 the trainer also logs them per epoch, so a few epochs on one batch show whether the update moves logprobs in the advantage's direction. Adds run_gsm8k_qwen3_8_flash_next_fullft_2node.sh and run_dapo_qwen3_8_flash_next_fullft_sync_2node.sh (8xB200 nodes, host-resident optimizer, bf16 grad reduce). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YbshX4DEqk3oji2hEQBhvz
torch's pinned-host allocator rounds each block up to a power of two (a 2.2 GiB pinned tensor costs 4.3 GiB of host RAM). With the HybridDeviceOptimizer's FP32 masters and grad copies pinned, the first optimizer step pushed a 1.76 TB B200 node past Ray's 95% memory-monitor threshold. With pin_cpu_grads/pin_cpu_params=false, GSM8K full FT peaks at ~1.62 TB per node and runs 5 steps (rollout/train |dlogp| 0.020-0.022, ~300 s/step). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YbshX4DEqk3oji2hEQBhvz
Carry only Qwen4ExpModelState.ngram_context_offsets across a level-2 sleep, keyed by the model_state class name, instead of every model_state tensor. Other models' model_state is no longer copied or rewritten. vLLM #60009 lists this tensor as not fixed upstream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FT1xhhWniiXEKhg52FSrMc
Drop the class-name allowlist: save ngram_context_offsets only when the runner's model_state has it, matching how the other vLLM patches stay a no-op for models they don't apply to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FT1xhhWniiXEKhg52FSrMc
Removed (generic; to go in separate PRs if wanted): - merge_lora sync_lora_targets_only filter (merged_lora_filter.py, config, validation, client keep_weights_on_sleep, docs, tests); it saved ~1.5% for this model and did not fix the merged-LoRA OOM. - Advantage-split logratio metrics and the per-epoch log line (direction-test diagnostics). - WANDB_ENTITY / WANDB_BASE_URL forwarding. - mtp/grad_clip.py shard_float16_groups pair: Qwen3.8 trains no MTP head. Instead, the host-masters optimizer routing now skips MTP models, so their clipping keeps finding FP32 shards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FT1xhhWniiXEKhg52FSrMc
…I#2532 This PR keeps mhc_transformer_layer.py and its test as on main. The qwen3.8 recipes with R3 (DAPO) and multi-rank EP need NovaSky-AI#2532. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014jADLGSS2eEaKAzLuAL7PK
| return torch.cat([out for out, _ in outputs], dim=0)[:seq], outputs[0][1] | ||
|
|
||
|
|
||
| class _ChunkedRouterReplay: |
There was a problem hiding this comment.
Let's check the status upstream PR, they should include this. If they don't include, let's comment in megatron/megatron bridge.
…play (R3) per chunk (#2532) Fixes two bugs in `chunked_mlp` (`patches/megatron/mcore_ext/mhc_transformer_layer.py`). The mHC layers carry this function because they bypass megatron-core's `TransformerLayer._forward_mlp`, which normally does the `mlp_chunks_for_training` chunking. GLM-5.3-Flash uses it today; Qwen3.8-Flash-Next (#2513) needs it too. - **Equal MoE call counts per rank.** The chunk count was capped at the sequence length. A rank on a short micro-batch (e.g. the one-token padding micro-batches that even out micro-batch counts across DP ranks) made fewer MoE calls, so fewer EP collectives, and the group deadlocked. Now every rank makes exactly `mlp_chunks_for_training` calls, padding the sequence with masked dummy tokens whose outputs are dropped. - **Router replay (R3) per chunk.** On replay, `RouterReplay.get_replay_topk` uses `target_topk_idx` as the routing and gathers the router's scores with it, so it expects `[tokens of this call, topk]`. But `target_topk_idx` holds the whole micro-batch, `[s * b, topk]`, while the router runs once per chunk on `[chunk_tokens, experts]` scores, so the gather failed on the first chunk ("Size does not match at dimension 0"). Recompute pops one `replay_backward_list` entry per call (the second chunk would take the next micro-batch's entry), and recording kept only the last chunk. `_ChunkedRouterReplay` gives each chunk its slice of the forward target and of the recompute entry, and concatenates recorded indices back into one tensor. `test_mhc_moe_chunking.py` adds short-sequence cases and R3 forward / recompute / record tests: 19 pass with this change, 10 fail on `main`. The fix was also exercised end to end in #2513's Qwen3.8 DAPO runs with R3 on 4×8 B200. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_014jADLGSS2eEaKAzLuAL7PK <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Changes MoE chunking, padding, and router-replay state on the mHC training path; mismatches could cause EP deadlocks or incorrect R3 routing during recompute. > > **Overview** > **`chunked_mlp`** in the mHC Megatron layer now always performs exactly **`num_chunks`** MoE/MLP invocations per rank, fixing expert-parallel deadlocks when a micro-batch is shorter than the chunk count (e.g. DP padding batches). Short sequences are padded with **masked dummy tokens** whose outputs are stripped before return; synthetic padding gets a mask even when the caller did not pass one, so router aux/z-loss token counts stay correct. Splitting uses **`tensor_split`** instead of capped **`chunk`**. > > A new **`_ChunkedRouterReplay`** wrapper aligns Megatron **router replay (R3)** with per-chunk routing: it slices **`target_topk_idx`** and **`replay_backward_list`** entries per chunk for forward/recompute replay, pads replay indices for dummy tokens, and concatenates recorded top-k across chunks. > > Tests cover extra short-sequence chunking cases, padding/mask expectations, and R3 forward, backward replay, and record paths. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 46a6ade. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… trials; switch to whichever lands Megatron-LM#7393, Megatron-Bridge#6123, Megatron-LM#7553 and Megatron-Bridge#6354 are separate implementations (only MB#6123 builds on MLM#7393). List what each needs to land, what to check before switching (construction path, recompute, PLE placement, packing, LoRA, R3), and verify with the checkpoint row and a short DAPO run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014jADLGSS2eEaKAzLuAL7PK
…te lost on sleep vllm#60009 explicitly leaves Qwen4Exp's ngram_context_offsets out of scope (it is held by the model runner's model state, not a module). vllm#63548 builds the MRV2 model state in the runtime pool under sleep mode; patch_vllm_sleep_model_state can go once it is in our vLLM pin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014jADLGSS2eEaKAzLuAL7PK
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
…A kernels on Blackwell qsa_sparse_attention sends the whole pack through cudnn.ops.qsa_forward / qsa_backward (NVIDIA/cudnn-frontend#1765) when the GPU is SM100/SM103, the heads are 24Q/2KV or 12Q/1KV with head dim 256, and cudnn-frontend has the QSA ops (>= 1.32; the DAPO recipes take the GitHub dev wheel via QSA_CUDNN_WHEEL). Otherwise FlexAttention runs as before. SKYRL_QWEN4_QSA_CUDNN=0 forces FlexAttention. 1.7-3.5x FlexAttention fwd+bwd per layer at 8-10K tokens on B200, same results within bf16 rounding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
…ws in the recipe log Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
QSA sparse attention on cuDNN frontend (5ca3778, b0339cf): findings on B200Setup: 1. Kernel benchmark (one layer, 1×B200, random top-512 selection, ms)
2. Correctness in the model
3. 8K, 2-node DAPO (EP16, LoRA, R3)
4. Long context (1 node, EP8, LoRA, full recompute, one sequence per rank, warm steps)
Takeaway: at the 8K DAPO recipe, the cuDNN path is a small win (~8% of the logprob pass), and training there is bound by MoE/host time. From ~30K it is a large win (~30% faster, up to 62 GB less memory), and at 62K it is the only path that trains in this setup. |
The n-gram table was split by head, so EP had to divide the 16 heads. Split it by contiguous rows instead (as Megatron-LM#7553) and route each id to its owner rank with a variable-split all-to-all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
Allow TP > 1 for Qwen3.8-Flash-Next when sequence parallelism is on (SkyRL enables it with TP > 1). GDN, gated attention and the MoE already handle TP/SP; the qwen4_exp pieces now do too: - PLE runs on the TP-gathered sequence (n-gram context and the dilated conv cross rank boundaries) and keeps this rank's tokens of its output. - The QSA indexer selects on the TP-gathered attention input (no grad; frozen). - Hyper-connection, final mixer and PLE params are marked sequence_parallel so their grads are summed over TP. Tests: a 2-rank gloo CPU test of PLE under SP against the full-sequence PLE (outputs, input and parameter grads), and tp2_ep4 rows for the model, LoRA adapter and QSA 8K GPU tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
|
TP2 + sequence parallelism, end to end: 30-step full-FT DAPO on 4×8 B200 Committed recipe
AIME-2024 avg@12:
Means over steps 1-30 (TP2 / TP1):
The run finished with no errors and learns like TP1. TP2 saves ~17 GB per GPU, at the cost of a slower train phase; total step time is about the same because generation dominates. Policy entropy ran higher than TP1 for steps 7-19 (peak 0.93 at step 12 vs 0.31-0.53), then the two converged again from step 20 (steps 1-6 match, so the metric is not biased by TP). This could be ordinary run-to-run divergence, but the reference ran on older code, so it isn't a like-for-like comparison. A same-batch gradient replay comparison (TP1 vs TP2/TP4/ETP2) is in progress. |
The DAPO and GSM8K 4-node recipes read MEGATRON_TP/PP/CP/EP/ETP from the environment (defaults unchanged: TP=PP=CP=1, EP=16, ETP=1). TP is 1 or 2; at TP=4 the cuDNN QSA kernels don't cover the per-rank head ratio. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
Conflicts: patches/megatron README test table (keep both rows) and the full-checkpointing params (the Qwen3.8 LoRA row takes main's new optimizer_offload_fraction arg as None). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
There was a problem hiding this comment.
Code Review
This pull request introduces support for the Qwen3.8-Flash-Next (qwen4_exp) model in the Megatron backend, including custom modules for gated low-rank hyper-connections, PLE n-gram embedding, and QSA indexer/sparse attention. It also adds scripts for model slicing and export completion, training recipes for DAPO and GRPO, and patches vLLM sleep/wake states to preserve model runner tensors. Feedback on the changes suggests using context managers for file operations in complete_export.py and make_slice.py to prevent unclosed file descriptors, and using getattr when accessing checkpoint_core_attention in layer_specs.py to avoid potential AttributeErrors.
[Medium impact] The PR adds a specialized Megatron training path and changes vLLM sleep/wake behavior. Fix the B200 recipes’ GDN backend choice before merging. Findings
|
| with Timer("megatron_forward_8k"): | ||
| results = ray.get(policy.async_run_ray_method("mesh", "forward", data=training_input)) |
There was a problem hiding this comment.
FlexAttention gradients lack GPU coverage
This long-context test calls only forward. The cuDNN gradient test explicitly disables FlexAttention for its reference run, so neither checks gradients through the default GPU sparse path when the optional cuDNN wheel is absent.
Add a GPU test that forces FlexAttention and compares outputs and query/key/value gradients with SDPA on a pack containing sparse sequences and padding. Otherwise, wrong gradients or backward-only failures can pass the added tests.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Reviewed by Cursor Bugbot for commit 3c63288. Configure here.
Eager create_block_mask materializes the full [padded, padded] mask; at 62K that is a 30 GiB allocation and the Flex training pass OOMed in recompute. With _compile=True the mask is built tile by tile: 62K now trains at the same peak as cuDNN (133-137 GB, 1 node EP8 LoRA), 30K uses ~3 GB less, no extra recompiles, and the qsa8k rows are unchanged (0.055). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
…ecipes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
Conflict: new_inference_worker_wrap.py sleep. Keep the qwen3.8 model-state save next to main's vLLM 0.32 runtime/cudagraph offload tags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015m9Uy57LhVrqUZwKFH2VdR
…cstrings The package docstring, Qwen4ExpCoreAttention and the patches README still named masked torch SDPA as the sparse-regime attention. It now runs cuDNN frontend's QSA kernels on Blackwell when available, else FlexAttention on CUDA, else chunked SDPA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>


What
Adds Qwen3.8-Flash-Next (HF
qwen4_exp,Qwen4ExpForConditionalGeneration) to the Megatron backend, for both full fine-tuning and LoRA, with vLLM rollouts.The architecture is new to both upstreams. It has 48 layers of 3:1 Gated DeltaNet / gated full attention, and the full-attention layers use a QSA sparse indexer (top-512 blocks of 4 tokens). The MoE has 512 routed experts (top-10) and a shared expert. Every block has gated low-rank hyper-connections over a 4-stream residual, and a frozen 51B-parameter n-gram embedding (PLE) is injected at layer 1. It ships as a VL checkpoint, and SkyRL trains the language model only (
language_model_only=True).No dependency pins change: this runs on
main's vLLM 0.31.0, megatron-core and Megatron-Bridge. What the pinned upstreams don't have yet is underpatches/.Components
patches/megatron/qwen4_exp/: the Megatron-Bridge modelSkyRL's own implementation; the open upstream PRs are independent trials (see Upstream PRs to track). The modules here were written against HF transformers'
modeling_qwen4_exp.py, and their parameter names mirror the HF classes. The bridge builds on Megatron-Bridge'sQwen35MoEBridgemappings.modules.py:Qwen4ExpGatedResidual/Qwen4ExpFinalMixer: the gated low-rank hyper-connection and the final stream mixer, which takes the block'sfinal_layernormslot.Qwen4ExpGatedDeltaNet: megatron-core's GDN with a sigmoid output gate and SiLU on the conv. megatron-core uses one activation for both.Qwen4ExpPLE: the PLE n-gram embedding.Qwen4ExpQSAIndexerandqsa_sparse_attention: HF's top-k block selection. At up toindexer_budget + compress_ratio - 1= 2051 tokens every block is kept, so attention stays on the fused TE kernel. Past that it runs cuDNN frontend's QSA kernels on Blackwell when the installed cudnn-frontend has them (cudnn.ops.qsa_forward/qsa_backward, Add packed QSA training kernels for Blackwell and Rubin NVIDIA/cudnn-frontend#1765; dev wheel via the recipes'QSA_CUDNN_WHEEL,SKYRL_QWEN4_QSA_CUDNN=0to disable), else a FlexAttention path (44 ms per layer fwd+bwd at 8K, vs 138 ms for SDPA's math kernel). B200 results: comment.layer_specs.py:Qwen4ExpTransformerLayer(expand → PLE → HC-wrapped mixer → HC-wrapped MoE), andQwen4ExpCoreAttention, which switches between TE dense and the QSA sparse path.provider.py:Qwen4ExpModelProvider. It stashesinput_ids/cu_seqlensfor PLE through a forward pre-hook, and requires CP=PP=1 (TP > 1 needs sequence parallelism).bridge.py:Qwen4ExpBridge. It uses the Qwen3.5 MoE mappings minus the fused pre-norms, plus replicated mappings for the HC, PLE projection and indexer weights. The 51B PLE table, the vision tower and the MTP head aren't bridged.patches/megatron/README.mdis the removal plan: what retires each piece upstream, and what to compare before deleting it.chunked_mlpfixes (#2532, merged)The Qwen3.8 layer reuses
chunked_mlpfrommcore_ext/mhc_transformer_layer.py. Its two fixes, equal MoE call counts per rank (an EP deadlock otherwise) and per-chunk router replay (R3 crashed at DAPO step 1 otherwise), landed in #2532 and are merged in here.inference_servers/vllm_compat.py+new_inference_worker_wrap.py: model state lost on sleepvLLM's wake path restores only the model's parameters and buffers. Tensors on Model Runner V2's
model_statethat live in the weights pool can therefore come back wrong. Qwen4Exp's constant n-gram offsets (arange(-2, 0)) come back as zeros, which corrupts every later PLE n-gram context. Upstream fix: vllm-project/vllm#63548.patch_vllm_sleep_model_statesaves the tensors listed inMODEL_STATE_TENSORS_LOST_ON_SLEEPbefore any sleep, and restores them when the weights wake.Other SkyRL changes
megatron_worker.py: qwen4_exp is text-only on Megatron, so it rejectslanguage_model_only=falsewith a clear message.model_bridges.pyregisters the bridge.examples/train/qwen3_8_flash_next/, 4 nodes of 8×B200, EP16, 4 vLLM engines × TP8, colocated.run_gsm8k_qwen3_8_flash_next_{lora,fullft}_4node.shrun_dapo_qwen3_8_flash_next_{lora,fullft}_sync_4node.sh: 2k/8k, R3 on, evals every 5 steps.disable_custom_all_reduceinengine_init_kwargs. vLLM's custom all-reduce registers CUDA-graph buffers over CUDA IPC, which fails on sleep mode's cumem allocator, so every TP worker dies at engine start.make_slice.py: the 4-layer CI slice with a small random PLE table (23 GB).complete_export.py: makes a full-FT HF export loadable (see Known limitations).gpu_ci/megatron/test_megatron_models.pyandtest_megatron_lora_models.py, andtest_qwen38_qsa_long_context.py(sparse regime).Progress vs
.agents/docs/new_model_support.mdStage 0: Megatron port and roundtrip logprobs
patches/megatron/main's vLLM 0.31.0 (no custom wheel)test_megatron_models.py/test_megatron_lora_models.pyrows on the 4-layer slice pass on 4×H100 (re-run 2026-10-22 on aeeb72d)xguihugging/Qwen3.8-Flash-Next-4layer(the rows' default)Stage 1: GSM8K, LoRA and full fine-tuning
Stage 2: DAPO, LoRA and full fine-tuning
test_trainer_full_checkpointing.pyLoRA row: save, resume, second save (re-run 2026-10-22 on 4×H100)Stage 3: performance and quantization (follow-up)
weight_sync/fp8/modelsand an MXFP8 run matching the BF16 curveValidation
LoRA roundtrip (
..._ep4_adapter):DAPO 2k/8k with the committed 4-node recipes (4×8 B200 with EFA, EP16 / DP32, vLLM 4 × TP8, optimizer on GPU): 30 steps, R3, 32 prompts × 8 samples, 2 optimizer steps per batch. Held-out AIME-2024 avg@12 (30 problems × 12 samples, temperature 1.0, top-p 0.7):
Also passing:
test_trainer_full_checkpointing.pyLoRA row on the slice (save, resume, second save). Like GLM-5.3-Flash, it has no full-FT row: with the optimizer on the GPU and expert-DP 1 on 4 GPUs, full FT of the slice peaks at ~85 GB/GPU.Known limitations
examples/train/qwen3_8_flash_next/complete_export.py <export_dir> <base_dir>to merge them from the base. The MTP drafter is then the base's, so speculative-decoding acceptance may drop.sqrt(|key·query|)·sign) is steep near zero, so per-parameter grads ofkey_proj/norm_key/norm_queryare sensitive to tiny input differences (also in HF fp32). Comparing these params across parallel layouts or against HF shows large per-param scatter even though overall grads match.SKYRL_QWEN38_4LAYER_PATHmust keepQwen3.8-Flash-Nextin its path: the tests key the qwen3.8 settings on the model name.Upstream PRs to track
All open. The Megatron PRs are independent trials at
qwen4_exp, not one stack: different model classes, config fields and PLE designs. Whichever lands first (and SkyRL bumps the pin to it), switch to it and deletepatches/megatron/qwen4_exp/as a whole.patches/megatron/README.mdlists what to check before switching (construction path, activation recompute, PLE placement next to vLLM, packing, LoRA, R3).GPTModel, TP + SP. Both have to land.HybridModelwith its own modules inside Bridge; lands on its own.HybridModel, PP/VPP, CP, MTP, Triton QSA). Needs a Bridge bridge on top beforeqwen4_exp/can go.patch_vllm_sleep_model_stateinvllm_compat.py.Upstream issues
qwen_sparse_attentionAutoMappingdetects parallelism by exact class name, so subclasses fail🤖 Generated with Claude Code
https://claude.ai/code/session_01LT2b2yhk8WTqxCaKwZD1ML
Note
Medium Risk
Large new model path touches Megatron forward, MoE routing replay, and colocated vLLM weight sync/sleep; mistakes could cause silent logprob drift or training failures, but changes are mostly additive patches and examples.
Overview
Adds Qwen3.8-Flash-Next (
qwen4_exp) to the Megatron training path via a new vendored stack underpatches/megatron/qwen4_exp/(bridge, provider, layer specs, and modules for gated hyper-connections, EP-sharded frozen PLE, sigmoid GDN, and QSA indexer plus sparse attention with cuDNN/FlexAttention/SDPA fallbacks). The bridge registers on import throughmodel_bridges.py, andmegatron_worker.pynow requires text-only training for this VLM checkpoint.Fixes colocated vLLM sleep/wake corrupting Qwen4Exp rollout state by saving and restoring listed
model_runner.model_statetensors (e.g.ngram_context_offsets) invllm_compat.pyand the weight-sync sleep path innew_inference_worker_wrap.py.Ships 4-node B200 recipes (GSM8K GRPO and DAPO sync for LoRA/full FT),
make_slice.py/complete_export.pyfor CI-sized checkpoints and loadable HF exports, and extends GPU CI with 4-layer parity rows, ~8K QSA long-context logprob tests, LoRA checkpointing, and CPU tests for modules vs HF and the sleep patch. Megatron patch README documents upstream removal criteria forqwen4_exp/.Reviewed by Cursor Bugbot for commit f0edea6. Bugbot is set up for automated code reviews on this repo. Configure here.