Skip to content

[WS1][CUDA][Qwen3-Next] Comparison benchmarks and plots for the Qwen3-Next stack: MoE, attention, logprob, EP (RFC #428 stack 11/11) - #551

Open
fusheng-ji wants to merge 192 commits into
RL-Align:test-qwennextfrom
fusheng-ji:feat/qwen3-next-logprob-benchmarks
Open

fusheng-ji wants to merge 192 commits into
RL-Align:test-qwennextfrom
fusheng-ji:feat/qwen3-next-logprob-benchmarks

Conversation

@fusheng-ji

@fusheng-ji fusheng-ji commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Stack: … → #549 → #557 → this PR, base test-qwennext; merges after #557; kept draft until the validation PRs land. This PR's own diff.

What

Per the layout review on #549, every comparison benchmark and plot of the Qwen3-Next stack lives here, under benchmarks/models/qwen3_next/; the validation PRs keep only checks and CI tests. Code is moved from the validation PRs with imports and paths adjusted, except the new EP benchmark.

Validation PR Candidates module Runner Plot
#503 routed MoE moe_route_candidates.py qwen3_next_moe_prior_art.py plot_qwen3_next_moe_prior_art.py
#507 full attention attention_candidates.py qwen3_next_attention_prior_art.py plot_qwen3_next_attention_prior_art.py
#543 chain — — plot_qwen3_next_chain.py
#544 batch invariance — — plot_qwen3_next_batch_invariance.py
#549 TP logprob logprob_candidates.py qwen3_next_logprob_prior_art.py plot_qwen3_next_distributed_logprob.py
#557 moe_ep_candidates.py qwen3_next_moe_ep_prior_art.py plot_qwen3_next_moe_ep.py

Miles and verl are measured, not read: thirdparty_sources.py imports their own kernels unmodified from checkouts of pinned commits (radixark/miles@23d3d38, volcengine/verl@e5a627c) and records the SHA-256 of every file used. New here: qwen3_next_miles_moe_backward.py (Miles' routed-MoE backward, 20 identical launches on the real layer-0 weights, RL-Kernel as the control), qwen3_next_logprob_backward.py (verl, Miles and RL-Kernel logprob backward), miles_fused and verl_fused in the logprob candidates, and plot_qwen3_next_thirdparty.py.

Results summaries (download link, SHA-256, tested commit, commands) are in reports/experiments/qwen3_next/: qwen3-next-logprob-comparison-b200.md, qwen3-next-moe-ep-comparison-b200.md and qwen3-next-thirdparty-comparison-b200.md here; the MoE-route, attention, chain and BI summaries in their validation PRs.

Prior art & reuse decision

N/A: benchmarks only. The reuse decisions are in each validation PR; this PR provides the code that measures them.

Results

Expert-parallel MoE (new, for the EP PR), layer 0 real weights, EP4 under 13 NCCL settings and EP1, 4 × B200, measured at 7db4a14b (summary, raw results ZIP, SHA-256 2375ec36…97c6cf):

Candidate Fwd EP4 == EP1 Fwd distinct results / 13 NCCL settings Fwd repeat / batch inv. Bwd EP4 == EP1 (dx, dW) Bwd batch inv. Max abs vs FP64 EP4 fwd 64 / 1024 / 4096 tokens EP4 bwd
RL-Kernel EP4 yes 1 yes / yes yes, yes yes 1.8e-5 1.85 / 2.27 / 4.08 ms 51 ms
vLLM + vLLM all-reduce no 1 yes / yes no backward – 8.7e-4 0.35 / 0.48 / 0.68 ms –
vLLM, VLLM_BATCH_INVARIANT=1 no 1 yes / yes no backward – 8.7e-4 0.41 / 0.51 / 0.81 ms –
vLLM + NCCL all_reduce no 5 yes / no no backward – 8.7e-4 0.39 / 0.52 / 0.74 ms –
FlashInfer cutlass_fused_moe (CUTLASS) no 1 yes / yes no backward – 8.7e-4 0.33 / 0.39 / 0.48 ms –
Megatron alltoall no 13 no / no no, no no 4.2e-5 11.8 / 14.6 / 14.9 ms 41 ms
Megatron allgather no 13 no / no no, no no 2.5e-5 10.8 / 13.2 / 13.6 ms 37 ms
Megatron alltoall, deterministic yes 1 yes / yes no, no no 4.2e-5 12.7 / 15.4 / 15.9 ms 42 ms
Megatron allgather, deterministic no 1 yes / yes no, no no 2.5e-5 11.6 / 14.3 / 15.0 ms 40 ms

Qwen3-Next EP MoE prior art

Miles and verl (for #503, #549, EP PR), measured at 6ad63611 (MoE backward, logprob forward) and 03a5898e (logprob backward, TP1 and TP4) (summary, raw results ZIP, SHA-256 d0eaa326…f2ff):

Miles and verl vs RL-Kernel

Measurement Miles verl RL-Kernel
routed-MoE backward, 20 identical launches: dx / dW gate/up / dW down equal the first 1 / 20 each (BF16 tl.atomic_add) no MoE kernel 20 / 20
routed-MoE dx of probe rows alone == in batch (10 cells) 0 / 10 – 10 / 10
logprob forward, TP4 == TP1 no no yes
logprob forward, distinct TP4 results over 13 NCCL settings 2 2 1
logprob backward, 10 launches equal (TP1) 10 / 10 10 / 10 10 / 10
logprob backward row gradient, probe rows alone == in batch (10 cells; logits of Miles and RL-Kernel from the batch-invariant GEMM) 10 / 10 1 / 10 10 / 10
logprob backward, TP4 == TP1 no no yes
logprob backward, distinct TP4 results over 13 NCCL settings (10 launches each) 2 2 1

Miles' MoE backward is not reproducible; both libraries' logprob forward depends on the NCCL algorithm like Megatron's and VIME's. Their logprob backward also depends on the NCCL algorithm at TP4 (it consumes the all-reduced forward statistics) and differs from TP1, while RL-Kernel's does not. Not what the source suggested: verl's backward tl.atomic_add did not change the bits between launches, and its forward atomic_add only runs for reduction sum/mean.

TP logprob (#549): measured in #549 (summary and ZIP there): TP4 median latency 2.55 ms (RL-Kernel), 1.51 ms (vLLM), 0.57 ms (Megatron), 0.56 ms (VIME); invariance as in #549.

The routed-MoE, attention, chain and BI figures are rendered by the moved plot scripts from the ZIPs attached to their PRs (checked: each renders from the downloaded evidence layout).

Figures of the validation PRs, rendered by the plot scripts in this PR from the evidence ZIPs of those PRs:

#503 routed MoE #507 attention
MoE route prior art TP4 attention prior art
#543 full-model chain #544 batch invariance (97 / 97 cells; reruns at EP1 and EP4 in #544 and #557)
Chain on the stack top Batch invariance

Status

History

Tests

python benchmarks/models/qwen3_next/qwen3_next_moe_ep_prior_art.py --help
torchrun --nproc-per-node 4 benchmarks/models/qwen3_next/qwen3_next_moe_ep_prior_art.py --checkpoint <ckpt> --output runs/ep4-default
torchrun --nproc-per-node 1 benchmarks/models/qwen3_next/qwen3_next_moe_ep_prior_art.py --checkpoint <ckpt> --output runs/ep1
python benchmarks/models/qwen3_next/qwen3_next_moe_ep_prior_art.py --merge runs/ep4-default runs/ep4-<...> runs/ep1 --out report.json
Check Result
every runner --help and every plot rendered from its published evidence passed
pre-commit on all files clean

Scope

CUDA, B200. Benchmarks and plots only.

…rouped expert GEMM

RFC RL-Align#428 moe_route_combine_contract (CUDA).

- Routing: FP32 router through the pinned vLLM batch-invariant GEMM, a
  fixed-order softmax, stable descending argsort (ties go to the lower
  expert id) and FP32 renormalisation of the ten selected weights.
- Experts: per-route gate_up and down projections through vLLM 0.30.0's
  fused_moe Triton kernel with the batch-invariant tile passed explicitly
  (BLOCK 64/64/32, SPLIT_K=1); no routed-weight multiply and no sum inside
  the kernel, so every (token, route) row has one writer. Bitwise equal to
  the previous per-expert GEMM loop at TP4 and TP1 widths; fails closed if
  VLLM_TRITON_USE_TD is enabled.
- Combine: ten ordered FP32 multiply-adds and one BF16 cast; no atomics.
- Backward: per-expert pinned GEMMs into one dense gradient buffer; dx is
  summed in ascending expert order.
- TP4 block: HF load/export for all 512 experts, replicated router and
  shared-expert gate, Megatron-style copy/reduce autograd boundaries moved
  into qwen3_next_tp.py.
- Prior-art runner and plot comparing HF transformers, vLLM fused_moe
  (BI=0/1) and FlashInfer cutlass_fused_moe; TP4 real-weight gate script.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Prep for RFC RL-Align#428 full_model_chain; serves the gdn_state_bridge row.

- GDN: packed recurrent prefill/decode through the pinned vLLM 0.30.0
  provider with an explicit state boundary (qwen3_next_gdn.py), the
  depthwise causal convolution with cache handoff (qwen3_next_conv.py),
  their combined shared core (qwen3_next_provider.py) and the TP4 block
  with HF GQA-interleaved weight mapping (qwen3_next_tp_gdn.py), now on the
  shared TP boundaries in qwen3_next_tp.py.
- Attention: the deterministic CUDA attention kernel accepts head dim 256
  (ROCm stays 128) and validates backward inputs; shared_attention and
  TP4FullAttention with the KV head replicated on each rank pair, partial
  RoPE at absolute positions and explicit per-slot KV state.
- Real-weight TP4 gates for GDN (layer 0) and attention (layer 3), sharing
  scripts/qwen3_next_gate_common.py with the MoE gate.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…nce (B200, 2e950f3)

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Compares RL-Kernel's deterministic attention with torch SDPA, vLLM FlashAttention-2 (num_splits auto and 1), vLLM's Triton unified attention and FlashInfer's ragged prefill: batch, chunked-prefill and decode invariance, backward repeatability, accuracy against FP64 and latency.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…nce (B200, 9e6c4f2/6517077)

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
RFC RL-Align#428 full_model_chain (CUDA).

- Engine adapters: the native vLLM rollout (exact-prefill scheduler, paged
  KV, traced response rows), the VIME/Megatron replay and training actor,
  engine weight import/export and the logprob adapter, all on the shared
  TP4 providers.
- Stage runners (preflight, layers, l2, train1, train2, train) with the
  validated acceptance contract (version 4) and scripts/qwen3_next_run.py,
  a launcher that works inside any four-GPU allocation: each stage gets a
  clean clone, an immutable run.json identified by a fresh execution_id,
  and refuses a predecessor that did not pass or pins another identity.
  Scheduler-independent helpers live in scripts/qwen3_next_runtime.py.
- Sub-node digests: both engines observe the normed mixer input, the mixer
  output, the normed MLP input, the selected expert ids and the MoE output
  of every layer, plus the embedding; the first divergence names the layer
  and sub-node.
- scripts/qwen3_next_chain_report.py writes a ChainGateReport-compatible
  report (schema qwen3-next-chain-v1, compatible with ws1-c10-c11-v5) with
  first_drift_family/layer/cell; scripts/qwen3_next_publish_evidence.py
  publishes a chain's records without site paths, hosts or users.
- Native rollout caps raised to 64 sequences, 16384 batched tokens and a
  4 GiB KV budget so the batch-invariance matrix runs under the same
  certified profile; the exact-prefill scheduler still fixes every chunk.
- The operator-case registry is regenerated from collection by
  scripts/qwen3_next_operator_cases.py.
- Packaging: transformers may be 5.13.1-5.17.0; a qwen3-next extra pins the
  audited runtime.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
RFC RL-Align#428 batch_invariance_ci (CUDA).

- Stage bi (after a passed l2, same certified profile): 97 pinned cells in
  rl_engine/testing/qwen3_next_bi_cases.py / qwen3_next_bi_manifest.json.
  concurrency (1-64 requests, target first or last), interference
  (companion content, lengths, arrival during prefill/decode, generation
  lengths), chunk boundaries (prompts 63-1024 around 64/128/528/1024, chunk
  64/128, first chunk 1/63/65, compared with the replay recompute) and
  replay THD packing (1-7 neighbours, target first or last). A quick CI
  subset runs one cell per family plus 64 concurrent requests.
- Native rollout: version 2 requests run one traced target next to
  untraced companions; the exact-prefill scheduler admits each request at
  its arrival step, pins the target's planned chunk at every step and
  fails on any preemption; the trace records only the target.
- Replay: packed_layout() packs variable-length neighbours with the traced
  target in one THD batch.
- bi-matrix.json names each failing cell's first divergent layer, sub-node
  and layer family; the chain report gains a batch_invariance section, and
  the evidence publisher takes the bi run with --also.
- gtest: bitwise judgments compare raw bits (signed zero, NaN/Inf and
  dtype now count), not allclose with zero tolerance.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…lout profile

Batch-invariance chunk cells use prompts such as 129 tokens; the state-shape check only admitted the four L2 lengths, which their own validators already pin.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Editable sources, base interpreters and build directories in package metadata now keep only their last path component; cgroup paths keep only their file name. The leftover check matches absolute paths only, so package paths such as vllm/.../cpu/data/ are not flagged, and any mention of the batch scheduler blocks publication.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…r-art runner

- Megatron-core 0.16 MoELayer with Transformer Engine grouped GEMM and fused
  permute, configured as VIME runs Qwen3-Next (FP32 softmax router, top-10,
  all-to-all dispatcher), including its backward.
- SGLang 0.5.21 Triton fused_moe in default and deterministic-inference mode.
  sgl_kernel cannot load next to torch 2.13, so its two kernels on this path
  are replaced by SGLang's own Triton moe_sum_reduce and JIT
  moe_align_block_size; any other sgl_kernel call fails loudly.
- --only and --merge combine reports from different Python environments.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…ured (B200, c58816b)

Every library in the reuse table is now measured: Megatron-core 0.16 + TE 2.16 as VIME configures Qwen3-Next, and SGLang 0.5.21 in default and deterministic-inference mode. The TP4 gate evidence is unchanged (2e950f3, identical MoE code).

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…gatron-core attention

Add three engines to the attention prior-art runner:
- FlashAttention-4 (CuTe DSL) varlen as vendored by vLLM, forward and backward.
- Transformer Engine DotProductAttention on THD-packed sequences, in
  inference mode (cuDNN fused) and in training mode (on SM100 TE has no
  fused head-dim-256 backward and falls back to its unfused path).
- Megatron-core's local DotProductAttention, one launch per sequence.

The backward check now compares the target's dq/dk/dv alone against the
same sequence packed first and last, so engines with a packed backward are
checked for batch invariance rather than repeatability. A prefill/decode
crash is recorded per candidate without skipping its other checks.
--only and --merge combine reports from different Python environments.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
The decode and forward+backward latency sections are keyed by candidate
directly, the prefill section by size first; merge them at any depth.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…p as E402-exempt

The gate scripts insert the repo root into sys.path before importing
rl_engine and tools, which flake8 reports as E402. Mark those imports
with noqa, as the other tools/ scripts do.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…p as E402-exempt

The gate scripts insert the repo root into sys.path before importing
rl_engine and tools, which flake8 reports as E402. Mark those imports
with noqa, as the other tools/ scripts do.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Annotate the latency samples and accept the default-argument lambdas used as
timed calls. No behaviour change.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
… runner

Annotate the latency samples and accept the default-argument lambdas used as
timed calls. No behaviour change.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Merge the migrated TP4 mixers (and through them the refactored
test-qwennext) and move the full-model chain into the new layout:

- model-level TP4 pieces (embedding, engine weights, logprob, sub-node
  observations) into rl_engine/models/qwen3_next/;
- vLLM adapters and the native rollout into
  rl_engine/integrations/engines/rollout/vllm/;
- VIME providers into rl_engine/integrations/orchestrators/vime/providers/
  and the native VIME lifecycle (recompute, training, workers, profile)
  into rl_engine/integrations/orchestrators/vime/experiments/qwen3_next/;
- the provider identity into rl_engine/integrations/common/;
- chain report, L2/case fixtures and manifests into
  rl_engine/validation/models/;
- stage runners and the launcher into tools/validation/models/, CI scripts
  into ci/scripts/, tests into tests/models/qwen3_next/.

Imports point at canonical modules; the provider-source digest list names
the canonical RMSNorm backend instead of its compatibility alias. Recorded
contract identities (e.g. sample_counter_owner) are unchanged.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…nce matrix

Merge the migrated full-model chain and move the batch-invariance work into
the new layout: the case generator and manifest into
rl_engine/validation/models/, the stage runner into tools/validation/models/,
its tests into tests/models/qwen3_next/. The raw-bit comparison for bitwise
judgments now lives in the canonical
rl_engine/validation/operators/forward_invariance.py (the old module is a
compatibility alias). The evidence README gives the commands in the current
layout and says where the runners were at the measured commit; no measured
file changed (SHA256SUMS verified).

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…ouched

The published chain evidence is a byte-exact copy covered by SHA256SUMS; a
pytest JUnit XML without a final newline would otherwise be rewritten by
end-of-file-fixer and break the checksum. Same treatment as reports/archive/.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…ouched

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

🔍 OpenCodeReview found 8 issue(s) in this PR.

  • ✅ Successfully posted inline: 0 comment(s)
  • 📋 Routed to summary by policy: 8 comment(s)

⚠️ 4 warning(s) occurred during review.


bug · medium

📄 benchmarks/models/qwen3_next/moe_ep_candidates.py (L135-L139)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity medium · category bug)

_vllm_batch_invariant mutates os.environ["VLLM_BATCH_INVARIANT"] but never restores the previous value on exit (it is a @contextmanager with no finally). Trigger: run moe_report with MOE_EP_CANDIDATES selecting vllm_ep_bi (or any run where it is measured last); afterwards the whole process keeps VLLM_BATCH_INVARIANT=1, so any later candidate or harness code that does its own batch-invariance qualification silently runs under vLLM's batch-invariant kernel overrides. That can change the numbers reported for candidates measured after this one. Note moe_route_candidates._env correctly saves/restores; mirror that here.

💡 Suggested Change

Before:

    value = "1" if enabled else "0"
    if os.environ.get("VLLM_BATCH_INVARIANT", "0") != value:
        os.environ["VLLM_BATCH_INVARIANT"] = value
        get_moe_configs.cache_clear()
    yield

After:

    value = "1" if enabled else "0"
    previous = os.environ.get("VLLM_BATCH_INVARIANT")
    if previous != value:
        os.environ["VLLM_BATCH_INVARIANT"] = value
        get_moe_configs.cache_clear()
    try:
        yield
    finally:
        if previous is None:
            os.environ.pop("VLLM_BATCH_INVARIANT", None)
        else:
            os.environ["VLLM_BATCH_INVARIANT"] = previous

bug · medium

📄 benchmarks/models/qwen3_next/moe_ep_candidates.py (L155-L157)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity medium · category bug)

For the default "vllm" backend _combine ignores the group argument and calls tensor_model_parallel_all_reduce(), which reduces over vLLM's model-parallel group rather than the EP group passed to candidate_report/_vllm/_flashinfer. If the EP group membership differs from vLLM's TP group (e.g. EP4 with TP1, or the benchmark's group is a subgroup not registered as vLLM's TP group), the partial per-rank expert outputs are combined over the wrong ranks, corrupting the accuracy/BI numbers for vllm_ep, vllm_ep_bi and flashinfer_cutlass_ep. If vLLM's distributed state is not initialized, the helper returns the input unchanged, so no combine happens at all. Use the explicit group (as the nccl branch does) so all candidates reduce over the same ranks, or assert the groups match.

💡 Suggested Change

Before:

    from vllm.distributed import tensor_model_parallel_all_reduce

    return tensor_model_parallel_all_reduce(out)

After:

    from vllm.distributed import tensor_model_parallel_all_reduce

    # Reduce over the same EP group the other candidates use.
    dist.all_reduce(out, group=group)
    return out

performance · medium

📄 benchmarks/models/qwen3_next/attention_candidates.py (L219-L220)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity medium · category performance)

_vllm_triton.run rebuilds the entire paged KV cache (k_cache/v_cache zero-init + pad/copy, and the block table) from scratch on every invocation. _latency times run per call, so the reported prefill (up to 8192 tokens) and decode latencies for vllm_triton_2d are dominated by cache materialization rather than the attention kernel, while the other candidates time only their kernel. This is not an equivalent workload and overstates this candidate's latency. Build the cached K/V and block table once per (fixed) seqs outside the timed region.


bug · medium

📄 benchmarks/models/qwen3_next/plot_qwen3_next_thirdparty.py (L55-L55)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity medium · category bug)

