-
Notifications
You must be signed in to change notification settings - Fork 2
[DRAFT] Add native GQA attention support for ORT GenAI compatibility #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
|
|
||
| from mobius._configs import ArchitectureConfig | ||
| from mobius.components._attention import Attention, StaticCacheState | ||
| from mobius.components._gqa_attention import GQAContext | ||
| from mobius.components._mlp import MLP | ||
| from mobius.components._rms_norm import RMSNorm | ||
|
|
||
|
|
@@ -52,15 +53,18 @@ def __init__( | |
| attention_scale: float | None = None, | ||
| post_norm: bool = False, | ||
| linear_class: type | None = None, | ||
| attention_class: type[nn.Module] | None = None, | ||
| ): | ||
| super().__init__() | ||
| if norm_class is None: | ||
| norm_class = RMSNorm | ||
| if attention_class is None: | ||
| attention_class = Attention | ||
|
|
||
| self._post_norm = post_norm | ||
| self._residual_multiplier = residual_multiplier | ||
|
|
||
| self.self_attn = Attention( | ||
| self.self_attn = attention_class( | ||
| config, | ||
| rms_norm_class=norm_class, | ||
| scale=attention_scale, | ||
|
|
@@ -97,6 +101,17 @@ def forward( | |
| else: | ||
| static_cache = None | ||
|
|
||
| # GQA mode: when attention_bias is a GQAContext, the attention | ||
| # component is GQAAttention and handles masking + RoPE internally. | ||
| if isinstance(attention_bias, GQAContext): | ||
| gqa_context = attention_bias | ||
| return self._forward_gqa( | ||
| op, | ||
| hidden_states, | ||
| gqa_context, | ||
| past_key_value, | ||
| ) | ||
|
Comment on lines
+104
to
+113
|
||
|
|
||
| if self._post_norm: | ||
| return self._forward_post_norm( | ||
| op, | ||
|
|
@@ -150,6 +165,42 @@ def _forward_pre_norm( | |
|
|
||
| return hidden_states, present_key_value | ||
|
|
||
| def _forward_gqa( | ||
| self, | ||
| op: builder.OpBuilder, | ||
| hidden_states: ir.Value, | ||
| gqa_context: GQAContext, | ||
| past_key_value: tuple | None, | ||
| ): | ||
| """Forward pass for GQA mode (pre-norm only). | ||
|
|
||
| GQAAttention handles RoPE and causal masking internally, so | ||
| position_embeddings and attention_bias are not needed. | ||
| """ | ||
| residual = hidden_states | ||
| hidden_states = self.input_layernorm(op, hidden_states) | ||
|
|
||
| attn_output, present_key_value = self.self_attn( | ||
| op, | ||
| hidden_states=hidden_states, | ||
| gqa_context=gqa_context, | ||
| past_key_value=past_key_value, | ||
| ) | ||
|
|
||
| if not math.isclose(self._residual_multiplier, 1.0): | ||
| attn_output = op.Mul(attn_output, self._residual_multiplier) | ||
| hidden_states = op.Add(residual, attn_output) | ||
|
|
||
| residual = hidden_states | ||
| hidden_states = self.post_attention_layernorm(op, hidden_states) | ||
| hidden_states = self.mlp(op, hidden_states) | ||
|
|
||
| if not math.isclose(self._residual_multiplier, 1.0): | ||
| hidden_states = op.Mul(hidden_states, self._residual_multiplier) | ||
| hidden_states = op.Add(residual, hidden_states) | ||
|
|
||
| return hidden_states, present_key_value | ||
|
|
||
| def _forward_post_norm( | ||
| self, | ||
| op: builder.OpBuilder, | ||
|
|
@@ -185,6 +236,7 @@ def create_decoder_layer( | |
| norm_class: type[nn.Module] | None = None, | ||
| post_norm: bool = False, | ||
| linear_class: type | None = None, | ||
| attention_class: type[nn.Module] | None = None, | ||
| ) -> DecoderLayer: | ||
| """Config-driven factory for creating decoder layers. | ||
|
|
||
|
|
@@ -200,6 +252,8 @@ def create_decoder_layer( | |
| post_norm: If True, use post-norm residual connections (OLMo-2 style). | ||
| linear_class: Factory callable for projection layers. Pass a LoRA | ||
| factory for LoRA-adapted layers. | ||
| attention_class: Attention module class override (default: Attention). | ||
| Pass GQAAttention for ORT GenAI-compatible models. | ||
|
|
||
| Returns: | ||
| A configured DecoderLayer instance. | ||
|
|
@@ -214,6 +268,7 @@ def create_decoder_layer( | |
| attention_scale=attention_scale, | ||
| post_norm=post_norm, | ||
| linear_class=linear_class, | ||
| attention_class=attention_class, | ||
| ) | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--gqaunconditionally setsmodule_kwargs = {"attention_class": GQAAttention}and then instantiatesmodule_class(config, **module_kwargs). Many registered model classes don’t accept anattention_classkwarg (e.g., models with custom__init__signatures), so this will raise aTypeErrorwith a confusing message. Consider detecting support (e.g., viainspect.signature/try/except TypeError) and failing with a clear CLI error listing supported model types, or only injectingattention_classfor known compatible base models.