1616from typing import Optional , Tuple , Union
1717
1818import jax
19+ from jax .experimental import layout
20+ from jax .experimental .pallas .ops .tpu .splash_attention import splash_attention_mask as mask_lib
21+ from jax .experimental .xla_metadata import must_fuse_call
1922import jax .numpy as jnp
2023from maxtext .kernels .attention import splash_attention_kernel
2124
25+
26+ DLL = layout .Layout
27+ Layout = layout .Format
28+
2229SegmentIds = splash_attention_kernel .SegmentIds
2330
2431
@@ -35,7 +42,7 @@ def flash_attention_block_masked(
3542 segment_ids : SegmentIds | None ,
3643 block_kv : int ,
3744 block_q : int ,
38- mask : jnp . ndarray ,
45+ mask : mask_lib . Mask ,
3946 mask_value : float ,
4047 cap : Optional [float ] = None ,
4148 save_residuals : bool = False ,
@@ -91,15 +98,39 @@ def flash_attention_block_masked(
9198 num_kv_blocks = kv_seq_len // block_kv
9299 num_q_blocks = q_seq_len // block_q
93100
101+ mask_array = mask [:, :]
94102 # Before applying the segment mask, we need to broadcast the mask in batch
95103 # dimension since we have same logic for all batches.
96- mask_full = jnp .broadcast_to (mask [None , :, :], (batch_size , q_seq_len , kv_seq_len ))
104+ mask_full = jnp .broadcast_to (mask_array [None , :, :], (batch_size , q_seq_len , kv_seq_len ))
97105
98106 if segment_ids is not None :
99107 segment_ids_q = segment_ids .q [:, :, None ]
100108 segment_ids_kv = segment_ids .kv [:, None , :]
101109 mask_full = jnp .logical_and (mask_full , segment_ids_q == segment_ids_kv )
102- mask_blocked = jax .jit (mask_blocker , static_argnums = [1 , 2 ])(mask_full , block_q , block_kv )
110+
111+ if isinstance (mask , mask_lib .CausalMask ):
112+ # In the case of a causal mask, the compute_attention_block should be executed
113+ # if the current block (i, j) falls within the lower triangle. This means that
114+ # the maximum query index in block i must be greater than or equal to the
115+ # minimum key/value index in block j.
116+ # Max q_idx in block i: (i + 1) * block_q - 1
117+ # Min kv_idx in block j: j * block_kv
118+ # Condition: (i + 1) * block_q - 1 >= j * block_kv
119+ # Which simplifies to: (i + 1) * block_q > j * block_kv
120+ should_compute_block = lambda i , j : (i + 1 ) * block_q > j * block_kv
121+ else :
122+ mask_blocked = jax .jit (mask_blocker , static_argnums = [1 , 2 ])(
123+ mask_full , block_q , block_kv
124+ )
125+
126+ def should_compute_block (i , j ):
127+ mask_i_j_slice = jax .lax .dynamic_slice (
128+ mask_blocked , (0 , i , j ), (batch_size , 1 , 1 )
129+ )
130+ # The compute_attention_block should be executed if at least one element
131+ # in the slice is non-zero, meaning at least one batch requires work for
132+ # this block.
133+ return jnp .any (jnp .not_equal (mask_i_j_slice , 0 ))
103134
104135 # Initialize `l` (logsumexp) and `m` (max_logits) for the online softmax.
105136 # `l` is initialized to 0 since no blocks have been processed yet and the sum
@@ -127,28 +158,17 @@ def flash_attention_block_masked(
127158 # Outer loop over the key/value blocks.
128159 def outer_loop_body (j , carried ):
129160 output , l , m = carried
130- k_j_slice = jax .lax .dynamic_slice_in_dim (k , j * block_kv , block_kv , axis = - 2 )
131- v_j_slice = jax .lax .dynamic_slice_in_dim (v , j * block_kv , block_kv , axis = - 2 )
132161
133162 # Inner loop over the query blocks.
134163 def inner_loop_body (i , carried_inner ):
135164 output , l , m = carried_inner
136165
137- # let's get the slice of Q in N dimension
138- q_slice = jax .lax .dynamic_slice_in_dim (q , i * block_q , block_q , axis = - 2 )
139-
140166 # Calculates the attention computation (Q@K.T)@V with online softmax for
141167 # the current query and key/value blocks.
142168 def compute_attention_block (output , l , m ):
143169 output_i_slice = jax .lax .dynamic_slice_in_dim (output , i * block_q , block_q , axis = - 2 )
144170 l_i_slice = jax .lax .dynamic_slice_in_dim (l , i * block_q , block_q , axis = - 1 )
145171 m_i_slice = jax .lax .dynamic_slice_in_dim (m , i * block_q , block_q , axis = - 1 )
146- s_i_j = jnp .einsum (
147- "bxhqc,bxkc->bxhqk" ,
148- q_slice ,
149- k_j_slice ,
150- preferred_element_type = data_type ,
151- )
152172 full_mask_i_j_slice = jax .lax .dynamic_slice (
153173 mask_full ,
154174 (0 , i * block_q , j * block_kv ),
@@ -159,12 +179,41 @@ def compute_attention_block(output, l, m):
159179 (batch_size , num_kv_heads , q_groups , block_q , block_kv ),
160180 )
161181
182+ k_j_slice = jax .lax .dynamic_slice_in_dim (k , j * block_kv , block_kv , axis = - 2 )
183+ v_j_slice = jax .lax .dynamic_slice_in_dim (v , j * block_kv , block_kv , axis = - 2 )
184+
185+ # let's get the slice of Q in N dimension
186+ q_slice = jax .lax .dynamic_slice_in_dim (q , i * block_q , block_q , axis = - 2 )
187+
188+ s_i_j_dup = jnp .einsum (
189+ "bxhqc,bxkc->bxhqk" ,
190+ q_slice ,
191+ k_j_slice ,
192+ preferred_element_type = data_type ,
193+ )
194+ s_i_j_dup = jnp .where (broadcasted_mask , s_i_j_dup , mask_value )
162195 if cap is not None :
163- s_i_j = jnp .tanh (s_i_j / cap )
164- s_i_j = s_i_j * cap
165- s_i_j = jnp .where (broadcasted_mask , s_i_j , mask_value )
166- m_i_j = s_i_j .max (axis = - 1 )
167- p_i_j = jnp .exp (s_i_j - m_i_j [..., None ])
196+ s_i_j_dup = jnp .tanh (s_i_j_dup / cap )
197+ s_i_j_dup = s_i_j_dup * cap
198+ m_i_j = s_i_j_dup .max (axis = - 1 )
199+
200+ def fuse_this (q_slice , k_j_slice , mask_value , broadcasted_mask , m_i_j ):
201+ s_i_j = jnp .einsum (
202+ "bxhqc,bxkc->bxhqk" ,
203+ q_slice ,
204+ k_j_slice ,
205+ preferred_element_type = jnp .bfloat16 ,
206+ )
207+ s_i_j = jnp .where (broadcasted_mask , s_i_j , mask_value )
208+ if cap is not None :
209+ s_i_j = jnp .tanh (s_i_j / cap )
210+ s_i_j = s_i_j * cap
211+ p_i_j = jnp .exp (s_i_j - m_i_j [..., None ])
212+ return p_i_j
213+
214+ p_i_j = must_fuse_call ("1" )(fuse_this )(
215+ q_slice , k_j_slice , mask_value , broadcasted_mask , m_i_j
216+ )
168217 l_i_j = p_i_j .sum (axis = - 1 )
169218 assert m_i_j .shape == m_i_slice .shape
170219 m_i_new = jnp .maximum (m_i_slice , m_i_j )
@@ -173,14 +222,24 @@ def compute_attention_block(output, l, m):
173222 l_i_new = m_i_difference * l_i_slice + m_i_j_difference * l_i_j
174223
175224 divider = l_i_new [..., None ]
176- numerator = l_i_slice [..., None ] * m_i_difference [..., None ] * output_i_slice + m_i_j_difference [
177- ..., None
178- ] * jnp .einsum (
225+ pv = jnp .einsum (
179226 "bxhqk,bxkc->bxhqc" ,
180227 p_i_j ,
181228 v_j_slice ,
182229 preferred_element_type = data_type ,
183230 )
231+ # This forces the layout of the final @V to have better utilization by
232+ # avoiding using XLU:
233+ # Changes conv emitter type from
234+ # EmitAllInputFeatureInSublanesOutputBatchInSublanesXposeReuse to
235+ # EmitInputBatchInLanes
236+ pv = layout .with_layout_constraint (
237+ pv , DLL (major_to_minor = (0 , 1 , 2 , 4 , 3 ))
238+ )
239+ numerator = (
240+ l_i_slice [..., None ] * m_i_difference [..., None ] * output_i_slice
241+ + m_i_j_difference [..., None ] * pv
242+ )
184243
185244 output_i_slice_new = numerator / divider
186245 output = jax .lax .dynamic_update_index_in_dim (output , output_i_slice_new , i * block_q , axis = - 2 )
@@ -193,13 +252,8 @@ def identity(output, l, m):
193252
194253 return output , l , m
195254
196- batch_size = mask_blocked .shape [0 ]
197- mask_i_j_slice = jax .lax .dynamic_slice (mask_blocked , (0 , i , j ), (batch_size , 1 , 1 ))
198- # The compute_attention_block should be executed if at least one element
199- # in the slice is non-zero, meaning at least one batch requires work for
200- # this block.
201255 output , l , m = jax .lax .cond (
202- jnp . any ( jnp . not_equal ( mask_i_j_slice , 0 ) ),
256+ should_compute_block ( i , j ),
203257 compute_attention_block ,
204258 identity ,
205259 output ,
0 commit comments