Skip to content

Commit 2ea7bb9

Browse files
committed
perf(attention): shard-major Ulysses output all-to-all (wan_ulysses_out_a2a="chunked"/"shard_major")
Third of four pieces split out of the Wan fast-inference optimizations PR. The switch defaults off ("flat") in the Wan YAMLs and in wan_runtime_options; it is a plain config key (`"flat"` | `"chunked"` | `"shard_major"`) plumbed to FlaxWanAttention / NNXAttentionOp / AttentionOp and down to the Ulysses kernels through the dispatch context. - `_ulysses_seq_to_heads` is the inverse Ulysses exchange (attention output, head-sharded -> sequence-sharded), and validates `seq_len % num_shards == 0` up front for all modes. `"flat"` is the previous `all_to_all(split_axis=seq, concat_axis=1)`; XLA reorders the whole output so the sequence axis is major before the collective and back again after, four relayout copies per layer at the 720p shard shape (~1.5 ms on v6e, ~0.7 ms on tpu7x). `"chunked"` / `"shard_major"` uses a single layout-preserving `all_to_all` over a reshaped `(num_shards, chunk)` leading axis, so no relayout copies are inserted (also wired into the non-custom `attention="ulysses"` branch). Same data movement, same values. - With the custom splash kernel (`S_local % block_q == 0`, `D == 128`, `heads_per_tile == 1`), the forward Q exchange also stays shard-major (4-D q, `[B, U, H/U, S_local, D]`) and the kernel writes the shard-major output directly (`out_num_shards`), on both the 1D Ulysses path (v6e) and the 2D Ulysses+Ring path (tpu7x): `custom_splash_attention.py` gains a 4-D q BlockSpec and a 4-D shard-major output for `_splash_attention_forward` and `_splash_attention_forward_ring`; `ring_attention_kernel.py` threads `out_num_shards` through `_custom_ring_attention_forward` / `make_custom_ring_attention` and sizes the o/l/m accumulators accordingly (`save_residuals` with `out_num_shards > 1` raises `NotImplementedError`: the lse contract stays `(num_q_heads, orig_q_seq_len)`). `_compute_fixed_m_metadata` accepts the 5-D shard-major query. - `"shard_major"` vs `"chunked"`: `"shard_major"` is the strict mode that requires the 4-D `q` / `out_num_shards` Pallas kernel path and raises `ValueError` when any gate precondition (`use_custom_kernel`, `S_local % block_q == 0`, `D == 128`, `heads_per_tile == 1`, `not transpose_out`, `attention_mask is None`) fails; `"chunked"` is the lenient mode that takes the 4-D kernel path when eligible and falls back to the chunked inverse `all_to_all` in `_ulysses_seq_to_heads` otherwise. - Reachability: at 720p/81f with CP=4 the unpadded per-device sequence is 18,900 tokens, which no launcher `block_q` divides (v6e `BQ=9472`, v7 `BQ=6400` at this PR), so at this PR the launcher's `v6e`/`v7` profiles set `DEFAULT_ULYSSES_OUT_A2A=chunked`; once `feat/wan-fast-inference-optimizations` pads the sequence to 18,944 (`wan_seq_pad=lane`) and sets v7 `BQ=4736` (`9472 * 2 = 18,944` on v6e, `4736 * 4 = 18,944` on v7), it switches both profiles to `DEFAULT_ULYSSES_OUT_A2A=shard_major`. Tests - `ulysses_out_a2a_test.py` (8): flat vs chunked vs shard_major parity of the inverse exchange, the mode guard, the `seq_len % num_shards != 0` guard, the `save_residuals + shard-major` guard, the strict `"shard_major"` kernel-gate `ValueError` guard, the 4-D q ring kernel bit-exact vs 3-D (running in CI via `interpret=True`), and bit-exact wrapper output for the shard-major 1D Ulysses and 2D Ulysses+Ring paths vs the flat layout (spy-asserting `out_num_shards == U`). - `wan_runtime_options_test.py` (12): the new switch's YAML default and enum validation. - `end_to_end/tpu/run_wan_stack_tests.sh`: adds `ulysses_out_a2a_test.py`.
1 parent b31bd66 commit 2ea7bb9

