Перейти к содержимому
Ступень 55.5 Длинный контекст

Расширение контекста — PI, NTK, YaRN

Почему модель, обученная на 4K токенов, ломается на 16K, и как правкой частот RoPE (PI, NTK-aware, YaRN) DeepSeek и Qwen растягивают контекст до 128K и дальше за доли процента бюджета претрейна.

УРОКstage-5/5-5-long-context/01-rope-scaling.mdx2 чУрок 1 из 3
После урока вы сможете
  • Объяснить, почему RoPE-модель не обобщается на длины больше обучающих, и найти «медленные» пары частот, которые в этом виноваты
  • Вывести формулы Position Interpolation, NTK-aware и YaRN и сравнить, что каждый метод делает с быстрыми и медленными частотами
  • Реализовать все три метода на NumPy и проверить, что PI и YaRN не создают «невиданных» углов
  • Прочитать rope_scaling в конфигах Qwen и DeepSeek и объяснить, как они расширяют контекст поэтапно

Длинный контекстдлинный контекстEN long context стал обязательной частью современных LLM. Qwen3 «из коробки» работает с 32K токенов и растягивается до 128K, DeepSeek-V3 — со 128K, Qwen2.5-1M и MiniMax-01 — с миллионом. Агенту для работы с кодом нужно держать в голове весь репозиторий, юристу — договор на 300 страниц.

Но обучать модель на 128K токенов с первого шага — разорительно. Внимание стоит O(n2)O(n^2) (квадратичная сложность вниманияквадратичная сложность вниманияEN quadratic complexity (O(n²))Каждый из n токенов сравнивается с каждым — n² оценок на голову. При длинном контексте это главная стоимость внимания; отсюда FlashAttention, разреженное и линейное внимание., урок 4.4.3), а большинство документов в корпусе всё равно короче нескольких тысяч токенов. Поэтому все делают одинаково: претрейн на коротком контексте (4K–32K), затем короткое дообучение на длинном. DeepSeek-V3 прошла 14.8 трлн токенов на длине 4K, а расширение до 128K стоило около 126 млрд токенов — 0.85% бюджета.

Чтобы такое дообучение вообще сработало, нужно правильно «растянуть» позиционное кодирование. Этим и занимается урок: почему наивное удлинение ломает модель и как три приёма — PI, NTK-aware и YaRN — это исправляют. В китайском сообществе вся тема называется «экстраполяцией по длине»экстраполяция по длинеEN length extrapolationСпособность модели работать на последовательностях длиннее тех, на которых её обучали. В китайском сообществе так называют всю тему расширения контекста..

В уроке 4.5.2 мы разобрали ротационные позиционные эмбеддингиротационные позиционные эмбеддинги (RoPE)EN rotary position embedding (RoPE)RoPE предложен в Китае (Су Цзяньлинь и соавторы, компания Zhuiyi). Сейчас используется почти во всех LLM.. Вектор запроса (и ключа) размера dd делится на d/2d/2 пар координат. Пару номер ii на позиции mm поворачивают на угол mθim\theta_i, где

θi=b−2i/d,i=0,1,…,d2−1.\theta_i = b^{-2i/d}, \qquad i = 0, 1, \ldots, \tfrac{d}{2} - 1 .

Здесь bb — база RoPEбаза RoPEEN RoPE base (rope_theta)Число b в частотах RoPE θ_i = b^(−2i/d). Классическое значение 10 000; в длинноконтекстных моделях его увеличивают (Llama 3 — 500 000, Qwen2.5 — 1 000 000). (в конфигах rope_theta), классически b=10 000b = 10\,000. Скалярное произведение повёрнутых qq и kk зависит только от разности позиций m−nm - n — за это RoPE и любят.

Удобно представить часы с 64 стрелками (для d=128d = 128). Пара 0 поворачивается на 1 радиан за токен: полный оборот за 2π≈6.32\pi \approx 6.3 токена, как секундная стрелка. Последняя пара — очень медленная «часовая». Время полного оборота называют длиной волны λi=2π/θi\lambda_i = 2\pi / \theta_i (частота и период волны — урок 1.0.4):

