Skip to content

Commit c6b1fed

Browse files
committed
Optimize WAN attention by fusing Pallas transposes and Output Projection contractions
1 parent fc7c03e commit c6b1fed

2 files changed

Lines changed: 33 additions & 20 deletions

File tree

src/maxdiffusion/kernels/custom_splash_attention.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,12 @@ def end():
237237
l = l_scratch_ref[...]
238238
if fuse_reciprocal:
239239
l_inv = jnp.tile(1.0 / l, (head_dim_v_repeats, 1))
240-
o_ref[...] = (o_scratch_ref[...] * l_inv).astype(o_ref.dtype)
240+
o_ref[...] = (o_scratch_ref[...] * l_inv).T.astype(o_ref.dtype)
241241
else:
242242
# Ring path: emit the un-normalized numerator plus the running softmax
243243
# stats (max logit `m` and linear denominator `l`) so the outer ring loop
244244
# can merge shard contributions and normalize only once at the very end.
245-
o_ref[...] = o_scratch_ref[...].astype(o_ref.dtype)
245+
o_ref[...] = o_scratch_ref[...].T.astype(o_ref.dtype)
246246
if l_ring_ref is not None:
247247
l_ring_ref[...] = l.astype(l_ring_ref.dtype)
248248
if m_ring_ref is not None:
@@ -399,7 +399,7 @@ def end():
399399
for h_local in range(heads_per_tile):
400400
l = l_scratch_ref[h_local]
401401
l_inv = jnp.tile(1.0 / l, (head_dim_v_repeats, 1))
402-
o_ref[h_local] = (o_scratch_ref[h_local] * l_inv).astype(o_ref.dtype)
402+
o_ref[h_local] = (o_scratch_ref[h_local] * l_inv).T.astype(o_ref.dtype)
403403

404404

405405
def _splash_attention_forward(
@@ -436,7 +436,7 @@ def q_index_map(h, i, j, *_):
436436
return (h, i, 0)
437437

438438
def out_index_map(h, i, j, *_):
439-
return h, 0, i
439+
return h, i, 0
440440

441441
def k_index_map(h, i, j, *_):
442442
return (h // q_heads_per_kv_head, j, 0)
@@ -453,13 +453,13 @@ def v_index_map(h, i, j, *_):
453453
jax.ShapeDtypeStruct((NUM_SUBLANES, bq), jnp.float32),
454454
jax.ShapeDtypeStruct((NUM_SUBLANES, bq), jnp.float32),
455455
jax.ShapeDtypeStruct((head_dim_v, bq), jnp.float32),
456-
jax.ShapeDtypeStruct((num_q_heads, head_dim_v, actual_q_seq_len), q.dtype),
456+
jax.ShapeDtypeStruct((num_q_heads, actual_q_seq_len, head_dim_v), q.dtype),
457457
]
458458
out_specs = [
459459
pl.BlockSpec((NUM_SUBLANES, bq), lambda *_: (0, 0)),
460460
pl.BlockSpec((NUM_SUBLANES, bq), lambda *_: (0, 0)),
461461
pl.BlockSpec((head_dim_v, bq), lambda *_: (0, 0)),
462-
pl.BlockSpec((None, head_dim_v, bq), out_index_map),
462+
pl.BlockSpec((None, bq, head_dim_v), out_index_map),
463463
]
464464
grid_width = (actual_kv_seq_len + bkv - 1) // bkv
465465
grid_height = (actual_q_seq_len + bq - 1) // bq
@@ -540,7 +540,10 @@ def _splash_attention_forward_ring(
540540
def q_index_map(h, i, j, *_):
541541
return (h, i, 0)
542542

543-
def out_index_map(h, i, j, *_):
543+
def out_index_map_o(h, i, j, *_):
544+
return h, i, 0
545+
546+
def out_index_map_stats(h, i, j, *_):
544547
return h, 0, i
545548

546549
def k_index_map(h, i, j, *_):
@@ -558,17 +561,17 @@ def v_index_map(h, i, j, *_):
558561
jax.ShapeDtypeStruct((NUM_SUBLANES, bq), jnp.float32),
559562
jax.ShapeDtypeStruct((NUM_SUBLANES, bq), jnp.float32),
560563
jax.ShapeDtypeStruct((head_dim_v, bq), jnp.float32),
561-
jax.ShapeDtypeStruct((num_q_heads, head_dim_v, actual_q_seq_len), jnp.float32),
564+
jax.ShapeDtypeStruct((num_q_heads, actual_q_seq_len, head_dim_v), jnp.float32),
562565
jax.ShapeDtypeStruct((num_q_heads, NUM_SUBLANES, actual_q_seq_len), jnp.float32),
563566
jax.ShapeDtypeStruct((num_q_heads, NUM_SUBLANES, actual_q_seq_len), jnp.float32),
564567
]
565568
out_specs = [
566569
pl.BlockSpec((NUM_SUBLANES, bq), lambda *_: (0, 0)),
567570
pl.BlockSpec((NUM_SUBLANES, bq), lambda *_: (0, 0)),
568571
pl.BlockSpec((head_dim_v, bq), lambda *_: (0, 0)),
569-
pl.BlockSpec((None, head_dim_v, bq), out_index_map),
570-
pl.BlockSpec((None, NUM_SUBLANES, bq), out_index_map),
571-
pl.BlockSpec((None, NUM_SUBLANES, bq), out_index_map),
572+
pl.BlockSpec((None, bq, head_dim_v), out_index_map_o),
573+
pl.BlockSpec((None, NUM_SUBLANES, bq), out_index_map_stats),
574+
pl.BlockSpec((None, NUM_SUBLANES, bq), out_index_map_stats),
572575
]
573576
grid_width = (actual_kv_seq_len + bkv - 1) // bkv
574577
grid_height = (actual_q_seq_len + bq - 1) // bq
@@ -612,7 +615,7 @@ def v_index_map(h, i, j, *_):
612615
),
613616
out_shape=out_shapes,
614617
)(mk, q, k, v)
615-
out = jnp.swapaxes(all_out[3], 1, 2) # (h, head_dim_v, s) -> (h, s, head_dim_v)
618+
out = all_out[3]
616619
l = all_out[4][:, 0, :] # (h, s)
617620
m = all_out[5][:, 0, :] # (h, s)
618621
return out, m, l
@@ -653,7 +656,7 @@ def v_index_map(h, i, j, *_):
653656
return (h, j, 0)
654657

