Skip to content

Commit 7014e2b

Browse files
Merge pull request #4298 from huytransformer:htn/tpu-splash-load-balanced-masks
PiperOrigin-RevId: 941433331
2 parents a679dab + b4e0472 commit 7014e2b

2 files changed

Lines changed: 68 additions & 15 deletions

File tree

src/maxtext/layers/attention_op.py

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,20 +1275,28 @@ def create_sa_config(config, query, key, attn_logits_soft_cap):
12751275
else:
12761276
mask = mask_module.CausalMask(shape=mask_shape)
12771277

1278-
# Create LoadBalancedCausalMask if cp and load_balancing
1279-
if cp_size > 1 and load_balanced_context_parallel:
1278+
use_load_balanced_cp = cp_size > 1 and load_balanced_context_parallel
1279+
if use_load_balanced_cp and self.attention_type != AttentionType.FULL:
12801280
mask = LoadBalancedCausalMask(shape=mask_shape, cp_size=cp_size)
12811281

1282-
# TODO: figure out local_sliding attention + load_balancing, default is global
12831282
# Apply local masking if local sliding attention is enabled.
12841283
if self.attention_type == AttentionType.LOCAL_SLIDING:
12851284
if self.sliding_window_size is None:
12861285
raise ValueError("Sliding_window_size must be set if Local Sliding attention type")
1287-
mask &= mask_module.LocalMask(
1288-
shape=(query.shape[2], key.shape[2]),
1289-
window_size=(self.sliding_window_size - 1, self.sliding_window_size),
1290-
offset=0,
1291-
)
1286+
local_window_size = (self.sliding_window_size - 1, self.sliding_window_size)
1287+
if use_load_balanced_cp:
1288+
mask &= LoadBalancedLocalMask(
1289+
shape=(query.shape[2], key.shape[2]),
1290+
window_size=local_window_size,
1291+
offset=0,
1292+
cp_size=cp_size,
1293+
)
1294+
else:
1295+
mask &= mask_module.LocalMask(
1296+
shape=(query.shape[2], key.shape[2]),
1297+
window_size=local_window_size,
1298+
offset=0,
1299+
)
12921300
elif self.attention_type == AttentionType.CHUNK:
12931301
if self.chunk_attn_window_size is None:
12941302
raise ValueError("chunk_attn_window_size must be set for chunk attention type")
@@ -2164,6 +2172,12 @@ def __call__(
21642172
return prefill_unnormalized_output / prefill_exponentials_sum
21652173

21662174

2175+
def _load_balanced_q_sequence(shape: tuple[int, int], cp_size: int):
2176+
"""Reorders query positions the same way as load-balanced input tokens."""
2177+
arr = np.arange(shape[0])
2178+
return max_utils.reorder_mask_load_balancing(arr, cp_size, 0)
2179+
2180+
21672181
# pylint: disable=protected-access
21682182
class LoadBalancedCausalMask(splash_attention_mask._ComputableMask):
21692183
"""Lazy causal mask, prevents the model from attending to future tokens.
@@ -2194,20 +2208,14 @@ def causal_mask_function(q_ids, kv_ids):
21942208
else:
21952209
return q_ids + self.offset >= kv_ids
21962210

2197-
arr = np.arange(shape[0])
2198-
# we reorder the mask to be load balanced following the same approach as
2199-
# used to reorder the input tokens
2200-
out = max_utils.reorder_mask_load_balancing(arr[None, :, None, None], cp_size, 1)
2201-
q_sequence = out[0, :, 0, 0]
2202-
22032211
mask_function = causal_mask_function
22042212

22052213
super().__init__(
22062214
shape=shape,
22072215
mask_function=mask_function,
22082216
shard_count=shard_count,
22092217
)
2210-
self.q_sequence = q_sequence
2218+
self.q_sequence = _load_balanced_q_sequence(shape, cp_size)
22112219

22122220
def __eq__(self, other: object):
22132221
if not isinstance(other, type(self)):
@@ -2224,3 +2232,25 @@ def __hash__(self):
22242232
self.q_sequence.tobytes() if self.q_sequence is not None else None,
22252233
)
22262234
)
2235+
2236+
2237+
class LoadBalancedLocalMask(splash_attention_mask.LocalMask):
2238+
"""Lazy local mask with load-balanced query positions."""
2239+
2240+
def __init__(
2241+
self,
2242+
shape: tuple[int, int],
2243+
window_size: tuple[int | None, int | None],
2244+
offset: int,
2245+
shard_count: int = 1,
2246+
cp_size: int = -1,
2247+
):
2248+
super().__init__(
2249+
shape=shape,
2250+
window_size=window_size,
2251+
offset=offset,
2252+
shard_count=shard_count,
2253+
)
2254+
# LocalMask uses shard_count for mask-shard validation. cp_size is the
2255+
# context-parallel size used for the load-balanced query order.
2256+
self.q_sequence = _load_balanced_q_sequence(shape, cp_size)

tests/unit/attention_test.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,29 @@ def test_value_error_on_zero_chunk_size(self):
328328
_generate_chunk_attention_mask(mask_shape=(4, 4), chunk_size=0)
329329

330330

331+
class LoadBalancedMaskTest(unittest.TestCase):
332+
"""Tests for load-balanced Splash masks."""
333+
334+
def test_load_balanced_local_window(self):
335+
seq_len = 8
336+
window_size = 3
337+
q_sequence = np.asarray([0, 1, 6, 7, 2, 3, 4, 5])
338+
kv_sequence = np.arange(seq_len)
339+
causal_mask = attention_op.LoadBalancedCausalMask(shape=(seq_len, seq_len), cp_size=2)
340+
local_mask = attention_op.LoadBalancedLocalMask(
341+
shape=(seq_len, seq_len),
342+
window_size=(window_size - 1, window_size),
343+
offset=0,
344+
cp_size=2,
345+
)
346+
347+
expected_mask = (kv_sequence[None, :] <= q_sequence[:, None]) & (
348+
kv_sequence[None, :] > q_sequence[:, None] - window_size
349+
)
350+
351+
np.testing.assert_array_equal((causal_mask & local_mask)[:, :], expected_mask)
352+
353+
331354
class CudnnTePackedSequenceDescriptorTest(unittest.TestCase):
332355
"""Tests packed Transformer Engine attention metadata handling."""
333356

0 commit comments

Comments
 (0)