Пара iiЧастота θi\theta_iДлина волны λi\lambda_i, токеновОборотов за 4096 токенов
016.3652
160.16365
320.016286.5
460.00134 7120.87
630.00011554 4100.075

Посмотрите на последний столбец. Быстрые пары за обучающие 4096 токенов провернулись десятки и сотни раз и видели все возможные углы. А пары с 46-й по 63-ю (18 пар из 64) не сделали ни одного полного оборота.

Почему модель ломается за границей обучающей длины

Заголовок раздела «Почему модель ломается за границей обучающей длины»

Возьмите модель, обученную на 4K, и подайте ей 16K токенов. Perplexity ровная до 4096, а сразу за границей резко взлетает, и модель начинает выдавать бессмыслицу. Причин две.

1. Невиданные углы. Пара 63 за всё обучение повернулась максимум на 4096⋅0.000115≈0.474096 \cdot 0.000115 \approx 0.47 радиана. Её веса выучили «мир», в котором угол этой пары лежит между 0 и 0.47. На позиции 16 000 угол будет 1.85 — почти в четыре раза больше всего, что модель видела. Это как человек, который видел часы только между часом и половиной второго: покажите ему девять вечера — и он не поймёт, что происходит. Для быстрых пар всё в порядке: позиция 16 000 даёт угол, который уже встречался тысячи раз (углы повторяются через 2π2\pi).

2. Размывание внимания. Чем больше ключей, тем сильнее softmax «размазывает» веса: энтропия внимания растёт примерно как ln⁡n\ln n. Модель, привыкшая выбирать среди 4 тысяч токенов, на 16 тысячах смотрит «расфокусированно». Мы обсуждали это в уроке 4.4.2 вместе со статьёй Су Цзяньлиня об энтропии и множителе log⁡n\log n.

Значит, рецепт расширения должен (а) не выводить медленные пары за обученный диапазон углов и (б) вернуть вниманию прежнюю «резкость». Первую задачу решают PI и NTK, обе — YaRN.

Летом 2023 года энтузиаст kaiokendev (проект SuperHOT) и группа Meta (Chen et al.) независимо предложили одно и то же. Если модель не умеет экстраполировать на невиданные позиции, давайте интерполировать: позицию mm заменим на m/sm/s, где s=L′/Ls = L'/L — во сколько раз растягиваем контекст. Позиции от 0 до sLsL превращаются в числа от 0 до LL, и все углы остаются в обученном диапазоне. Для частот это то же самое, что поделить каждую на ss:

θiPI=θis.\theta_i^{\mathrm{PI}} = \frac{\theta_i}{s}.

интерполяция позицийинтерполяция позиций (PI)EN position interpolation (PI)Позиции делятся на коэффициент растяжения s, и все углы RoPE остаются в обученном диапазоне. В китайских текстах — дословный перевод того же термина. работает поразительно хорошо: Meta растянула LLaMA до 32 768 токенов, дообучив её не более чем за 1000 шагов. Но у метода есть цена — разрешение.

Пара 0 теперь поворачивается не на 1, а на 0.25 радиана за токен (при s=4s = 4). Соседние токены стали в 4 раза «ближе» друг к другу по самой точной стрелке. Представьте линейку, которую сжали вчетверо: миллиметровые деления превратились в четвертинки миллиметра, и их трудно различить. Ниже в практике мы посчитаем: разница между оценкой внимания соседнего токена и оценкой токена «с самим собой» у PI в 16 раз меньше, чем у исходного RoPE. Без дообучения модель путает порядок близких слов, с дообучением — заново учится различать мелкие углы.

Метод 2: NTK-aware — поменять систему счисления

Заголовок раздела «Метод 2: NTK-aware — поменять систему счисления»

Через несколько дней после PI пользователь Reddit bloc97 заметил: быстрым парам помощь не нужна, они и так видели все углы. Страдают только медленные. Значит, надо экстраполировать быстрые частоты и интерполировать медленные. Сделать это можно одной строкой — увеличить базу RoPE:

b′=b⋅sd/(d−2),θi′=b′−2i/d=θi⋅s−2i/(d−2).b' = b \cdot s^{d/(d-2)}, \qquad \theta'_i = b'^{-2i/d} = \theta_i \cdot s^{-2i/(d-2)} .

