File size: 2,360 Bytes
37d8122 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | """Rotary Position Embedding (RoPE) — implémenté à la main.
RoPE encode la position en **faisant tourner** par paires les dimensions de Q et K,
au lieu d'**ajouter** un vecteur de position aux embeddings. Propriétés :
* la position devient **relative** dans le produit scalaire Q·K ;
* aucun paramètre appris, aucune contrainte sur la longueur ;
* meilleure extrapolation en longueur qu'un embedding positionnel absolu appris.
Justification complète : docs/architecture.md §2.1.
Convention : les dimensions ``(2i, 2i+1)`` d'un vecteur forment la paire ``i``.
"""
from __future__ import annotations
import torch
def build_rope_cache(
seq_len: int,
head_dim: int,
theta: float = 10000.0,
device: torch.device | None = None,
dtype: torch.dtype = torch.float32,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Précalcule ``cos`` et ``sin`` pour les positions ``0..seq_len-1``.
Retourne deux tenseurs de forme ``(seq_len, head_dim)``, où chaque paire
``(2i, 2i+1)`` partage le même angle.
"""
if head_dim % 2 != 0:
raise ValueError(f"head_dim doit être pair pour RoPE, reçu {head_dim}")
half = head_dim // 2
# Fréquences décroissantes : inv_freq[i] = theta^(-2i/head_dim)
inv_freq = 1.0 / (theta ** (torch.arange(0, half, device=device, dtype=torch.float32) / half))
positions = torch.arange(seq_len, device=device, dtype=torch.float32)
freqs = torch.outer(positions, inv_freq) # (seq_len, half)
emb = torch.cat([freqs, freqs], dim=-1) # (seq_len, head_dim)
return emb.cos().to(dtype), emb.sin().to(dtype)
def rotate_half(x: torch.Tensor) -> torch.Tensor:
"""Échange et inverse les deux moitiés : ``[a, b] -> [-b, a]`` sur la dernière dim."""
half = x.shape[-1] // 2
x1, x2 = x[..., :half], x[..., half:]
return torch.cat([-x2, x1], dim=-1)
def apply_rope(
q: torch.Tensor,
k: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Applique RoPE à Q et K.
``q``, ``k`` : ``(batch, n_head, seq_len, head_dim)``.
``cos``, ``sin`` : ``(seq_len, head_dim)`` (broadcast sur batch et têtes).
"""
cos = cos[None, None, :, :]
sin = sin[None, None, :, :]
q_rot = q * cos + rotate_half(q) * sin
k_rot = k * cos + rotate_half(k) * sin
return q_rot, k_rot
|