Skip to content

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
parambole/ag-l3-ulyssesfrom
mohit/ulysses-gqa-fixes
Open

khatwanimohit wants to merge 3 commits into
parambole/ag-l3-ulyssesfrom
mohit/ulysses-gqa-fixes

Conversation

@khatwanimohit

Copy link
Copy Markdown
Collaborator

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).

Commit Change Effect (60-layer AOT, per microbatch)
bf0abf8cd Pin the attention block's input/output (and cotangent) layout under Ulysses Removes the 8 transposed 512 MiB MoE-token copies per block ahead of the EP reduce-scatter; copy/transpose traffic 685 → 631 GiB
3d5b76d47 ulysses_qkv remat key: save the exchanged Q/K/V With offload: remat-replayed all-to-alls 30 → 15, all exchanges stay async, device peak 103.35 → 99.14 GiB (+4.69 GiB host)
59904c5ab One packed all-to-all for Q/K/V (fwd, remat and bwd) Ulysses all-to-alls 183 → 93; bit-identical to separate exchanges

Full comparison (device peak is AOT, which overestimates runtime HBM):

Variant Device peak GiB All-to-alls async / sync Remat replays Copy GiB Transposed MoE RS
#5721 baseline (all_gather CP + attention LB) 105.16 – – 618 0
+ #5779 Ulysses 103.43 183 / 0 60 685 8 per block
+ layout pin + packed exchange 103.35 93 / 0 30 631 0
+ ulysses_qkv=device 112.01 48 / 30 15 645 0
+ ulysses_qkv=offload 99.14 (+4.69 host) 80 / 0 15 633 0

ulysses_qkv=device is 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:

context_parallel_strategy=ulysses
context_parallel_load_balance=false
context_parallel_attention_load_balance=false
ulysses_qkv=offload

libtpu (trainer LIBTPU_INIT_ARGS, e.g. TRAINER_LIBTPU_INIT_ARGS in the tunix recipe; no recipe sets it today, and without it every Ulysses all-to-all is synchronous):

--xla_tpu_enable_async_all_to_all=true

The layout pin and the packed exchange need no flag; they apply whenever context_parallel_strategy=ulysses. ulysses_qkv defaults to remat, so nothing changes for other configs.

Testing

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

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.

1 participant