Skip to content

Qwen3.5-397B RL trainer on 128 v7x: the tuned recipe turns on the fp8 fake-quant hoist, fused SwiGLU backward, attention savings, the 2048 wi forward tile and the GDN context-parallel m scan, and pins the MaxText that defines them - #2948

Draft
parambole wants to merge 6 commits into
parambole/rl-4600-final-tunix-basefrom
parambole/rl-4600-final-tunix

Conversation

@parambole

@parambole parambole commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #2884 at b16ee522f. That commit is pushed here as parambole/rl-4600-final-tunix-base because #2884's branch lives in a fork, so this PR's diff is only its six commits; once #2884 merges, retarget to atwigg/mlperf. Companion MaxText PR: AI-Hypercomputer/maxtext#5799, which defines the keys.

With TRAINER_TUNED_FLAGS=true, the Qwen3.5-397B v7x recipe now turns on six MaxText trainer keys from #5799 and, at the 128-chip mesh, sets its wi forward tile to 2048; the MaxText pin moves to #5799's head 1bf840cff, which defines the keys. On the RL trainer bench (128 TPU v7x) the settings before the last two take 79.34-79.54 s per step. The last two were each measured without the other: the 2048 wi tile at 78.99-79.14 s per step = 3,727-3,734 tok/s/chip (-0.32 and -0.36 s/step against a control in the same pod), and gdn_cp_m_scan at 78.80-78.82 s per step = 3,742-3,743 tok/s/chip (-0.66 s/step in the same pod). Together they measured 78.35 s per step = 3,764 tok/s/chip (-1.01 s/step against a control in the same pod; under Results). All six numerics gates pass for each lever; a numerics run of the combination was cancelled before it ran. This PR's base (#2884 with MaxText bbd0f6f10, i.e. #5721's code with the dense mesh rule) measured 81.17-81.23 s (3,631-3,633 tok/s/chip) in an earlier booking, so this PR adds about -2.0 to -2.2 s/step with the tile and -2.4 with the m scan across bookings, each without the other, and -2.8 to -2.9 with both. The locked #5758 code measured 82.33 s (3,582 tok/s/chip). TRAINER_TUNED_FLAGS=false, and the other 397B v7x recipes at their defaults, render byte-identically to before.

What the recipe passes to the trainer

flowchart LR
  classDef pr fill:#dbeafe,stroke:#1d4ed8,stroke-width:2px,color:#0b1f4d
  classDef same fill:#f3f4f6,stroke:#9ca3af,color:#6b7280

  T["TRAINER_TUNED_FLAGS=true"]:::same --> M{"trainer mesh"}
  M -->|"128 chips, 32x2x4 (default)"| K128["moe_wo_gmm_fused_dswiglu=true<br/>context_parallel_attention_load_balance_hoist_ids=true<br/>query_proj_kernel_gather_once=true<br/>fp8_fake_quant_hoist=gather8 (only with TRAINER_FP8=true, the default)<br/>wi_tile_fwd_mlp_dim=2048 (the recipe's 1024 replaced)<br/>wi_tile_fwd_rhs_buffer_count=2<br/>gdn_cp_m_scan=true"]:::pr
  M -->|"256 chips, 32x2x8 or 64x1x8"| K256["moe_wo_gmm_fused_dswiglu=true<br/>context_parallel_attention_load_balance_hoist_ids=true<br/>(these meshes keep query_proj=device, so no gather-once)"]:::pr
  P["requirements/maxtext_requirements.txt"]:::same --> PIN["MaxText pin: 1bf840cff<br/>head of maxtext PR 5799"]:::pr
Loading
trainer mesh MaxText keys this PR adds
128 chips, 32x2x4 (default) context_parallel_attention_load_balance_hoist_ids=true moe_wo_gmm_fused_dswiglu=true query_proj_kernel_gather_once=true fp8_fake_quant_hoist=gather8 wi_tile_fwd_rhs_buffer_count=2 gdn_cp_m_scan=true (fp8_fake_quant_hoist only with TRAINER_FP8=true, the default), and wi_tile_fwd_mlp_dim=2048 in place of the recipe's 1024
256 chips, 32x2x8 and 64x1x8 context_parallel_attention_load_balance_hoist_ids=true moe_wo_gmm_fused_dswiglu=true
  • query_proj_kernel_gather_once is left out at the two 256-chip meshes: they keep query_proj=device, and MaxText refuses the key there.
  • fp8_fake_quant_hoist needs fp8_moe_fake_quant=true, which TRAINER_FP8=true sets. Without it, MaxText raises when the trainer traces the layer scan, so the recipe leaves the key out when TRAINER_FP8=false.
  • wi_tile_fwd_mlp_dim=2048 with wi_tile_fwd_rhs_buffer_count=2: the fused SwiGLU wi forward GMM covers each half of its [gate | up] output with one N tile instead of two. At 2048 the kernel's default three weight buffers do not fit VMEM (66.95 of 57.60 MiB) and two do (50.95 MiB), so the two values come together. A gate before the recipe's MaxText flags sets the recipe's own tile, so the trainer's flags carry one value of it, not 1024 followed by 2048. Neither depends on TRAINER_FP8.
  • gdn_cp_m_scan=true: with gdn_cp_mode=seq (set by this block), the GDN context-parallel forward folds each rank's chunk transitions with one Pallas scan in VMEM instead of an XLA reduction tree. Its backward twin gdn_cp_dh_scan is never passed: it made the step slower on the bench.

