Repository navigation
Ulysses attention: layout pin, saved exchanged Q/K/V, packed Q/K/V all-to-all (on #5779) - #5780
Open
khatwanimohit wants to merge 3 commits into
Open
khatwanimohit wants to merge 3 commits into
khatwanimohit wants to merge 3 commits into
Conversation
Under Ulysses the attention backward wants the hidden-state gradient tokens-minor ({1,2,0}), for the
[B, S, H, D] -> [B, H, S, D] transpose ahead of the inverse all-to-all. XLA carried that layout out
through the residual add into the neighbouring MoE layers, where it transposed the expert-parallel
gathered token buffer (bf16[8,8192,4096], 8x the local tokens) in front of the token reduce-scatter:
8 x 512 MiB copies per scanned 4-layer block on Qwen3.5-397B (128 v7x, fsdp 32, cp 4, ep 8).
A custom_vjp identity constrains the attention block's input and output, and their cotangents, to the
row-major layout (with_layout_constraint, as #5685 does for in_proj_qkvz), so the relayout stays on the
attention-local tensors. It applies only in training under context_parallel_strategy=ulysses.
60-layer AOT (tpu7x-256): all 16 MoE token reduce-scatters per block take {2,1,0} again (8 were
transposed), and copy/transpose traffic drops by ~54 GiB per microbatch.
…sses_qkv) The Splash backward needs the head-sharded Q/K/V that the Ulysses all-to-all produces. Nothing saved them, so the remat backward replayed the forward exchanges to rebuild them: on Qwen3.5-397B that is the Q, K and V all-to-alls of all 15 attention layers again per microbatch, one of them with almost no compute to hide behind. Tag the exchanged tensors "ulysses_qkv" and add the matching remat key (default remat, so nothing changes unless it is set). ulysses_qkv=offload is the setting to use: on the 128-chip v7x trainer (60-layer AOT, tpu7x-256) it keeps all all-to-alls async, leaves only the output exchange to replay, and lowers the device peak, at 4.69 GiB of host memory per device. ulysses_qkv=device costs +8.6 GiB of HBM and, with no replayed work left to overlap them with, XLA runs both backward all-to-alls synchronously.
XLA TPU does not combine the three same-axis all-to-alls (checked in compiled HLO), and the K/V ones (1 KV head per rank after the replication) are small enough to be mostly latency: their forward overlap windows were 4 and 8 instructions. ulysses_all_to_all_qkv lays rank r's block of each head axis next to the others ([its H_q/U query heads, its H_kv/U key heads, its H_kv/U value heads]), so one tiled all-to-all over the concatenated head axis hands every rank the same heads, and its transpose is one all-to-all in the backward too. It falls back to three exchanges when the tensors can't be concatenated. Bit-identical to the separate exchanges on a CPU mesh (loss and dQ/dK/dV diff 0.0). On the 60-layer 397B AOT the Ulysses all-to-alls per microbatch drop from 183 to 93. The test's HLO count now expects 2 forward all-to-alls (packed Q/K/V, output) instead of 4.
khatwanimohit
requested review from
A9isha,
NuojCheng,
RissyRan,
SurbhiJainUSC,
abhinavclemson,
aireenmei,
bvandermoon,
darisoy,
dipannita08,
gagika,
gobbleturk,
hengtaoguo,
huytransformer,
igorts-git,
jiangjy1982,
parambole,
richjames0,
shralex,
shuningjin,
vipannalla and
xibinliu
as code owners
October 15, 2026 21:16
There was a problem hiding this comment.
Code Review
This pull request introduces optimizations for Ulysses context-parallel attention in MaxText. Key changes include implementing a packed all-to-all collective for Q, K, and V tensors to reduce communication overhead, adding a custom VJP to pin tensor layouts to row-major to prevent inefficient layout propagation in MoE layers, and updating the configuration to support rematerialization policies for the new packed QKV exchange. Unit tests were updated to reflect the reduced number of all-to-all collectives. There are no review comments to address.
This branch has not been deployed
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.
Three follow-ups to #5779 (Ulysses + KV-head replication), one commit each. All three come from diffing the compiled 60-layer Qwen3.5-397B trainer HLO (128 v7x / tpu7x-256 AOT, tunix#2884 recipe flags + the Ulysses keys below).
bf0abf8cd3d5b76d47ulysses_qkvremat key: save the exchanged Q/K/Voffload: remat-replayed all-to-alls 30 → 15, all exchanges stay async, device peak 103.35 → 99.14 GiB (+4.69 GiB host)59904c5abFull comparison (device peak is AOT, which overestimates runtime HBM):
ulysses_qkv=deviceulysses_qkv=offloadulysses_qkv=deviceis not recommended: it costs +8.6 GiB and, with no replayed work left to overlap them with, XLA runs both backward all-to-alls synchronously.Flags to enable
MaxText:
libtpu (trainer
LIBTPU_INIT_ARGS, e.g.TRAINER_LIBTPU_INIT_ARGSin the tunix recipe; no recipe sets it today, and without it every Ulysses all-to-all is synchronous):The layout pin and the packed exchange need no flag; they apply whenever
context_parallel_strategy=ulysses.ulysses_qkvdefaults toremat, so nothing changes for other configs.Testing
pytest tests/unit/ulysses_kv_head_replication_test.py tests/unit/ulysses_attention_test.py tests/unit/configs_value_test.py tests/unit/attention_test.py -k "ulysses or kv_head or replication"(CPU): 35 passed, 55 subtests passed, 9 TPU-only skipped. The attention-op check runs the packed path against all_gather and a dense reference (max normalized error ≤ 3.4e-7, positive controls still fail as intended). The HLO count now expects 2 forward all-to-alls (packed Q/K/V, output) instead of 4.