@@ -571,9 +571,11 @@ def _splash_attention(q, k, v):
571571
572572# ---------------------------------------------------------------------------
573573# High-level attention function with shard_map
574+ # TODO: Move `tpu_custom_attention` and `make_custom_splash_sdpa`
575+ # out of this file and into `attention_flax.py` where other orchestration
576+ # functions live. This file should be strictly Pallas kernel definitions.
574577# ---------------------------------------------------------------------------
575578
576-
577579def tpu_custom_attention (
578580 query ,
579581 key ,
@@ -666,28 +668,55 @@ def _kernel_3d(q_3d, k_3d, v_3d):
666668 kv_partition_spec = P ()
667669 out_constraint = P ()
668670 else :
669- axis_names = mesh .axis_names
670- if len (axis_names ) == 1 :
671- tp_axis = axis_names [0 ]
672- q_partition_spec = P (None , tp_axis , None , None )
673- kv_partition_spec = P (None , tp_axis , None , None )
674- out_constraint = P (None , None , tp_axis , None )
675- elif len (axis_names ) == 2 :
676- dp_axis , tp_axis = axis_names [0 ], axis_names [1 ]
677- dp_size = mesh .shape [dp_axis ]
678- if batch_size >= dp_size :
679- q_partition_spec = P (dp_axis , tp_axis , None , None )
680- kv_partition_spec = P (dp_axis , tp_axis , None , None )
681- out_constraint = P (dp_axis , None , tp_axis , None )
671+ # Name-based axis resolution for the standard 4D mesh ("data", "fsdp", "context", "tensor").
672+ # Axes are grouped by semantic role:
673+ # - Batch-parallel: "data", "fsdp" (shard the batch dimension)
674+ # - Head-parallel: "context", "tensor" (shard the head dimension)
675+ # Only axes with size > 1 participate in sharding to avoid shard_map errors.
676+ # Q and KV are sharded identically (symmetric head-parallel), matching the
677+ # strategy that the legacy 2D (dp, tp) path used.
678+ batch_axes = tuple (a for a in ("data" , "fsdp" ) if a in mesh .shape and mesh .shape [a ] > 1 )
679+ head_axes = tuple (a for a in ("context" , "tensor" ) if a in mesh .shape and mesh .shape [a ] > 1 )
680+
681+ batch_parallel_size = 1
682+ for a in batch_axes :
683+ batch_parallel_size *= mesh .shape [a ]
684+ head_parallel_size = 1
685+ for a in head_axes :
686+ head_parallel_size *= mesh .shape [a ]
687+
688+ # Flatten singleton tuples for clean PartitionSpecs.
689+ batch_spec = batch_axes [0 ] if len (batch_axes ) == 1 else (batch_axes if batch_axes else None )
690+ head_spec = head_axes [0 ] if len (head_axes ) == 1 else (head_axes if head_axes else None )
691+
692+ if (
693+ batch_parallel_size > 1
694+ and batch_size >= batch_parallel_size
695+ and num_heads % head_parallel_size == 0
696+ ):
697+ # Head-parallel: batch on data/fsdp, heads on context/tensor.
698+ q_partition_spec = P (batch_spec , head_spec , None , None )
699+ kv_partition_spec = P (batch_spec , head_spec , None , None )
700+ out_constraint = P (batch_spec , None , head_spec , None )
701+ elif head_parallel_size > 1 or batch_parallel_size > 1 :
702+ # Batch too small for batch-parallel; shard all parallelism onto heads.
703+ all_axes = batch_axes + head_axes
704+ all_parallel_size = batch_parallel_size * head_parallel_size
705+ all_spec = all_axes [0 ] if len (all_axes ) == 1 else (all_axes if all_axes else None )
706+ if num_heads % all_parallel_size == 0 :
707+ q_partition_spec = P (None , all_spec , None , None )
708+ kv_partition_spec = P (None , all_spec , None , None )
709+ out_constraint = P (None , None , all_spec , None )
682710 else :
683- all_axes = tuple ( axis_names )
684- q_partition_spec = P (None , all_axes , None , None )
685- kv_partition_spec = P (None , all_axes , None , None )
686- out_constraint = P (None , None , all_axes , None )
711+ # Heads not divisible by available parallelism; fall back to replicated.
712+ q_partition_spec = P ()
713+ kv_partition_spec = P ()
714+ out_constraint = P ()
687715 else :
688- q_partition_spec = P (axis_names [0 ], axis_names [1 ], axis_names [2 ], None )
689- kv_partition_spec = P (axis_names [0 ], axis_names [1 ], None , None )
690- out_constraint = P (axis_names [0 ], None , (axis_names [1 ], axis_names [2 ]), None )
716+ # All axes are size-1; fully replicated (e.g. single-device).
717+ q_partition_spec = P ()
718+ kv_partition_spec = P ()
719+ out_constraint = P ()
691720
692721 sharded_fn = shard_map (
693722 _attention_on_slices ,
0 commit comments