Перейти к содержанию

Позиционное кодирование

~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"

Решение: добавить позиционную информацию к эмбеддингам.

Требования к хорошему позиционному кодированию:

  1. Уникальность для каждой позиции
  2. Ограниченность (не взрывается для длинных последовательностей)
  3. Кодирование относительного расстояния
  4. Экстраполяция на невиданные длины

Синусоидальное кодирование (Original Transformer)

\[PE_{(pos, 2i)} = \sin(pos / 10000^{2i/d})\]
\[PE_{(pos, 2i+1)} = \cos(pos / 10000^{2i/d})\]

Где:

  • \(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\):

\[R(\theta)_m = \begin{bmatrix} \cos(m\theta) & -\sin(m\theta) \\ \sin(m\theta) & \cos(m\theta) \end{bmatrix}\]

Применяется к каждой паре измерений: \([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
YaRN Combine both 64×

ALiBi (Attention with Linear Biases)

Ключевая идея

ALiBi добавляет штраф к attention scores на основе расстояния между токенами:

\[\text{score}(i, j) = q_i \cdot k_j - m \cdot |i - j|\]

Где \(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 на целевой длине."


Самопроверка

  1. Для sinusoidal PE с \(d=512\): вычислите \(PE_{(pos=100, 2i=0)}\) и \(PE_{(pos=100, 2i=510)}\). Какой из компонентов меняется быстрее с позицией?
  2. Модель обучена с RoPE на длине 4096. Нужно обработать 32K токенов. Опишите 3 стратегии и их trade-offs.
  3. Объясните, почему NoPE может работать с causal masking, но не с bidirectional attention (как в BERT).

Sources

  1. mbrenndoerfer.com — "Position Encoding Comparison: Sinusoidal, Learned, RoPE & ALiBi Guide"
  2. Towards Data Science — "Positional Embeddings in Transformers: A Math Guide to RoPE & ALiBi"
  3. AI Edge Newsletter — "All About The Modern Positional Encodings In LLMs"
  4. arXiv — "RoFormer: Enhanced Transformer with Rotary Position Embedding"
  5. arXiv — "Train Short, Test Long: Attention with Linear Biases (ALiBi)"
  6. arXiv — "RoPE to NoPE and Back Again: A New Hybrid Attention Strategy" (2501.18795)
  7. OpenReview — "Bayesian Attention Mechanism: A Probabilistic Framework"
  8. 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 -- архитектурные оптимизации трансформеров