|
from typing import * |
|
import torch |
|
import torch.nn as nn |
|
import torch.nn.functional as F |
|
from .. import SparseTensor |
|
from .full_attn import sparse_scaled_dot_product_attention |
|
from .serialized_attn import SerializeMode, sparse_serialized_scaled_dot_product_self_attention |
|
from .windowed_attn import sparse_windowed_scaled_dot_product_self_attention |
|
from ...attention import RotaryPositionEmbedder |
|
|
|
|
|
class SparseMultiHeadRMSNorm(nn.Module): |
|
def __init__(self, dim: int, heads: int): |
|
super().__init__() |
|
self.scale = dim ** 0.5 |
|
self.gamma = nn.Parameter(torch.ones(heads, dim)) |
|
|
|
def forward(self, x: Union[SparseTensor, torch.Tensor]) -> Union[SparseTensor, torch.Tensor]: |
|
x_type = x.dtype |
|
x = x.float() |
|
if isinstance(x, SparseTensor): |
|
x = x.replace(F.normalize(x.feats, dim=-1)) |
|
else: |
|
x = F.normalize(x, dim=-1) |
|
return (x * self.gamma * self.scale).to(x_type) |
|
|
|
|
|
class SparseMultiHeadAttention(nn.Module): |
|
def __init__( |
|
self, |
|
channels: int, |
|
num_heads: int, |
|
ctx_channels: Optional[int] = None, |
|
type: Literal["self", "cross"] = "self", |
|
attn_mode: Literal["full", "serialized", "windowed"] = "full", |
|
window_size: Optional[int] = None, |
|
shift_sequence: Optional[int] = None, |
|
shift_window: Optional[Tuple[int, int, int]] = None, |
|
serialize_mode: Optional[SerializeMode] = None, |
|
qkv_bias: bool = True, |
|
use_rope: bool = False, |
|
qk_rms_norm: bool = False, |
|
): |
|
super().__init__() |
|
assert channels % num_heads == 0 |
|
assert type in ["self", "cross"], f"Invalid attention type: {type}" |
|
assert attn_mode in ["full", "serialized", "windowed"], f"Invalid attention mode: {attn_mode}" |
|
assert type == "self" or attn_mode == "full", "Cross-attention only supports full attention" |
|
assert type == "self" or use_rope is False, "Rotary position embeddings only supported for self-attention" |
|
self.channels = channels |
|
self.ctx_channels = ctx_channels if ctx_channels is not None else channels |
|
self.num_heads = num_heads |
|
self._type = type |
|
self.attn_mode = attn_mode |
|
self.window_size = window_size |
|
self.shift_sequence = shift_sequence |
|
self.shift_window = shift_window |
|
self.serialize_mode = serialize_mode |
|
self.use_rope = use_rope |
|
self.qk_rms_norm = qk_rms_norm |
|
|
|
if self._type == "self": |
|
self.to_qkv = nn.Linear(channels, channels * 3, bias=qkv_bias) |
|
else: |
|
self.to_q = nn.Linear(channels, channels, bias=qkv_bias) |
|
self.to_kv = nn.Linear(self.ctx_channels, channels * 2, bias=qkv_bias) |
|
|
|
if self.qk_rms_norm: |
|
self.q_rms_norm = SparseMultiHeadRMSNorm(channels // num_heads, num_heads) |
|
self.k_rms_norm = SparseMultiHeadRMSNorm(channels // num_heads, num_heads) |
|
|
|
self.to_out = nn.Linear(channels, channels) |
|
|
|
if use_rope: |
|
self.rope = RotaryPositionEmbedder(channels) |
|
|
|
@staticmethod |
|
def _linear(module: nn.Linear, x: Union[SparseTensor, torch.Tensor]) -> Union[SparseTensor, torch.Tensor]: |
|
if isinstance(x, SparseTensor): |
|
return x.replace(module(x.feats)) |
|
else: |
|
return module(x) |
|
|
|
@staticmethod |
|
def _reshape_chs(x: Union[SparseTensor, torch.Tensor], shape: Tuple[int, ...]) -> Union[SparseTensor, torch.Tensor]: |
|
if isinstance(x, SparseTensor): |
|
return x.reshape(*shape) |
|
else: |
|
return x.reshape(*x.shape[:2], *shape) |
|
|
|
def _fused_pre(self, x: Union[SparseTensor, torch.Tensor], num_fused: int) -> Union[SparseTensor, torch.Tensor]: |
|
if isinstance(x, SparseTensor): |
|
x_feats = x.feats.unsqueeze(0) |
|
else: |
|
x_feats = x |
|
x_feats = x_feats.reshape(*x_feats.shape[:2], num_fused, self.num_heads, -1) |
|
return x.replace(x_feats.squeeze(0)) if isinstance(x, SparseTensor) else x_feats |
|
|
|
def _rope(self, qkv: SparseTensor) -> SparseTensor: |
|
q, k, v = qkv.feats.unbind(dim=1) |
|
q, k = self.rope(q, k, qkv.coords[:, 1:]) |
|
qkv = qkv.replace(torch.stack([q, k, v], dim=1)) |
|
return qkv |
|
|
|
def forward(self, x: Union[SparseTensor, torch.Tensor], context: Optional[Union[SparseTensor, torch.Tensor]] = None) -> Union[SparseTensor, torch.Tensor]: |
|
if self._type == "self": |
|
qkv = self._linear(self.to_qkv, x) |
|
qkv = self._fused_pre(qkv, num_fused=3) |
|
if self.use_rope: |
|
qkv = self._rope(qkv) |
|
if self.qk_rms_norm: |
|
q, k, v = qkv.unbind(dim=1) |
|
q = self.q_rms_norm(q) |
|
k = self.k_rms_norm(k) |
|
qkv = qkv.replace(torch.stack([q.feats, k.feats, v.feats], dim=1)) |
|
if self.attn_mode == "full": |
|
h = sparse_scaled_dot_product_attention(qkv) |
|
elif self.attn_mode == "serialized": |
|
h = sparse_serialized_scaled_dot_product_self_attention( |
|
qkv, self.window_size, serialize_mode=self.serialize_mode, shift_sequence=self.shift_sequence, shift_window=self.shift_window |
|
) |
|
elif self.attn_mode == "windowed": |
|
h = sparse_windowed_scaled_dot_product_self_attention( |
|
qkv, self.window_size, shift_window=self.shift_window |
|
) |
|
else: |
|
q = self._linear(self.to_q, x) |
|
q = self._reshape_chs(q, (self.num_heads, -1)) |
|
kv = self._linear(self.to_kv, context) |
|
kv = self._fused_pre(kv, num_fused=2) |
|
if self.qk_rms_norm: |
|
q = self.q_rms_norm(q) |
|
k, v = kv.unbind(dim=1) |
|
k = self.k_rms_norm(k) |
|
kv = kv.replace(torch.stack([k.feats, v.feats], dim=1)) |
|
h = sparse_scaled_dot_product_attention(q, kv) |
|
h = self._reshape_chs(h, (-1,)) |
|
h = self._linear(self.to_out, h) |
|
return h |
|
|