MustaqiLLM / attention.py
kmamaroziqov's picture
MilliyLM-5B: instruction-tuned Uzbek chat model (SFT of NeuronAI-5B-Base)
80c3430 verified
Raw
History Blame Contribute Delete
9.95 kB
from __future__ import annotations
from typing import Any
import torch
import torch.nn.functional as F
from torch import Tensor, nn
from transformers import Cache
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
from .configuration_neuron_lm import NeuronLMConfig
from .layers import RMSNorm
from .rotary import apply_rotary_pos_emb
__all__ = ["NeuronLMAttention"]
def _repeat_kv(hidden_states: Tensor, repeats: int) -> Tensor:
if repeats == 1:
return hidden_states
return hidden_states.repeat_interleave(repeats, dim=1)
def eager_attention_forward(
module: NeuronLMAttention,
query: Tensor,
key: Tensor,
value: Tensor,
attention_mask: Tensor | None,
*,
scaling: float,
dropout: float = 0.0,
**_: Any,
) -> tuple[Tensor, Tensor]:
"""Numerically clear GQA reference used for attention-weight outputs."""
key = _repeat_kv(key, module.num_key_value_groups)
value = _repeat_kv(value, module.num_key_value_groups)
attention_weights = (
torch.matmul(
query,
key.transpose(-2, -1),
)
* scaling
)
fully_masked: Tensor | None = None
if attention_mask is not None:
if attention_mask.dtype == torch.bool:
fully_masked = ~attention_mask.any(
dim=-1,
keepdim=True,
)
attention_weights = attention_weights.masked_fill(
~attention_mask,
torch.finfo(attention_weights.dtype).min,
)
else:
minimum = torch.finfo(attention_mask.dtype).min
fully_masked = (
torch.isneginf(attention_mask) | (attention_mask == minimum)
).all(dim=-1, keepdim=True)
attention_weights = attention_weights + attention_mask
if fully_masked is not None:
attention_weights = attention_weights.masked_fill(
fully_masked,
0.0,
)
attention_weights = F.softmax(
attention_weights,
dim=-1,
dtype=torch.float32,
).to(query.dtype)
attention_weights = torch.nan_to_num(
attention_weights,
nan=0.0,
)
if fully_masked is not None:
attention_weights = attention_weights.masked_fill(
fully_masked,
0.0,
)
attention_weights = F.dropout(
attention_weights,
p=dropout,
training=module.training,
)
attention_output = torch.matmul(attention_weights, value)
return (
attention_output.transpose(1, 2).contiguous(),
attention_weights,
)
class NeuronLMAttention(nn.Module):
"""Fused, bias-free GQA using a checkpoint-stable ``[Q, K, V]`` layout."""
def __init__(
self,
config: NeuronLMConfig,
layer_idx: int = 0,
) -> None:
super().__init__()
if type(layer_idx) is not int or layer_idx < 0:
raise ValueError(
f"layer_idx must be a non-negative integer, got {layer_idx!r}"
)
self.config = config
self.hidden_size = config.hidden_size
self.num_heads = config.num_attention_heads
self.num_key_value_heads = config.num_key_value_heads
self.num_key_value_groups = self.num_heads // self.num_key_value_heads
self.head_dim = config.head_dim
self.scaling = self.head_dim**-0.5
self.attention_dropout = config.attention_dropout
self.layer_idx = layer_idx
self.is_causal = True
self.query_size = self.num_heads * self.head_dim
self.key_value_size = self.num_key_value_heads * self.head_dim
# State-dict contract: rows are Q, then K, then V.
self.qkv_proj = nn.Linear(
in_features=self.hidden_size,
out_features=config.qkv_projection_size,
bias=False,
)
self.out_proj = nn.Linear(
in_features=self.query_size,
out_features=self.hidden_size,
bias=False,
)
# Per-head normalization of queries and keys before RoPE, as in
# Qwen3 / OLMo-2 / Gemma-3. Bounds the growth of q.k during long bf16
# runs, which depth-scaled initialization does not address: init
# controls the residual stream at step 0, while attention logits
# drift as the projection norms are learned.
self.use_qk_norm = config.use_qk_norm
if self.use_qk_norm:
self.q_norm = RMSNorm(
hidden_size=self.head_dim,
eps=config.rms_norm_eps,
)
self.k_norm = RMSNorm(
hidden_size=self.head_dim,
eps=config.rms_norm_eps,
)
def forward(
self,
hidden_states: Tensor,
position_embeddings: tuple[Tensor, Tensor],
attention_mask: Tensor | None = None,
past_key_values: Cache | None = None,
output_attentions: bool = False,
**kwargs: Any,
) -> Tensor | tuple[Tensor, Tensor | None]:
if hidden_states.ndim != 3:
raise ValueError(
"hidden_states must have shape "
"(batch_size, sequence_length, hidden_size), "
f"got shape={tuple(hidden_states.shape)}"
)
batch_size, sequence_length, hidden_size = hidden_states.shape
if hidden_size != self.hidden_size:
raise ValueError(
f"Expected hidden_size={self.hidden_size}, "
f"got hidden_size={hidden_size}"
)
if sequence_length == 0:
raise ValueError("sequence_length must be greater than zero")
cos, sin = position_embeddings
query_states, key_states, value_states = self._project_qkv(hidden_states)
query_states, key_states = apply_rotary_pos_emb(
query=query_states,
key=key_states,
cos=cos,
sin=sin,
)
if past_key_values is not None:
# Transformers v5 caches track their own write offset; passing
# cache_position here was removed from the library's convention.
key_states, value_states = past_key_values.update(
key_states,
value_states,
self.layer_idx,
)
implementation = self.config._attn_implementation or "sdpa"
if output_attentions:
implementation = "eager"
attention_interface = ALL_ATTENTION_FUNCTIONS.get_interface(
implementation,
eager_attention_forward,
)
attention_output, attention_weights = attention_interface(
self,
query_states,
key_states,
value_states,
attention_mask,
dropout=(self.attention_dropout if self.training else 0.0),
scaling=self.scaling,
output_attentions=output_attentions,
**kwargs,
)
attention_output = attention_output.reshape(
batch_size,
sequence_length,
self.query_size,
)
attention_output = self.out_proj(attention_output)
if output_attentions:
return attention_output, attention_weights
return attention_output
def _project_qkv(
self,
hidden_states: Tensor,
) -> tuple[Tensor, Tensor, Tensor]:
batch_size, sequence_length, _ = hidden_states.shape
qkv_states = self.qkv_proj(hidden_states)
query_states, key_states, value_states = qkv_states.split(
(
self.query_size,
self.key_value_size,
self.key_value_size,
),
dim=-1,
)
query_states = query_states.view(
batch_size,
sequence_length,
self.num_heads,
self.head_dim,
).transpose(1, 2)
key_states = key_states.view(
batch_size,
sequence_length,
self.num_key_value_heads,
self.head_dim,
).transpose(1, 2)
value_states = value_states.view(
batch_size,
sequence_length,
self.num_key_value_heads,
self.head_dim,
).transpose(1, 2)
# Applied before RoPE so the rotation acts on unit-scale vectors and
# the norm never sees position-dependent structure.
if self.use_qk_norm:
query_states = self.q_norm(query_states)
key_states = self.k_norm(key_states)
return query_states, key_states, value_states
def _load_from_state_dict(
self,
state_dict: dict[str, Tensor],
prefix: str,
local_metadata: dict[str, Any],
strict: bool,
missing_keys: list[str],
unexpected_keys: list[str],
error_msgs: list[str],
) -> None:
qkv_key = f"{prefix}qkv_proj.weight"
qkv_weight = state_dict.get(qkv_key)
expected_shape = tuple(self.qkv_proj.weight.shape)
if qkv_weight is not None and tuple(qkv_weight.shape) != expected_shape:
error_msgs.append(
f"{qkv_key} must use fused [Q, K, V] layout with shape "
f"{expected_shape}, got {tuple(qkv_weight.shape)}"
)
super()._load_from_state_dict(
state_dict,
prefix,
local_metadata,
strict,
missing_keys,
unexpected_keys,
error_msgs,
)
def extra_repr(self) -> str:
return (
f"hidden_size={self.hidden_size}, "
f"num_heads={self.num_heads}, "
f"num_key_value_heads={self.num_key_value_heads}, "
f"head_dim={self.head_dim}, "
f"attention_dropout={self.attention_dropout}, "
f"use_qk_norm={self.use_qk_norm}, "
f"layer_idx={self.layer_idx}"
)