RiboSphere / src /models /attention.py
zz312's picture
Upload folder using huggingface_hub
cf5d356 verified
Raw
History Blame
11.9 kB
"""Attention building blocks used by RiboSphere."""
from __future__ import annotations
from typing import Any
import torch
from torch import Tensor, nn
import torch.nn.functional as F
from torch.nn.attention.flex_attention import create_block_mask, flex_attention
from .layers import FeedForward
from .rotary import RotaryEmbedding
AttentionArguments = dict[str, Any]
def root_mean_square_norm(tensor: Tensor) -> Tensor:
"""Apply parameter-free RMS normalization over the final dimension."""
return F.rms_norm(tensor, (tensor.shape[-1],))
class TransformerStack(nn.Module):
"""Stack of local self-attention blocks."""
def __init__(
self,
*,
num_channels: int,
num_heads: int,
mlp_factor: int,
window_size: int,
num_layers: int,
attention_backend: str = "flex",
dropout: float = 0.1,
pairwise_channels: int = 0,
is_causal: bool = False,
) -> None:
super().__init__()
if num_channels <= 0 or num_heads <= 0 or num_layers <= 0:
raise ValueError(
"num_channels, num_heads, and num_layers must be positive."
)
if num_channels % num_heads != 0:
raise ValueError("num_channels must be divisible by num_heads.")
if window_size <= 0:
raise ValueError("window_size must be positive.")
if pairwise_channels < 0:
raise ValueError("pairwise_channels cannot be negative.")
attention_backend = attention_backend.lower()
if attention_backend not in {"sdpa", "flex"}:
raise ValueError("attention_backend must be 'sdpa' or 'flex'.")
use_pair_bias = pairwise_channels > 0
self.blocks = nn.ModuleList(
[
TransformerBlock(
num_channels=num_channels,
num_heads=num_heads,
mlp_factor=mlp_factor,
attention_backend=attention_backend,
dropout=dropout,
use_pairwise_bias=use_pair_bias,
pairwise_channels=pairwise_channels,
)
for _ in range(num_layers)
]
)
self.window_size = window_size
self.is_causal = is_causal
self.attention_backend = attention_backend
def _window_mask(
self,
batch_index: Tensor,
head_index: Tensor,
query_index: Tensor,
key_value_index: Tensor,
) -> Tensor:
del batch_index, head_index
within_window = (query_index - key_value_index).abs() <= self.window_size
if self.is_causal:
within_window = within_window & (query_index >= key_value_index)
return within_window
def forward(
self,
hidden_states: Tensor,
pairwise_features: Tensor | None = None,
) -> Tensor:
"""Transform ``[B, L, D]`` token features."""
if hidden_states.ndim != 3:
raise ValueError("hidden_states must have shape [B, L, D].")
sequence_length = hidden_states.shape[1]
if pairwise_features is not None and pairwise_features.shape[:3] != (
hidden_states.shape[0],
sequence_length,
sequence_length,
):
raise ValueError(
"pairwise_features must have shape [B, L, L, P]."
)
if self.attention_backend == "flex":
attention_arguments: AttentionArguments = {
"block_mask": create_block_mask(
self._window_mask,
B=None,
H=None,
Q_LEN=sequence_length,
KV_LEN=sequence_length,
device=hidden_states.device,
),
"score_mod": None,
}
else:
positions = torch.arange(
sequence_length,
device=hidden_states.device,
)
attention_mask = (
positions[:, None] - positions[None, :]
).abs() <= self.window_size
if self.is_causal:
attention_mask = attention_mask & (
positions[:, None] >= positions[None, :]
)
attention_arguments = {"attn_mask": attention_mask.unsqueeze(0)}
for block in self.blocks:
hidden_states = block(
hidden_states,
pairwise_features=pairwise_features,
**attention_arguments,
)
return hidden_states
class TransformerBlock(nn.Module):
"""Pre-normalized self-attention and feed-forward block."""
def __init__(
self,
*,
num_channels: int,
num_heads: int,
mlp_factor: int,
attention_backend: str = "flex",
dropout: float = 0.1,
use_pairwise_bias: bool = False,
pairwise_channels: int = 0,
) -> None:
super().__init__()
self.attention_backend = attention_backend
self.attention = SelfAttention(
model_dimension=num_channels,
num_heads=num_heads,
dropout=dropout,
attention_backend=attention_backend,
)
self.feed_forward = FeedForward(
num_channels,
num_channels * mlp_factor,
num_channels,
activation=nn.GELU,
dropout=dropout,
)
self.use_pairwise_bias = use_pairwise_bias
if use_pairwise_bias:
if pairwise_channels <= 0:
raise ValueError(
"pairwise_channels must be positive when pair bias is enabled."
)
self.pair_bias_projection = nn.Linear(
pairwise_channels, 1, bias=True
)
self.pair_bias_norm = nn.LayerNorm(pairwise_channels)
else:
self.pair_bias_projection = None
self.pair_bias_norm = None
def _add_pair_bias(
self,
pairwise_features: Tensor,
attention_arguments: AttentionArguments,
) -> AttentionArguments:
if self.pair_bias_projection is None or self.pair_bias_norm is None:
return attention_arguments
pair_bias = self.pair_bias_projection(
self.pair_bias_norm(pairwise_features)
).squeeze(-1)
attention_arguments = dict(attention_arguments)
if self.attention_backend == "flex":
def pair_biased_score(
score: Tensor,
batch_index: Tensor,
head_index: Tensor,
query_index: Tensor,
key_value_index: Tensor,
) -> Tensor:
del head_index
return score + pair_bias[
batch_index,
query_index,
key_value_index,
]
attention_arguments["score_mod"] = pair_biased_score
else:
attention_mask = attention_arguments["attn_mask"]
additive_pair_bias = torch.where(
attention_mask,
pair_bias,
torch.full_like(pair_bias, -torch.inf),
)
attention_arguments["attn_mask"] = additive_pair_bias.unsqueeze(1)
return attention_arguments
def forward(
self,
hidden_states: Tensor,
*,
pairwise_features: Tensor | None = None,
**attention_arguments: Any,
) -> Tensor:
if self.use_pairwise_bias:
if pairwise_features is None:
raise ValueError(
"pairwise_features are required when pair bias is enabled."
)
attention_arguments = self._add_pair_bias(
pairwise_features,
attention_arguments,
)
hidden_states = hidden_states + self.attention(
root_mean_square_norm(hidden_states),
**attention_arguments,
)
hidden_states = hidden_states + self.feed_forward(
root_mean_square_norm(hidden_states)
)
return hidden_states
class SelfAttention(nn.Module):
"""Multi-head self-attention with rotary position embeddings."""
def __init__(
self,
model_dimension: int,
num_heads: int,
*,
normalize_queries_and_keys: bool = False,
attention_backend: str = "flex",
dropout: float = 0.1,
) -> None:
super().__init__()
if model_dimension <= 0 or num_heads <= 0:
raise ValueError("model_dimension and num_heads must be positive.")
if model_dimension % num_heads != 0:
raise ValueError("model_dimension must be divisible by num_heads.")
if not 0.0 <= dropout < 1.0:
raise ValueError("dropout must be in [0, 1).")
attention_backend = attention_backend.lower()
if attention_backend not in {"flex", "sdpa"}:
raise ValueError("backend must be 'flex' or 'sdpa'.")
self.model_dimension = model_dimension
self.num_heads = num_heads
self.head_dimension = self.model_dimension // self.num_heads
self.attention_dropout = nn.Dropout(dropout)
self.dropout = dropout
self.normalize_queries_and_keys = normalize_queries_and_keys
self.rotary_embedding = RotaryEmbedding(self.head_dimension)
self.qkv_projection = nn.Linear(
model_dimension, 3 * model_dimension, bias=True
)
self.output_projection = nn.Linear(model_dimension, model_dimension)
self.residual_dropout = nn.Dropout(dropout)
self.attention_backend = attention_backend
def forward(
self,
hidden_states: Tensor,
**attention_arguments: Any,
) -> Tensor:
"""Apply self-attention to ``[B, L, D]`` hidden states."""
if hidden_states.ndim != 3:
raise ValueError("hidden_states must have shape [B, L, D].")
batch_size, sequence_length, hidden_dimension = hidden_states.shape
if hidden_dimension != self.model_dimension:
raise ValueError(
f"Expected hidden dimension {self.model_dimension}, "
f"received {hidden_dimension}."
)
query, key, value = self.qkv_projection(hidden_states).split(
self.model_dimension, dim=-1
)
def split_heads(tensor: Tensor) -> Tensor:
return tensor.reshape(
batch_size,
sequence_length,
self.num_heads,
self.head_dimension,
).transpose(1, 2)
query, key, value = map(split_heads, (query, key, value))
if self.normalize_queries_and_keys:
query = root_mean_square_norm(query)
key = root_mean_square_norm(key)
query, key = self.rotary_embedding(query, key)
if self.attention_backend == "flex":
attention_output = flex_attention(
query,
key,
value,
block_mask=attention_arguments.get("block_mask"),
score_mod=attention_arguments.get("score_mod"),
)
else:
attention_output = F.scaled_dot_product_attention(
query,
key,
value,
**attention_arguments,
)
attention_output = self.attention_dropout(attention_output)
attention_output = attention_output.transpose(1, 2).contiguous().view(
batch_size,
sequence_length,
self.model_dimension,
)
attention_output = self.residual_dropout(
self.output_projection(attention_output)
)
return attention_output