6868from maxtext .layers .quantizations import AqtQuantization as Quant
6969from maxtext .inference import kvcache
7070from maxtext .inference .kvcache import KVQuant
71- from maxtext .utils .sharding import maybe_shard_with_logical , create_sharding
71+ from maxtext .utils .sharding import maybe_shard_with_logical , create_sharding , logical_to_mesh_axes
7272
7373# pylint: disable=line-too-long, g-doc-args, g-doc-return-or-yield, bad-continuation, g-inconsistent-quotes
7474# pytype: disable=attribute-error
@@ -558,6 +558,20 @@ def __init__(
558558 debug_sharding = config .debug_sharding ,
559559 )
560560
561+ def _logical_to_mesh_axes (self , logical_name ):
562+ # Pipeline parallelism uses context managers for logical rules instead of the config,
563+ # so pass None to ensure `logical_to_mesh_axes` defers to using the current Flax context manager
564+ logical_rules = None if self .config .using_pipeline_parallelism else self .config .logical_axis_rules
565+ return logical_to_mesh_axes (logical_name , mesh = self .mesh , rules = logical_rules )
566+
567+ def _validate_kv_heads (self ) -> None :
568+ """Validates the number of key/value heads."""
569+ if self .num_kv_heads == - 1 :
570+ raise ValueError ("num_kv_heads is not defined." )
571+
572+ if self .num_query_heads % self .num_kv_heads != 0 :
573+ raise ValueError ("Invalid num_kv_heads for GQA." )
574+
561575 def _init_projections (self , inputs_q_shape : Tuple , inputs_kv_shape : Tuple ) -> None :
562576 """Initializes the query, key, value, and output projections."""
563577 if self .config .fused_qkv :
@@ -625,11 +639,7 @@ def init_kv_w(self, inputs_kv_shape: Tuple) -> nnx.Module:
625639 Returns:
626640 A DenseGeneral module that performs the key or value projection.
627641 """
628- if self .num_kv_heads == - 1 :
629- raise ValueError ("num_kv_heads is not defined." )
630-
631- if self .num_query_heads % self .num_kv_heads != 0 :
632- raise ValueError ("Invalid num_kv_heads for GQA." )
642+ self ._validate_kv_heads ()
633643
634644 kernel_axes = (
635645 (None , None , None )
@@ -675,12 +685,15 @@ def kv_projection(self, inputs_kv: Array, proj_name: str, out_sharding: NamedSha
675685 raise ValueError (f"proj_name must be 'key' or 'value', but got { proj_name } " )
676686
677687 def init_qkv_w (self , inputs_shape : Tuple ) -> nnx .Module :
688+ """Initializes the a fused QKV projection using only one DenseGeneral module."""
689+ self ._validate_kv_heads ()
690+
678691 return DenseGeneral (
679692 in_features_shape = self .convert_dense_general_inputs_shape (inputs_shape ),
680- out_features_shape = (3 , self .num_query_heads , self .head_dim ),
693+ out_features_shape = (self .num_query_heads + 2 * self . num_kv_heads , self .head_dim ),
681694 axis = - 1 ,
682695 kernel_init = self .kernel_init ,
683- kernel_axes = ("embed" , "qkv" , " heads" , "kv" ),
696+ kernel_axes = ("embed" , "heads" , "kv" ),
684697 dtype = self .dtype ,
685698 weight_dtype = self .weight_dtype ,
686699 quant = self .quant ,
@@ -695,8 +708,22 @@ def qkv_projection(self, inputs: Array, proj_name: str, out_sharding: NamedShard
695708
696709 qkv_proj = self .qkv_proj (inputs , out_sharding )
697710 qkv_proj = checkpoint_name (qkv_proj , "qkv_proj" )
698- query , key , value = qkv_proj [:, :, 0 , ...], qkv_proj [:, :, 1 , ...], qkv_proj [:, :, 2 , ...]
699- return query , key , value
711+
712+ # Since fused QKV projection places all heads along the same axis which could be tensor
713+ # parallel partitioned, we must use shard_map to split into equally partitioned Q, K, V arrays.
714+ q_bshd = self ._logical_to_mesh_axes (self .query_axis_names )
715+ k_bshd = self ._logical_to_mesh_axes (self .key_axis_names )
716+ v_bshd = self ._logical_to_mesh_axes (self .value_axis_names )
717+
718+ @jax .shard_map (mesh = self .mesh , in_specs = (q_bshd ,), out_specs = (q_bshd , k_bshd , v_bshd ))
719+ def split_qkv (qkv_proj : Array ) -> tuple [Array , Array , Array ]:
720+ num_local_heads = qkv_proj .shape [2 ]
721+ num_query_heads = (num_local_heads * self .num_query_heads ) // (self .num_query_heads + 2 * self .num_kv_heads )
722+ num_kv_heads = (num_local_heads - num_query_heads ) // 2
723+
724+ return tuple (jnp .split (qkv_proj , [num_query_heads , num_query_heads + num_kv_heads ], axis = 2 ))
725+
726+ return split_qkv (qkv_proj )
700727
701728 @property
702729 def out_head_dim (self ) -> int :
0 commit comments