Skip to content

Commit 7f367e4

Browse files
committed
Support causal splash attention in Qwen3, enforce prompt_embeds sharding constraint, and fix unit test sharding handling
1 parent 06a7827 commit 7f367e4

4 files changed

Lines changed: 44 additions & 21 deletions

File tree

src/maxdiffusion/generate_flux2klein.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ def main(argv):
278278
ulysses_shards=getattr(config, "ulysses_shards", -1),
279279
ulysses_attention_chunks=getattr(config, "ulysses_attention_chunks", 1),
280280
max_layer_to_run=getattr(config, "text_encoder_max_layer", 27),
281+
is_causal=getattr(config, "text_encoder_is_causal", True),
281282
)
282283
qwen3_model = FlaxQwen3Model(qwen3_config)
283284

src/maxdiffusion/models/attention_flax.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,7 @@ def _reshape_batch_dim_to_heads(tensor, heads):
127127
tensor = tensor.reshape(batch_size // head_size, head_size, seq_len, dim)
128128
tensor = jnp.transpose(tensor, (0, 2, 1, 3))
129129
reshaped_tensor = tensor.reshape(batch_size // head_size, seq_len, dim * head_size)
130-
axis_names = nn.logical_to_mesh_axes((BATCH, LENGTH, HEAD))
131-
return jax.lax.with_sharding_constraint(reshaped_tensor, axis_names)
130+
return nn.with_logical_constraint(reshaped_tensor, (BATCH, LENGTH, HEAD))
132131

133132

134133
def _reshape_heads_to_batch_dim(tensor, heads):
@@ -141,8 +140,7 @@ def _reshape_heads_to_batch_dim(tensor, heads):
141140
else:
142141
batch_size, head_size, seq_len, head_dim = tensor.shape
143142
reshaped_tensor = tensor.reshape(batch_size * head_size, seq_len, head_dim)
144-
axis_names = nn.logical_to_mesh_axes((BATCH, LENGTH, HEAD))
145-
return jax.lax.with_sharding_constraint(reshaped_tensor, axis_names)
143+
return nn.with_logical_constraint(reshaped_tensor, (BATCH, LENGTH, HEAD))
146144

147145

148146
def _reshape_heads_to_head_dim(tensor):
@@ -151,8 +149,7 @@ def _reshape_heads_to_head_dim(tensor):
151149
b, h, s, d = tensor.shape
152150
tensor = jnp.transpose(tensor, axes=[0, 2, 1, 3])
153151
reshaped_tensor = jnp.reshape(tensor, (b, -1, h * d))
154-
axis_names = nn.logical_to_mesh_axes((BATCH, LENGTH, HEAD))
155-
return jax.lax.with_sharding_constraint(reshaped_tensor, axis_names)
152+
return nn.with_logical_constraint(reshaped_tensor, (BATCH, LENGTH, HEAD))
156153

157154

158155
def _unflatten_heads(tensor, heads):
@@ -579,6 +576,7 @@ def _tpu_flash_attention(
579576
attention_mask: jax.Array = None,
580577
use_base2_exp: bool = False,
581578
use_experimental_scheduler: bool = False,
579+
is_causal: bool = False,
582580
) -> jax.Array:
583581
"""TPU Flash Attention"""
584582

@@ -656,8 +654,12 @@ def wrap_flash_attention(query, key, value, attention_mask):
656654
key, _, key_seq_len = _pad_data_for_flash(key, heads, block_kv)
657655
value, _, _ = _pad_data_for_flash(value, heads, block_kv)
658656

659-
mask = splash_attention_mask.FullMask(_shape=(query.shape[2], key.shape[2]))
660-
multi_head_mask = splash_attention_mask.MultiHeadMask(masks=(mask,) * query.shape[1])
657+
if is_causal:
658+
mask = splash_attention_mask.CausalMask((query.shape[2], key.shape[2]))
659+
multi_head_mask = splash_attention_mask.MultiHeadMask(masks=(mask,) * query.shape[1])
660+
else:
661+
mask = splash_attention_mask.FullMask(_shape=(query.shape[2], key.shape[2]))
662+
multi_head_mask = splash_attention_mask.MultiHeadMask(masks=(mask,) * query.shape[1])
661663

662664
segment_ids_cls = (
663665
tokamax_splash_base.SegmentIds if attention_kernel == "tokamax_ring" else splash_attention_kernel.SegmentIds
@@ -674,9 +676,14 @@ def wrap_flash_attention(query, key, value, attention_mask):
674676
# make_splash_mha is wrapped around shardmap and seq and head is already
675677
# sharded based on in_specs, therefore setting head_shards=1 and q_seq_shards=1.
676678
if attention_kernel == "tokamax_flash":
677-
mask = tokamax_splash_attention_mask.FullMask(
678-
_shape=(query.shape[2], key.shape[2]),
679-
)
679+
if is_causal:
680+
mask = tokamax_splash_attention_mask.CausalMask(
681+
(query.shape[2], key.shape[2]),
682+
)
683+
else:
684+
mask = tokamax_splash_attention_mask.FullMask(
685+
_shape=(query.shape[2], key.shape[2]),
686+
)
680687
splash_kernel = tokamax_splash_attention_kernel.make_splash_mha(
681688
mask=mask,
682689
q_seq_shards=1, # the sizes of the axis is sharding over seq_len
@@ -1571,10 +1578,9 @@ def _cudnn_flash_attention(query: Array, key: Array, value: Array, heads: int, m
15711578
key = _reshape_data_for_cudnn_flash(key, heads)
15721579
value = _reshape_data_for_cudnn_flash(value, heads)
15731580

1574-
axis_names = nn.logical_to_mesh_axes((BATCH, LENGTH, HEAD, D_KV))
1575-
query = jax.lax.with_sharding_constraint(query, axis_names)
1576-
key = jax.lax.with_sharding_constraint(key, axis_names)
1577-
value = jax.lax.with_sharding_constraint(value, axis_names)
1581+
query = nn.with_logical_constraint(query, (BATCH, LENGTH, HEAD, D_KV))
1582+
key = nn.with_logical_constraint(key, (BATCH, LENGTH, HEAD, D_KV))
1583+
value = nn.with_logical_constraint(value, (BATCH, LENGTH, HEAD, D_KV))
15781584

15791585
out = dpa_layer(query, key, value, mask=None)
15801586
return _reshape_data_from_cudnn_flash(out)
@@ -1788,6 +1794,7 @@ def flash_kernel(q, k, v, context):
17881794
attention_mask=context["attention_mask"],
17891795
use_base2_exp=context["use_base2_exp"],
17901796
use_experimental_scheduler=context["use_experimental_scheduler"],
1797+
is_causal=context.get("is_causal", False),
17911798
)
17921799

17931800

@@ -1809,6 +1816,7 @@ def tokamax_flash_kernel(q, k, v, context):
18091816
attention_mask=context["attention_mask"],
18101817
use_base2_exp=context["use_base2_exp"],
18111818
use_experimental_scheduler=context["use_experimental_scheduler"],
1819+
is_causal=context.get("is_causal", False),
18121820
)
18131821

18141822

@@ -1830,6 +1838,7 @@ def tokamax_ring_kernel(q, k, v, context):
18301838
attention_mask=context["attention_mask"],
18311839
use_base2_exp=context["use_base2_exp"],
18321840
use_experimental_scheduler=context["use_experimental_scheduler"],
1841+
is_causal=context.get("is_causal", False),
18331842
)
18341843

18351844

@@ -1883,6 +1892,7 @@ def _apply_attention(
18831892
use_experimental_scheduler: bool = False,
18841893
ulysses_shards: int = -1,
18851894
ulysses_attention_chunks: int = 1,
1895+
is_causal: bool = False,
18861896
):
18871897
"""Routes to different attention kernels using a module-level registry."""
18881898

@@ -1948,6 +1958,7 @@ def _apply_attention(
19481958
"float32_qk_product": float32_qk_product,
19491959
"use_memory_efficient_attention": use_memory_efficient_attention,
19501960
"dpa_layer": dpa_layer,
1961+
"is_causal": is_causal,
19511962
}
19521963

19531964
# Module-level Registry lookup
@@ -2281,6 +2292,7 @@ class AttentionOp(nn.Module):
22812292
use_experimental_scheduler: bool = False
22822293
ulysses_shards: int = -1
22832294
ulysses_attention_chunks: int = 1
2295+
is_causal: bool = False
22842296

22852297
def setup(self):
22862298
self.dpa_layer = None
@@ -2330,6 +2342,7 @@ def apply_attention(self, query: Array, key: Array, value: Array, attention_mask
23302342
use_experimental_scheduler=self.use_experimental_scheduler,
23312343
ulysses_shards=self.ulysses_shards,
23322344
ulysses_attention_chunks=self.ulysses_attention_chunks,
2345+
is_causal=self.is_causal,
23332346
)
23342347

23352348

@@ -2620,9 +2633,9 @@ def __call__(
26202633
rngs: nnx.Rngs = None,
26212634
cached_kv: Optional[Dict[str, Tuple[jax.Array, jax.Array]]] = None,
26222635
) -> jax.Array:
2623-
axis_names = nn.logical_to_mesh_axes((BATCH, LENGTH, HEAD))
2624-
hidden_states = jax.lax.with_sharding_constraint(hidden_states, axis_names)
2625-
encoder_hidden_states = jax.lax.with_sharding_constraint(encoder_hidden_states, axis_names)
2636+
hidden_states = nn.with_logical_constraint(hidden_states, (BATCH, LENGTH, HEAD))
2637+
if encoder_hidden_states is not None:
2638+
encoder_hidden_states = nn.with_logical_constraint(encoder_hidden_states, (BATCH, LENGTH, HEAD))
26262639
dtype = hidden_states.dtype
26272640
is_self_attention = encoder_hidden_states is None
26282641
if encoder_hidden_states is None:

src/maxdiffusion/models/qwen3_flax.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def __init__(
4747
ulysses_shards: int = -1,
4848
ulysses_attention_chunks: int = 1,
4949
max_layer_to_run: Optional[int] = 27,
50+
is_causal: bool = True,
5051
):
5152
self.vocab_size = vocab_size
5253
self.hidden_size = hidden_size
@@ -65,6 +66,7 @@ def __init__(
6566
self.ulysses_shards = ulysses_shards
6667
self.ulysses_attention_chunks = ulysses_attention_chunks
6768
self.max_layer_to_run = max_layer_to_run
69+
self.is_causal = is_causal
6870

6971

7072
# -----------------------------------------------------------------------------
@@ -278,6 +280,7 @@ def __call__(
278280
dtype=self.config.dtype,
279281
ulysses_shards=self.config.ulysses_shards,
280282
ulysses_attention_chunks=self.config.ulysses_attention_chunks,
283+
is_causal=getattr(self.config, "is_causal", True),
281284
)
282285
out = attn_op.apply_attention(q_3d, k_3d, v_3d, attention_mask=attention_mask)
283286
else:
@@ -293,9 +296,10 @@ def __call__(
293296

294297
scores = jnp.matmul(q_f, jnp.transpose(k_f, (0, 1, 3, 2))) / math.sqrt(self.config.head_dim)
295298

296-
# 7. Apply causal attention mask
297-
causal_mask = jnp.tril(jnp.ones((seq_len, seq_len), dtype=jnp.bool_))
298-
scores = jnp.where(causal_mask, scores, -1e4)
299+
# 7. Apply causal attention mask if configured
300+
if getattr(self.config, "is_causal", True):
301+
causal_mask = jnp.tril(jnp.ones((seq_len, seq_len), dtype=jnp.bool_))
302+
scores = jnp.where(causal_mask, scores, -1e4)
299303

300304
# 8. Apply padding attention mask if provided
301305
if attention_mask is not None:

src/maxdiffusion/pipelines/flux/flux2klein_pipeline.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ def qwen3_forward(q_params, ids, mask):
119119
h_27 = all_hidden_states[27]
120120
out = jnp.stack([h_9, h_18, h_27], axis=1)
121121
prompt_embeds = jnp.transpose(out, (0, 2, 1, 3)).reshape((ids.shape[0], ids.shape[1], -1))
122+
context_spec = P(None, "context") if "context" in self.mesh.axis_names and self.mesh.shape["context"] > 1 else P()
123+
prompt_embeds = jax.lax.with_sharding_constraint(prompt_embeds, jax.sharding.NamedSharding(self.mesh, context_spec))
122124
return prompt_embeds
123125

124126
@jax.jit(static_argnums=(4, 5), donate_argnums=(1,))
@@ -489,6 +491,9 @@ def put_data_on_devices(x, sharding):
489491
max_logging.log(f"{host_prefix} Passed Phase A Sync Barrier (phase_a_complete) successfully! ✅")
490492

491493
latents_jax = put_data_on_devices(latents_jax, data_sharding)
494+
context_spec = P(None, "context") if "context" in self.mesh.axis_names and self.mesh.shape["context"] > 1 else P()
495+
context_sharding = jax.sharding.NamedSharding(self.mesh, context_spec)
496+
prompt_embeds_jax = put_data_on_devices(prompt_embeds_jax, context_sharding)
492497
txt_ids_val = put_data_on_devices(txt_ids_val, data_sharding)
493498
img_ids_val = put_data_on_devices(img_ids_val, data_sharding)
494499

0 commit comments

Comments
 (0)