Skip to content

Commit 3bb2bd7

Browse files
ds-hwangchanglan
authored andcommitted
Remove unnecessary head_dim restriction in TPU Flash Attention.
GitOrigin-RevId: 1f1b365
1 parent 59e7840 commit 3bb2bd7

3 files changed

Lines changed: 20 additions & 34 deletions

File tree

axlearn/common/flash_attention/tpu_attention.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -899,17 +899,10 @@ def is_supported(
899899
return False
900900
bias: BaseAttentionBias = input_batch["bias"]
901901
_, _, explicit_bias = split(bias, MaskFnAttentionBias, SegmentIdAttentionBias)
902-
query: Tensor = input_batch["query"]
903-
head_dim = query.shape[-1]
904902

905903
if explicit_bias.has_value():
906904
return self._log_unsupported("explicit bias is not supported.")
907905

908-
if head_dim % splash_attention_kernel.NUM_LANES != 0:
909-
return self._log_unsupported(
910-
f"{head_dim=} is not divisible by {splash_attention_kernel.NUM_LANES=}"
911-
)
912-
913906
if (
914907
not self.get_backend_overrides("splash_use_fused_bwd_kernel", True)
915908
and self.cfg.dropout_rate > 0.0

axlearn/common/flash_attention/tpu_attention_test.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def inside_tracing(mask):
126126
kv_len=[128],
127127
num_heads=[4],
128128
mask=[None, causal_mask],
129-
per_head_dim=[128],
129+
per_head_dim=[64, 128],
130130
q_dtype=[jnp.float32, jnp.bfloat16],
131131
kv_dtype=[jnp.float32, jnp.bfloat16],
132132
)
@@ -209,15 +209,15 @@ def test_gradient(
209209
self.skipTest("Backward path is broken on CPU")
210210
if mask not in (None, causal_mask) and query_length_multiplier > 1:
211211
self.skipTest("Sliding window attention does not make sense when q_len != kv_len.")
212-
if dropout_rate > 0.0 and (attention_bias_type is not None or per_head_dim % 128 != 0):
212+
if dropout_rate > 0.0 and attention_bias_type is not None:
213213
self.skipTest(
214-
"Dropout is only supported with SplashAttention (which requires \
215-
no bias, and per_head_dim being a multiple of 128.)"
214+
"Dropout is only supported with SplashAttention (which requires no bias.)"
216215
)
217216
if q_dtype == jnp.bfloat16 and kv_dtype == jnp.float32:
218217
self.skipTest("Q must have higher precision than KV.")
218+
219219
# pylint: disable=protected-access
220-
fallback_to_legacy = per_head_dim % 128 != 0 or (attention_bias_type is not None)
220+
fallback_to_legacy = attention_bias_type is not None
221221
num_kv_heads = num_heads // head_group_size
222222
q, k, v, bias = generate_attention_data(
223223
batch_size,

axlearn/common/flash_attention/tpu_splash_attention.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,11 @@ def flash_attention_kernel(
138138
mask_function: MaskFunctionType | None,
139139
dropout_rate: float,
140140
):
141+
del head_dim
141142
float32 = jnp.float32
142143
# pylint: disable=invalid-name
143144
HEAD_DIM_MINOR = QKVLayout.HEAD_DIM_MINOR
144145

145-
head_dim_repeats, rem = divmod(head_dim, NUM_LANES)
146-
if rem != 0:
147-
raise NotImplementedError(f"{head_dim=} should be a multiple of {NUM_LANES}")
148-
149146
h, i, j = pl.program_id(0), pl.program_id(1), pl.program_id(2)
150147

151148
@pl.when(j == 0)
@@ -171,8 +168,8 @@ def init():
171168
def body(kv_compute_index, _):
172169
slice_k = pl.ds(kv_compute_index * bkv_compute, bkv_compute)
173170
m_prev, l_prev = m_scratch_ref[...], l_scratch_ref[...]
174-
assert m_prev.shape == (bq, NUM_LANES)
175-
assert l_prev.shape == (bq, NUM_LANES)
171+
assert m_prev.shape == (bq, 1)
172+
assert l_prev.shape == (bq, 1)
176173

177174
q = q_ref[...] if q_layout == HEAD_DIM_MINOR else q_ref[...].T
178175
qk_dims = NT_DIM_NUMBERS if k_layout == HEAD_DIM_MINOR else NN_DIM_NUMBERS
@@ -217,17 +214,13 @@ def body(kv_compute_index, _):
217214
m_curr = qk.max(axis=-1)[:, None]
218215
assert m_curr.shape == (bq, 1)
219216
m_next = jnp.maximum(m_prev, m_curr)
220-
assert m_next.shape == (bq, NUM_LANES)
221-
222-
bkv_repeats, rem = divmod(bkv_compute, NUM_LANES)
223-
if rem != 0:
224-
raise NotImplementedError(f"{bkv_compute=} should be a multiple of {NUM_LANES}")
217+
assert m_next.shape == (bq, 1)
225218

226-
s_curr = jnp.exp(qk - pltpu.repeat(m_next, bkv_repeats, axis=1))
219+
s_curr = jnp.exp(qk - m_next)
227220
assert s_curr.shape == (bq, bkv_compute)
228221

229222
l_curr = jax.lax.broadcast_in_dim(s_curr.sum(axis=-1), l_prev.shape, (0,))
230-
assert l_curr.shape == (bq, NUM_LANES)
223+
assert l_curr.shape == (bq, 1)
231224

232225
alpha = jnp.exp(m_prev - m_next)
233226
l_next = l_curr + alpha * l_prev
@@ -254,7 +247,7 @@ def body(kv_compute_index, _):
254247
s_curr = jnp.where(dropout_mask, 0.0, s_curr) / (1.0 - dropout_rate)
255248
o_curr = lax.dot_general(s_curr, v, sv_dims)
256249

257-
alpha_o = pltpu.repeat(alpha, head_dim_repeats, axis=1)
250+
alpha_o = alpha
258251
o_scratch_ref[:] = alpha_o * o_scratch_ref[:] + o_curr
259252

260253
@pl.when(should_run)
@@ -269,10 +262,10 @@ def end():
269262
if logit_sink_ref is not None:
270263
sink_value = logit_sink_ref[h].astype(jnp.float32)
271264
l = l + jnp.exp(sink_value - m_scratch_ref[...])
272-
l_inv = pltpu.repeat(1.0 / l, head_dim_repeats, axis=1)
265+
l_inv = 1.0 / l # TODO(dhwang2): dividing directly by `l` is a more stable operation.
273266
o_ref[...] = (o_scratch_ref[...] * l_inv).astype(o_ref.dtype)
274267
if logsumexp_ref is not None:
275-
assert logsumexp_ref.shape == (bq, NUM_LANES)
268+
assert logsumexp_ref.shape == (bq, 1)
276269
logsumexp_ref[...] = (jnp.log(l) + m_scratch_ref[...]).astype(logsumexp_ref.dtype)
277270

278271
m_scratch_ref[...] = jnp.zeros_like(m_scratch_ref)
@@ -546,28 +539,28 @@ def kv_segment_ids_index_map(
546539
num_scalar_prefetch = 4
547540

548541
out_shapes = [
549-
jax.ShapeDtypeStruct((bq, NUM_LANES), jnp.float32), # m_scratch
550-
jax.ShapeDtypeStruct((bq, NUM_LANES), jnp.float32), # l_scratch
542+
jax.ShapeDtypeStruct((bq, 1), jnp.float32), # m_scratch
543+
jax.ShapeDtypeStruct((bq, 1), jnp.float32), # l_scratch
551544
jax.ShapeDtypeStruct((bq, head_dim), jnp.float32), # o_scratch
552545
jax.ShapeDtypeStruct((num_q_heads, q_seq_len, head_dim), q.dtype),
553546
]
554547
out_specs = [
555548
# TODO(sharadmv): convert m/l to be scratch
556-
pl.BlockSpec((bq, NUM_LANES), lambda h, i, j, *_: (0, 0)),
557-
pl.BlockSpec((bq, NUM_LANES), lambda h, i, j, *_: (0, 0)),
549+
pl.BlockSpec((bq, 1), lambda h, i, j, *_: (0, 0)),
550+
pl.BlockSpec((bq, 1), lambda h, i, j, *_: (0, 0)),
558551
pl.BlockSpec((bq, head_dim), lambda h, i, j, *_: (0, 0)),
559552
pl.BlockSpec((None, bq, head_dim), out_index_map),
560553
]
561554
if save_residuals:
562555
out_shapes += [
563-
jax.ShapeDtypeStruct((num_q_heads, q_seq_len, NUM_LANES), jnp.float32), # logsumexp
556+
jax.ShapeDtypeStruct((num_q_heads, q_seq_len, 1), jnp.float32), # logsumexp
564557
]
565558

566559
def logsumexp_index_map(h, i, *_):
567560
return h, i, 0
568561

569562
out_specs += [
570-
pl.BlockSpec((None, bq, NUM_LANES), logsumexp_index_map),
563+
pl.BlockSpec((None, bq, 1), logsumexp_index_map),
571564
]
572565
else:
573566
out_shapes += [None]

0 commit comments

Comments
 (0)