Skip to content

Commit a56309f

Browse files
Fix soft cap in JAX Splash attention
1 parent 87331f6 commit a56309f

3 files changed

Lines changed: 38 additions & 1 deletion

File tree

src/maxtext/kernels/attention/jax_flash_attention.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,10 @@ def compute_attention_block(output, l, m):
159159
(batch_size, num_kv_heads, q_groups, block_q, block_kv),
160160
)
161161

162-
s_i_j = jnp.where(broadcasted_mask, s_i_j, mask_value)
163162
if cap is not None:
164163
s_i_j = jnp.tanh(s_i_j / cap)
165164
s_i_j = s_i_j * cap
165+
s_i_j = jnp.where(broadcasted_mask, s_i_j, mask_value)
166166
m_i_j = s_i_j.max(axis=-1)
167167
p_i_j = jnp.exp(s_i_j - m_i_j[..., None])
168168
l_i_j = p_i_j.sum(axis=-1)

src/maxtext/layers/attention_op.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,6 +1500,7 @@ def kernel_fn(q, k, v, d, s):
15001500
block_q=self.block_q,
15011501
mask=materialized_mask,
15021502
mask_value=DEFAULT_MASK_VALUE,
1503+
cap=attn_logits_soft_cap,
15031504
)
15041505
if record_max_logits:
15051506
# The native JAX splash attention implementation does not currently expose the softmax statistics

tests/unit/attention_test.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
)
4848
from maxtext.layers.attentions import Attention
4949
from maxtext.layers import embeddings
50+
from maxtext.kernels.attention import jax_flash_attention
5051
from maxtext.configs import pyconfig
5152
from maxtext.models.qwen3 import Qwen3NextGatedDeltaNet
5253
import numpy as np
@@ -56,6 +57,41 @@
5657
from tests.utils.test_helpers import get_test_config_path
5758

5859

60+
class JaxFlashAttentionTest(unittest.TestCase):
61+
"""Tests for JAX flash attention."""
62+
63+
def test_flash_attention_block_masked_soft_cap(self):
64+
cap = 1.0
65+
mask_value = -1.0e9
66+
query = jnp.array([[[[10.0], [10.0]]]], dtype=jnp.float32)
67+
key = jnp.array([[[[10.0], [0.0]]]], dtype=jnp.float32)
68+
value = jnp.array([[[[1.0], [3.0]]]], dtype=jnp.float32)
69+
mask = jnp.array([[True, True], [True, False]])
70+
71+
output = jax_flash_attention.flash_attention_block_masked(
72+
query,
73+
key,
74+
value,
75+
segment_ids=None,
76+
block_kv=2,
77+
block_q=1,
78+
mask=mask,
79+
mask_value=mask_value,
80+
cap=cap,
81+
)
82+
83+
logits = jnp.einsum("bhqd,bhkd->bhqk", query, key)
84+
logits = jnp.tanh(logits / cap) * cap
85+
logits = jnp.where(mask[None, None, :, :], logits, mask_value)
86+
expected = jnp.einsum("bhqk,bhkd->bhqd", jax.nn.softmax(logits, axis=-1), value)
87+
np.testing.assert_allclose(
88+
np.asarray(output),
89+
np.asarray(expected),
90+
rtol=1e-6,
91+
atol=1e-6,
92+
)
93+
94+
5995
class BidirectionalBlockMaskTest(unittest.TestCase):
6096
"""Test for make_bidirectional_block_mask."""
6197

0 commit comments

Comments
 (0)