Skip to content

Commit b5bb27b

Browse files
committed
Add support for GQA with fused QKV
1 parent 8e7ff2d commit b5bb27b

1 file changed

Lines changed: 35 additions & 10 deletions

File tree

src/maxtext/layers/attentions.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
from maxtext.layers.quantizations import AqtQuantization as Quant
6868
from maxtext.inference import kvcache
6969
from maxtext.inference.kvcache import KVQuant
70-
from maxtext.utils.sharding import maybe_shard_with_logical, create_sharding
70+
from maxtext.utils.sharding import maybe_shard_with_logical, create_sharding, logical_to_mesh_axes
7171

7272
# pylint: disable=line-too-long, g-doc-args, g-doc-return-or-yield, bad-continuation, g-inconsistent-quotes
7373
# pytype: disable=attribute-error
@@ -555,6 +555,18 @@ def __init__(
555555
debug_sharding=config.debug_sharding,
556556
)
557557

558+
def _logical_to_mesh_axes(self, logical_name):
559+
logical_rules = None if self.config.using_pipeline_parallelism else self.config.logical_axis_rules
560+
return logical_to_mesh_axes(logical_name, mesh=self.mesh, rules=logical_rules)
561+
562+
def _validate_kv_heads(self) -> None:
563+
"""Validates the number of key/value heads."""
564+
if self.num_kv_heads == -1:
565+
raise ValueError("num_kv_heads is not defined.")
566+
567+
if self.num_query_heads % self.num_kv_heads != 0:
568+
raise ValueError("Invalid num_kv_heads for GQA.")
569+
558570
def _init_projections(self, inputs_q_shape: Tuple, inputs_kv_shape: Tuple) -> None:
559571
"""Initializes the query, key, value, and output projections."""
560572
if self.config.fused_qkv:
@@ -622,11 +634,7 @@ def init_kv_w(self, inputs_kv_shape: Tuple) -> nnx.Module:
622634
Returns:
623635
A DenseGeneral module that performs the key or value projection.
624636
"""
625-
if self.num_kv_heads == -1:
626-
raise ValueError("num_kv_heads is not defined.")
627-
628-
if self.num_query_heads % self.num_kv_heads != 0:
629-
raise ValueError("Invalid num_kv_heads for GQA.")
637+
self._validate_kv_heads()
630638

631639
kernel_axes = (
632640
(None, None, None)
@@ -672,12 +680,15 @@ def kv_projection(self, inputs_kv: Array, proj_name: str, out_sharding: NamedSha
672680
raise ValueError(f"proj_name must be 'key' or 'value', but got {proj_name}")
673681

674682
def init_qkv_w(self, inputs_shape: Tuple) -> nnx.Module:
683+
"""Initializes the a fused QKV projection using only one DenseGeneral module."""
684+
self._validate_kv_heads()
685+
675686
return DenseGeneral(
676687
in_features_shape=self.convert_dense_general_inputs_shape(inputs_shape),
677-
out_features_shape=(3, self.num_query_heads, self.head_dim),
688+
out_features_shape=(self.num_query_heads + 2 * self.num_kv_heads, self.head_dim),
678689
axis=-1,
679690
kernel_init=self.kernel_init,
680-
kernel_axes=("embed", "qkv", "heads", "kv"),
691+
kernel_axes=("embed", "heads", "kv"),
681692
dtype=self.dtype,
682693
weight_dtype=self.weight_dtype,
683694
quant=self.quant,
@@ -692,8 +703,22 @@ def qkv_projection(self, inputs: Array, proj_name: str, out_sharding: NamedShard
692703

693704
qkv_proj = self.qkv_proj(inputs, out_sharding)
694705
qkv_proj = checkpoint_name(qkv_proj, "qkv_proj")
695-
query, key, value = qkv_proj[:, :, 0, ...], qkv_proj[:, :, 1, ...], qkv_proj[:, :, 2, ...]
696-
return query, key, value
706+
707+
# Since fused QKV projection places all heads along the same axis which could be tensor
708+
# parallel partitioned, we must use shard_map to split into equally partitioned Q, K, V arrays.
709+
q_bshd = self._logical_to_mesh_axes(self.query_axis_names)
710+
k_bshd = self._logical_to_mesh_axes(self.key_axis_names)
711+
v_bshd = self._logical_to_mesh_axes(self.value_axis_names)
712+
713+
@jax.shard_map(mesh=self.mesh, in_specs=(q_bshd,), out_specs=(q_bshd, k_bshd, v_bshd))
714+
def split_qkv(qkv_proj: Array) -> tuple[Array, Array, Array]:
715+
num_local_heads = qkv_proj.shape[2]
716+
num_query_heads = (num_local_heads * self.num_query_heads) // (self.num_query_heads + 2 * self.num_kv_heads)
717+
num_kv_heads = (num_local_heads - num_query_heads) // 2
718+
719+
return tuple(jnp.split(qkv_proj, [num_query_heads, num_query_heads + num_kv_heads], axis=2))
720+
721+
return split_qkv(qkv_proj)
697722

698723
@property
699724
def out_head_dim(self) -> int:

0 commit comments

Comments
 (0)