Позиционное кодирование
Реализация:
core/positional_embeddings.py· классPositionalEmbeddings(GPT-1, GPT-2);core/rope.py· классRoPE(LLaMA, Mistral, Mixtral, Gemma)
Что вы узнаете
Заголовок раздела «Что вы узнаете»- Почему механизм внимания сам по себе не знает порядка токенов и как это строго доказать.
- Как устроены обучаемые абсолютные позиционные эмбеддинги GPT-1 и GPT-2 и чем ограничена их длина.
- Как синусоидальное кодирование из «Attention Is All You Need» превращает сдвиг позиции в поворот — и почему это прямой путь к RoPE.
- Полный вывод RoPE: матрицы поворота, частоты , доказательство того, что оценка внимания зависит только от расстояния между токенами, эффективная реализация и перенос весов из HuggingFace.
- Как база
rope_thetaсвязана с длиной контекста и как RoPE работает вместе с KV-кэшем.
Предварительные знания
Заголовок раздела «Предварительные знания»- Авторегрессия и общая схема decoder-only трансформера — language-modeling.md.
- Эмбеддинги токенов и матрица — embeddings.md.
- Формулу attention мы кратко напомним ниже; подробный разбор — в следующей главе, attention.md.
- Из математики: скалярное произведение, умножение матриц, транспонирование, и суммы углов. Для одного необязательного вывода — комплексные числа в форме .
Обозначения — как во всём пособии (notation.md): — длина последовательности, — размерность модели, — размер головы, — максимальная длина контекста (max_position_embeddings), позиции нумеруются с 0.
Зачем нужна позиция
Заголовок раздела «Зачем нужна позиция»Attention не знает порядка
Заголовок раздела «Attention не знает порядка»Напомним формулу одной головы внимания без маски (подробно — в attention.md):
где:
- — матрица входных состояний, строка — вектор токена на позиции ;
- — матрицы проекций;
- — запросы (queries), ключи (keys) и значения (values);
- применяется к каждой строке матрицы отдельно;
- — нормирующий множитель.
В этой формуле нет ни одного места, где использовался бы номер строки. Вес, с которым токен смотрит на токен , зависит только от содержимого векторов и . Отсюда следует свойство, которое называется переставочной эквивариантностью (permutation equivariance): если переставить токены на входе, выход переставится точно так же и больше никак не изменится.
Формально: пусть — матрица перестановки (в каждой строке и каждом столбце ровно одна единица; в следующем разделе буквой будет обозначена другая матрица — позиционных эмбеддингов). Умножение переставляет строки . Утверждение:
Доказательство
Шаг 1. Проекции переставляются вместе со входом, потому что умножение справа на действует на каждую строку отдельно:
Шаг 2. Матрица оценок:
Умножение слева на переставляет строки , справа на — столбцы, той же перестановкой.
Шаг 3. Softmax по строкам коммутирует с такой двойной перестановкой: перестановка строк просто меняет порядок, в котором строки обрабатываются, а перестановка столбцов внутри строки переставляет слагаемые знаменателя (сумма от этого не меняется) и числители. Поэтому
Шаг 4. Для матрицы перестановки (это ортогональная матрица). Собираем:
Шаг 5. Остальные части блока трансформера — FFN, LayerNorm/RMSNorm, residual-сложение — применяются к каждой строке независимо, а значит, тоже эквивариантны. Композиция эквивариантных отображений эквивариантна, поэтому эквивариантен и весь стек слоёв.
Что это значит на практике. Фразы «кот ест рыбу» и «рыбу ест кот» состоят из одних и тех же токенов. Без позиционной информации вектор слова «кот» на выходе будет одинаковым в обеих фразах: модель видит мешок токенов, а не последовательность. Для языка это неприемлемо: смысл определяется порядком. Нужно как-то сообщить модели, где стоит каждый токен.
Проверить эквивариантность можно численно:
import torch
torch.manual_seed(0)T, d, d_h = 6, 16, 8X = torch.randn(T, d)W_q, W_k, W_v = torch.randn(3, d, d_h)
def attn(X): S = (X @ W_q) @ (X @ W_k).T / d_h ** 0.5 return torch.softmax(S, dim=-1) @ (X @ W_v)
perm = torch.randperm(T)print(torch.allclose(attn(X[perm]), attn(X)[perm], atol=1e-5)) # TrueЧто меняет causal-маска
Заголовок раздела «Что меняет causal-маска»В decoder-only моделях к оценкам добавляется causal-маска (masks.md): токен видит только . Маска привязана к номерам строк, поэтому она сама нарушает эквивариантность: в коде выше с маской allclose даёт False. Косвенно модель может извлечь из маски позицию — например, первый токен смотрит только на себя, а сотый усредняет сто векторов. Haviv et al. (2022) показали, что языковые модели с causal-маской и вовсе без позиционного кодирования учатся почти так же хорошо. Но это неявная и грубая информация; все модели этого пособия кодируют позицию явно.
Абсолютная и относительная позиция
Заголовок раздела «Абсолютная и относительная позиция»Позицию можно сообщить модели двумя способами:
- абсолютная позиция (absolute position) — каждому токену сообщается его номер : «я пятый»;
- относительная позиция (relative position) — паре токенов сообщается расстояние между ними : «ты на три токена левее меня».
Для языка важнее относительная: смысл «прилагательное перед существительным» не зависит от того, стоит пара в начале документа или на тысячной позиции. Дальше мы увидим, как методы постепенно шли от абсолютной позиции к относительной — и как RoPE получает относительную позицию, поворачивая векторы по абсолютной.
Обучаемые абсолютные эмбеддинги (GPT-1, GPT-2)
Заголовок раздела «Обучаемые абсолютные эмбеддинги (GPT-1, GPT-2)»Формула
Заголовок раздела «Формула»Самый простой способ: завести для каждой позиции свой обучаемый вектор и прибавить его к эмбеддингу токена. Так сделано в GPT-1 (Radford et al., 2018, раздел 4.1: авторы явно выбирают обучаемые позиционные эмбеддинги вместо синусоидальных) и GPT-2 (Radford et al., 2019):
где:
- — индекс токена на позиции ;
- — матрица эмбеддингов токенов, — её строка (embeddings.md);
- — матрица позиционных эмбеддингов, обучаемый параметр (не матрица перестановки из предыдущего раздела); — строка для позиции ;
- — вход первого блока декодера для позиции (строка матрицы , как в embeddings.md). Для всей последовательности , где и — строки с номерами и соответственно.
Интуиция. Позиционный вектор — такой же «словарный» вектор, только словарь состоит из номеров позиций . После сложения вектор строки несёт одновременно «что за токен» и «где он стоит», и эквивариантность нарушается: одинаковые токены на разных позициях дают разные входы. Сложение, а не конкатенация, не увеличивает размерность; модель сама учится разносить информацию о токене и позиции по разным направлениям -мерного пространства.
Численный пример. Пусть , , , . Тогда «кот» на позиции 0 даёт , а на позиции 3 — : attention увидит два разных вектора.
Стоимость и ограничение длины
Заголовок раздела «Стоимость и ограничение длины»Параметров : для GPT-1 (, ) — 393 216, для GPT-2 small (, ) — 786 432, около 0,6 % от 124,4M параметров модели (gpt2.md). Немного.
Главный недостаток — жёсткий предел длины. Строк для нет вовсе, и модель не может обработать больше токенов за раз. Даже если добавить строки, их нечем обучить. Кроме того, строки для редких больших позиций обучаются хуже: длинных примеров в данных меньше. И каждая позиция кодируется независимо, поэтому связь «позиции 17 и 18 соседние» модель должна выучить сама.
Реализация: PositionalEmbeddings
Заголовок раздела «Реализация: PositionalEmbeddings»Класс PositionalEmbeddings — обёртка над nn.Embedding(max_seq_len, emb_size): его матрица весов и есть . Метод forward(seq_len, start_pos=0, positions=None) возвращает строки матрицы с номерами start_pos … start_pos + seq_len − 1 формы [seq_len, emb_size], а с positions формы [batch, seq_len] — строки с этими номерами, [batch, seq_len, emb_size]:
if positions is not None: # паддинг: своя позиция у каждой строки return self.embedding(positions) # [batch, seq_len, emb_size]if seq_len < 1 or start_pos + seq_len > self.max_seq_len: raise IndexError(...)...positions = torch.arange(start=start_pos, end=start_pos + seq_len, device=...)return self.embedding(positions)В моделях GPT и GPT2 сложение выглядит так (forward):
tok_out = self._token_embeddings(x) # [batch, seq_len, emb_size]if padding is None: pos_out = self._position_embeddings(seq_len, start_pos=start_pos).unsqueeze(0) # [1, seq_len, emb_size]else: pos_out = self._position_embeddings(seq_len, positions=padding.positions) # [batch, seq_len, emb_size]out = self._dropout(tok_out + pos_out) # broadcast по батчу без паддингаБез паддинга unsqueeze(0) превращает [seq_len, emb_size] в [1, seq_len, emb_size], и одна и та же матрица позиций прибавляется ко всем примерам батча. С паддингом в attention_mask позиции у строк разные: позиция токена — его номер среди настоящих токенов строки, cumsum(mask) − 1 (у строки [pad, pad, a, b] токен a — на позиции 0). Их считает padding_from_attention_mask из core/padding.py и передаёт в positions; подробно — в masks.md. Веса инициализируются вместе с остальными Embedding/Linear нормальным распределением ( по умолчанию, init_normal_ в core/weight_init.py). При загрузке весов OpenAI/HF матрица приходит из ключа wpe.weight (GPT-2) или positions_embed.weight (GPT-1), см. models/gpt/hf_weights.py.
Докстринг PositionalEmbeddings упоминает и синусоидальный вариант, но реализован только обучаемый: синусоидального кодирования в репозитории нет.
start_pos и KV-кэш
Заголовок раздела «start_pos и KV-кэш»При генерации с KV-кэшем (generation.md) модель на каждом шаге получает только новый токен, seq_len = 1. Его позиция — не 0, а число уже обработанных токенов. Эту позицию считает функция cache_start_pos в core/generation.py: для кэша слоёв MHA она равна длине закэшированной последовательности K. Модель передаёт её в PositionalEmbeddings как start_pos и сначала проверяет check_sequence_length: если start_pos + seq_len > max_position_embeddings, выбрасывается ValueError.
Что будет, если забыть start_pos? Каждый новый токен получит — модель будет «думать», что каждое следующее слово стоит в начале текста.
Когда генерация выходит за , generate (функция next_generation_input) берёт последние токенов и пересчитывает их без кэша: окно сдвинулось, позиции всех токенов уменьшились на единицу, и старые K/V, посчитанные со старыми позициями, больше не годятся.
Синусоидальное кодирование (Vaswani et al., 2017)
Заголовок раздела «Синусоидальное кодирование (Vaswani et al., 2017)»Формулы
Заголовок раздела «Формулы»В исходном трансформере (Vaswani et al., 2017, раздел 3.5) позиционный вектор не обучается, а вычисляется по формуле и так же прибавляется к эмбеддингу токена:
где:
- — позиция токена;
- — номер пары координат; координаты и используют одну частоту;
- — размерность модели (в статье );
- — позиционный вектор, — таблица для всех позиций; параметров нет.
Обозначим частоту пары . Тогда пара — это точка на единичной окружности, которая с ростом равномерно вращается с угловой скоростью радиан на позицию.
Частоты в геометрической прогрессии
Заголовок раздела «Частоты в геометрической прогрессии»Частоты образуют геометрическую прогрессию: — постоянное отношение. Периоды (длины волн) растут от (пара 0) до почти (последняя пара) — так и сказано в статье.
Интуиция: двоичный счётчик. Запишите числа 0, 1, 2, … в двоичной системе: младший разряд меняется каждый шаг, следующий — каждые два шага, дальше — каждые четыре. Синусоидальное кодирование — «гладкий» аналог такого счётчика: быстрые пары различают соседние позиции, медленные — далёкие. Геометрическая прогрессия частот даёт шкалу, одинаково подробную на всех масштабах (как разряды числа).
Численный пример. : частоты , . Для :
Первая пара заметно повернулась, вторая — почти нет: её «стрелка» пройдёт полный круг только за позиций.
Сдвиг позиции — линейная функция
Заголовок раздела «Сдвиг позиции — линейная функция»Авторы выбрали синусоиды, потому что, по их гипотезе, модели будет легко научиться обращать внимание по относительным позициям: для любого фиксированного сдвига вектор — линейная функция , причём матрица не зависит от .
Выведем это для одной пары. По формулам синуса и косинуса суммы:
В матричном виде:
где:
- — частота пары;
- — сдвиг позиции;
- — матрица поворота на угол (здесь по часовой стрелке, потому что порядок координат «синус, косинус»). Она зависит от , но не от .
Для всего вектора матрица сдвига блочно-диагональная: на диагонали стоят блоки , по одному на каждую пару со своей частотой . Итак, сдвиг позиции на — это поворот каждой пары на угол .
Второе следствие: скалярное произведение двух позиционных векторов зависит только от расстояния:
(использована формула ). Например, при произведения и оба равны .
Почему этого недостаточно. Хорошее свойство есть у самих векторов , но в модель они попадают через сложение с эмбеддингом токена, а затем через проекции , . Оценка внимания раскладывается на четыре слагаемых, и в трёх из них позиция смешана с содержимым произвольными матрицами. Относительность не гарантирована — модель должна её выучить. RoPE берёт ту же идею «сдвиг = поворот» и применяет поворот прямо к и , где он работает точно.
История. Vaswani et al. сравнили синусоиды с обучаемыми эмбеддингами и получили почти одинаковое качество (таблица 3, строка E); синусоиды выбрали в надежде на экстраполяцию на длины, большие, чем при обучении. GPT-1 и GPT-2 вернулись к обучаемым эмбеддингам.
В этом репозитории синусоидальное кодирование не реализовано.
Относительные позиции: Shaw et al., T5, ALiBi
Заголовок раздела «Относительные позиции: Shaw et al., T5, ALiBi»Эти методы в репозитории не реализованы; они нужны как контекст, чтобы увидеть, какое место занимает RoPE.
Shaw et al. (2018) первыми встроили относительную позицию прямо в attention. Для каждой пары берётся обучаемый вектор , зависящий только от обрезанного расстояния , и прибавляется к ключу:
где — максимальное учитываемое расстояние (обозначение статьи; здесь это число, а не вектор ключа), дальше все расстояния считаются одинаковыми; аналогичный вектор прибавляется к значениям. Цена — дополнительные параметры и тензор оценок, зависящий от пары позиций, что усложняет эффективную реализацию.
T5 (Raffel et al., 2019) упростил идею: к оценке прибавляется обучаемое число (скаляр), зависящее от расстояния , разбитого на корзины (buckets), своё для каждой головы.
ALiBi (Press et al., 2022) обходится без обучаемых параметров: к оценке прибавляется штраф, линейно растущий с расстоянием,
где — фиксированный наклон головы ; наклоны образуют геометрическую прогрессию (для 8 голов — ). Далёкие токены получают меньший вес, «крутизна» у каждой головы своя. Позиционных эмбеддингов на входе нет. Статья называется «Train Short, Test Long»: главный аргумент — модель, обученная на коротких последовательностях, продолжает работать на более длинных (экстраполяция).
RoPE: поворот вместо сложения
Заголовок раздела «RoPE: поворот вместо сложения»RoPE (Rotary Position Embedding, «ротационные позиционные эмбеддинги»; Su et al., 2021) ставит задачу так: найти преобразование вектора , стоящего на позиции , такое, что скалярное произведение преобразованных запроса и ключа зависит от позиций только через разность:
где:
- — запрос токена на позиции и ключ токена на позиции (одной головы, уже после проекций , );
- — скалярное произведение;
- — какая-то функция, в которую позиции входят только как .
Ответ RoFormer (раздел 3.2): , где — поворот на угол, пропорциональный . Разберём по шагам.
В этом разделе удобнее считать и векторами-столбцами и записывать скалярное произведение как ; в коде векторы — строки, но скалярное произведение от этого не меняется.
Поворот на плоскости
Заголовок раздела «Поворот на плоскости»Матрица поворота на угол против часовой стрелки:
где — угол в радианах; действует на столбец .
Пример: переводит в , а — в .
Нам понадобятся три свойства.
- Повороты складываются: .
- Обратный поворот — транспонирование: , и (матрица ортогональна).
- Как следствие: .
Вывод свойств
Свойство 1. Перемножаем матрицы:
По формулам и это ровно
Свойство 2. Транспонирование меняет местами внедиагональные элементы:
(косинус чётный, синус нечётный). Тогда по свойству 1: .
Свойство 3. .
Блочно-диагональная матрица для вектора головы
Заголовок раздела «Блочно-диагональная матрица для вектора головы»Вектор головы имеет размерность (чётную). Разобьём его на пар соседних координат и повернём каждую пару на свой угол :
где:
- — позиция токена (целое, );
- — частота пары (в радианах на позицию); здесь — не параметры модели, а фиксированные числа;
- — блок из предыдущего пункта; вне диагональных блоков — нули.
Запрос и ключ преобразуются так:
где и — обычные проекции (в столбцовой записи). Для пары это покоординатно:
Именно эти две строки записаны в докстринге класса RoPE и реализованы в forward.
Все свойства поворотов переносятся на поблочно: блоки не взаимодействуют, поэтому , , и .
Частоты
Заголовок раздела «Частоты»Частоты берутся как в синусоидальном кодировании:
где:
- — база (в конфигах
rope_theta), обычно ; должна быть больше 1; - — размер головы (не модели: поворачиваются векторы голов);
- радиан на позицию, последняя частота .
Как и у Vaswani, частоты убывают в геометрической прогрессии: у пары 0 угол растёт на радиан за токен, у последней — на десятитысячную долю радиана. Подробнее о выборе базы — в разделе «Скорости вращения и база».
Пример. , (маленькая база для наглядности): , .
Главное свойство: оценка зависит только от m − n
Заголовок раздела «Главное свойство: оценка зависит только от m − n»Утверждение. Для любых и позиций :
Правая часть зависит от позиций только через разность .
Доказательство.
Шаг 1. Транспонирование произведения: . Значит,
Шаг 2. (ортогональность поворота, свойство 2 поблочно).
Шаг 3. (повороты складываются, свойство 1 поблочно: блок равен ).
Шаг 4. Подставляем: . ∎
Интуиция. Каждый вектор поворачивается на угол, зависящий от своей абсолютной позиции. Но скалярное произведение двух векторов зависит только от их длин и угла между ними. Если оба вектора повернуть ещё на один и тот же угол (сдвинуть обе позиции на ), угол между ними не изменится. Поэтому и дают одну и ту же оценку внимания.
Явная формула. Распишем вклад одной пары. Пусть , а и — пары векторов и (чётная и нечётная координаты). Тогда
где — скалярное произведение пар, — их «косое» произведение (площадь параллелограмма со знаком). Оценка — это сумма синусоид по расстоянию с частотами и амплитудами, которые задаёт содержимое и . Модель через и управляет амплитудами и может, например, сделать голову, которая смотрит на соседний токен.
Вывод явной формулы
По доказанному, вклад пары равен (с учётом ). Обозначим . Поворот на :
Скалярно умножаем на :
Суммируем по парам.
Та же выкладка в комплексных числах
Заголовок раздела «Та же выкладка в комплексных числах»Поворот на плоскости — это умножение на комплексное число единичной длины. Сопоставим паре вектора комплексное число:
где — мнимая единица. Тогда поворот пары на угол — это умножение на :
— те же формулы, что для , выше. Весь RoPE — это комплексных умножений (RoFormer, раздел 3.4.1; так же устроен эталонный код Meta, который переводит пары в комплексные числа).
Скалярное произведение через комплексные числа. Для пар и (вещественные — координаты двух пар): , так что — скалярное произведение пар и . Поэтому
где , — комплексные числа пар и , черта — комплексное сопряжение.
Относительное свойство. Сопряжение произведения — произведение сопряжений, и . Тогда
Абсолютные позиции сократились в показателе экспоненты: осталась только разность . Это одна строчка вместо четырёх шагов матричного доказательства — показатели экспонент складываются так же, как углы поворотов.
Норма сохраняется
Заголовок раздела «Норма сохраняется»Поворот не меняет длину вектора:
где — евклидова норма. Следствия: масштаб оценок после RoPE остаётся тем же, что без него, и нормирование на по-прежнему корректно (attention.md). Позиция не «раздувает» и не «гасит» векторы, как это может делать прибавленный позиционный вектор. И при (токен смотрит на себя) в формуле — оценка та же, что без RoPE.
Почему V не поворачивается
Заголовок раздела «Почему V не поворачивается»RoPE применяется только к и . Причины:
- Позиция нужна, чтобы решить, куда смотреть. Относительная позиция должна влиять на веса внимания — а они вычисляются только из и . Значения — это то, что забирается с выбранных позиций.
- Поворот V сломал бы относительность. Выход головы — взвешенная сумма . Если бы были повёрнуты на угол своей абсолютной позиции , в выход попала бы абсолютная позиция каждого источника: одинаковый контекст, сдвинутый на позиций, давал бы другой выход. В скалярном произведении повороты сокращаются, а во взвешенной сумме сокращаться нечему.
Итог: RoPE не добавляет позицию к содержимому, которое передаётся дальше по residual-потоку, а только управляет маршрутизацией внимания.
Численный пример
Заголовок раздела «Численный пример»Возьмём , , то есть , , и векторы
Без RoPE .
Поворот на позиции . Пара 0, угол : , .
x̃₀ = 1·(−0.9900) − 2·0.1411 = −1.2722x̃₁ = 1·0.1411 + 2·(−0.9900) = −1.8389Пара 1, угол : , .
x̃₂ = 0·0.9553 − 1·0.2955 = −0.2955x̃₃ = 0·0.2955 + 1·0.9553 = 0.9553Поворот на позиции . Пара 0, угол 1: , → . Пара 1, угол 0.1: , → .
Скалярное произведение:
пара 0: (−1.2722)(−0.8415) + (−1.8389)(0.5403) = 1.0706 − 0.9936 = 0.0770пара 1: (−0.2955)(0.8952) + (0.9553)(1.0948) = −0.2645 + 1.0459 = 0.7814итого: 0.8584Проверка по явной формуле. . Пара 0: , , вклад . Пара 1: , , вклад . Совпадает.
Сдвиг обеих позиций. Те же векторы на других позициях:
| 3 | 1 | (−1.2722, −1.8389, −0.2955, 0.9553) | (−0.8415, 0.5403, 0.8952, 1.0948) | 0.8584 |
| 5 | 3 | (2.2015, −0.3916, −0.4794, 0.8776) | (−0.1411, −0.9900, 0.6598, 1.2509) | 0.8584 |
| 10 | 8 | (0.2490, −2.2222, −0.8415, 0.5403) | (−0.9894, −0.1455, −0.0206, 1.4141) | 0.8584 |
| 1 | 3 | (−1.1426, 1.9221, −0.0998, 0.9950) | (−0.1411, −0.9900, 0.6598, 1.2509) | −0.5629 |
| 0 | 2 | (1, 2, 0, 1) | (−0.9093, −0.4161, 0.7814, 1.1787) | −0.5629 |
Повёрнутые векторы в каждой строке разные, а оценки при одинаковой разности совпадают. При оценка другая: зависит от знака разности, то есть RoPE различает «ключ слева» и «ключ справа» (в causal-модели нужен только случай ).
Тот же результат даёт класс из репозитория:
import torchfrom llm.core.rope import RoPE
rope = RoPE(head_size=4, max_seq_len=16, base=100)q = torch.tensor([1., 2., 0., 1.]).expand(1, 1, 16, 4) # один и тот же q на всех 16 позицияхk = torch.tensor([0., 1., 1., 1.]).expand(1, 1, 16, 4)q_rot, k_rot = rope(q)[0, 0], rope(k)[0, 0] # [16, 4]print(q_rot[3] @ k_rot[1], q_rot[5] @ k_rot[3]) # tensor(0.8584) tensor(0.8584)print(torch.allclose(q_rot.norm(dim=-1), torch.tensor(6.0).sqrt())) # True: норма √6 сохраниласьЭффективная реализация без матриц
Заголовок раздела «Эффективная реализация без матриц»Хранить и умножать матрицу расточительно: она почти вся из нулей, а умножение стоило бы операций на вектор. Раскроем формулы поворота так, чтобы остались только поэлементные операции (RoFormer, раздел 3.4.2):
где:
- — вектор головы на позиции ;
- — поэлементное умножение;
- — каждая частота повторена дважды, по разу на координату пары; — аналогично;
- — в каждой паре координаты меняются местами, у первой меняется знак (поворот пары на 90°).
Проверим для пары 0: , — ровно формулы поворота. Стоимость — на вектор, а таблицы и для всех позиций можно посчитать заранее.
В RoPE.forward то же записано чуть иначе — через раздельные чётные и нечётные координаты, без повторения частот:
x_even = x[..., 0::2] # x₀, x₂, x₄, … [B, H, T, d_h/2]x_odd = x[..., 1::2] # x₁, x₃, x₅, …x_rotated_even = x_even * cos - x_odd * sin # x̃₂ᵢx_rotated_odd = x_even * sin + x_odd * cos # x̃₂ᵢ₊₁x_rotated = torch.stack([x_rotated_even, x_rotated_odd], dim=-1).flatten(-2) # чередуем обратноstack(..., dim=-1) даёт тензор [..., d_h/2, 2] с парами , а flatten(-2) раскладывает его обратно в порядок .
Два способа выбрать пары: соседние координаты и половины вектора
Заголовок раздела «Два способа выбрать пары: соседние координаты и половины вектора»Какие координаты объединять в пару — вопрос соглашения. Существуют два варианта:
| Пара | Функция поворота | Где | |
|---|---|---|---|
| чередующиеся пары (interleaved) | статья RoFormer, эталонный код Meta (LLaMA), этот репозиторий | ||
| половины вектора | rotate_half: | HuggingFace transformers |
В HF-варианте — таблица частот повторена целиком, а не поэлементно; сами частоты те же.
Математически это одна и та же операция в разных системах координат: переставьте координаты вектора так, чтобы и оказались соседями, — и половинный вариант превратится в чередующийся. Обозначим эту перестановку матрицей . Для скалярного произведения — перестановка, применённая к и одинаково, оценок не меняет.
Следствие для весов. Модель, обученная с одним вариантом, не работает с другим: её , выучены так, что пары частот лежат в определённых координатах. Но перестановку можно «впечатать» в веса: переставить выходы (столбцы , в нашей записи ) — тогда проекция сразу выдаёт векторы в нужном порядке.
Функция _hf_to_meta_rows в models/llama/hf_weights.py делает именно это. В PyTorch nn.Linear хранит вес формы [out, in], то есть , поэтому переставляются строки тензора веса. Внутри каждой головы для и :
где:
- — веса
q_proj.weightиз HF и_q.weightв этом репозитории (дляk_proj— голов вместо ); - — первая координата пары, — вторая;
- — номер головы; перестановка не выходит за пределы головы.
Словами: строки головы в HF идут как (первые и вторые координаты пар), здесь — . Для строка здесь берётся из строки HF
j здесь: 0 1 2 3 4 5 6 7строка HF: 0 4 1 5 2 6 3 7В коде это три операции с формой:
value.reshape(num_heads, 2, head_size // 2, *rest) # [голова, s, i, …] .transpose(1, 2) # [голова, i, s, …] .reshape(value.shape) # строка h·d_h + 2i + sПрименяется к q_proj с num_heads и к k_proj с num_kv_heads (у GQA голов K меньше). v_proj и o_proj не переставляются: V не поворачивается, и порядок его координат ни с чем не должен совпадать. Скрипт конвертации HF (convert_llama_weights_to_hf.py) при переходе от весов Meta к HF делает прямую перестановку; здесь — обратная. Подробнее о загрузке весов — в llama.md.
Проверить эквивалентность можно так: посчитать оценки HF-способом (rotate_half) на исходных весах и способом этого репозитория на переставленных — они совпадут; без перестановки — нет.
Затухание дальних зависимостей
Заголовок раздела «Затухание дальних зависимостей»RoFormer (раздел 3.4.3) отмечает ещё одно свойство — затухание на больших расстояниях (long-term decay). Запишем оценку в комплексной форме: , где , . Суммирование по частям (преобразование Абеля) даёт оценку сверху:
где — частичные суммы единичных векторов с углами (считаем ). При все слагаемые смотрят в одну сторону, и . С ростом углы расходятся, единичные векторы начинают гасить друг друга, и суммы уменьшаются. Среднее при , :
| 0 | 1 | 5 | 10 | 50 | 100 | 200 | 1000 | |
|---|---|---|---|---|---|---|---|---|
| среднее | 32.5 | 31.5 | 20.8 | 18.0 | 12.6 | 10.2 | 7.2 | 4.5 |
Спад идёт с колебаниями (например, при — 7.2, больше, чем при 250 — 6.5). Это верхняя граница, а не сама оценка: она не запрещает модели смотреть далеко, но при прочих равных далёкие пары токенов получают меньше «максимально возможного» веса. Такое смещение в сторону близких токенов считается желательным для языка.
Скорости вращения и база
Заголовок раздела «Скорости вращения и база»Пары вращаются с разной скоростью: убывает от радиана на позицию у первой пары до у последней. Это похоже на часы с стрелками: быстрые делают оборот за несколько токенов, медленные — за тысячи. Период пары — через сколько токенов её угол повторяется — равен . Для :
| Пара | при base = 10⁴ | период | при base = 10⁶ | период |
|---|---|---|---|---|
| 0 | 1 | 6 токенов | 1 | 6 токенов |
| 8 | 0.1 | 63 | 0.032 | 199 |
| 16 | 0.01 | 628 | 0.001 | 6 283 |
| 24 | 0.001 | 6 283 | 3.2·10⁻⁵ | ~199 000 |
| 31 | 1.3·10⁻⁴ | ~47 000 | 1.5·10⁻⁶ | ~4 000 000 |
Каждая пара различает расстояния на своём масштабе. Быстрые пары точно кодируют соседство, но на большом расстоянии их угол успевает много раз провернуться, и расстояния 1000 и 1006 для них почти неразличимы (). Дальние расстояния однозначно кодируют только медленные пары — пока их угол в пределах контекста не делает полного оборота.
База задаёт, насколько медленной будет последняя пара, то есть под какую длину контекста рассчитана «шкала». Угол самой медленной пары (, ) на позиции :
| Позиция | 512 | 4 096 | 8 192 | 32 768 |
|---|---|---|---|---|
| base = 10⁴ | 3.9° | 31° | 63° | 250° |
| base = 10⁶ | 0.04° | 0.4° | 0.7° | 2.9° |
При базе для контекста 32k запаса почти нет: медленная пара проходит больше двух третей оборота. При она поворачивается лишь на 3°. Цена большой базы — все пары, кроме первой, вращаются медленнее, и ближние расстояния кодируются грубее (сравните строки 8 и 16 первой таблицы).
| Модель | rope_theta | Контекст |
|---|---|---|
| RoFormer, LLaMA-1, Mistral 7B v0.1, Gemma | 10 000 | до 8k |
| Mixtral 8x7B | 1 000 000 | 32k |
В репозитории база задаётся ключом конфига rope_theta (по умолчанию 10000) у LLaMA, Mistral, Mixtral и Gemma и передаётся в RoPE(head_size, max_seq_len, base=...). Для учебных конфигов () разница между и почти не видна: самая медленная пара при к позиции 512 поворачивается примерно на 4°.
База — не обучаемый параметр, но веса модели выучиваются под конкретные углы. Поэтому у обученной модели её нельзя менять без дообучения: с другой базой те же позиции дают другие повороты, и внимание работает хуже.
Расширение контекста
Заголовок раздела «Расширение контекста»Что будет, если подать модели позицию больше той, на которой её обучали? Быстрые пары при этом не видят ничего нового — их углы давно прошли все значения на окружности. А медленные пары получают углы, которых модель никогда не видела, и оценки внимания становятся непредсказуемыми. Chen et al. (2023) показали, что прямая экстраполяция LLaMA за длину обучения быстро разрушает качество. Известные способы расширить контекст готовой модели (в репозитории их нет: конфига rope_scaling не существует, и hf_weights.py рассчитан на модели без него):
- Position Interpolation (Chen et al., 2023): позиции сжимаются, , где — длина обучения, — новая длина. Все углы остаются в знакомом диапазоне, но становятся дробными. После короткого дообучения (порядка 1000 шагов) LLaMA работает с контекстом до 32 768.
- Увеличение базы («NTK-aware»-масштабирование, описано в YaRN, Peng et al., 2023): вместо равномерного сжатия увеличивается база. Быстрые пары почти не меняются (ближние расстояния кодируются как раньше), медленные замедляются сильнее.
- Llama 2 Long (Xiong et al., 2023): база увеличена с 10 000 до 500 000 и модель дообучена на длинных последовательностях (контекст 32k).
- Code Llama (Rozière et al., 2023): база 1 000 000, дообучение на последовательностях 16k; модель устойчиво работает на ещё более длинных входах.
Mixtral 8x7B сразу обучен с базой под контекст 32k.
RoPE и KV-кэш
Заголовок раздела «RoPE и KV-кэш»При генерации с KV-кэшем (generation.md) ключи прошлых токенов не пересчитываются, а берутся из кэша. Для RoPE это означает:
- Кэш хранит K уже повёрнутыми. Поворот зависит только от позиции ключа, а она не меняется, пока токен в окне. Поэтому ключ поворачивают один раз, когда токен появился, и кладут в кэш. V кэшируется как есть.
- Новый токен поворачивается по своей абсолютной позиции. Это
start_pos— позиция первого нового токена.RoPE.forward(x, start_pos)берёт строки таблицcos/sin[start_pos : start_pos + seq_len]. - Откуда берётся
start_pos. ВMultiHeadAttention(LLaMA) — длина кэша:start_pos = cache[0].size(2). ВGroupedQueryAttention(Mistral, Mixtral, Gemma) кэш со скользящим окном обрезается доwindow_sizeпозиций, и его длина перестаёт совпадать с позицией токена; поэтому слой хранит кэш как тройку(K, V, next_pos)и берётstart_pos = cache[2], а после шага записываетnext_pos = start_pos + seq_len.
Если start_pos передать неправильно (например, 0 на каждом шаге), новый запрос будет повёрнут так, будто он стоит в начале текста, и все расстояния до ключей из кэша окажутся неверными.
Когда генерация выходит за , generate пересчитывает последние токенов без кэша — как и у GPT: при сдвиге окна позиции всех токенов меняются, и повёрнутые K из кэша больше не годятся. Заметим, что для RoPE оценки зависят только от разностей позиций, поэтому математически старые K можно было бы оставить; однако в таблицах cos/sin нет строк за , и реализация выбирает простой путь.
Где позиция входит в модель
Заголовок раздела «Где позиция входит в модель»У GPT позиция добавляется один раз, на входе, и дальше путешествует по residual-потоку вместе с содержимым. У RoPE-моделей позиции на входе нет вовсе: она вносится в каждом слое, в каждой голове, и только в и .
%%{init: {"flowchart": {"rankSpacing": 28, "nodeSpacing": 28}}}%%
flowchart TB
subgraph GPT["GPT-1, GPT-2: сложение на входе"]
direction TB
I1(["token ids"]):::io --> TE1["Token Embedding"]:::blue
I1 --> PE1["Position Embedding<br/>(обучаемая таблица)"]:::purple
TE1 --> S1(("+")):::add
PE1 --> S1
S1 --> D1["Decoder × L<br/>attention без позиции"]:::gray
D1 --> O1(["logits"]):::io
end
subgraph ROPE["LLaMA, Mistral, Mixtral, Gemma: поворот внутри attention"]
direction TB
I2(["token ids"]):::io --> TE2["Token Embedding"]:::blue
TE2 --> X2(["x слоя"]):::io
X2 --> Q2["W_q → Q"]:::gray
X2 --> K2["W_k → K"]:::gray
X2 --> V2["W_v → V"]:::gray
Tab["RoPE: таблицы cos/sin<br/>один модуль на все слои"]:::rope
Q2 --> RQ["поворот Q"]:::rope
K2 --> RK["поворот K"]:::rope
Tab -.-> RQ
Tab -.-> RK
RQ --> SC["оценки Q·Kᵀ → softmax"]:::gray
RK --> SC
SC --> AV["веса · V"]:::gray
V2 -- "V не поворачивается" --> AV
AV --> Rep(["повторяется в каждом из L слоёв"]):::io
end
style GPT fill:transparent,stroke:#9673a6,stroke-width:2px
style ROPE fill:transparent,stroke:#3a9e8f,stroke-width:2px
classDef io fill:#ffffff,stroke:#999999,color:#1a1a1a;
classDef add fill:#ffffff,stroke:#666666,color:#1a1a1a;
classDef blue fill:#dae8fc,stroke:#6c8ebf,color:#1a1a1a;
classDef blueHl fill:#dae8fc,stroke:#2f5f9e,stroke-width:3px,color:#1a1a1a;
classDef purple fill:#e1d5e7,stroke:#9673a6,color:#1a1a1a;
classDef purpleHl fill:#e1d5e7,stroke:#6a3d85,stroke-width:3px,color:#1a1a1a;
classDef gray fill:#f5f5f5,stroke:#666666,color:#1a1a1a;
classDef grayHl fill:#f5f5f5,stroke:#333333,stroke-width:3px,color:#1a1a1a;
classDef gold fill:#fff2cc,stroke:#d6b656,color:#1a1a1a;
classDef rope fill:#d5f0ec,stroke:#3a9e8f,color:#1a1a1a;
classDef ropeHl fill:#d5f0ec,stroke:#1f6f63,stroke-width:3px,color:#1a1a1a;
classDef dim fill:#f5f5f5,stroke:#bbbbbb,color:#999999,stroke-dasharray:4 3;
Разница существенная. У GPT информация о позиции должна «дожить» до глубоких слоёв через residual-поток и смешивается с содержимым. У RoPE каждый слой получает точную относительную позицию заново, а в residual-поток она не попадает.
Реализация RoPE в репозитории
Заголовок раздела «Реализация RoPE в репозитории»Класс RoPE — модуль без обучаемых параметров. Один экземпляр создаётся в __init__ модели (Llama, Mistral, Mixtral, Gemma) и под именем _position_embeddings передаётся во все слои attention:
self._position_embeddings = RoPE( head_size=head_size, max_seq_len=config["max_position_embeddings"], base=config.get("rope_theta", 10_000),)Конструктор: проверки и таблицы
Заголовок раздела «Конструктор: проверки и таблицы»def __init__(self, head_size: int, max_seq_len: int, base: float = 10_000): super().__init__() if base <= 1: raise ValueError(f"base должна быть > 1, получено {base}") if head_size % 2 != 0: raise ValueError(f"head_size={head_size} должен быть чётным: RoPE поворачивает пары координат")- База больше 1. При все частоты равны 1 — все пары вращаются одинаково быстро, и медленных «стрелок» нет. При частоты растут, а не убывают. Оба случая — почти наверняка ошибка в конфиге.
- Чётный
head_size. Поворачиваются пары координат, одна координата осталась бы без пары. Модели проверяют это ещё раньше, при разборе конфига:resolve_head_size(config, ..., rope=True)вcore/config_checks.py.
freqs = 1.0 / (base ** (2 * torch.arange(head_size // 2).float() / head_size)) # θᵢ, [d_h/2]positions = torch.arange(max_seq_len).float() # m, [T_max]freq_matrix = positions.unsqueeze(1) * freqs.unsqueeze(0) # m·θᵢ, [T_max, d_h/2]freqs— формула для (arange(head_size // 2)даёт , умножение на 2 — ).freq_matrix— внешнее произведение векторов позиций и частот:[T_max, 1] * [1, d_h/2]→[T_max, d_h/2], элемент равен углу .
self.register_buffer("cos_matrix", torch.cos(freq_matrix), persistent=False)self.register_buffer("sin_matrix", torch.sin(freq_matrix), persistent=False)- Таблицы вычисляются один раз и хранятся как буферы: они переезжают на GPU вместе с моделью (
model.to(device)), но не являются параметрами и не обучаются. persistent=False— буферы не попадают вstate_dict. Их незачем хранить: они полностью определяютсяhead_size,max_seq_lenиbase. К тому же один объектRoPEзарегистрирован и в модели, и в каждом слое attention, и сpersistent=Trueчекпоинт содержал бы одни и те же таблицыnum_layers + 1раз._load_from_state_dictвыбрасывает ключиcos_matrix/sin_matrix, если они есть в старом чекпоинте (сохранённом до перехода наpersistent=False), поэтому такие чекпоинты загружаются и сstrict=True.
Размер таблиц: чисел — не зависит ни от числа слоёв, ни от числа голов.
forward(x, start_pos, positions=None)
Заголовок раздела «forward(x, start_pos, positions=None)»assert x.ndim == 4, "RoPE поддерживает только 4D-вход [batch, num_heads, seq_len, head_size]"batch_size, num_heads, seq_len, head_size = x.shape
if positions is None: cos = self.cos_matrix[start_pos:start_pos+seq_len].to(x.dtype) # [seq_len, head_size//2] sin = self.sin_matrix[start_pos:start_pos+seq_len].to(x.dtype) cos = cos.reshape(1, 1, seq_len, head_size // 2) sin = sin.reshape(1, 1, seq_len, head_size // 2)else: cos = self.cos_matrix[positions].to(x.dtype).unsqueeze(1) # [batch, 1, seq_len, head_size//2] sin = self.sin_matrix[positions].to(x.dtype).unsqueeze(1)- Вход — или уже после разбиения на головы:
[B, H, T, d_h](для K в GQA —[B, G, T, d_h]). - Срез
[start_pos : start_pos + seq_len]выбирает углы для абсолютных позиций новых токенов. Без кэшаstart_pos = 0. .to(x.dtype)приводит таблицы (они во float32) к типу входа, например bfloat16.reshape(1, 1, seq_len, d_h/2)готовит таблицы к broadcasting: одни и те же углы для всех примеров батча и всех голов — позиция токена от головы не зависит.- При паддинге в
attention_maskвместоstart_posпередаютсяpositionsформы[B, T]— позиции токенов среди настоящих токенов своей строки (cumsum(mask) − 1, см. masks.md). Индексацияcos_matrix[positions]берёт для каждой строки свои углы,unsqueeze(1)добавляет ось голов: углы по-прежнему общие для всех голов, но уже свои у каждого примера батча.
Далее — разделение на чётные/нечётные координаты, поворот и обратная склейка, разобранные в разделе «Эффективная реализация без матриц». Результат — новый тензор той же формы и типа, вход не изменяется.
Где вызывается
Заголовок раздела «Где вызывается»В MultiHeadAttention (LLaMA) и GroupedQueryAttention (Mistral, Mixtral, Gemma) — после разбиения на головы и до склейки с кэшем:
positions = padding.positions if padding is not None else Noneif self._rope is not None: q = self._rope(q, start_pos=start_pos, positions=positions) k = self._rope(k, start_pos=start_pos, positions=positions)# затем: k = torch.cat([k_cache, k], dim=2) — в кэше K уже повёрнутыПеред этим слой проверяет, что start_pos + seq_len не превышает max_seq_len, и выбрасывает ValueError. Сам RoPE.forward границу не проверяет: при выходе за таблицу срез окажется короче seq_len, и reshape упадёт с RuntimeError. При прямом использовании класса следите за длиной сами.
Для GPT-1 и GPT-2 параметр rope у MultiHeadAttention равен None, и поворот не применяется.
Сравнение методов
Заголовок раздела «Сравнение методов»| Метод | Где входит позиция | Обучаемых параметров | Относительная позиция | За пределами длины обучения | Где используется | В репозитории |
|---|---|---|---|---|---|---|
| Обучаемые абсолютные | сумма с эмбеддингом на входе | нет, только если модель выучит | невозможно: строк нет | GPT-1, GPT-2 | PositionalEmbeddings | |
| Синусоидальные (Vaswani) | сумма с эмбеддингом на входе | 0 | сдвиг = линейное отображение , но после проекций не гарантирована | формула определена для любых , качество не гарантировано | исходный Transformer | нет |
| Shaw et al. | векторы в attention | на слой | да, до расстояния | дальше расстояния неразличимы | — | нет |
| T5 bias | скаляр к оценке | число корзин × число голов | да | дальние корзины общие | T5 | нет |
| ALiBi | линейный штраф к оценке | 0 | да | главное преимущество: работает на длинах больше обучения | — | нет |
| RoPE | поворот Q и K в каждом слое | 0 | да, точно: | плохо без дообучения; расширяется интерполяцией или увеличением базы | LLaMA, Mistral, Mixtral, Gemma | RoPE |
Типичные ошибки и тонкости
Заголовок раздела «Типичные ошибки и тонкости»- Забытый
start_posпри генерации с кэшем. И дляPositionalEmbeddings, и дляRoPEновый токен получит позицию 0. В репозиторииstart_posвычисляется из кэша автоматически; ошибка возможна при самостоятельной сборке модели из модулейcore. - Несовпадение соглашения о парах при переносе весов. Веса HF нельзя загрузить «как есть»: без перестановки строк
q_proj/k_projмодель выдаёт мусор, хотя все формы тензоров совпадают. Ошибка не видна по формам — только по качеству. - Поворот V. Лишний поворот не вызывает ошибок формы, но ломает относительность и расходится с эталонными весами.
- Смена
rope_thetaу обученной модели. Меняет все углы; без дообучения качество падает. При загрузке весов HFrope_thetaв конфиге должен совпадать с HF (для Mixtral 8x7B — ). - Нечётный
head_size. RoPE неприменим; конфиг отклоняется сValueError. d_h, а неd. Частоты считаются по размеру головы. Если перепутать, углы будут другими, и веса HF не подойдут.- Точность. Углы считаются во float32 один раз при создании модуля. Таблицы приводятся к типу входа (
.to(x.dtype)); в bfloat16 (8 бит мантиссы) это даёт ошибку до в значениях / — так же поступает и HF. - Causal-маска тоже несёт позицию. Модель без позиционного кодирования, но с causal-маской — не «мешок токенов» (см. выше). Поэтому «позиция не нужна» — неверный вывод из того, что модель без неё как-то обучилась.
- Attention без маски переставочно-эквивариантен: . Без позиционной информации модель видит мешок токенов.
- GPT-1/GPT-2 прибавляют к эмбеддингу токена обучаемый вектор позиции : просто, параметров, жёсткий предел длины .
- Синусоидальное кодирование: частоты в геометрической прогрессии, сдвиг позиции — поворот пар координат. Эта идея ведёт к RoPE.
- RoPE поворачивает и на углы : — оценка зависит только от расстояния, норма сохраняется, параметров нет, V не поворачивается.
- Реализация — поэлементно через таблицы / за ; соседние пары (Meta, этот репозиторий) и половины вектора (HF) эквивалентны с точностью до перестановки строк , .
- База
rope_thetaзадаёт самую медленную частоту и тем самым длину контекста, под которую рассчитана шкала; — классика, — Mixtral 32k. - С KV-кэшем K хранятся повёрнутыми, а новые токены поворачиваются по абсолютной позиции
start_pos(в GQA —next_posиз кэша).
Вопросы и упражнения
Заголовок раздела «Вопросы и упражнения»-
Докажите, что без маски attention переставочно-эквивариантен, а с causal-маской — нет. Приведите пример из двух токенов, где перестановка меняет выход первого токена.
Ответ
Доказательство — в разделе «Attention не знает порядка». Пример с маской: токены . В порядке первый токен видит только себя, его выход — . В порядке токен стоит вторым, видит и себя, его выход — взвешенная сумма и , в общем случае не равная . Значит, выход токена зависит от его места.
-
Сколько параметров занимают позиционные эмбеддинги GPT-2 small (, )? Какую долю это составляет от 124,4M?
Ответ
параметров, около .
-
Выведите из формул суммы углов, что , и объясните, почему отсюда следует для блочно-диагональных матриц RoPE.
Ответ
Вывод для — в разделе «Поворот на плоскости». Блочно-диагональные матрицы перемножаются поблочно: -й блок произведения равен произведению -х блоков. Поэтому имеет блоки , то есть это .
-
Посчитайте частоты и периоды для , .
Ответ
: , периоды токенов.
-
Почему одной частоты мало? Пусть , , . Найдите оценку как функцию и сравните и .
Ответ
По явной формуле , : оценка . При — 1, при — (). Одна быстрая частота не отличает «тот же токен» от «44 токена назад». Медленные частоты разрешают эту неоднозначность — поэтому их набор в геометрической прогрессии.
-
Почему RoPE применяют к Q и K, но не к V? Что сломается, если повернуть и V?
Ответ
См. раздел «Почему V не поворачивается»: позиция должна влиять на веса внимания, а они вычисляются из , где повороты сокращаются до . В выходе повороты не сокращаются, и выход начнёт зависеть от абсолютных позиций источников; кроме того, веса, обученные без поворота V, перестанут подходить.
-
Для запишите, из каких строк HF-матрицы
q_proj.weightодной головы берутся строки 0, 1, 2, 3 в этом репозитории. Проверьте ответ вызовом_hf_to_meta_rows.Ответ
По формуле с : строка 0 ← 0, 1 ← 2, 2 ← 1, 3 ← 3.
import torchfrom llm.models.llama.hf_weights import _hf_to_meta_rowsrows = torch.arange(4.).unsqueeze(1) # «вес» [4, 1]: номер строки HFprint(_hf_to_meta_rows(rows, num_heads=1).squeeze(1)) # tensor([0., 2., 1., 3.]) -
Самая медленная пара RoPE при : через сколько токенов она делает полный оборот при базе и при базе (Llama 2 Long)? Почему для контекста 32k удобнее вторая?
Ответ
. При : , период . При : , период . При базе на контексте 32k медленная пара проходит — больше двух третей оборота, и её угол становится неоднозначным ближе к краю контекста; при она поворачивается на несколько градусов и остаётся однозначной мерой дальних расстояний.
Литература
Заголовок раздела «Литература»- Vaswani et al. Attention Is All You Need. 2017. arXiv:1706.03762 — раздел 3.5, синусоидальное кодирование
- Radford, Narasimhan, Salimans, Sutskever. Improving Language Understanding by Generative Pre-Training. OpenAI, 2018. PDF — обучаемые позиционные эмбеддинги GPT-1
- Radford, Wu, Child, Luan, Amodei, Sutskever. Language Models are Unsupervised Multitask Learners. OpenAI, 2019. PDF — GPT-2
- Shaw, Uszkoreit, Vaswani. Self-Attention with Relative Position Representations. 2018. arXiv:1803.02155
- Raffel et al. Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer. 2019. arXiv:1910.10683 — T5, относительный bias
- Press, Smith, Lewis. Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation. 2022. arXiv:2108.12409 — ALiBi
- Su et al. RoFormer: Enhanced Transformer with Rotary Position Embedding. 2021. arXiv:2104.09864 — RoPE; разделы 3.2, 3.4.1–3.4.3
- Haviv, Ram, Press, Izsak, Levy. Transformer Language Models without Positional Encodings Still Learn Positional Information. 2022. arXiv:2203.16634
- Chen, Wong, Chen, Tian. Extending Context Window of Large Language Models via Positional Interpolation. 2023. arXiv:2306.15595
- Peng, Quesnelle, Fan, Shippole. YaRN: Efficient Context Window Extension of Large Language Models. 2023. arXiv:2309.00071 — в том числе обзор «NTK-aware»-масштабирования
- Xiong et al. Effective Long-Context Scaling of Foundation Models. 2023. arXiv:2309.16039 — Llama 2 Long
- Rozière et al. Code Llama: Open Foundation Models for Code. 2023. arXiv:2308.12950
- Jiang et al. Mixtral of Experts. 2024. arXiv:2401.04088