Unlike the peer prior-art plots in this PR (plot_qwen3_next_attention_prior_art.py, plot_qwen3_next_moe_prior_art.py), this script selects candidates purely by key presence and never filters entries flagged unavailable/failed. Every later access assumes the full result payload (selected_sha256, tp4_equals_tp1, nccl_setting_invariant, batch_bitwise, latency_us_tp4, and the gradients/batch_grid blocks). A run where one implementation is unavailable or failed will therefore either raise KeyError/TypeError or index a diagnostic string, aborting the whole figure — exactly the degraded case the evidence figure needs to document. Filter the candidates the same way the sibling plots do (and skip them in the latency/backward panels).


bug · medium

📄 benchmarks/models/qwen3_next/plot_qwen3_next_thirdparty.py (L74-L75)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity medium · category bug)

The digest heatmap hard-codes digests["tp4-default"] as the reference with no existence check, and also indexes each per-run candidate k unconditionally. The referenced runs come from separate multi-GPU jobs, so a missing/renamed run (runs only guarantees the directories that exist) or a candidate absent from one run's rank-0.json produces an opaque KeyError instead of a diagnosable error. plot_qwen3_next_distributed_logprob.py handles the analogous situation with an explicit SystemExit when the reference run is absent; do the same here and skip candidates missing from a run.