Проверим края. При i=0i = 0 множитель s0=1s^0 = 1 — самая быстрая пара не изменилась. При i=d/2−1i = d/2 - 1 показатель равен −1-1, и самая медленная частота делится ровно на ss, как в PI. Между ними множитель плавно убывает. Для d=128d = 128, s=4s = 4 новая база b′=10 000⋅4128/126≈40 890b' = 10\,000 \cdot 4^{128/126} \approx 40\,890.

Метод назвали NTK-awareNTK-aware масштабирование RoPEEN NTK-aware scaled RoPEВместо позиций увеличивают базу RoPE в s^(d/(d−2)) раз — быстрые частоты почти не меняются, медленные интерполируются. — это отсылка к теории нейронного касательного ядра (NTK): сетям трудно выучивать высокочастотные зависимости, поэтому сжимать быстрые частоты, как PI, вредно. По словам автора, LLaMA 7B с такой правкой работала на 8K+ токенах без всякого дообучения.

Интуиция Су Цзяньлиня: RoPE — это система счисления

Заголовок раздела «Интуиция Су Цзяньлиня: RoPE — это система счисления»

Лучшее объяснение NTK-aware написал Су Цзяньлинь в статье «RoPE — это кодирование в β-ичной системе». Позицию mm можно записать в системе счисления с основанием β\beta: младшая цифра меняется на каждом шаге, старшие — всё реже. RoPE устроен так же: быстрые пары — «младшие цифры», медленные — «старшие», и β=b2/d\beta = b^{2/d}.

Теперь аналогия. Трёхзначный десятичный счётчик считает до 999. Как досчитать до 3999, не добавляя цифр?

  • PI: сжать шкалу — каждый реальный шаг сдвигает счётчик на 0.25. Числа до 3999 уложатся в диапазон 0–999.75, но младший разряд теперь меняется на четверть деления за шаг: соседние числа становятся трудно различимы.
  • NTK-aware: перейти в шестнадцатеричную систему. Три цифры дают 163=409616^3 = 4096 значений, а младший разряд по-прежнему меняется на 1 за шаг. Локальная точность не пострадала, старшие разряды просто «растянулись». Цена — «цифры» 10–15, которых счётчик раньше не видел. В младших разрядах это не страшно, они и так перебрали все положения, а в старших это и есть те «невиданные углы», которые NTK-aware прячет не до конца.

NTK-aware не идеален. В практике ниже вы увидите, что при s=4s = 4 у 17 медленных пар углы всё же выходят за виденный диапазон — до 1.45 раза. Поэтому без дообучения коэффициент обычно берут с запасом. Есть и Dynamic NTK: коэффициент пересчитывают под текущую длину, s=max⁡(1,n/L)s = \max(1, n/L), и короткие тексты вообще не затрагиваются. Так в 2023 году растягивала контекст первая Qwen (подробнее — в «Китайском угле»).

Ещё проще поступают при обучении: сразу берут большую базу и дообучают на длинных текстах. Этот приём называют ABF (adjusted base frequency). Code Llama взяла b=106b = 10^6, Llama 3 — 5⋅1055 \cdot 10^5, Qwen2.5 и Qwen3 поднимают базу с 10410^4 до 10610^6 на этапе с длиной 32K, Qwen2.5-1M — до 10710^7. У Су Цзяньлиня есть отдельная статья о выборе базы («Принципы выбора базы RoPE»): чем длиннее нужный контекст, тем больше должна быть база.

YaRN (Yet another RoPE extensioN; Peng, Quesnelle, Fan, Shippole, 2023; среди авторов и сам bloc97) соединяет обе идеи аккуратнее. Каждая пара сама решает, интерполироваться ей или нет, по числу оборотов за обучающую длину LL:

ri=Lλi=L θi2π,γi=clip ⁣(ri−αβ−α,  0,  1),θiYaRN=(1−γi) θis+γi θi.\begin{gathered} r_i = \frac{L}{\lambda_i} = \frac{L\,\theta_i}{2\pi}, \qquad \gamma_i = \mathrm{clip}\!\left(\frac{r_i - \alpha}{\beta - \alpha},\; 0,\; 1\right), \\ \theta^{\mathrm{YaRN}}_i = (1 - \gamma_i)\,\frac{\theta_i}{s} + \gamma_i\,\theta_i . \end{gathered}