14 files changed

Lines changed: 864 additions & 97 deletions

‎end_to_end/tpu/run_wan_fast_inference.sh‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
# v6e seed 777, 17.92 dB / 0.6556 on v6e seed 12345, and
6969
# 17.69 dB / 0.6918 on tpu7x seed 12345 (visually clean).
7070
# USE_FUSED_ROPE_KERNEL=false restores the XLA producer.
71-
# WAN_CROSS_ATTN_KERNEL / WAN_PATCH_EMBED_MODE
71+
# WAN_CROSS_ATTN_KERNEL / WAN_PATCH_EMBED_MODE / WAN_ULYSSES_OUT_A2A
7272
# override Wan fast-inference kernel/layout switches
7373
# DP / CP / PER_DEVICE_BATCH / SEED
7474
# override mesh parallelism, per-device batch, or RNG seed (default 12345)
@@ -183,6 +183,7 @@ case "$TPU_PROFILE" in
183183
DEFAULT_FUSED_ROPE=true
184184
DEFAULT_CROSS_ATTN_KERNEL=pallas
185185
DEFAULT_PATCH_EMBED_MODE=tokens
186+
DEFAULT_ULYSSES_OUT_A2A=chunked
186187
;;
187188
v7)
188189
PLATFORM_LIBTPU="$V7_LIBTPU"
@@ -201,6 +202,7 @@ case "$TPU_PROFILE" in
201202
DEFAULT_FUSED_ROPE=true
202203
DEFAULT_CROSS_ATTN_KERNEL=pallas
203204
DEFAULT_PATCH_EMBED_MODE=tokens
205+
DEFAULT_ULYSSES_OUT_A2A=chunked
204206
;;
205207
*)
206208
echo "== warning: unrecognised or non-v6e/v7 accelerator '${ACCEL_TYPE:-unknown}';" \
@@ -221,6 +223,7 @@ case "$TPU_PROFILE" in
221223
DEFAULT_FUSED_ROPE=false
222224
DEFAULT_CROSS_ATTN_KERNEL=xla
223225
DEFAULT_PATCH_EMBED_MODE=tokens
226+
DEFAULT_ULYSSES_OUT_A2A=flat
224227
;;
225228
esac
226229

@@ -254,6 +257,7 @@ FUSED_ROPE_ARGS=()
254257
[ -n "${FUSED_ROPE_HEAD_BLOCK:-}" ] && FUSED_ROPE_ARGS+=("fused_rope_head_block=$FUSED_ROPE_HEAD_BLOCK")
255258
WAN_CROSS_ATTN_KERNEL=${WAN_CROSS_ATTN_KERNEL:-$DEFAULT_CROSS_ATTN_KERNEL}
256259
WAN_PATCH_EMBED_MODE=${WAN_PATCH_EMBED_MODE:-$DEFAULT_PATCH_EMBED_MODE}
260+
WAN_ULYSSES_OUT_A2A=${WAN_ULYSSES_OUT_A2A:-$DEFAULT_ULYSSES_OUT_A2A}
257261

258262
# Mesh: context parallelism carries the Ulysses shards, data parallelism takes
259263
# whatever devices remain. Defaults to CP=4 / DP=2 on an 8-device slice. On a
@@ -322,6 +326,7 @@ python src/maxdiffusion/generate_wan.py "$CONFIG" \
322326
${FUSED_ROPE_ARGS[@]+"${FUSED_ROPE_ARGS[@]}"} \
323327
wan_cross_attn_kernel="$WAN_CROSS_ATTN_KERNEL" \
324328
wan_patch_embed_mode="$WAN_PATCH_EMBED_MODE" \
329+
wan_ulysses_out_a2a="$WAN_ULYSSES_OUT_A2A" \
325330
fps=16 ${GUIDANCE_ARGS[@]+"${GUIDANCE_ARGS[@]}"} \
326331
seed="${SEED:-12345}" \
327332
flash_block_sizes="$FLASH_BLOCK_SIZES" \