bug · medium

📄 benchmarks/models/qwen3_next/plot_qwen3_next_moe_prior_art.py (L74-L74)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity medium · category bug)

cands only filters unavailable, not failed, and then assumes the first surviving candidate carries an output_rows_bitwise mapping. Two failure modes follow: next(iter(cands)) raises StopIteration if every candidate is filtered out, and KeyError if the first candidate is a failed/inference-only entry that omits that field. The peer attention plot filters "failed" as well; align the filter here and derive sizes from a candidate known to have the field (or from the report metadata) rather than from an arbitrary first element.


bug · medium

📄 benchmarks/models/qwen3_next/plot_qwen3_next_moe_prior_art.py (L121-L121)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity medium · category bug)

max() over the filtered generator has no default, so if no candidate reports an accuracy block (all rel values are nan) the plot aborts with ValueError: max() arg is an empty sequence. Note plot_qwen3_next_attention_prior_art.py guards the identical computation with default=0.0 ... or 1.0; apply the same guard here.

💡 Suggested Change

Before:

    ax.set_xlim(0, 2.2 * max(r for r in rel if r == r))

After:

    ax.set_xlim(0, 2.2 * max((r for r in rel if r == r), default=0.0) or 1.0)

documentation · low

📄 benchmarks/models/qwen3_next/plot_qwen3_next_distributed_logprob.py (L106-L106)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category documentation)