Функция clip(x,0,1)\mathrm{clip}(x, 0, 1) «обрезает» число до отрезка от 0 до 1: всё меньше нуля становится 0, всё больше единицы — 1 (np.clip в NumPy). Что здесь написано:

  • ri>βr_i > \beta (у LLaMA и DeepSeek β=32\beta = 32): пара сделала больше 32 оборотов, она видела все углы. γ=1\gamma = 1, частота не меняется.
  • ri<αr_i < \alpha (α=1\alpha = 1): пара не сделала ни одного оборота. γ=0\gamma = 0, частота делится на ss, как в PI.
  • Между ними — плавный переход по линейной «рампе».

Пример для пары 32 из таблицы выше (θ=0.01\theta = 0.01, длина волны 628 токенов) при s=4s = 4: r=4096/628≈6.5r = 4096 / 628 \approx 6.5, γ=(6.5−1)/31≈0.18\gamma = (6.5 - 1) / 31 \approx 0.18, новая частота 0.82⋅θ/4+0.18⋅θ≈0.38 θ0.82 \cdot \theta / 4 + 0.18 \cdot \theta \approx 0.38\,\theta — пара замедлилась, но не вчетверо, как в PI, а примерно в 2.6 раза.

Эта часть называется NTK-by-parts. Для d=128d = 128, b=104b = 10^4, L=4096L = 4096 получается 21 нетронутая пара (длина волны меньше 128 токенов), 25 пар в переходной зоне и 18 интерполированных — ровно тех медленных, что не успели сделать оборот.

flowchart LR
P["Пара i: оборотов за L токенов<br/>r = L·θ/2π"] --> Q{"r ?"}
Q -->|"r > 32"| A["быстрая: θ без изменений"]
Q -->|"1 ≤ r ≤ 32"| B["средняя: смесь θ и θ/s"]
Q -->|"r меньше 1"| C["медленная: θ/s, как в PI"]
NTK-by-parts в YaRN: решение принимает каждая пара частот

Вторая часть YaRN — температура внимания. Против размывания внимания логиты делят на tt, где

1/t=0.1ln⁡s+1.\sqrt{1/t} = 0.1 \ln s + 1 .

На практике таблицы cos⁡\cos и sin⁡\sin RoPE умножают на 0.1ln⁡s+10.1 \ln s + 1. Этот множитель попадает и в qq, и в kk, поэтому логиты растут в (0.1ln⁡s+1)2(0.1 \ln s + 1)^2 раз, а на инференсе это ничего не стоит (в коде DeepSeek-V3 тот же квадрат множителя просто вшит в масштаб softmax). При s=4s = 4 множитель логитов 1.30, при s=40s = 40 — 1.87. Коэффициент 0.1 подобран авторами эмпирически.

По данным статьи, YaRN требует в 10 раз меньше токенов и в 2.5 раза меньше шагов дообучения, чем предыдущие методы, и растягивает Llama 2 до 128K. Похожую схему по частям использует Llama 3.1 (тип llama3 в rope_scaling).

МетодЧто меняетБыстрые парыМедленные парыДообучениеГде встречается
Без измененийничегокак былиневиданные углыне помогает—
PIпозиции m→m/sm \to m/sсжаты в ss разв диапазоне~1000 шаговранние расширения LLaMA
NTK-awareбазу b→b sd/(d−2)b \to b\,s^{d/(d-2)}почти как быличастично в диапазонеможно без негоllama.cpp, Qwen (2023, динамический вариант)
ABFбазу (сильно), с обучениемкак былив диапазоне после обученияобязательноCode Llama, Qwen, Llama 3
YaRNчастоты по частям + температуракак былив диапазонесотни шаговDeepSeek-V2/V3, Qwen

Обе лаборатории растягивают контекст поэтапно: короткий претрейн, затем несколько ступенек длины.