‎end_to_end/tpu/run_wan_stack_tests.sh‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,7 @@ TESTS=(
4949
"$T/cross_attention_pallas_test.py"
5050
# Token-sharded patch embedding (feat/wan-token-patch-embed)
5151
"$T/wan_patch_embed_test.py"
52+
# Shard-major Ulysses output A2A (feat/ulysses-shard-major-a2a)
53+
"$T/ulysses_out_a2a_test.py"
5254
)
5355
PYTHONPATH="src${PYTHONPATH:+:$PYTHONPATH}" exec "${PYTHON:-python3}" -m pytest -q -rs "${TESTS[@]}" "$@"

‎src/maxdiffusion/configs/base_wan_14b.yml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,13 @@ wan_cross_attn_kernel: "xla"
140140
# token, an all-gather on tpu7x) or "tokens" (token-sharded patches times the
141141
# replicated kernel, no activation collective; equal up to accumulation order).
142142
wan_patch_embed_mode: "conv"
143+
# Inverse Ulysses all-to-all (attention output, head-sharded -> sequence-sharded):
144+
# "flat" splits the sequence axis directly; "chunked" exchanges over an explicit
145+
# leading sequence-chunk axis so XLA does not relayout the output around the
146+
# collective (and opportunistically uses the Pallas 4D-q / out_num_shards path
147+
# when S_local % block_q == 0); "shard_major" strictly requires the Pallas
148+
# 4D-q / out_num_shards path and raises if any kernel precondition fails.
149+
wan_ulysses_out_a2a: "flat"
143150
# For attention=ulysses_ring, hidden Ulysses shard count; ring shards are context / this.
144151
ulysses_shards: -1
145152
# Splits Ulysses all-to-all into head-group chunks. The last chunk carries any remainder.

‎src/maxdiffusion/configs/base_wan_1_3b.yml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ wan_cross_attn_kernel: "xla"
137137
# token, an all-gather on tpu7x) or "tokens" (token-sharded patches times the
138138
# replicated kernel, no activation collective; equal up to accumulation order).
139139
wan_patch_embed_mode: "conv"
140+
# Inverse Ulysses all-to-all (attention output, head-sharded -> sequence-sharded):
141+
# "flat" splits the sequence axis directly; "chunked" exchanges over an explicit
142+
# leading sequence-chunk axis so XLA does not relayout the output around the
143+
# collective (and opportunistically uses the Pallas 4D-q / out_num_shards path
144+
# when S_local % block_q == 0); "shard_major" strictly requires the Pallas
145+
# 4D-q / out_num_shards path and raises if any kernel precondition fails.
146+
wan_ulysses_out_a2a: "flat"
140147
# For attention=ulysses_ring, hidden Ulysses shard count; ring shards are context / this.
141148
ulysses_shards: -1
142149
# Splits Ulysses all-to-all into head-group chunks. The last chunk carries any remainder.

‎src/maxdiffusion/configs/base_wan_27b.yml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,13 @@ wan_cross_attn_kernel: "xla"
174174
# token, an all-gather on tpu7x) or "tokens" (token-sharded patches times the
175175
# replicated kernel, no activation collective; equal up to accumulation order).
176176
wan_patch_embed_mode: "conv"
177+
# Inverse Ulysses all-to-all (attention output, head-sharded -> sequence-sharded):
178+
# "flat" splits the sequence axis directly; "chunked" exchanges over an explicit
179+
# leading sequence-chunk axis so XLA does not relayout the output around the
180+
# collective (and opportunistically uses the Pallas 4D-q / out_num_shards path
181+
# when S_local % block_q == 0); "shard_major" strictly requires the Pallas
182+
# 4D-q / out_num_shards path and raises if any kernel precondition fails.
183+
wan_ulysses_out_a2a: "flat"
177184
# For attention=ulysses_ring*, hidden Ulysses shard count; ring shards are context / this.
178185
ulysses_shards: -1
179186
# Splits Ulysses all-to-all into head-group chunks. The last chunk carries any remainder.

