Files
ModelHub XC 4244787e58 初始化项目,由ModelHub XC社区提供模型
Model: sabari2005/cyberslm-instruct
Source: Original Platform
2026-08-29 19:29:19 +08:00

292 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Rotary Position Embedding (RoPE)
=================================
Reference: "RoFormer: Enhanced Transformer with Rotary Position Embedding"
(Su et al., 2021) — https://arxiv.org/abs/2104.09864
Mathematical definition
-----------------------
For a query (or key) vector **q** at position ``m`` with head dimension ``d``:
1. Partition **q** into pairs: (q₁, q₂), (q₃, q₄), ..., (q_{d-1}, q_d).
2. For each pair index ``i ∈ {0, 1, ..., d/2 - 1}`` define the frequency:
θᵢ = 1 / base^(2i / d) (base = 10 000)
3. Apply a 2-D rotation to each pair at position ``m``:
R(m, θᵢ) · (q_{2i}, q_{2i+1}) =
(q_{2i} cos(m·θᵢ) q_{2i+1} sin(m·θᵢ),
q_{2i} sin(m·θᵢ) + q_{2i+1} cos(m·θᵢ))
This is equivalent to multiplying **q** (viewed as complex numbers) by
``exp(i · m · θ)``, which preserves the inner product of relative positions:
⟨R(m)q, R(n)k⟩ depends only on (m n),
giving translation-equivariant attention without absolute position tokens.
Efficient implementation
------------------------
The rotation can be expressed without complex arithmetic:
q_rot = [q_even · cos q_odd · sin,
q_even · sin + q_odd · cos]
where ``q_even = q[..., 0::2]``, ``q_odd = q[..., 1::2]``.
Numerically interleaved form (even/odd) vs. split-half form (first/second half)
are equivalent; we use the interleaved form for clarity.
Precomputation
--------------
``cos`` and ``sin`` tensors of shape ``(max_seq_len, head_dim // 2)`` are
computed once and registered as non-parameter buffers so they move with the
module (CPU ↔ GPU) and are not included in ``state_dict`` checkpoints.
Stability notes
---------------
- Frequencies are computed in float64 then cast to float32 to minimise
floating-point error in ``pow`` and ``arange``.
- All rotations are executed in float32 to prevent loss of precision.
"""
from __future__ import annotations
import math
from typing import Tuple
import torch
import torch.nn as nn
from torch import Tensor
class RotaryPositionEmbedding(nn.Module):
"""
Precomputed Rotary Position Embedding cache.
Registers ``cos`` and ``sin`` buffers of shape
``(max_seq_len, head_dim // 2)`` at construction time. Applying RoPE
to a query or key tensor costs only element-wise multiplications and
additions — no matrix multiplications.
Parameters
----------
head_dim : int
Dimension of each attention head. Must be even.
max_seq_len : int
Maximum sequence length to pre-compute. Sequences longer than this
will raise an error at runtime.
base : int
RoPE base frequency (10 000 in the original paper).
Shape of ``apply``
------------------
Input : ``(batch, num_heads, seq_len, head_dim)``
Output : ``(batch, num_heads, seq_len, head_dim)``
"""
def __init__(
self,
head_dim: int,
max_seq_len: int,
base: int = 10_000,
) -> None:
super().__init__()
if head_dim <= 0 or head_dim % 2 != 0:
raise ValueError(
f"head_dim must be a positive even integer, got {head_dim}"
)
if max_seq_len <= 0:
raise ValueError(f"max_seq_len must be positive, got {max_seq_len}")
if base <= 0:
raise ValueError(f"base must be positive, got {base}")
self.head_dim = head_dim
self.max_seq_len = max_seq_len
self.base = base
# Pre-compute and register buffers (not model parameters).
cos_cache, sin_cache = self._build_cache(head_dim, max_seq_len, base)
self.register_buffer("cos_cache", cos_cache, persistent=False)
self.register_buffer("sin_cache", sin_cache, persistent=False)
@staticmethod
def _build_cache(
head_dim: int,
max_seq_len: int,
base: int,
) -> Tuple[Tensor, Tensor]:
"""
Build ``(cos, sin)`` caches of shape ``(max_seq_len, head_dim // 2)``.
Computation is performed in float64 for precision, then cast to
float32 for storage.
Returns
-------
Tuple[Tensor, Tensor]
``cos_cache`` and ``sin_cache``, each of shape
``(max_seq_len, head_dim // 2)``.
"""
half_dim = head_dim // 2
# θᵢ = 1 / base^(2i / head_dim) for i ∈ {0, ..., half_dim - 1}
# Computed in float64 to avoid precision loss in the exponent.
inv_freq = 1.0 / (
base ** (torch.arange(0, head_dim, 2, dtype=torch.float64) / head_dim)
)
# Shape: (half_dim,)
# Position indices m ∈ {0, 1, ..., max_seq_len - 1}
positions = torch.arange(max_seq_len, dtype=torch.float64)
# Shape: (max_seq_len,)
# Outer product: angles[m, i] = m * θᵢ
angles = torch.outer(positions, inv_freq)
# Shape: (max_seq_len, half_dim)
cos_cache = angles.cos().to(torch.float32)
sin_cache = angles.sin().to(torch.float32)
return cos_cache, sin_cache
@staticmethod
def _rotate_half(x: Tensor) -> Tensor:
"""
Rotate the last dimension by interleaving even/odd pairs.
For input ``x`` of shape ``(..., head_dim)``:
x_even = x[..., 0::2] (positions 0, 2, 4, ...)
x_odd = x[..., 1::2] (positions 1, 3, 5, ...)
Returns ``[-x_odd, x_even]`` interleaved back into ``(..., head_dim)``.
This is the standard rotation that implements the complex-number trick.
Parameters
----------
x : Tensor
Shape ``(..., head_dim)`` where ``head_dim`` is even.
Returns
-------
Tensor
Same shape as ``x``.
"""
x_even = x[..., 0::2] # (..., head_dim // 2)
x_odd = x[..., 1::2] # (..., head_dim // 2)
# Interleave: stack along new dim then flatten.
rotated = torch.stack([-x_odd, x_even], dim=-1)
# Shape: (..., head_dim // 2, 2) → (..., head_dim)
return rotated.flatten(start_dim=-2)
def apply(self, x: Tensor, offset: int = 0) -> Tensor:
"""
Apply Rotary Position Embeddings to ``x``.
Parameters
----------
x : Tensor
Query or key tensor of shape
``(batch, num_heads, seq_len, head_dim)``.
offset : int
Absolute position of ``x[..., 0, :]`` in the full sequence.
This is what makes incremental decoding correct. With a KV cache the
model feeds one token at a time, so ``seq_len == 1`` and the naive
``cos_cache[:1]`` would rotate every generated token as if it were at
position 0 -- destroying all positional information after the prompt.
Passing ``offset=len(cache)`` selects the true absolute position.
Returns
-------
Tensor
Rotated tensor with the same shape and dtype as ``x``.
Raises
------
ValueError
If ``seq_len`` exceeds ``max_seq_len``.
"""
seq_len = x.size(2)
if offset < 0:
raise ValueError(f"offset must be >= 0, got {offset}")
if offset + seq_len > self.max_seq_len:
raise ValueError(
f"Positions [{offset}, {offset + seq_len}) exceed RoPE cache size "
f"{self.max_seq_len}. Re-instantiate with a larger max_seq_len."
)
# Retrieve cached values for this absolute position span.
# cos_cache: (seq_len, head_dim // 2)
# sin_cache: (seq_len, head_dim // 2)
cos = self.cos_cache[offset : offset + seq_len] # type: ignore[index]
sin = self.sin_cache[offset : offset + seq_len] # type: ignore[index]
# Expand to broadcast over batch and head dimensions:
# (1, 1, seq_len, head_dim // 2) → broadcasts with (B, H, T, D/2)
cos = cos.unsqueeze(0).unsqueeze(0)
sin = sin.unsqueeze(0).unsqueeze(0)
# Interleave cos and sin to match full head_dim.
# Each of (cos, sin) has shape (1, 1, T, D/2).
# We need (1, 1, T, D) by interleaving even positions with cos,
# odd positions with sin. The _rotate_half trick handles this:
#
# x_rot = x * cos_full + rotate_half(x) * sin_full
#
# where cos_full[..., 0::2] = cos and cos_full[..., 1::2] = cos,
# i.e. each cos value applies to both the even AND its paired odd slot.
# Achieved by repeating each half-dim value into both slots.
cos_full = cos.repeat_interleave(2, dim=-1) # (1, 1, T, D)
sin_full = sin.repeat_interleave(2, dim=-1) # (1, 1, T, D)
# Work in float32 for stability, then restore original dtype.
x_fp32 = x.float()
x_rot = x_fp32 * cos_full + self._rotate_half(x_fp32) * sin_full
return x_rot.to(x.dtype)
def forward(self, x: Tensor, offset: int = 0) -> Tensor:
"""Alias for :meth:`apply` to support ``nn.Sequential`` usage."""
return self.apply(x, offset=offset)
def extra_repr(self) -> str:
return (
f"head_dim={self.head_dim}, "
f"max_seq_len={self.max_seq_len}, "
f"base={self.base}"
)
# ---------------------------------------------------------------------------
# Functional helper
# ---------------------------------------------------------------------------
def apply_rope(
q: Tensor,
k: Tensor,
rope: RotaryPositionEmbedding,
offset: int = 0,
) -> Tuple[Tensor, Tensor]:
"""
Apply the same RoPE instance to both query and key tensors.
Parameters
----------
q : Tensor
Query tensor of shape ``(batch, num_heads, seq_len, head_dim)``.
k : Tensor
Key tensor of shape ``(batch, num_heads, seq_len, head_dim)``.
rope : RotaryPositionEmbedding
Pre-built RoPE module (carries the cos/sin cache on the correct device).
Returns
-------
Tuple[Tensor, Tensor]
``(q_rot, k_rot)`` — rotated queries and keys.
"""
return rope.apply(q, offset=offset), rope.apply(k, offset=offset)