When a pair has no max_abs_vs entry the cell value is nan, which is rendered by :.1e as the literal string "nan" — visually indistinguishable from a measured value. Since this panel is meant to show that the max |Δ logprob| is exactly zero between implementations, label missing comparisons explicitly (e.g. "n/a") so an unmeasured pair cannot be read as a measured difference.

💡 Suggested Change

Before:

            text = "0" if delta[i, j] == 0 else f"{delta[i, j]:.1e}"

After:

            text = "not run" if delta[i, j] != delta[i, j] else ("0" if delta[i, j] == 0 else f"{delta[i, j]:.1e}")

⚠️ Warnings:

  • .github/workflows/ci.yml,.github/workflows/qwen3-next-provider-gpu.yml,ci/scripts/run_qwen3_next_chain_gate.sh,ci/scripts/run_qwen3_next_operator_gates.sh,ci/scripts/run_ws1_gtest.sh (review_limit_reached): reached the aggregate token budget before finishing; the grace round completed the task
  • benchmarks/models/qwen3_next/qwen3_next_attention_prior_art.py (token_budget_reached): stopped dispatch: used 626145 tokens + group estimate 269140 = projected 895285 exceeds budget 500000
  • benchmarks/models/qwen3_next/__init__.py,benchmarks/models/qwen3_next/attention_candidates.py,benchmarks/models/qwen3_next/logprob_candidates.py,benchmarks/models/qwen3_next/moe_ep_candidates.py,benchmarks/models/qwen3_next/moe_route_candidates.py (review_limit_reached): reached the aggregate token budget before finishing; the grace round did not complete the task
  • benchmarks/models/qwen3_next/plot_qwen3_next_attention_prior_art.py,benchmarks/models/qwen3_next/plot_qwen3_next_batch_invariance.py,benchmarks/models/qwen3_next/plot_qwen3_next_chain.py,benchmarks/models/qwen3_next/plot_qwen3_next_distributed_logprob.py,benchmarks/models/qwen3_next/plot_qwen3_next_moe_ep.py,benchmarks/models/qwen3_next/plot_qwen3_next_moe_prior_art.py,benchmarks/models/qwen3_next/plot_qwen3_next_thirdparty.py (review_limit_reached): reached the aggregate token budget before finishing; the grace round did not complete the task