flowchart TB
subgraph D["DeepSeek-V3"]
  direction LR
  d1["Претрейн<br/>14.8T токенов, 4K"] --> d2["YaRN s=40<br/>1000 шагов, 32K"] --> d3["1000 шагов<br/>128K"]
end
subgraph Q["Qwen2.5 / Qwen3"]
  direction LR
  q1["Претрейн, 4K<br/>база 10^4"] --> q2["Длинный этап, 32K<br/>база 10^6 (ABF)"] --> q3["Инференс: YaRN x4 + DCA<br/>до 128K"]
end
Поэтапное расширение контекста у DeepSeek и Qwen

DeepSeek-V3 после претрейна на 4K делает два этапа по 1000 шагов. Первый — длина 32K, батч 1920 последовательностей. Второй — длина 128K, батч 480. Каждый этап — около 62.9 млрд токенов (1000⋅1920⋅32 7681000 \cdot 1920 \cdot 32\,768 и 1000⋅480⋅131 0721000 \cdot 480 \cdot 131\,072). Параметры YaRN одинаковы: s=40s = 40, α=1\alpha = 1, β=32\beta = 32, 1/t=0.1ln⁡s+1\sqrt{1/t} = 0.1 \ln s + 1. В опубликованном конфиге:

"rope_scaling": {"type": "yarn", "factor": 40, "original_max_position_embeddings": 4096,
"beta_fast": 32, "beta_slow": 1, "mscale": 1.0, "mscale_all_dim": 1.0}

4096⋅40=163 8404096 \cdot 40 = 163\,840 — формальный потолок с запасом над 128K. Тонкость, связанная с уроком 5.2.2: в MLA позиции несёт только развязанная RoPE-часть ключа (64 измерения из 192), поэтому YaRN применяется только к ней.

Qwen идёт двумя дорогами одновременно. При обучении — ABF: Qwen2.5 и Qwen3 проходят основной претрейн на 4K, а на завершающем этапе удлиняют последовательности до 32K и поднимают базу RoPE с 10410^4 до 10610^6. На инференсе — YaRN и Dual Chunk Attention (DCA) дают ещё четырёхкратный запас, до 128K. В карточках моделей Qwen предлагают дописать в config.json:

"rope_scaling": {"type": "yarn", "factor": 4.0, "original_max_position_embeddings": 32768}

и сразу предупреждают: многие движки поддерживают только статический YaRN, где коэффициент действует при любой длине, и на коротких текстах качество может слегка просесть. Поэтому включать его стоит, только когда длинный контекст действительно нужен.

Qwen2.5-1M доводит идею до предела: пять ступенек длины при обучении (4K → 32K → 65K → 131K → 262K) с базой 10710^7, а до миллиона токенов модель дотягивается на инференсе через DCA. Идея DCA (статья HKU и Alibaba, 2024): длинную последовательность режут на куски и пересчитывают относительные расстояния так, чтобы ни одно не превышало обученного. Ближние токены сохраняют точные расстояния, дальние получают «сжатые». Для ускорения prefill на таких длинах добавлено разреженное вниманиеразреженное вниманиеEN sparse attention — о нём следующий урок.

Реализуйте частоты RoPE и три способа их растянуть. Тесты проверят края NTK-aware, три режима YaRN и главное свойство: на длине sLsL у PI и YaRN нет ни одной медленной пары с «невиданным» углом.

Частоты RoPE при PI, NTK-aware и YaRNnumpy · нужен интернет7 тестов
import math

import numpy as np


def rope_freqs(d, base=10000.0):
    """Частоты RoPE для головы размера d (d чётное).

    Возвращает массив из d // 2 чисел: theta_i = base ** (-2 * i / d), i = 0, 1, ..., d/2 - 1.
    Пара координат i поворачивается на угол m * theta_i, где m — позиция токена.
    Пример: rope_freqs(8) -> [1.0, 0.1, 0.01, 0.001].
    """
    # TODO: np.arange(0, d, 2) даёт 0, 2, 4, ... — это и есть 2i
    ...


def pi_freqs(freqs, s):
    """Position Interpolation: позиции сжимаются в s раз, m -> m / s.

    Для частот это то же самое, что поделить каждую частоту на s.
    Входной массив не изменяйте — верните новый.
    """
    # TODO
    ...