Results

RL trainer bench (AI-Hypercomputer/maxtext#5701), Qwen3.5-397B-A17B, 128 TPU v7x, mesh fsdp 32 x context 4 x expert 2. The bench cell uses padding first, the fused log-prob head and the staged step boundary. 9 micro-batches per step, mean of steps 1-4, 0 dropped tokens.

flowchart LR
  A["locked PR 5758 code<br/>82.33 s/step<br/>3,582 tok/s/chip"] -->|"-1.93 to -1.96 s, same pod"| F["F2: MaxText 94f11c09d without<br/>three PR 5721 commits<br/>80.37-80.40 s/step"]
  F -.->|"+ the three PR 5721 commits<br/>(other pods, same night)"| B["MaxText 94f11c09d<br/>(pin before commit 4)<br/>80.29-80.39 s/step"]
  B -->|"-0.99 s, same pod<br/>+ fp8_fake_quant_hoist=gather8"| C["pin e1cd8bf1d<br/>(this PR before commit 5)<br/>79.34-79.54 s/step"]
  C -->|"-0.32 and -0.36 s, same pod<br/>+ the 2048 wi forward tile<br/>with two weight buffers"| D["+ the 2048 wi tile<br/>78.99-79.14 s/step<br/>3,727-3,734 tok/s/chip"]
  C -->|"-0.66 s, same pod<br/>+ gdn_cp_m_scan=true"| G["+ the m scan<br/>78.80-78.82 s/step<br/>3,742-3,743 tok/s/chip"]
  D -.->|"together: not run yet"| H["this PR: pin 1bf840cff"]
  G -.-> H
Loading
configuration job / arm s per step tok/s/chip numerics
locked #5758 code parambole-bf2-v18 / o275x-prfc-f2 82.327 3,582 reference
#2884 with MaxText bbd0f6f10 (#5721's code with the dense rule; this PR's base) parambole-rb4-v16 / o275x-rbd-v16, o275x-rbd-r-v16 (booking r-67724) 81.230 / 81.168 3,631 / 3,633 PASS 6/6 (nxrbd-v16)
F2: the first three keys + dense rule, same pod as the #5758 row parambole-bf2-v18 / o275x-f2-l3-prof, o275x-f2-l3-r 80.395 / 80.368 3,668 / 3,670 PASS 6/6 (nxf2)
MaxText 94f11c09d (this PR's pin before commit 4) with this PR's flags before commit 4 parambole-bpr-v18 / o275x-pr-v18, o275x-pr-r-v18; parambole-v19a / o275x-v19a-ctl 80.379 / 80.291 / 80.385 3,669-3,673 PASS 6/6 (nxpr-v18)
pin e1cd8bf1d with fp8_fake_quant_hoist=gather8 (this PR before commit 5) parambole-v19a / o275x-v19a-fq8 (same pod as o275x-v19a-ctl); parambole-v19b / o275x-v19b-fq8r (new pod); booking r-b6424: parambole-w21ds / o275x-d1-ctl, parambole-w21cm / o275x-c1-ctl (two more pods) 79.397 / 79.355; 79.384 / 79.540 3,714 / 3,716; 3,715 / 3,708 PASS 6/6 (nx-v19b-fq8)
pin 15285fd74 with the two tile keys off (commit 4's flags), same pod as the next row parambole-w21ec / o275x-ec-ctl 79.342 3,717 -
the 2048 wi tile: pin 15285fd74, with wi_tile_fwd_mlp_dim=2048 and wi_tile_fwd_rhs_buffer_count=2 parambole-w21ec / o275x-ec-e6k, o275x-ec-e6kr (same pod as o275x-ec-ctl); parambole-w21e6k / o275x-e6k1, parambole-w21p3 / o275x-p3-e6k (two more pods) 79.024 / 78.985; 79.140 / 79.045 3,732 / 3,734; 3,727 / 3,731 PASS 6/6 (nx-e6k1)
the scan code with both scan keys off (no tile), same pod as the next row parambole-w21g2a / o275x-g2-ctl 79.480 3,711 -
the m scan (no tile): gdn_cp_m_scan=true parambole-w21g2a / o275x-g2-m (same pod as o275x-g2-ctl); parambole-w21gr / o275x-gr-m (new pod) 78.822; 78.795 3,742; 3,743 PASS 6/6 (nx-gm-m)
not passed: gdn_cp_dh_scan=true alone; both scans parambole-w21g3 / o275x-g3-dh (same pod as o275x-g3-ctl, 79.403); parambole-w21g2a / o275x-g2-both (same pod as o275x-g2-ctl) 80.610; 80.152 3,659; 3,679 -

This PR's head, the tile and the m scan together, ran in one pod with its own control (job parambole-w22k1a), from an overlay whose ten files are this branch's at 1bf840cff (tgz 4b980f9c, md5-checked). A replicate and a numerics arm of the combination were planned in parambole-w22k1b but cancelled before they ran (chip budget); each lever passed the numerics gates on its own:

TPU combined: parambole-w22k1a / o275x-k1-ctl (this overlay, all keys off) 79.361 s/step (3,716 tok/s/chip), o275x-k1-em (the tile and the m scan, same pod) 78.349 s/step (3,764 tok/s/chip): -1.012 s/step. The prediction written before the run was -1.00 (band -1.30 to -0.70); the two levers' separate same-pod deltas sum to -0.996. Logs: md5 a17b17b3 (control), 8d7b0411 (combined).

  • The fp8 hoist: -0.988 s/step, measured in the same pod. The prediction written before the run was -0.9, band -2.7 to +0.7.
  • The fp8 replicate: in a new pod, within 0.042 s/step of the first run.
  • The tile: -0.318 s/step in the same pod, and -0.357 in a replicate there (0.039 s/step from the first run). The prediction written before the same-pod run was -0.32, band -0.50 to -0.15. In two more pods: 79.140 and 79.045 s/step. On TPU, the tile arm's scheduler memory line equals its control's.
  • The m scan: -0.658 s/step in the same pod, and 78.795 s/step in a replicate in another pod. The prediction written before the run was -1.0, band -2.5 to +0.3. gdn_cp_dh_scan made the step slower: +1.207 s/step alone, +0.672 next to the m scan.
  • The flags: they are this PR's own launcher render at the bench cell. Before commit 4 they resolve to F2's flags with 0 value differences; commit 4 adds fp8_fake_quant_hoist=gather8. The tile arms passed wi_tile_fwd_mlp_dim=2048 wi_tile_fwd_rhs_buffer_count=2 after the recipe's flags (MaxText keeps the last value: their config logs read 2048 and 2); commit 5 renders the same config with one value of the tile. The m-scan arms passed gdn_cp_m_scan=true after the recipe's flags; commit 6 puts it at the end of the block's flags, the same config. The bench adds its own enable_checkpointing=false.
  • The tunix code: the runtime files the trainer uses, rl/common.py and rl/packing.py, come from [Qwen3.5-397B MLPerf] Fused sharded logps head, start-padded packing, and 128c/256c trainer recipe defaults #2884. This PR does not change them.
  • Numerics: with the hoist, the loss is identical to the run without it (0.0304453575 in all 3 steps). At the first step, the only one the gates check, the gradient norm is within 3e-7 relative of it (two runs of identical code: 2e-5). Over steps 1-2, which are logged but not gated, the two differ by up to 1.2e-4 relative, against up to 4.7e-5 between the reference runs; all far inside 2e-3. With the tile, the loss is again 0.0304453575 in all 3 steps; at the first step the gradient norm is within 1.1e-5 relative of the hoist run's, and over steps 1-2 within 7.1e-5. With the m scan, the loss is again 0.0304453575 in all 3 steps; at the first step the gradient norm is within 2.5e-6 relative of the hoist run's, and over steps 1-2 within 1.5e-4. Details are in the MaxText PR.

Commits

# commit change
1 d1f0fa44e requirements: pin MaxText bbd0f6f10 -> 94f11c09d, then the head of #5799. The new keys default to off, so this commit changes no rendered launch.
2 d88ed4b48 recipes: context_parallel_attention_load_balance_hoist_ids=true on every trainer mesh
3 ddacc81af recipes: moe_wo_gmm_fused_dswiglu=true on every trainer mesh, and query_proj_kernel_gather_once=true on every mesh except the 256-chip 32x2x8 and 64x1x8
4 ff2f9cbcf recipes + requirements: pin MaxText 94f11c09d -> e1cd8bf1d (+ fp8_fake_quant_hoist + its test), and fp8_fake_quant_hoist=gather8 only at 32x2x4 with TRAINER_FP8=true
5 6de2ec73d recipes + requirements: pin MaxText e1cd8bf1d -> 15285fd74 (#5799's head: + wi_tile_fwd_rhs_buffer_count + its test), and at 32x2x4 with TRAINER_TUNED_FLAGS=true the recipe's wi_tile_fwd_mlp_dim at 2048 with wi_tile_fwd_rhs_buffer_count=2
6 a94b7e980 recipes + requirements: pin MaxText 15285fd74 -> 1bf840cff (#5799's head: + the GDN context-parallel scans, their keys and the keys' test), and gdn_cp_m_scan=true only at 32x2x4 with TRAINER_TUNED_FLAGS=true; gdn_cp_dh_scan is never passed

Testing

  • Launcher test: mlperf_submission_launcher_test.py, run on CPU in the bench image with stubs for kubectl, gcloud and gsutil.
  • Deliberately wrong recipes: sixteen wrong versions each failed exactly the tests predicted for them:
    • dswiglu dropped;
    • load-balance hoist dropped;
    • gather-once branch emptied;
    • gather-once at the 256-chip meshes;
    • dswiglu only with the flags off;
    • the fp8 key at every mesh;
    • the fp8 key dropped;
    • the TRAINER_FP8 guard removed;
    • the tile and its buffer count at every mesh;
    • both dropped;
    • the buffer count without the tile;
    • the tile without the buffer count;
    • gdn_cp_m_scan at every mesh;
    • gdn_cp_m_scan dropped;
    • gdn_cp_dh_scan passed next to it;
    • gdn_cp_m_scan passed also with TRAINER_TUNED_FLAGS=false.
  • Cross checks: the new test file on the previous recipe, and the previous test file on the new recipe, fail exactly the predicted tests (6 and 5, for each of commits 4, 5 and 6).
  • Renders, commit by commit:
    • Commit 1 changes no render.
    • Commits 2-4 add only their keys to the tuned-on trainer JobSet.
    • Commit 4 adds exactly one token, once, at the 32x2x4 cell.
    • Commit 5 changes only the 32x2x4 tuned-on trainer JobSet: wi_tile_fwd_mlp_dim 1024 -> 2048 in place and one added token, wi_tile_fwd_rhs_buffer_count=2 (also with TRAINER_FP8=false).
    • Commit 6 adds exactly one token to that JobSet, once: gdn_cp_m_scan=true (also with TRAINER_FP8=false).
    • TRAINER_TUNED_FLAGS=false and the other recipes at their defaults are byte-identical.
  • MaxText config check: the rendered trainer JobSet goes through run_trainer_node, build_maxtext_config and pyconfig.initialize.
    • The pin 1bf840cff accepts 32x2x4, 32x2x8, 64x1x8, tuned off, TRAINER_FP8=false and commit 5's render. At 32x2x4 it resolves gdn_cp_m_scan=True with gdn_cp_dh_scan=False, and wi_tile_fwd_mlp_dim=2048 with wi_tile_fwd_rhs_buffer_count=2 (also with TRAINER_FP8=false), elsewhere False, 1024 and 0; MaxText's own fp8 mode check returns gather8.
    • The previous pins reject the key they lack: 15285fd74 gdn_cp_m_scan, e1cd8bf1d wi_tile_fwd_rhs_buffer_count, and 94f11c09d fp8_fake_quant_hoist.
    • A recipe without the TRAINER_FP8 guard, run with TRAINER_FP8=false, makes MaxText's mode check raise. That is why the guard is there.
    • A recipe with the 2048 tile but without the buffer count passes MaxText's config check: only a TPU compile sees that the tile overflows VMEM, so the launcher test is the guard for that pairing.
    • MaxText's config check also accepts gdn_cp_dh_scan=true; only the launcher test keeps the losing scan out of the recipe.

Not done / known gaps

  • Coverage: not run end to end, and the keys have not run at the 256-chip meshes on TPU.
  • The tile and the m scan together: timed once on TPU, in one pod against its own control (-1.012 s/step, parambole-w22k1a). No replicate and no numerics run of the combination: both were cancelled before they ran. Each lever passed the numerics gates on its own.
  • The tile without fp8: the recipe also passes the tile and its buffer count with TRAINER_FP8=false, which they do not depend on; that combination was not timed.
  • Opt-in recipes: the recipe gates the keys by trainer mesh, the same convention as the other tuned keys. So mlperf_397b_512_v7x.sh, mlperf_397b_1024_v7x.sh, mlperf_gbs512.sh and mlperf_397b_256_v7x.sh, where TRAINER_TUNED_FLAGS defaults to false, also pass them when run with TRAINER_TUNED_FLAGS=true at their default 32x2x4 mesh. That combination was not timed.
  • Pin: the MaxText pin is a commit on the unmerged #5799 branch. Move it to the merged commit once #5799 merges, and do not force-push that branch before then.
  • Base: [Qwen3.5-397B MLPerf] Fused sharded logps head, start-padded packing, and 128c/256c trainer recipe defaults #2884's head as of 2026-10-15 21:19 PDT (b16ee522f). If [Qwen3.5-397B MLPerf] Fused sharded logps head, start-padded packing, and 128c/256c trainer recipe defaults #2884 moves, this needs a rebase.
Logs

gs://parambole-397b-replay-nap/win-tpu/sched/final/<job>/<arm>.log, md5 prefixes below.

run md5 prefix
m scan parambole-w21g2a/o275x-g2-m, control o275x-g2-ctl, both scans o275x-g2-both f6e75b5b, 4df47f3e, 7c015e48
m scan replicate parambole-w21gr/o275x-gr-m 232dd0d4
dh scan parambole-w21g3/o275x-g3-dh, control o275x-g3-ctl d59ec173, c4973e8a
m-scan numerics parambole-w21gm/nx-gm-m 5327dc1f
tile parambole-w21ec/o275x-ec-e6k, replicate o275x-ec-e6kr, control o275x-ec-ctl (booking r-b6424) 82b0b106, 8ca2dbab, d9f8d7fd
tile in two more pods parambole-w21e6k/o275x-e6k1, parambole-w21p3/o275x-p3-e6k 95a199f6, bbea197b
controls in two more pods parambole-w21ds/o275x-d1-ctl, parambole-w21cm/o275x-c1-ctl 885735d0, 186559ce
tile numerics parambole-w21e6k/nx-e6k1 7b07c72d
fp8 hoist parambole-v19a/o275x-v19a-fq8 e76134c5
control parambole-v19a/o275x-v19a-ctl 626677a7
replicate parambole-v19b/o275x-v19b-fq8r 85857f67
numerics parambole-v19b/nx-v19b-fq8 f37b64b7
before the hoist parambole-bpr-v18/o275x-pr-v18, o275x-pr-r-v18, nxpr-v18 dcb54567, e159e06a, 8b6cfab3
base: gs://parambole-397b-replay-nap/win-tpu/sched/v16/parambole-rb4-v16/ o275x-rbd-v16, o275x-rbd-r-v16, nxrbd-v16 5ebc3a94, b334e811, 5fe85036
parambole-bf2-v18/o275x-prfc-f2, o275x-f2-l3-prof, o275x-f2-l3-r, nxf2 def54d64, 65202b28, cc4c9a07, bd38e443

Moves the MaxText pin from atwigg/mlperf at bbd0f6f10 (the squash merge of
AI-Hypercomputer/maxtext#5721) to the head of the branch
parambole/rl-4600-final-maxtext of AI-Hypercomputer/maxtext,
94f11c09df83e9903c093e4544beba3bb198ff8b. That branch is bbd0f6f10 plus six
commits: the SwiGLU backward in the epilogue of the MoE down projection's
input-gradient GMM (moe_wo_gmm_fused_dswiglu, two commits), the forward's
load-balanced positions and segment ids reused in the backward
(context_parallel_attention_load_balance_hoist_ids), the query kernel gathered
once per attention layer in the backward (query_proj_kernel_gather_once), and
two test-only commits. The three keys default to off, so this commit alone
changes no rendered launch: the next two commits pass them under
TRAINER_TUNED_FLAGS=true.

Comments that named the pin follow it: the recipe's sentence on where the
tuned keys are defined names the branch, and the launcher test's comment on
_BENCH_MESH_RULE no longer says that the pinned MaxText lacks
cp-as-ep-dense-no-cp (it has had it since bbd0f6f10).

Tests, in the bench image (dbdcf5f8dcba; CPU, no network, the tree read-only):
with this commit alone mlperf_submission_launcher_test.py passes 48 of 48, and
the renders of mlperf_gbs1024_submission.sh (TRAINER_TUNED_FLAGS on and off,
the 256-chip meshes 32x2x8 and 64x1x8) and of the other 397B v7x recipes are
byte-identical to the parent's.
…joins TRAINER_TUNED_FLAGS

TRAINER_TUNED_FLAGS=true now also passes attention's load-balance reorder of
the positions and segment ids, kept out of the remat replay: the one MaxText
change of the measured cell that the block did not pass.

In mlperf_397b_256_v7x.sh the block of MaxText keys (part 2) gains
context_parallel_attention_load_balance_hoist_ids=true, before the block's
custom_mesh_and_rule=cp-as-ep-dense-no-cp: remat_policy=custom saves each
attention layer's load-balance-ordered positions and segment ids. They depend
only on the batch, so jax.lax.scan keeps one copy outside the layer loop, and
the remat replay of every attention layer reads it instead of reordering them
again. The values are the same. Compiled for TPU v7x, XLA already moves the
reorder's collective-permutes out of the loop without the key; the key moves
out the rest of the reorder and the work on its result (per the MaxText
commit: an AOT compile of the bench's 128-chip trainer program at an earlier
cell has 47 fewer instructions in its while loops with the change, the same
collective-permutes and the same memory). MaxText refuses the key without
context_parallel_attention_load_balance=true, an even context parallelism of at
least 2, remat_policy=custom and scan_layers=true. This recipe's trainer has
them at 32x2x4 and at the 256-chip meshes 32x2x8 and 64x1x8 (context 8), so
the key goes on every trainer mesh, like the block's other keys.

The key is defined in the MaxText change on top of AI-Hypercomputer/maxtext#5721
(branch parambole/rl-4600-final-maxtext), which the previous commit pins;
atwigg/mlperf at bbd0f6f10 does not define it. TRAINER_TUNED_FLAGS=false and
the other 397B v7x recipes do not pass it.

Bench (AI-Hypercomputer/maxtext#5701 driver, Qwen3.5-397B-A17B, 128 TPU v7x,
mesh fsdp 32 x context 4 x expert 2, ragged_buffer_factor 2.75, 9 micro-batches
per step, mean of steps 1 to 4, no token dropped in any arm), alone, in three
comparisons against controls in other pods whose arm commands differ only by
the change's switch: 99.504 -> 99.267 s per step (o275x-gdn-ctl ->
o275x-s3-x17h), 99.248 -> 99.267 (o275x-mf-ctl -> o275x-s3-x17h) and
91.7513 -> 91.8665 (o275x-stk3-l3-prof -> o275x-s5c-l3-prof), i.e. -0.237,
+0.019 and +0.115 s per step. One unchanged configuration spans 0.255 s per
step over four pods (o275x-hd), so no effect is resolved. Together with the
next commit's two keys and the mesh rule, see the next commit (F2).

This is 11c096bb0 (branch parambole/rl-trainer-4600-next of google/tunix), as
ported onto #2884, replayed onto #2884's head b16ee52. Two hunks
conflicted, both comments that #2884 had just rewritten for its new pin: the
recipe's sentence on where the tuned keys are defined and the test's comment
on _BENCH_MAXTEXT_KEYS. Each keeps #2884's wording and adds the key. The
test's comment on _MAXTEXT_DEFAULTS now says the change is on top of #5721.

Tests (mlperf_submission_launcher_test.py): _MAXTEXT_DEFAULTS has the key's
default (false), the exact-set test expects it among _TUNED_KEYS, and the
switch-off and other-recipe tests check that it is not passed
(_BENCH_MAXTEXT_KEYS).
In the bench image (dbdcf5f8dcba; CPU, no network) 48 of 48 pass at this
commit. Rendered, the submission launch differs from the parent's only by this
key in the trainer's MaxText flags, at 32x2x4, 32x2x8 and 64x1x8; with
TRAINER_TUNED_FLAGS=false, and the other 397B v7x recipes at their defaults,
the renders are byte-identical.
…her_once join TRAINER_TUNED_FLAGS

TRAINER_TUNED_FLAGS=true now also turns on the two remaining keys of the
MaxText change on top of AI-Hypercomputer/maxtext#5721 (branch
parambole/rl-4600-final-maxtext, pinned two commits ago): the SwiGLU backward
fused into the down projection's input-gradient GMM, and the query kernel
gathered once per attention layer in the backward.

In mlperf_397b_256_v7x.sh the block of MaxText keys (part 2) gains:

- moe_wo_gmm_fused_dswiglu=true, on every trainer mesh: the input-gradient GMM
  of the MoE down projection turns its float32 accumulator into d[gate | up]
  in its epilogue (with moe_router_grad_from_wo_input, also the routing
  weight's gradient) instead of writing the activation's gradient in bfloat16
  for separate passes to read back. The gradients of wi and of everything
  before it change by bfloat16 rounding. MaxText refuses the key unless that
  GMM has one n tile (wo_tile_dlhs_mlp_dim = the MoE MLP dim, 1024 in this
  recipe) and, with moe_wi_gmm_fused_swiglu, wi_tile_fwd_mlp_dim halves to
  that dim or to a multiple of 128 dividing it (this recipe's 1024 gives two n
  tiles of 512 per half); it also needs explicit tiles, prefuse_moe_weights,
  use_tokamax_gmm, use_gmm_v2 and a remat policy that does not save the routed
  experts' gate or up half.
- query_proj_kernel_gather_once=true, at every trainer mesh except the
  256-chip 32x2x8 and 64x1x8: the attention query projection keeps the kernel
  it gathered in the remat replay for its input gradient, so the backward
  gathers the query kernel once per attention layer instead of twice. The
  values are the same. MaxText refuses the key unless the backward replays the
  query projection (remat_policy=custom with query_proj=remat). #2884's block
  sets query_proj=device at 32x2x8 and 64x1x8, where the backward does not
  replay the projection, so the key goes in a new default branch of that mesh
  case instead of on the tuned line: passed there, MaxText would refuse the
  256-chip trainer when it starts.

Measured on TPU together (F2): the bench cell of this block at 32x2x4 with
these two keys, context_parallel_attention_load_balance_hoist_ids and
cp-as-ep-dense-no-cp, on the MaxText of AI-Hypercomputer/maxtext#5758 with the
change on top of it (4ed82d793, whose src/maxtext is the pinned MaxText's
without #5721's three commits of Rohan Bierneni and two model YAMLs). Bench
(AI-Hypercomputer/maxtext#5701 driver, Qwen3.5-397B-A17B, 128 TPU v7x, mesh
fsdp 32 x context 4 x expert 2, ragged_buffer_factor 2.75, padding first, 9
micro-batches per step, mean of steps 1 to 4, no token dropped), booking
r-7a4cc: 80.398 and 80.342 s per step in two pods (o275x-f2-l3-prof in
parambole-bf2-book, o275x-f2-l3-r in parambole-bf2r-book), 3,668 and 3,671
tokens/s/chip, regular micro-batch 8.748 and 8.749 s, against 82.373 s per
step, 3,580 tokens/s/chip and 8.966 s for the code of #5758 with the keys
before these four (o275x-prfc-f1 in parambole-bf1-book). The F2 pods' own
control arms did not finish, so the comparison is across pods: -1.974 and
-2.030 s per step. Numerics in both pods (nxf2: loss gates off, random
advantages, three steps): all six gates pass against that code under the same
protocol (nxprf-l3) and against the reference pair (nxref, nxref2); the loss
is 0.0304453575 in all three steps, nxprf-l3's to the ten digits logged, and
the gradient norm (gate N3) is within 1.97e-4 and 1.23e-4 relative of
nxprf-l3's (tolerance 2e-3). Each key's own measurement:
- moe_wo_gmm_fused_dswiglu, behind an environment switch on an overlay build
  with the keys of #2913's recipe, the hoist and padding first, without the
  fused log-prob head, against a control in another pod: 83.316 -> 82.148 s
  per step (o275x-c9pfxctl-l3 -> o275x-c9ypfctl-l3), 9.0845 -> 8.9545 s per
  regular micro-batch. On one TPU v7x chip its kernels at the cell's shapes
  match a float32 SwiGLU backward to 1.658e-3 relative L2
  (parambole-bck-2d4c2), and its numerics run passes all six gates
  (nxc9ypfckhv).
- query_proj_kernel_gather_once: not timed on TPU alone. An AOT compile of the
  cell with cp-as-ep gathers the query kernel once per attention layer in the
  backward instead of twice at the same device peak (104.46 GiB), and the
  MaxText change's test, executed on TPU v5p, gives bit-identical values and
  gradients with and without it.
Not run: with #5721's three commits (#5721's own content with the rule ran at
81.230 and 81.168 s per step in booking r-67724, o275x-rbd-v16 and
o275x-rbd-r-v16), at the 256-chip meshes, or end to end.

This is b7f7a6570 (branch parambole/rl-trainer-4600-next of google/tunix)
without its third value, custom_mesh_and_rule=cp-as-ep-dense-no-cp, which
#2884 already passes, ported onto #2884 and replayed onto its head b16ee52.
The port keeps #2884's structure: the header (#2884 rewrote it for the
256-chip meshes; it gains one line naming the rule under TRAINER_TUNED_FLAGS),
the end of the block's comment, the tuned line (#2884's, with
moe_wo_gmm_fused_dswiglu after the hoist key), the mesh case (the new default
branch) and, in the test, the defaults (the two keys join the MaxText-change
defaults ahead of #2884's per-tensor remat keys). On replay the same two
comments as in the previous commit conflicted (the recipe's sentence on
where the keys are defined and the test's comment on _BENCH_MAXTEXT_KEYS),
resolved the same way, now naming all three keys. The block's comment on
the whole cell replaces "has not run on TPU" with the F2 measurement above.

Tests, in the bench image (dbdcf5f8dcba; CPU, no network, the tree read-only,
stubs for kubectl, gcloud and gsutil first on PATH):
- mlperf_submission_launcher_test.py: 48 of 48 pass at this commit, at each of
  the two commits before it, and at #2884's b16ee52 and 8647d69.
- Five deliberately wrong copies of the recipe each fail exactly the tests
  predicted before the run: moe_wo_gmm_fused_dswiglu or
  context_parallel_attention_load_balance_hoist_ids dropped from the block
  (the 3 exact-set tests); the default branch not passing
  query_proj_kernel_gather_once (those 3, the default-cell test and the two
  256-chip mesh tests); query_proj_kernel_gather_once also on the tuned line
  (the 4 tests of the 256-chip meshes); moe_wo_gmm_fused_dswiglu passed only
  with TRAINER_TUNED_FLAGS=false (the 3 and the two switch-off tests). This
  test fails 6 tests on b16ee52's recipe, and b16ee52's test fails 5 on
  this recipe, both as predicted.
- Rendered, the submission launch differs from the parent's only in the
  trainer's MaxText flags: moe_wo_gmm_fused_dswiglu=true and
  query_proj_kernel_gather_once=true at 32x2x4, moe_wo_gmm_fused_dswiglu=true
  at 32x2x8 and 64x1x8. With TRAINER_TUNED_FLAGS=false, and the other 397B v7x
  recipes at their defaults, the renders are byte-identical.
- MaxText config check (the rendered trainer JobSet through
  run_trainer_node's argument parsing and build_maxtext_config to
  pyconfig.initialize, which runs the validators, with mocked devices): the
  pinned MaxText (94f11c09d) accepts the trainer at 32x2x4 (256 devices; all
  three keys on, query_proj remat), at 32x2x8 and 64x1x8 (512 devices;
  query_proj_kernel_gather_once off, query_proj device) and with
  TRAINER_TUNED_FLAGS=false. It refuses the 32x2x4 trainer on the previous pin
  bbd0f6f10, which does not define
  context_parallel_attention_load_balance_hoist_ids, and refuses
  query_proj_kernel_gather_once at 32x2x8 next to the block's
  query_proj=device ("query_proj_kernel_gather_once=True requires a remat
  policy that replays the query projection").
…LAGS at 32x2x4

- requirements: pin MaxText to e1cd8bf1d, the new head of
  AI-Hypercomputer/maxtext branch parambole/rl-4600-final-maxtext
  (94f11c09d + the fp8_fake_quant_hoist commit + its test). The key defaults
  to off, so the pin alone changes no rendered launch.
- mlperf_397b_256_v7x.sh: TRAINER_TUNED_FLAGS=true passes
  fp8_fake_quant_hoist=gather8 only at the 128-chip 32x2x4 trainer mesh, the
  one it was timed at, and only with TRAINER_FP8=true (fp8_moe_fake_quant=true),
  without which MaxText raises when the trainer traces it. The fp8 fake-quant
  absmax of every routed-expert weight is computed once for the layer stack,
  before the scan, and the expert weights' FSDP all-gathers move fp8 codes;
  the same values (bit-identical on CPU). Measured on the RL trainer bench at
  32x2x4, same pod: 80.385 -> 79.397 s/step (3,669 -> 3,714 tokens/s/chip),
  0 tokens dropped.
- mlperf_submission_launcher_test.py: the bench cell gains exactly this key,
  once; it is absent with the tuned flags off, at the 256-chip meshes and with
  TRAINER_FP8=false.
@parambole parambole changed the title [Qwen3.5-397B MLPerf] TRAINER_TUNED_FLAGS: the three new MaxText keys of the F2 cell, and the MaxText pin that defines them [Qwen3.5-397B MLPerf] TRAINER_TUNED_FLAGS: the F2 cell's MaxText keys and the fp8 fake-quant hoist, and the MaxText pin that defines them Oct 16, 2026
@parambole parambole changed the title [Qwen3.5-397B MLPerf] TRAINER_TUNED_FLAGS: the F2 cell's MaxText keys and the fp8 fake-quant hoist, and the MaxText pin that defines them Qwen3.5-397B RL trainer on 128 v7x: the tuned recipe turns on the fp8 fake-quant hoist, fused SwiGLU backward and attention savings, and pins the MaxText that defines them Oct 16, 2026
…r_count=2 at 32x2x4

- requirements: pin MaxText to 15285fd74, the new head of
  AI-Hypercomputer/maxtext branch parambole/rl-4600-final-maxtext
  (e1cd8bf1d + the wi_tile_fwd_rhs_buffer_count commit + its test). The key
  defaults to 0 (the kernel default), so the pin alone changes no rendered
  launch.
- mlperf_397b_256_v7x.sh: with TRAINER_TUNED_FLAGS=true, only at the 128-chip
  32x2x4 trainer mesh, the one they were timed at, the recipe's own
  wi_tile_fwd_mlp_dim becomes 2048 (one value in the flags, not an override)
  and the block passes wi_tile_fwd_rhs_buffer_count=2. The fused SwiGLU wi
  forward GMM then covers each half of [gate | up] with one N tile instead
  of two; with its default three weight buffers that tile does not fit VMEM
  (66.95 of 57.60 MiB), with two it does (50.95 MiB). Buffering only: the
  same values. Measured on the RL trainer bench at 32x2x4, same pod: 79.342
  -> 79.024 and 78.985 s/step (3,717 -> 3,732 and 3,734 tokens/s/chip), 0
  tokens dropped.
- mlperf_submission_launcher_test.py: the bench cell gains exactly these two
  values, once each; the tile stays 1024 and the buffer count absent with the
  tuned flags off and at the 256-chip meshes.
- requirements: pin MaxText to 1bf840cff, the new head of
  AI-Hypercomputer/maxtext branch parambole/rl-4600-final-maxtext
  (15285fd74 + the GDN context-parallel scan commits, their key gating and
  its test). Both new keys default to off, so the pin alone changes no
  rendered launch.
- mlperf_397b_256_v7x.sh: with TRAINER_TUNED_FLAGS=true, only at the 128-chip
  32x2x4 trainer mesh, the one it was timed at, the block passes
  gdn_cp_m_scan=true: with gdn_cp_mode=seq the GDN context-parallel forward
  folds each rank's chunk transitions with one Pallas scan in VMEM instead
  of an XLA reduction tree. Measured on the RL trainer bench at 32x2x4, same
  pod: 79.480 -> 78.822 s/step (3,711 -> 3,742 tokens/s/chip), 78.795 in a
  replicate in another pod, 0 tokens dropped. Its backward twin
  gdn_cp_dh_scan stays off: alone it made the step slower (79.403 -> 80.610).
- mlperf_submission_launcher_test.py: the bench cell gains exactly this key,
  once; it is absent with the tuned flags off and at the 256-chip meshes,
  and gdn_cp_dh_scan is in no render.
@parambole parambole changed the title Qwen3.5-397B RL trainer on 128 v7x: the tuned recipe turns on the fp8 fake-quant hoist, fused SwiGLU backward and attention savings, and pins the MaxText that defines them Qwen3.5-397B RL trainer on 128 v7x: the tuned recipe turns on the fp8 fake-quant hoist, fused SwiGLU backward, attention savings and the 2048 wi forward tile, and pins the MaxText that defines them Oct 17, 2026
@parambole parambole changed the title Qwen3.5-397B RL trainer on 128 v7x: the tuned recipe turns on the fp8 fake-quant hoist, fused SwiGLU backward, attention savings and the 2048 wi forward tile, and pins the MaxText that defines them Qwen3.5-397B RL trainer on 128 v7x: the tuned recipe turns on the fp8 fake-quant hoist, fused SwiGLU backward, attention savings, the 2048 wi forward tile and the GDN context-parallel m scan, and pins the MaxText that defines them Oct 17, 2026

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