Repository navigation
Conversation
…ranks Ulysses context parallelism hands each context rank a contiguous block of num_query_heads / cp query heads, so it required num_kv_heads to be divisible by the context parallel size: each rank had to receive whole KV heads. Qwen3.5 has 2 KV heads and trains at context 4 (8 at 256 chips), so it could not use Ulysses. When the context size is a multiple of the KV heads left on each device, repeat each KV head cp / num_kv_heads times in place ([kv0, kv0, kv1, kv1]) before the all-to-all. Rank r then receives a copy of KV head r // replication, which is the head its query block uses under Splash's grouped pairing (query head h uses KV head h // (H_q / H_kv)). Tiling the heads instead ([kv0, kv1, kv0, kv1]) would pair half of the query heads with the wrong KV head. The repeat is linear, so autodiff sums the dK/dV of the copies back onto each KV head. Each rank receives exactly the one KV head it needs, so the K/V all-to-all moves no more bytes than gathering that head. The config, layer KV-head sharding and attention-op validators accept the case. USP keeps the old requirement because its exchange does not repeat heads. Other strategies trace exactly as before. GatedDeltaNet layers keep their own sequence-parallel context parallelism, which does not read context_parallel_strategy. Tests: helper unit tests (replication factor, grouped order, query/KV pairing at Ulysses 4 and 8, validator flag); a forced 4-device CPU test of the collectives and of AttentionOp.tpu_flash_attention (with a dense stand-in for the Splash kernel), Ulysses vs all-gather vs dense on output and dQ/dK/dV, with tiled-heads and missing-group-sum positive controls that must fail; config and layer-validator acceptance; TPU-only Ulysses cases with 2 KV heads.
There was a problem hiding this comment.
Code Review
This pull request introduces support for Ulysses context parallelism when there are fewer KV heads than context ranks (such as 2 KV heads with a context parallel size of 4 or 8, which is common in models like Qwen 3.5). This is achieved by repeating each KV head across the corresponding context ranks before the Ulysses all-to-all exchange. The changes span configuration validation, head sharding validation, and the attention operator, and are accompanied by comprehensive unit tests verifying correctness and gradient matching. I have no feedback to provide as there are no review comments to evaluate.
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.
Summary
Allow Ulysses context parallelism (
context_parallel_strategy: "ulysses") whennum_kv_heads < ici_context_parallelismandici_context_parallelism % num_kv_heads == 0(such as Qwen3.5-397B-A17B withnum_query_heads=32, num_kv_heads=2atici_context_parallelism=4on 128 chips orici_context_parallelism=8on 256 chips) by repeating each KV headcp // num_kv_headstimes in grouped order ([kv0, kv0, kv1, kv1]atcp=4;[kv0, kv0, kv0, kv0, kv1, kv1, kv1, kv1]atcp=8) before the Ulyssesall-to-all.Why
With
context_parallel_strategy: "all_gather"andcontext_parallel_attention_load_balance: trueon packed 65k sequences:DUAL_CHUNK_SWAPreorder_sequencecollective-permutes on entry and exit (~200.3 ms/mbexposed across 15 layers) to keep GDN and MoE in contiguous sequence order.cp1..cp3) execute136fwd /132dkv Splash tiles (~1,162.5 ms/mb) vs~80tiles oncp0, causing fast chips (TPU:0,cp0on a light row) to stall for~749 ms/mbat the subsequent 256-way FSDP weightall-gatherbarriers (shared_expert wi_0/wi_1in fwd,attention wqin bwd).With
context_parallel_strategy: "ulysses"andcontext_parallel_load_balance: false(context_parallel_attention_load_balance: false):jnp.repeatalong head axis2):cp=4(128 chips, 256 TensorCores),replication = 4 // 2 = 2: KV heads[kv0, kv1]repeat in place to[kv0, kv0, kv1, kv1]. After the 4-wayall-to-all, CP rankrholds query heads[8r .. 8r+7](8Q heads) and KV headr // 2(1KV head) across the full65,536-token sequence.cp=8(256 chips, 512 TensorCores),replication = 8 // 2 = 4: KV heads[kv0, kv1]repeat in place to[kv0, kv0, kv0, kv0, kv1, kv1, kv1, kv1]. After the 8-wayall-to-all, CP rankrholds query heads[4r .. 4r+3](4Q heads) and KV headr // 4(1KV head) across the full65,536-token sequence._ulysses_Custom'scustom_vjpbackward,dkanddvare scaled by1 / replicationbefore the reverseall-to-all, and autodiff throughjnp.repeatsums thereplicationcopies back onto each unique KV head, matchingall_gatherCP gradients.context_parallel_load_balance=false), requiring zero sequence reordering between Attention, GDN, and MoE.Full-Cell 60-Layer AOT HLO Verification (128-chip v7x topology
fsdp=32, cp=4, ep=8)all_gather+attn_lb=true(#5758)ulysses+lb=false(this PR)collective-permutes (64 MiBeach)8(200.3 ms/mb)0-100%K/Vall-gathers (4-way,64 MiBeach)40(4-wayall-to-all)-100%dK/dVreduce-scatters (4-way,32 MiBeach)20-100%cp0..cp3on 61k segment)80(cp0) vs136(cp1..cp3)~80on all 4 CP rankswq256-way AG compute instructions between start/done18794.4×overlap104.46 GiB104.25 GiB-0.21 GiBTests
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:[kv0, kv0, kv1, kv1]vs tiled[kv0, kv1, kv0, kv1]), query/KV pairing atcp=4andcp=8, and validator flags.AttentionOp.tpu_flash_attention(ulyssesvsall_gathervs single-device reference): forwardmax |diff| = 0.0(bit-exact), backwarddQ/dK/dV max |diff| <= 1.66e-7.np.tile) or omitting the1 / replicationbackward factor fail as expected.