def ntk_freqs(d, base, s):
    """NTK-aware: позиции не трогаем, увеличиваем базу RoPE.

    Новая база: base * s ** (d / (d - 2)). Самая высокая частота остаётся 1.0,
    самая низкая уменьшается ровно в s раз.
    """
    # TODO: одна строка через rope_freqs
    ...


def yarn_freqs(freqs, s, L, alpha=1.0, beta=32.0):
    """YaRN (NTK-by-parts): каждая пара решает сама, интерполироваться ей или нет.

    freqs — исходные частоты, s — во сколько раз растягиваем контекст,
    L — обучающая длина контекста (в токенах).
    1) r_i = L * theta_i / (2 * pi) — сколько полных оборотов пара успела сделать за L токенов;
    2) gamma_i = clip((r_i - alpha) / (beta - alpha), 0, 1);
    3) новая частота = (1 - gamma_i) * theta_i / s + gamma_i * theta_i.
    Быстрые пары (r > beta) не меняются, медленные (r < alpha) делятся на s, средние — плавный переход.
    """
    # TODO
    ...


def yarn_attention_factor(s):
    """Множитель YaRN для q и k: 0.1 * ln(s) + 1 при s > 1, иначе 1.0.

    Он умножает и q, и k, поэтому логиты внимания растут в (0.1 ln s + 1)^2 раз —
    распределение внимания становится чуть острее.
    """
    # TODO
    ...


# Проверьте себя кнопкой «Запустить»:
np.set_printoptions(precision=6, suppress=True)
f = rope_freqs(8)
print("RoPE:", f)                                  # ожидается [1. 0.1 0.01 0.001]
if f is not None:
    print("PI x4:", pi_freqs(f, 4))                # ожидается [0.25 0.025 0.0025 0.00025]
    print("NTK x4:", ntk_freqs(8, 10000.0, 4))     # [1. 0.062996 0.003969 0.00025]: первая как у RoPE, последняя как у PI
    print("YaRN x4:", yarn_freqs(f, 4, L=1024))    # [1. 0.06201 0.002652 0.00025]: быстрая пара не тронута, медленная /4
print("множитель YaRN при s=40:", yarn_attention_factor(40))   # ≈ 1.3689

Скрипт ниже сравнивает четыре варианта для типичной головы (d=128d = 128, b=104b = 10^4), обученной на 4K, при растяжении до 16K. Для каждого метода он считает, сколько медленных пар получают невиданные углы, во сколько раз худший угол больше виденного и насколько оценка внимания соседнего токена отличается от оценки токена «с самим собой» (чем больше, тем легче отличить соседа).

import numpy as np
def rope_freqs(d, base=10000.0):
return base ** (-np.arange(0, d, 2) / d)
def yarn_freqs(f, s, L, alpha=1.0, beta=32.0):
r = L * f / (2 * np.pi) # оборотов за обучающую длину
gamma = np.clip((r - alpha) / (beta - alpha), 0, 1)
return (1 - gamma) * f / s + gamma * f
d, base, L, s = 128, 10000.0, 4096, 4 # голова 128, обучали на 4K, хотим 16K
f = rope_freqs(d, base)
methods = {
"без изменений": f,
"PI": f / s,
"NTK-aware": rope_freqs(d, base * s ** (d / (d - 2))),
"YaRN": yarn_freqs(f, s, L),
}
seen = L * f # максимальный угол каждой пары в обучении
slow = seen < 2 * np.pi # пары, не сделавшие за 4K токенов ни одного оборота
print(f"медленных пар: {slow.sum()} из {d // 2}")
print(f"{'метод':<14}{'новых углов':>12}{'превышение':>12}{'различие соседей':>18}")
for name, g in methods.items():
new = s * L * g # максимальный угол на длине 16K
n_bad = int(np.sum(slow & (new > seen + 1e-9)))
excess = np.max(new[slow] / seen[slow]) # во сколько раз худший угол больше виденного
near = 1 - np.mean(np.cos(g)) # насколько оценка соседа (Δ = 1) ниже оценки «самого себя»
print(f"{name:<14}{n_bad:>12}{excess:>11.2f}x{near:>18.4f}")

Ожидаемый вывод:

