Repository navigation
Conversation
…llelism
With context parallelism, the GRPO log-prob head fails to compile on TPU
at compute_logps_chunk_size 2048, 4096 and 8192 (libtpu
0.0.51.dev20261010, Pathways raiden_20261009183300):
FAILED_PRECONDITION: Reshape should have supported layout before
reaching the emitter. %reshape = s32[4,1,4,1024]{3,0,1,2:T(4,128)}
op_name="jit(fwd_bwd)/jvp(jit(compute_per_token_logps))/transpose"
The sequence is sharded over the context axis outside the chunk scan.
The partitioner moves that split from the chunk-index dim to the
in-chunk dim with an all-to-all on each array that crosses the loop
boundary. For the two 32-bit arrays (int32 targets in, f32 log-probs
out) layout assignment folds the transpose into a reshape that the TPU
emitter cannot lower once few chunks remain per device.
Replicate those two arrays over the sequence axes at the loop boundary
when chunk_size > 1024. They are 4 bytes per token, so each all-to-all
becomes a small one-shot all-gather plus a local slice. The bf16 hidden
states and the vocabulary matmuls are untouched. At or below 1024 the
traced program is unchanged, so results there are bit-identical by
construction. TUNIX_LOGPS_REPLICATE_SEQUENCE=always|never overrides the
rule; at cp=8 chunk 1024 already fails, so such meshes need "always" or
a larger chunk.
This was referenced Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
compute_chunked_logps) does not compile on TPU atcompute_logps_chunk_size2048 or 4096 (cp=4):Reshape should have supported layout before reaching the emitter.chunk_size > 1024, keep the 4-byte-per-token arrays that enter and leave the chunk loop (int32 targets in; f32 log-probs, and entropies when requested, out) replicated over the sequence axes. Chunk 4096 then compiles. This fixes the default path; [DO NOT MERGE] Opt-in sharded GRPO log-prob head: gather the vocab table once, scan local tokens (-11.8% step time, v7x 397B bench) #2873 is the opt-in alternative that bypasses it.chunk_size > 1024and a mesh is set withjax.set_mesh(as the MaxText training engine does). At 1024 and below the compiled program is unchanged. Override:TUNIX_LOGPS_REPLICATE_SEQUENCE=auto|always|never.How it works
flowchart LR subgraph S1["Today: chunk_size above 1024, sequence split over context"] direction TB A1["int32 targets [B, L]"] --> A2["all-to-all into the chunk loop"] A2 --> A3["lax.scan over chunks"] A3 --> A4["f32 log-probs: all-to-all back"] A4 --> A5["TPU compile error:<br/>Reshape should have supported layout"] end subgraph S2["This PR"] direction TB B1["int32 targets [B, L]"] --> B2["replicate sequence dims:<br/>one all-gather, 4 B/token"] B2 --> B3["lax.scan over chunks<br/>(bf16 hidden states unchanged)"] B3 --> B4["f32 log-probs and entropies:<br/>replicate sequence dims"] B4 --> B5["chunk 4096 compiles"] endThe scan takes one chunk per iteration, so XLA moves the context split into each chunk with an all-to-all on every array that crosses the loop boundary; for the two 32-bit arrays (and the log-prob cotangent in the backward pass) the TPU compiler folds a transpose into that all-to-all's reshape and cannot emit it. Replicating only those arrays over the sequence axes (256 KiB per device per array at the bench cell) turns each all-to-all into one all-gather plus a local slice, while the batch dim, the bf16 hidden states and the vocabulary matmuls are left alone.
Results
TPU rows: bench cell of maxtext#5701, 128 v7x chips, fsdp32 x cp4 x ep2,
ragged_buffer_factor=2.75, Pathwaysraiden_20261009183300, 2026-10-14.jit_fwd_bwdHLO (127,300 lines)Caveats: the step-time delta is cross-pod (the same-pod chunk-1024 arm was cut for time; the 1024 baseline varied by 0.026 s/step across three pods). Not yet run on the newer Pathways build
raiden_20261013212200. TPU loss/gradient numerics are not checked (the bench payload gives 0 loss by construction); they rest on the CPU tests, plus a forward probe within 1.8e-5 relative of chunk 1024.Changes
tunix/rl/common.py_replicate_sequence_dims(x, seq_dims)(new):with_sharding_constraintwithNoneon the sequence dims andUNCONSTRAINEDon the rest, under the mesh set byjax.set_mesh. Returnsxunchanged when no mesh is set._replicate_sequence_in_chunk_loop(chunk_size)(new): the on/off rule.compute_chunked_logps: constrains the int32 targets before the chunk reshape, and the f32 log-probs (and entropies) right after the scan.TUNIX_LOGPS_REPLICATE_SEQUENCE:auto(default, on whenchunk_size > 1024),always,never. Any other value raises. Read at trace time.tests/rl/chunked_logps_sequence_replication_test.py(new): 34 tests on an 8-device CPU mesh (batch overfsdp, sequence overcontext).Testing
CPU: 34/34 new tests, and 83/83 in
tests/rl/common_test.pywith and without the change; five deliberately broken copies of the change are each caught by the intended tests. AOT (CPU, tpu7x target, libtpu 0.0.51.dev20261010) at cp=4 / 128 chips and cp=8 / 256 chips, with negative controls (never, no mesh, ids only, batch-dim constraint) that reproduce the failure. TPU: 5/5 steps on the bench cell.Full tables and the per-test list are in the collapsed section below.
Risks / rollout
TUNIX_LOGPS_REPLICATE_SEQUENCE=never.alwaysthere (or a chunk above 1024).jax.set_mesh. Under a legacywith mesh:block it stays a no-op, as today. tunix's ownRLEngine(aliasRLCluster) enters its mesh that way (rl_cluster.py:1310), so that path is unchanged.Related
TUNIX_LOGPS_SHARDED_HEAD=local),compute_per_token_logpsreturns before callingcompute_chunked_logps, so this PR only changes the default path. The only textual conflict is one import line (import math/import os); with both applied, 19/19 + 34/34 + 83/83 CPU tests pass.COMPUTE_LOGPS_CHUNK_SIZE; the team hit the same error at chunk 2048 on it.atwigg/mlperf.Full evidence and notes (the previous description)
A bare
#5701below refers to maxtext#5701.What
With context parallelism, the GRPO log-prob head does not compile on TPU at
compute_logps_chunk_size2048 or 4096(team: 2048 on #2841; ours:
parambole-lp4096, bench #5701 cell, 2026-10-13 23:26Z):This PR replicates the chunk loop's two 32-bit arrays (int32 targets in, f32 log-probs out) over the sequence axes
when
chunk_size > 1024. 4096 then compiles. At or below 1024 the traced program is unchanged, so today's runs arebit-identical.
Why it fails
contextoutside thelax.scanincompute_chunked_logps(rows overfsdp x expert, 1 row per device at the bench cell; 16,384 tokens per device at cp=4).
in-chunk dim with an all-to-all on every array that crosses the loop boundary.
reshape (
s32[4,1,4,1024]{3,0,1,2:T(4,128)}at chunk 4096). The TPU emitter cannot lower that reshape.f32[4,1,4,1024]intranspose(jvp(...))/transpose. Fixing only the ids fails on it (shown below).raiden_20261009183300failure down to theinstruction name. The older
raiden_20261002162900build compiles unfixed 4096 (our earlier runparambole-bs-l4ko).The change
_replicate_sequence_dims(x, seq_dims):with_sharding_constraint(x, P(UNCONSTRAINED, ..., None at seq dims))under the active
jax.set_meshmesh, so the batch dim keeps whatever sharding the partitioner chose. It is a no-opwithout a mesh.
compute_chunked_logps, whenchunk_size > 1024it is applied to the int32 targets[B, L]before the chunkreshape and to the f32 log-probs (and entropies)
[chunks, B, chunk]right after the scan.plus a local slice. The bf16 hidden states and the vocabulary matmuls are untouched.
TUNIX_LOGPS_REPLICATE_SEQUENCE=auto|always|never(defaultauto= the rule above) overrides the rule. Any othervalue raises.
alwaysthere, or a chunk above1024.
Evidence
All AOT rows use libtpu 0.0.51.dev20261010 (CPU AOT for
tpu7x). A CPU AOT pass is not a TPU compile.compute_chunked_logpsinsidevalue_and_grad, bench shardings), cp=4, 128 chipss32[8,1,4,512], 4096 FAILs32[4,1,4,1024](the TPU signature); fixed: 2048 / 4096 / 8192 PASS; 1024 PASS with identical temp bytes (7,714,230,624)s32[8,1,8,128], 2048 FAILs32[4,1,8,256], 4096 FAILs32[2,1,8,512], 512 PASS; fixed: 2048 / 4096 PASS, 1024 PASS withalwaysneverat 4096: FAIL (same reshape). Fixed file with no mesh set: FAIL (same reshape). Ids-only variant: FAIL onf32[4,1,4,1024](the backward instance). Constraint on the batch dim instead of the sequence: FAILaot_trainer.py, exact bench env), with this exact filereshape.18785), device peak 88.81 GiB of 94.74. Chunk 1024: rc 0, and its optimizedjit_fwd_bwdHLO has 0 non-metadata lines that differ from the unpatched program (127,300 lines; only the source-location tables change). Control: the 1024 and 4096 programs differ in 185,945 linestests/rl/common_test.pyragged_buffer_factor=2.75), Pathwaysraiden_20261009183300, clienttrellis-experimental@sha256:b4647c18…, 2026-10-14sample_mask/mult_prob_error_mean333,638.1-333,643.1 against 333,644.1 at chunk 1024 (within 1.8e-5 relative). The same-pod chunk-1024 arm was cut for time, so this delta is cross-pod (the 1024 base varied by 0.026 s/step across the three pods). Not run yet: the newerraiden_20261013212200build, and the loss/gradient numerics (the bench payload gives 0 loss by construction; numerics rest on the CPU tests above)What the new tests check:
[B, L] i32 [{?}, {}]and[chunks, B, chunk] f32 [{}, {?}, {}](forward and itscotangent).
with and without entropy, packed/padded sequence);
alwaysat 1024 is bit-identical;max 83);
Each check was shown to fail on a deliberately wrong input:
>=: 4 failures;the constraint test, and FAIL the AOT compile. So that test is the one that matters.
Expected speed (pre-registered before the TPU run)
129.007 at 1024 (steps 1-4), i.e. -9.03 s/step = -1.00 s per micro-step. MEASURED,
parambole-bs-l4ko.Limits
jax.set_mesh(as the MaxText training engine does) engages it. A legacywith mesh:blockdoes not set the abstract mesh (MEASURED, jax 0.11.0), so there it stays a no-op, as today.
(<= 8 fails), which the code cannot see at trace time under Auto sharding; hence the override.
opt-in sharded head in [DO NOT MERGE] Opt-in sharded GRPO log-prob head: gather the vocab table once, scan local tokens (-11.8% step time, v7x 397B bench) #2873 does that, and when it engages (packed rows,
TUNIX_LOGPS_SHARDED_HEAD=local)compute_per_token_logpsreturns its result without callingcompute_chunked_logps, so this change only affectsthe default path. Both PRs edit
tunix/rl/common.py; the only textual conflict is one import line(
import math/import os). With both applied, [DO NOT MERGE] Opt-in sharded GRPO log-prob head: gather the vocab table once, scan local tokens (-11.8% step time, v7x 397B bench) #2873's tests (19/19), this PR's tests (34/34) andtests/rl/common_test.py(83/83) pass on CPU.