655658
def out_index_map(h, i, j, *_):
656-
return (h, 0, i)
659+
return (h, i, 0)
657660

658661
in_specs = [
659662
pl.BlockSpec((hpt, bq, head_dim_qk), q_index_map),
@@ -664,13 +667,13 @@ def out_index_map(h, i, j, *_):
664667
jax.ShapeDtypeStruct((hpt, NUM_SUBLANES, bq), jnp.float32),
665668
jax.ShapeDtypeStruct((hpt, NUM_SUBLANES, bq), jnp.float32),
666669
jax.ShapeDtypeStruct((hpt, head_dim_v, bq), jnp.float32),
667-
jax.ShapeDtypeStruct((num_q_heads, head_dim_v, actual_q_seq_len), q.dtype),
670+
jax.ShapeDtypeStruct((num_q_heads, actual_q_seq_len, head_dim_v), q.dtype),
668671
]
669672
out_specs = [
670673
pl.BlockSpec((hpt, NUM_SUBLANES, bq), lambda *_: (0, 0, 0)),
671674
pl.BlockSpec((hpt, NUM_SUBLANES, bq), lambda *_: (0, 0, 0)),
672675
pl.BlockSpec((hpt, head_dim_v, bq), lambda *_: (0, 0, 0)),
673-
pl.BlockSpec((hpt, head_dim_v, bq), out_index_map),
676+
pl.BlockSpec((hpt, bq, head_dim_v), out_index_map),
674677
]
675678
grid_width = (actual_kv_seq_len + bkv - 1) // bkv
676679
grid_height = (actual_q_seq_len + bq - 1) // bq

src/maxdiffusion/models/attention_flax.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,6 @@ def wrap_ulysses_attention(query, key, value):
826826
else:
827827
vmapped_splash = jax.vmap(splash_kernel, in_axes=(0, 0, 0))
828828
attention_output = vmapped_splash(query, key, value)
829-
attention_output = jnp.swapaxes(attention_output, 2, 3)
830829
attention_output = attention_output[:, :, :query_seq_len, :kv_size].astype(query.dtype)
831830
else:
832831
# Run the same local splash kernel as standard TPU flash attention, but now
@@ -907,6 +906,8 @@ def wrap_ulysses_attention(query, key, value):
907906
if fold_batch:
908907
x = x.reshape(batch, num_heads, *x.shape[2:])
909908
x = x[:, :, :orig_q_seq_len, :]
909+
if use_custom_kernel:
910+
return x
910911
x = _reshape_heads_to_head_dim(x)
911912

912913
return x
@@ -1218,9 +1219,9 @@ def wrap_ulysses_ring_attention(query, key, value):
12181219
use_fixed_m=use_fixed_m,
12191220
)
12201221
if use_fixed_m:
1221-
attention_output = jnp.swapaxes(jax.vmap(splash_kernel, in_axes=(0, 0, 0, None))(query, key, value, mk_arr), 2, 3)
1222+
attention_output = jax.vmap(splash_kernel, in_axes=(0, 0, 0, None))(query, key, value, mk_arr)
12221223
else:
1223-
attention_output = jnp.swapaxes(jax.vmap(splash_kernel, in_axes=(0, 0, 0))(query, key, value), 2, 3)
1224+
attention_output = jax.vmap(splash_kernel, in_axes=(0, 0, 0))(query, key, value)
12241225
else:
12251226
# (2b) Ring (full ppermute over the cross-chip ring axis) with the custom kernel.
12261227
# bidirectional=True -> wrap-free schedule (streams K/V both directions one hop
@@ -1257,7 +1258,6 @@ def wrap_ulysses_ring_attention(query, key, value):
12571258
)
12581259
x = jax.lax.with_sharding_constraint(x, q_axis_names)
12591260
x = x[:, :, :orig_q_seq_len, :]
1260-
x = _reshape_heads_to_head_dim(x)
12611261
return x
12621262

12631263

@@ -2460,7 +2460,17 @@ def __call__(
24602460
attn_output = checkpoint_name(attn_output, "attn_output")
24612461

24622462
with jax.named_scope("proj_attn"):
2463-
hidden_states = self.proj_attn(attn_output)
2463+
if attn_output.ndim == 4:
2464+
kernel = self.proj_attn.kernel.value.reshape(self.heads, self.dim_head, -1)
2465+
hidden_states = jax.lax.dot_general(
2466+
attn_output,
2467+
kernel,
2468+
(((1, 3), (0, 1)), ((), ())),
2469+
)
2470+
if self.proj_attn.bias is not None:
2471+
hidden_states = hidden_states + self.proj_attn.bias.value
2472+
else:
2473+
hidden_states = self.proj_attn(attn_output)
24642474
if self.drop_out.rate > 0:
24652475
hidden_states = self.drop_out(hidden_states, deterministic=deterministic, rngs=rngs)
24662476
return hidden_states

0 commit comments

Comments
 (0)