‎src/maxdiffusion/configs/base_wan_animate.yml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ wan_cross_attn_kernel: "xla"
119119
# token, an all-gather on tpu7x) or "tokens" (token-sharded patches times the
120120
# replicated kernel, no activation collective; equal up to accumulation order).
121121
wan_patch_embed_mode: "conv"
122+
# Inverse Ulysses all-to-all (attention output, head-sharded -> sequence-sharded):
123+
# "flat" splits the sequence axis directly; "chunked" exchanges over an explicit
124+
# leading sequence-chunk axis so XLA does not relayout the output around the
125+
# collective (and opportunistically uses the Pallas 4D-q / out_num_shards path
126+
# when S_local % block_q == 0); "shard_major" strictly requires the Pallas
127+
# 4D-q / out_num_shards path and raises if any kernel precondition fails.
128+
wan_ulysses_out_a2a: "flat"
122129
# For attention=ulysses_ring, hidden Ulysses shard count; ring shards are context / this.
123130
ulysses_shards: -1
124131
# Splits Ulysses all-to-all into head-group chunks. The last chunk carries any remainder.

‎src/maxdiffusion/configs/base_wan_i2v_14b.yml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ wan_cross_attn_kernel: "xla"
121121
# token, an all-gather on tpu7x) or "tokens" (token-sharded patches times the
122122
# replicated kernel, no activation collective; equal up to accumulation order).
123123
wan_patch_embed_mode: "conv"
124+
# Inverse Ulysses all-to-all (attention output, head-sharded -> sequence-sharded):
125+
# "flat" splits the sequence axis directly; "chunked" exchanges over an explicit
126+
# leading sequence-chunk axis so XLA does not relayout the output around the
127+
# collective (and opportunistically uses the Pallas 4D-q / out_num_shards path
128+
# when S_local % block_q == 0); "shard_major" strictly requires the Pallas
129+
# 4D-q / out_num_shards path and raises if any kernel precondition fails.
130+
wan_ulysses_out_a2a: "flat"
124131
# For attention=ulysses_ring, hidden Ulysses shard count; ring shards are context / this.
125132
ulysses_shards: -1
126133
# Splits Ulysses all-to-all into head-group chunks. The last chunk carries any remainder.

‎src/maxdiffusion/configs/base_wan_i2v_27b.yml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ wan_cross_attn_kernel: "xla"
121121
# token, an all-gather on tpu7x) or "tokens" (token-sharded patches times the
122122
# replicated kernel, no activation collective; equal up to accumulation order).
123123
wan_patch_embed_mode: "conv"
124+
# Inverse Ulysses all-to-all (attention output, head-sharded -> sequence-sharded):
125+
# "flat" splits the sequence axis directly; "chunked" exchanges over an explicit
126+
# leading sequence-chunk axis so XLA does not relayout the output around the
127+
# collective (and opportunistically uses the Pallas 4D-q / out_num_shards path
128+
# when S_local % block_q == 0); "shard_major" strictly requires the Pallas
129+
# 4D-q / out_num_shards path and raises if any kernel precondition fails.
130+
wan_ulysses_out_a2a: "flat"
124131
# For attention=ulysses_ring, hidden Ulysses shard count; ring shards are context / this.
125132
ulysses_shards: -1
126133
# Splits Ulysses all-to-all into head-group chunks. The last chunk carries any remainder.

0 commit comments

Comments
 (0)