Comment on lines +122 to +124
def run(local, targets):
loss = vocab_parallel_cross_entropy(local.float()[:, None, :], targets[:, None])
return -loss.squeeze(1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · high
vocab_parallel_cross_entropy performs its all-reduce over Megatron's global parallel_state tensor-parallel group, not the group argument passed to this factory, and this candidate never initializes parallel_state. At TP4 the call therefore either raises (tensor-parallel group uninitialized) and is recorded as unavailable, or — if a stale/other group happens to exist — reduces the max/target/sum-exp over the wrong ranks. In both cases the Megatron logprob prior-art number is not measuring Megatron's real TP4 reduction and will silently misrepresent it. Initialize parallel_state to match tp (and the rank layout) before calling, or dispatch through an explicit group-aware reduction.

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

@fusheng-ji The automated OCR review has completed.

Please review and address all automated review bot findings, then push your updates. Maintainers will begin manual review after the bot findings have been addressed.

a203719 parametrised test_cuda_gated_parameter_vjp_contributions_match_the_fold
over shape and added four norm tests, but the registry still pinned the old
ids, so the preflight operator gate reported them as not found. Regenerated
with qwen3_next_operator_cases.py --write.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…ecompute

create_actor(training=False) built the full actor through
setup_model_and_optimizer, which wraps the model in DDP and allocates
optimizer state. The recompute/logprob path never steps, and on 80 GB GPUs
that state does not fit. With training=False the model is now built through
get_model(wrap_with_ddp=False) and loaded without an optimizer, and
(args, model, None, None) is returned. The training=True path is unchanged.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
VIME's load_checkpoint requires skip_load_to_model_and_opt; both
create_actor calls omitted it, so the full-model l2 recompute failed with a
TypeError before any model work. The stub in the actor test now requires it.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…t.toml, gate the remaining new tests

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…k, not assert

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…arty, logprob and EP plots

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
@fusheng-ji

Copy link
Copy Markdown
Contributor Author

Replies to the six OpenCodeReview summary items. The plot fixes are compiled but I have not re-rendered the figures from the evidence.

  1. run_qwen3_next_chain_gate.sh L21-L22, bare assert: fixed in 74a1b1a6 (the script belongs to [WS1][CUDA][Qwen3-Next] Full-model chain: vLLM rollout vs VIME recompute, raw-bit through two updates and a reload (RFC #428 stack 7/11) #543 and reaches this PR through the merge). The checks are explicit now: a malformed report exits 2 with the missing field or schema named, a failed gate still exits 1. Run with PYTHONOPTIMIZE=1 on an empty report it exits 2.
  2. plot_qwen3_next_attention_prior_art.py L106, KeyError on accuracy: fixed in 026e0440. A candidate without it gets NaN and no label, as in the MoE sibling.
  3. same file L112, all-zero x-limit: fixed in 026e0440. The upper bound ignores NaN and falls back to 1.0 for an all-zero column.
  4. plot_qwen3_next_thirdparty.py L160-L161, cell keys from names[0]: fixed in 026e0440. A cell a grid did not run is drawn grey instead of raising KeyError. The columns still come from the Miles MoE grid.
  5. plot_qwen3_next_distributed_logprob.py L55-L57, iterdir(): fixed in 026e0440. Only directories count as runs, and a missing default run exits with a message.
  6. plot_qwen3_next_moe_ep.py L100, hard-coded ep4-default: fixed in 026e0440. A missing ep4-default launch exits with a message that names it. The name stays: it is the launch the sort key and the reference both use.

…issing TP group

run_qwen3_next_chain_gate.sh read the report with a bare open() and
indexed JSON-only keys, so a truncated file, a non-object report or one
without first_drift_family raised a traceback and exited 1, the code
reserved for a failed gate. Read it in a with-block, require an object
and the payload-only first_drift_family, and send every such failure
through invalid() (exit 2).

The TP reduce dereferenced collective_for_group(...), which returns None
for a missing group; raise a clear RuntimeError as the EP exchange does.

Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
@fusheng-ji

Copy link
Copy Markdown
Contributor Author

One OpenCodeReview summary item:

  1. run_qwen3_next_chain_gate.sh L26-L29, JSON-only keys: fixed in 4e2c33af (merge 2c699e3f). first_drift_family is required explicitly because it is a payload key and not a dataclass field, cells must be an object, and the load no longer leaks the file handle; every failure exits 2. A report without first_drift_family now exits 2 with the field named.

@Flink-ddd Flink-ddd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for the benchmark changes; please merge after #557 is ready.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants