Позиционное кодирование¶
~7 минут чтения
Предварительно: Реализация внимания с нуля
Зачем позиционное кодирование¶
Self-attention -- групповой разговор, где каждый слышит всех одинаково, но никто не знает, кто говорил первым, а кто последним. "The cat sat on the mat" и "The mat sat on the cat" для attention без позиционного кодирования -- одно и то же. Позиционное кодирование дает каждому токену "бирку" с его позицией, чтобы модель различала порядок слов.
Ключевой инсайт: RoPE стал стандартом в LLM (LLaMA, Mistral, Qwen), потому что кодирует относительную позицию через вращение векторов Q и K. Attention score \(\langle R_m q, R_n k \rangle = \langle R_{m-n} q, k \rangle\) зависит только от разности позиций -- модель естественно обобщается на новые длины.
Распространенность (2026)¶
| Метод | Используется в | Экстраполяция | Эффективность |
|---|---|---|---|
| RoPE | LLaMA, Mistral, Falcon | Хорошая | Высокая |
| ALiBi | MPT, BLOOM | Отличная | Высокая |
| NoPE | Отдельные модели | Варьируется | Максимальная |
| Learned | GPT-2, BERT | Плохая | Средняя |
| Sinusoidal | Original Transformer | Плохая | Высокая |
Проблема порядка¶
Self-attention инвариантен к перестановке: \(\text{Attention}(Q, K, V) = \text{softmax}(QK^T/\sqrt{d})V\) не зависит от порядка входных токенов.
Пример:
- Input:
["cat", "sat", "mat"] - Permuted:
["mat", "sat", "cat"] - Без позиционного кодирования модель не различает эти два варианта: "cat sat on mat" = "mat sat on cat"
Решение: добавить позиционную информацию к эмбеддингам.
Требования к хорошему позиционному кодированию:
- Уникальность для каждой позиции
- Ограниченность (не взрывается для длинных последовательностей)
- Кодирование относительного расстояния
- Экстраполяция на невиданные длины
Синусоидальное кодирование (Original Transformer)¶
Где:
- \(pos\) -- позиция в последовательности
- \(i\) -- индекс измерения
- \(d\) -- размерность эмбеддинга
Свойства¶
| Свойство | Значение |
|---|---|
| Тип | Абсолютное |
| Экстраполяция | Плохая |
| Относительная информация | Неявная (через линейную комбинацию) |
| Обучаемость | Нет |
Ограничения¶
- Обучена на длине \(L\), качество падает для \(> L\)
- Нет явного моделирования относительной позиции
- Фиксированный паттерн, не адаптируется к данным
Обучаемые позиционные эмбеддинги¶
Подход GPT-2 / BERT¶
class LearnedPositionalEmbedding(nn.Module):
"""Learned absolute position embeddings."""
def __init__(self, max_seq_len: int, d_model: int):
super().__init__()
self.embeddings = nn.Embedding(max_seq_len, d_model)
def forward(self, x: torch.Tensor) -> torch.Tensor:
seq_len = x.shape[1]
positions = torch.arange(seq_len, device=x.device)
return x + self.embeddings(positions)
Свойства¶
| Свойство | Значение |
|---|---|
| Тип | Абсолютное |
| Экстраполяция | Очень плохая |
| Относительная информация | Неявная |
| Обучаемость | Да |
Проблема: невозможность экстраполяции¶
- Обучено на максимальной длине \(L\)
- Для позиций \(> L\) нет обученных эмбеддингов
- Нужно обрезать или ошибка для длинных последовательностей
RoPE (Rotary Position Embedding)¶
Ключевая идея¶
Вместо сложения позиционного кодирования, RoPE вращает Q и K:
- Стандартный подход:
x + PE(pos) - RoPE:
rotate(x, pos)
RoPE -- вращение для кодирования позиции
Матрица вращения для позиции \(m\):
Применяется к каждой паре измерений: \([q_{2i}, q_{2i+1}] \rightarrow R(\theta_m) \times [q_{2i}, q_{2i+1}]\)
**Ключевое свойство:** $\langle R_m q, R_n k \rangle = \langle R_{m-n} q, k \rangle$ -- attention score зависит от **относительной** позиции $m-n$!
Где $\theta_i = 10000^{-2i/d}$ для измерения $i$.
**Числовой пример:** для $d=4$, позиция $m=3$:
- $\theta_0 = 10000^{0/4} = 1$, $\theta_1 = 10000^{-2/4} = 0.01$
- Быстрые измерения ($\theta_0$) вращаются на $3 \times 1 = 3$ рад -- кодируют **ближние** позиции
- Медленные измерения ($\theta_1$) вращаются на $3 \times 0.01 = 0.03$ рад -- кодируют **дальние** позиции
Реализация RoPE¶
import torch
import torch.nn as nn
class RotaryPositionalEmbedding(nn.Module):
"""Rotary Position Embedding (RoPE)."""
def __init__(self, dim: int, max_seq_len: int = 2048, base: int = 10000):
super().__init__()
self.dim = dim
self.max_seq_len = max_seq_len
# Compute inverse frequencies: θ_i = 10000^(-2i/d)
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
self.register_buffer("inv_freq", inv_freq)
# Precompute cos and sin for efficiency
self._build_cache(max_seq_len)
def _build_cache(self, seq_len: int):
"""Precompute cos and sin for all positions."""
t = torch.arange(seq_len, device=self.inv_freq.device)
freqs = torch.einsum("i,j->ij", t, self.inv_freq) # [seq_len, dim/2]
# Duplicate for complex rotation
emb = torch.cat([freqs, freqs], dim=-1) # [seq_len, dim]
self.register_buffer("cos_cached", emb.cos())
self.register_buffer("sin_cached", emb.sin())
def forward(self, x: torch.Tensor, start_pos: int = 0) -> torch.Tensor:
"""Apply rotary embeddings to input."""
seq_len = x.shape[2]
cos = self.cos_cached[start_pos:start_pos + seq_len]
sin = self.sin_cached[start_pos:start_pos + seq_len]
return self._apply_rotary_emb(x, cos, sin)
def _apply_rotary_emb(
self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor
) -> torch.Tensor:
"""Apply rotation to input tensor."""
# x: [batch, heads, seq_len, dim]
x1 = x[..., : x.shape[-1] // 2]
x2 = x[..., x.shape[-1] // 2 :]
# Rotate: [x1, x2] -> [x1*cos - x2*sin, x1*sin + x2*cos]
rotated = torch.cat(
[x1 * cos[..., : x1.shape[-1]] - x2 * sin[..., : x2.shape[-1]],
x1 * sin[..., : x1.shape[-1]] + x2 * cos[..., : x2.shape[-1]]],
dim=-1
)
return rotated
def apply_rotary_pos_emb(q, k, cos, sin):
"""Apply RoPE to query and key tensors."""
q_embed = (q * cos) + (rotate_half(q) * sin)
k_embed = (k * cos) + (rotate_half(k) * sin)
return q_embed, k_embed
def rotate_half(x):
"""Rotate half the hidden dims of the input."""
x1 = x[..., : x.shape[-1] // 2]
x2 = x[..., x.shape[-1] // 2 :]
return torch.cat([-x2, x1], dim=-1)
Свойства RoPE¶
| Свойство | Значение |
|---|---|
| Тип | Относительное (через вращение) |
| Экстраполяция | Хорошая (со scaling) |
| Относительная информация | Явная |
| Обучаемость | Нет (фиксированная) |
| Сложность | O(1) |
Техники экстраполяции RoPE¶
| Техника | Метод | Фактор экстраполяции |
|---|---|---|
| NTK-aware scaling | Scale base | 8-16× |
| Position interpolation | Interpolate positions | 8× |
| YaRN | Combine both | 64× |
ALiBi (Attention with Linear Biases)¶
Ключевая идея¶
ALiBi добавляет штраф к attention scores на основе расстояния между токенами:
Где \(m\) -- наклон (slope), разный для каждой attention head, \(|i - j|\) -- относительное расстояние.
Наклоны голов: \(m_h = 1 / 2^{h / \text{num\_heads}}\)
| Head | Slope |
|---|---|
| 1 | ½ |
| 2 | ¼ |
| 3 | ⅛ |
Эффект: далекие токены получают меньший attention score. Нет обучаемых параметров -- полностью фиксированная схема.
ALiBi Implementation¶
def get_alibi_bias(num_heads: int, seq_len: int, device: str = "cuda"):
"""Compute ALiBi attention bias."""
# Slopes: m_h = 2^(-h/num_heads)
slopes = torch.pow(2, -(torch.arange(1, num_heads * 2 + 1, 2) / num_heads))
slopes = slopes.to(device).unsqueeze(1).unsqueeze(1) # [heads, 1, 1]
# Relative positions: |i - j|
positions = torch.arange(seq_len, device=device)
relative_pos = positions.unsqueeze(0) - positions.unsqueeze(1) # [seq, seq]
relative_pos = relative_pos.abs().unsqueeze(0) # [1, seq, seq]
# ALiBi bias: -m * |i - j|
alibi_bias = -slopes * relative_pos # [heads, seq, seq]
return alibi_bias
def alibi_attention(q, k, v, num_heads):
"""Attention with ALiBi bias."""
batch, seq_len, dim = q.shape
head_dim = dim // num_heads
# Reshape for multi-head attention
q = q.view(batch, seq_len, num_heads, head_dim).transpose(1, 2)
k = k.view(batch, seq_len, num_heads, head_dim).transpose(1, 2)
v = v.view(batch, seq_len, num_heads, head_dim).transpose(1, 2)
# Standard attention scores
scores = torch.matmul(q, k.transpose(-2, -1)) / (head_dim ** 0.5)
# Add ALiBi bias
alibi_bias = get_alibi_bias(num_heads, seq_len, q.device)
scores = scores + alibi_bias
# Softmax and output
attn = F.softmax(scores, dim=-1)
output = torch.matmul(attn, v)
return output.transpose(1, 2).reshape(batch, seq_len, dim)
Свойства ALiBi¶
| Свойство | Значение |
|---|---|
| Тип | Относительное (через bias) |
| Экстраполяция | Отличная (1-2x от длины обучения) |
| Параметры | Нет (фиксированные наклоны) |
| Сложность | O(n^2) вычисление bias |
NoPE (No Position Encoding)¶
Неожиданный результат: иногда позиция не нужна¶
Некоторые модели работают без позиционного кодирования.
Почему NoPE может работать:
- Causal masking дает неявный порядок
- Порядок слоев обеспечивает последовательную обработку
- Token embeddings могут неявно кодировать позицию
Когда NoPE работает: короткие последовательности (< 4K), каузальные модели, достаточная глубина сети.
Когда NoPE ломается: длинные последовательности (> 8K), двунаправленные модели (BERT-style), задачи, требующие точного знания позиции.
Тренд 2026: гибридные подходы (RoPE + ALiBi).
Сравнение методов¶
Полная таблица¶
| Метод | Экстраполяция | Относительная позиция | Параметры | Используется в |
|---|---|---|---|---|
| Sinusoidal | Плохая | Неявная | 0 | Original Transformer |
| Learned | Очень плохая | Неявная | O(Ld) | GPT-2, BERT |
| RoPE | Хорошая | Явная | 0 | LLaMA, Mistral |
| ALiBi | Отличная | Явная | 0 | MPT, BLOOM |
| NoPE | Плохая | Неявная | 0 | Research |
Производительность экстраполяции¶
| Метод | Длина обучения | Экстраполирует до |
|---|---|---|
| Learned | 2048 | 2048 (ломается за пределами) |
| Sinusoidal | 2048 | 2048 (деградирует) |
| RoPE | 2048 | 4096 (2x) |
| RoPE + NTK | 2048 | 16384 (8x) |
| RoPE + YaRN | 2048 | 131072 (64x) |
| ALiBi | 2048 | 4096+ (2x+) |
Бенчмарки (Language Modeling)¶
| Метод | PPL @ 2K | PPL @ 4K | PPL @ 8K |
|---|---|---|---|
| Learned | 12.5 | 15.2 (extrapolated) | 18.4 |
| Sinusoidal | 12.8 | 14.5 | 16.8 |
| RoPE | 12.4 | 13.1 | 14.2 |
| ALiBi | 12.6 | 12.9 | 13.4 |
RoPE не экстраполирует автоматически
RoPE дает attention scores, зависящие от относительной позиции, но это не значит, что модель работает на произвольных длинах. Для экстраполяции за пределы тренировочного контекста нужен NTK-aware scaling, Position Interpolation или YaRN. Без них: модель обучена на 4K -> на 8K perplexity взлетает.
Learned PE != плохой
Learned positional embeddings (GPT-2, BERT) часто критикуют за неспособность экстраполировать. Но для фиксированной длины контекста они работают так же хорошо, как RoPE. Проблема не в качестве, а в гибкости -- если длина контекста известна заранее, learned PE вполне OK.
Ключевые числа¶
Использование в моделях¶
| Модель | Позиционное кодирование | Контекст |
|---|---|---|
| GPT-2 | Learned | 1024 |
| BERT | Learned | 512 |
| LLaMA | RoPE | 2048-131K |
| Mistral | RoPE | 8192 |
| MPT | ALiBi | 8192 |
| BLOOM | ALiBi | 2048 |
Наклоны ALiBi¶
| Голова | Наклон (m) |
|---|---|
| 1 | ½ = 0.5 |
| 2 | ¼ = 0.25 |
| 4 | 1/16 = 0.0625 |
| 8 | 1/256 = 0.0039 |
| 16 | 1/65536 ≈ 0.00002 |
Базовые частоты RoPE¶
| Модель | Base (\(\theta\)) | Причина |
|---|---|---|
| LLaMA | 10000 | По умолчанию |
| LLaMA-3 | 500000 | Лучше длинный контекст |
| CodeLLaMA | 1000000 | Код требует большего диапазона |
Interview Questions¶
1. Зачем трансформерам позиционное кодирование?¶
Red flag: "Чтобы модель знала, где какое слово стоит"
Strong answer: "Self-attention инвариантен к перестановкам -- \(\text{softmax}(QK^T/\sqrt{d})V\) не зависит от порядка токенов. Без позиционного кодирования 'cat sat on mat' и 'mat sat on cat' дают идентичные представления. PE инъектирует информацию о порядке: через сложение (sinusoidal, learned), вращение (RoPE) или bias (ALiBi)."
2. RoPE vs ALiBi: когда что использовать?¶
Red flag: "RoPE лучше, все его используют"
Strong answer: "RoPE кодирует относительную позицию через вращение Q и K, хорошо экстраполирует с NTK/YaRN (до 64x). Стандарт для LLaMA, Mistral. ALiBi добавляет линейный штраф к attention scores, отличная экстраполяция из коробки (2x+ без дополнительных трюков), нулевые параметры. ALiBi проще и надежнее для экстраполяции, RoPE мощнее с правильным scaling."
3. Как RoPE кодирует относительную позицию?¶
Red flag: "RoPE добавляет позицию к эмбеддингам"
Strong answer: "RoPE вращает Q и K вместо сложения. Для позиции \(m\) применяется матрица вращения \(R_m\) к каждой паре измерений. Ключевое свойство: \(\langle R_m q, R_n k \rangle = \langle R_{m-n} q, k \rangle\) -- attention score зависит только от разности позиций \(m-n\). Частоты вращения \(\theta_i = 10000^{-2i/d}\) -- разные измерения кодируют позицию на разных масштабах."
4. Модель обучена на 4K контексте, нужно обработать 32K. Что делать?¶
Red flag: "Просто подать 32K токенов"
Strong answer: "Зависит от PE. Learned PE -- нужно дообучение (нет embedding для позиций > 4K). RoPE -- применить scaling: Position Interpolation (сжать позиции в [0, 4K]), NTK-aware (увеличить base theta), или YaRN (комбинация, до 64x). ALiBi -- часто работает из коробки до 2x. В любом случае: проверить perplexity на целевой длине."
Самопроверка
- Для sinusoidal PE с \(d=512\): вычислите \(PE_{(pos=100, 2i=0)}\) и \(PE_{(pos=100, 2i=510)}\). Какой из компонентов меняется быстрее с позицией?
- Модель обучена с RoPE на длине 4096. Нужно обработать 32K токенов. Опишите 3 стратегии и их trade-offs.
- Объясните, почему NoPE может работать с causal masking, но не с bidirectional attention (как в BERT).
Sources¶
- mbrenndoerfer.com — "Position Encoding Comparison: Sinusoidal, Learned, RoPE & ALiBi Guide"
- Towards Data Science — "Positional Embeddings in Transformers: A Math Guide to RoPE & ALiBi"
- AI Edge Newsletter — "All About The Modern Positional Encodings In LLMs"
- arXiv — "RoFormer: Enhanced Transformer with Rotary Position Embedding"
- arXiv — "Train Short, Test Long: Attention with Linear Biases (ALiBi)"
- arXiv — "RoPE to NoPE and Back Again: A New Hybrid Attention Strategy" (2501.18795)
- OpenReview — "Bayesian Attention Mechanism: A Probabilistic Framework"
- arXiv — "DoPE: Denoising Rotary Position Embedding" (2511.09146)
See Also¶
- RoPE & Long Context -- углубленный разбор RoPE: PI, NTK, YaRN, экстраполяция
- Positional Encoding Comparison -- сводная таблица всех методов PE
- Flash Attention 3 -- оптимизация attention вычислений (работает с любым PE)
- MQA/GQA Attention -- KV cache оптимизация, тесно связана с позиционным кодированием
- Efficient Transformers -- архитектурные оптимизации трансформеров