| import math |
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
|
|
|
|
| def precompute_rope(head_dim: int, max_seq_len: int, base: int = 10000) -> tuple[torch.Tensor, torch.Tensor]: |
| theta = 1.0 / (base ** (torch.arange(0, head_dim, 2).float() / head_dim)) |
| pos = torch.arange(max_seq_len).float() |
| freqs = torch.outer(pos, theta) |
| cos = torch.cos(freqs) |
| sin = torch.sin(freqs) |
| return cos, sin |
|
|
|
|
| def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, start_pos: int = 0) -> torch.Tensor: |
| |
| T = x.size(-2) |
| cos = cos[start_pos:start_pos + T].unsqueeze(0).unsqueeze(0) |
| sin = sin[start_pos:start_pos + T].unsqueeze(0).unsqueeze(0) |
| x1, x2 = x[..., ::2], x[..., 1::2] |
| x_even = x1 * cos - x2 * sin |
| x_odd = x2 * cos + x1 * sin |
| return torch.stack([x_even, x_odd], dim=-1).flatten(-2) |
|
|
|
|
| class MultiHeadAttention(nn.Module): |
| """ |
| Self-attention with RoPE and optional KV cache. |
| |
| kv_cache: dict, mutated in-place. Pass {} on the first incremental step; |
| on subsequent steps pass the same dict to grow the cache. |
| """ |
| def __init__(self, config, causal: bool = False): |
| super().__init__() |
| assert config.d_model % config.num_heads == 0 |
| self.num_heads = config.num_heads |
| self.head_dim = config.d_model // config.num_heads |
| self.causal = causal |
| self.dropout_p = config.dropout |
|
|
| self.q = nn.Linear(config.d_model, config.d_model, bias=False) |
| self.k = nn.Linear(config.d_model, config.d_model, bias=False) |
| self.v = nn.Linear(config.d_model, config.d_model, bias=False) |
| self.out = nn.Linear(config.d_model, config.d_model, bias=False) |
|
|
| cos, sin = precompute_rope(self.head_dim, config.max_seq_len) |
| self.register_buffer("rope_cos", cos) |
| self.register_buffer("rope_sin", sin) |
|
|
| def _split_heads(self, t: torch.Tensor) -> torch.Tensor: |
| B, T, _ = t.shape |
| return t.view(B, T, self.num_heads, self.head_dim).transpose(1, 2) |
|
|
| def forward( |
| self, |
| x: torch.Tensor, |
| attn_mask: torch.Tensor | None = None, |
| kv_cache: dict | None = None, |
| start_pos: int = 0, |
| ) -> torch.Tensor: |
| B, T_new, C = x.shape |
|
|
| q = apply_rope(self._split_heads(self.q(x)), self.rope_cos, self.rope_sin, start_pos) |
| k_new = apply_rope(self._split_heads(self.k(x)), self.rope_cos, self.rope_sin, start_pos) |
| v_new = self._split_heads(self.v(x)) |
|
|
| if kv_cache is not None and kv_cache.get("k") is not None: |
| k = torch.cat([kv_cache["k"], k_new], dim=2) |
| v = torch.cat([kv_cache["v"], v_new], dim=2) |
| else: |
| k = k_new |
| v = v_new |
|
|
| if kv_cache is not None: |
| kv_cache["k"] = k |
| kv_cache["v"] = v |
|
|
| |
| |
| incremental = kv_cache is not None and k.size(2) > T_new |
| is_causal = self.causal and not incremental and attn_mask is None |
| dropout_p = self.dropout_p if self.training else 0.0 |
|
|
| out = F.scaled_dot_product_attention( |
| q, k, v, |
| attn_mask=attn_mask, |
| is_causal=is_causal, |
| dropout_p=dropout_p, |
| ) |
| out = out.transpose(1, 2).contiguous().view(B, T_new, C) |
| return self.out(out) |
|
|
|
|
| class CrossAttention(nn.Module): |
| """ |
| Encoder-decoder cross-attention. When `need_weights=True`, runs the manual |
| softmax path and returns full per-head attention weights (B, H, T_tgt, T_src) |
| for the pointer-generator copy mechanism. When False, dispatches to |
| `F.scaled_dot_product_attention` (Flash kernels) and returns weights=None. |
| Encoder K/V are cached on first call. |
| """ |
| def __init__(self, config): |
| super().__init__() |
| assert config.d_model % config.num_heads == 0 |
| self.num_heads = config.num_heads |
| self.head_dim = config.d_model // config.num_heads |
| self.dropout_p = config.dropout |
|
|
| self.q = nn.Linear(config.d_model, config.d_model, bias=False) |
| self.k = nn.Linear(config.d_model, config.d_model, bias=False) |
| self.v = nn.Linear(config.d_model, config.d_model, bias=False) |
| self.out = nn.Linear(config.d_model, config.d_model, bias=False) |
| self.drop = nn.Dropout(config.dropout) |
|
|
| def _split(self, t: torch.Tensor) -> torch.Tensor: |
| B, T, _ = t.shape |
| return t.view(B, T, self.num_heads, self.head_dim).transpose(1, 2) |
|
|
| def forward( |
| self, |
| x: torch.Tensor, |
| enc: torch.Tensor, |
| attn_mask: torch.Tensor | None = None, |
| kv_cache: dict | None = None, |
| need_weights: bool = False, |
| ) -> tuple[torch.Tensor, torch.Tensor | None]: |
| B, T_new, C = x.shape |
|
|
| q = self._split(self.q(x)) |
|
|
| if kv_cache is not None and kv_cache.get("k") is not None: |
| k = kv_cache["k"] |
| v = kv_cache["v"] |
| else: |
| k = self._split(self.k(enc)) |
| v = self._split(self.v(enc)) |
| if kv_cache is not None: |
| kv_cache["k"] = k |
| kv_cache["v"] = v |
|
|
| if not need_weights: |
| dropout_p = self.dropout_p if self.training else 0.0 |
| out = F.scaled_dot_product_attention( |
| q, k, v, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=False, |
| ) |
| out = out.transpose(1, 2).contiguous().view(B, T_new, C) |
| return self.out(out), None |
|
|
| |
| |
| scale = math.sqrt(self.head_dim) |
| scores = torch.matmul(q, k.transpose(-2, -1)) / scale |
|
|
| if attn_mask is not None: |
| scores = scores + attn_mask |
|
|
| attn = F.softmax(scores, dim=-1) |
| attn_drop = self.drop(attn) |
| out = torch.matmul(attn_drop, v) |
| out = out.transpose(1, 2).contiguous().view(B, T_new, C) |
| return self.out(out), attn |
|
|