Cortex-0 / src /model /rope.py
Frankenstein-Labs's picture
feat(model): Phase 2 — transformer texte from scratch
37d8122 verified
Raw History Blame Contribute Delete
2.36 kB
"""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