медленных пар: 18 из 64
метод новых углов превышение различие соседей
без изменений 18 4.00x 0.0298
PI 0 1.00x 0.0019
NTK-aware 17 1.45x 0.0264
YaRN 0 1.00x 0.0298

Таблица — весь урок в четырёх строках. Без изменений 18 пар уходят в неизвестность с четырёхкратным превышением. PI убирает проблему, но различие соседей падает с 0.0298 до 0.0019, в 16 раз (s2s^2: косинус малого угла ≈1−x2/2\approx 1 - x^2/2, урок 1.0.4). NTK-aware сохраняет разрешение, но не до конца прячет медленные пары. YaRN получает лучшее от обоих.

Второй скрипт показывает размывание внимания. Логиты здесь — случайные числа с разбросом σ=2\sigma = 2; у настоящей модели распределение другое, но тенденция та же.

import numpy as np
rng = np.random.default_rng(0)
def attn_entropy(n, mult=1.0, sigma=2.0, trials=50):
"""Средняя энтропия softmax по n ключам; логиты ~ N(0, sigma²), умноженные на mult."""
out = []
for _ in range(trials):
z = rng.normal(0, sigma, size=n) * mult
p = np.exp(z - z.max())
p /= p.sum()
out.append(-(p * np.log(p)).sum())
return np.mean(out)
m = (0.1 * np.log(4) + 1) ** 2 # YaRN, s = 4: логиты умножаются на ≈ 1.2965
print(f"4K ключей: энтропия {attn_entropy(4096):.2f}")
print(f"16K ключей: энтропия {attn_entropy(16384):.2f}")
print(f"16K ключей + YaRN x{m:.4f}: энтропия {attn_entropy(16384, m):.2f}")

Ожидаемый вывод:

4K ключей: энтропия 6.33
16K ключей: энтропия 7.72
16K ключей + YaRN x1.2965: энтропия 6.47

Энтропия в натах: e6.33≈560e^{6.33} \approx 560 — внимание «эффективно» распределено по нескольким сотням ключей. На 16K таких ключей уже e7.72≈2250e^{7.72} \approx 2250, внимание расфокусировано. Множитель YaRN возвращает его почти к исходной резкости.

Проверь себя6 вопросов
1 Модель с RoPE (d=128d = 128, b=104b = 10^4) обучили на 4K токенов. Какие пары частот виноваты в том, что на 16K она ломается?
2 Какие утверждения о Position Interpolation верны?Можно выбрать несколько ответов
3 Чему равна новая база NTK-aware для d=128d = 128, b=10 000b = 10\,000, s=4s = 4? Ответ округлите до сотен.
4 Во сколько раз YaRN увеличивает логиты внимания при s=16s = 16? Округлите до двух знаков.
5 YaRN, d=128d = 128, b=104b = 10^4, L=4096L = 4096, α=1\alpha = 1, β=32\beta = 32. Что произойдёт с парой i=10i = 10 (длина волны около 26.5 токена)?
6 Какую долю токенов претрейна DeepSeek-V3 потратила на расширение контекста с 4K до 128K?
  • RoPE-модель ломается за обучающей длиной из-за медленных пар частот, которые не сделали ни одного оборота, и из-за размывания внимания на большем числе ключей.
  • PI делит позиции на ss: углы в диапазоне, но падает разрешение на быстрых частотах. NTK-aware увеличивает базу: быстрые частоты почти не трогаются, медленные интерполируются. ABF — то же с большой базой и дообучением.
  • YaRN решает за каждую пару отдельно (по числу оборотов rr) и добавляет температуру (0.1ln⁡s+1)2(0.1 \ln s + 1)^2.
  • DeepSeek-V3 растянула контекст с 4K до 128K двумя этапами YaRN за 0.85% бюджета. Qwen сочетает ABF при обучении с YaRN и DCA на инференсе, Qwen2.5-1M доходит до миллиона токенов.

Дальше: растянуть позиции — полдела. Внимание на 128K всё ещё стоит O(n2)O(n^2). В следующем уроке разберём, как DeepSeek научила модель смотреть не на все токены, — скользящее окно, NSA и DSA: Разреженное внимание.