← Росток Своя LLM с нуля Словарик Код EN

Глава 15 из 17 30 мин

Больше модели

У Ростка-2 125 миллионов параметров в тридцати узких блоках. Где эти параметры сидят, что нужно модели такого размера, без чего маленькая обходилась, как мы проверили всё это на маленьких копиях и как прошли девять миллиардов токенов обучения.

В этой главе

  • посчитать, куда уходят параметры трансформера, и объяснить, почему Росток-2 глубокий и узкий
  • вычислить KV-кэш модели и увидеть, как его ужимает групповое внимание
  • объяснить маску документов, мягкий потолок для логитов и расписание с контрольной точкой перед спуском — и прочитать настоящие кривые обучения по источникам

Росток-1 — это 8 блоков шириной 384 числа, 6 голов внимания, словарь в 8 192 токена и окно в 512. Росток-2 — 30 блоков по 576, 9 голов запросов, которые делят между собой 3 головы ключей и значений, словарь в 32 768 токенов и окно в 2 048: 125,1 миллиона параметров вместо 17,3 миллиона. Росток-1 прочитал 330 миллионов токенов за 10 070 шагов — три с половиной часа на Mac. Росток-2 прочитал 9,0 миллиарда за 17 166 шагов по 524 288 токенов.

Ничто из глав 7, 8 и 10 не устарело: то же внимание, тот же блок из внимания и SwiGLU, те же Muon и AdamW, та же функция потерь. Когда модель растёт, меняется другое, менее заметное: её форма, то, чем занята память, пока она пишет, несколько ремней безопасности, без которых маленькая модель обходилась, и то, как всё это выбрать до долгого обучения. Но сначала — результат.

Как он рос

В одиннадцать моментов обучения Ростку-2 давали одни и те же пять затравок: начало сказки, начало объяснения, функцию на Python, команду shell и вопрос. Выберите затравку и поднимайтесь по лесенке — или нажмите «Вырастить».

Настоящие продолжения, записанные во время обучения. Кривая рядом с текстом — ошибка на проверочном тексте того же рода, что и затравка: сказки, веб-страницы, код, команды или вопросы с ответами. Затенённый конец кривой — спуск скорости.

На что посмотреть.

  • Шаг 200: 105 миллионов токенов, треть всего, что прочитал Росток-1. У сказки уже есть интонация детской истории. У функции отступы стоят на своих местах, но она ничего не вычисляет: weight = 0, height = 1. Команда превращается в конвейер из xargs и awk.
  • К шагу 2 000 сказка держится: кролик, бревно, желание заглянуть внутрь. К шагу 4 000 после команды tar идёт то, что напечатал бы терминал: ls -l и список файлов с правами и владельцами.
  • Функция даётся труднее всего. На шаге 8 000 у неё есть правильная строка документации — «Return the n-th fibonacci number.», — а возвращает она 2, 3, 4 и 5. На шаге 16 000 появляется рекурсия из учебника, fibonacci(n-1) + fibonacci(n-2), но с таким базовым случаем, что вычисление никогда не закончится. А на самом последнем шаге Росток-2 выдумывает «FIBONAREA». Одна запись — один бросок кубиков; измерение — это кривая рядом.
  • На вопрос он не отвечает ни на одном шаге. С шага 6 000 Росток-2 продолжает его так, как продолжается ветка на форуме: «Here is a list of all files that are hidden from view. How do I get the list of hidden files…?» На шаге 16 000 он даже предлагает команду find — и, как и положено на форуме, следом сообщение об ошибке, которое кто-то от неё получил. Запомните это: в конце главы мы к этому вернёмся.

А теперь заглянем внутрь.

Куда уходят параметры

Разберём Росток-2 так же, как глава 8 разбирала Росток-1. Рядом:

Росток-1Росток-2
блоков830
ширина потока $d$384576
головы запросов по 6469
головы ключей и значений63
ширина SwiGLU $d_{ff}$1 0241 536
словарь8 19232 768
окно5122 048
основание RoPE10 000100 000
логитыкак естьпод мягким потолком 30
параметров17 309 056125 081 664

Посчитаем один блок. У внимания четыре матрицы, но квадратных среди них только две: запросы $W_Q$ и выходная проекция $W_O$ — $576 \times 576$, а ключам и значениям хватает 3 голов по 64, так что $W_K$ и $W_V$ — $576 \times 192$. В сети SwiGLU три матрицы $576 \times 1\,536$. Остаются две нормировки и усиления QK-norm:

$$2 \cdot 576^2 + 2 \cdot 576 \cdot 192 + 3 \cdot 576 \cdot 1\,536 + 2 \cdot 576 + 2 \cdot 64 = 3\,540\,224.$$

Тридцать таких блоков, таблица эмбеддингов и последняя нормировка:

$$32\,768 \cdot 576 + 30 \cdot 3\,540\,224 + 576 = 125\,081\,664.$$
Посчитано по двум конфигурациям, слой за слоем, так же, как их строит model.py. Внизу — тот же бюджет при другой ширине и столько блоков, сколько влезает (головы по 64, треть из них — для ключей и значений, SwiGLU в 8/3 раза шире потока); площадь каждой части башни пропорциональна её параметрам.

Росток-2 больше Ростка-1 в 7,2 раза, а его блоки — в 7,5 раза: рост ушёл в те части, которые думают. Одни только сети SwiGLU держат 79,6 миллиона чисел — 64% модели. Таблица эмбеддингов выросла вчетверо по числу строк и в полтора раза по ширине, до 18,9 миллиона чисел, но её доля упала с 18% до 15%. И это по-прежнему одна таблица на входе и на выходе (глава 8): без этого трюка модели пришлось бы нести ещё 18,9 миллиона.

Две строки конфигурации не стоят ни одного параметра. Окно — 2 048 токенов, вчетверо больше, чем у Ростка-1, а основание RoPE — 100 000 вместо 10 000. Большее основание замедляет медленные повороты RoPE (глава 8) — самый медленный почти вдесятеро, — чтобы далёкие друг от друга позиции длинного окна по-прежнему различались: обычная поправка, когда окно растёт.

Глубокий и узкий

Почему 30 блоков по 576, а не, скажем, 16 по 768 — там почти столько же параметров? Переключайте ширину в нижней части виджета.

Широкая модель тратит больше бюджета на словарь: при ширине 1 152 таблица эмбеддингов съедает 31% модели, и блоков остаётся всего 6. Глубокая модель даёт каждому токену больше шагов обработки, один за другим, а почему шаги важны, мы видели в главе 8: индукционной голове нужен слой раньше неё, который подготовит то, что она прочитает. У глубины есть и цена: тридцать слоёв работают строго по очереди, а видеокарте удобнее несколько широких, и KV-кэш из следующего раздела растёт с каждым слоем.

Вот тот же подсчёт в виде функции, которую можно запустить, — с третьей моделью: Ростком-2 без группового внимания.

def count(vocab, d, layers, heads, kv_heads, d_ff): hd = d // heads # 64 numbers per head attn = 2 * d * heads * hd + 2 * d * kv_heads * hd # q and proj; the narrower k and v ffn = 3 * d * d_ff # SwiGLU: w1, w3 and w2 norms = 2 * d + 2 * hd # two RMSNorms, the QK-norm gains emb = vocab * d # one table, used at both ends total = emb + layers * (attn + ffn + norms) + d # + the final norm return total, emb, layers * attn, layers * ffn for name, cfg in [('Sprout-1', (8192, 384, 8, 6, 6, 1024)), ('Sprout-2', (32768, 576, 30, 9, 3, 1536)), ('Sprout-2, 9 k/v heads', (32768, 576, 30, 9, 9, 1536))]: total, emb, attn, ffn = count(*cfg) print(f'{name:21} {total:>11,} embeddings {emb / total:.0%}' f' attention {attn / total:.0%} SwiGLU {ffn / total:.0%}')

Будь у Ростка-2 столько же голов ключей и значений, сколько голов запросов, он весил бы 138,4 миллиона: на 13,3 миллиона больше, и все они — в $W_K$ и $W_V$. Это меньшая из двух экономий. Бо́льшая видна, только когда модель пишет.

Ключей меньше, чем запросов

В главе 11 появился KV-кэш: пока модель пишет, она хранит ключ и значение каждого прошлого токена в каждом слое, и новый токен обходится одним проходом. Платить за это приходится памятью, и её легко посчитать. На каждый токен модель хранит

$$2 \times \text{слоёв} \times \text{голов ключей и значений} \times \text{размер головы}$$

чисел: ключ и значение в каждом слое для каждой головы, у которой свои ключи и значения. У Ростка-1 это $2 \times 8 \times 6 \times 64 = 6\,144$ числа. Дайте Ростку-2 столько же голов ключей и значений, сколько голов запросов, — и получится $2 \times 30 \times 9 \times 64 = 34\,560$ чисел на токен: 69 КБ в 16-битных числах и 141,6 МБ на полное окно в 2 048 токенов, почти вдвое больше 78 МБ его 4-битных весов.

Групповое внимание (grouped-query attention, GQA) делит ключи и значения на несколько голов. 9 голов запросов Ростка-2 разбиты на 3 группы по три, и каждая группа читает один общий набор ключей и значений. Каждая голова по-прежнему задаёт свой вопрос — у неё своя часть $W_Q$; общими на три головы группы становятся только ярлыки, с которыми вопрос сравнивают, и содержимое, которое смешивают (глава 7). Кэш сжимается втрое, до 11 520 чисел на токен.

Кэш для 30 слоёв Ростка-2 при выбранной длине контекста. Пунктирные отметки — размер его весов: в 4 битах на вес, как у вас в браузере, и в 8 битах.

В браузере движок Ростка-2 хранит ключи и значения 32-битными числами, так что полное окно в 2 048 токенов занимает 94,4 МБ — больше, чем сами веса в 4 битах. С 9 головами ключей и значений было бы 283 МБ. Под кэши движок отводит не больше 256 МБ: с 3 головами туда помещаются два полных разговора, с 9 — ни одного. К тому же веса загружаются один раз, а кэш нужен каждому разговору свой, поэтому серверы, которые говорят с тысячами людей сразу, считают кэш ещё придирчивее, чем телефон.

В model.py вся идея — несколько строк класса Attention:

class Attention(nn.Module): def __init__(self, cfg): super().__init__() self.n_head, self.n_kv, self.hd = cfg.n_head, cfg.n_kv_head, cfg.head_dim self.q = nn.Linear(cfg.d_model, cfg.n_head * self.hd, bias=False) self.k = nn.Linear(cfg.d_model, cfg.n_kv_head * self.hd, bias=False) self.v = nn.Linear(cfg.d_model, cfg.n_kv_head * self.hd, bias=False) self.proj = nn.Linear(cfg.n_head * self.hd, cfg.d_model, bias=False) self.q_norm = RMSNorm(self.hd) self.k_norm = RMSNorm(self.hd) def forward(self, x, cos, sin, mask=None, cache=None): B, T, _ = x.shape # (B, T, C) -> (B, heads, T, head_dim) q = self.q(x).view(B, T, self.n_head, self.hd).transpose(1, 2) k = self.k(x).view(B, T, self.n_kv, self.hd).transpose(1, 2) v = self.v(x).view(B, T, self.n_kv, self.hd).transpose(1, 2) q, k = self.q_norm(q), self.k_norm(k) q, k = apply_rope(q, cos, sin), apply_rope(k, cos, sin) if cache is not None: # generation: remember keys and values, attend to all of them k, v = cache.add(k, v) if mask is not None and not torch.is_tensor(mask): # a FlexAttention block mask (CUDA) y = flex_attention(q, k, v, block_mask=mask, enable_gqa=True) else: rep = self.n_head // self.n_kv # every key/value head serves `rep` query heads k, v = k.repeat_interleave(rep, dim=1), v.repeat_interleave(rep, dim=1) # softmax(q·k / sqrt(d)) · v, every token sees only itself and the past if mask is None: y = F.scaled_dot_product_attention(q, k, v, is_causal=True) else: y = F.scaled_dot_product_attention(q, k, v, attn_mask=mask) y = y.transpose(1, 2).contiguous().view(B, T, -1) return self.proj(y)

По сравнению с Ростком-1 общая матрица qkv исчезла: у k и v теперь свои, более узкие слои — 192 выхода вместо 576. Посмотрите на порядок строк: кэш забирает ключи и значения до того, как repeat_interleave скопирует каждый для трёх его голов запросов, поэтому хранятся только три головы. После копирования всё остальное — обычное внимание из главы 7, а на видеокарте FlexAttention обходится даже без копий (enable_gqa=True).

У Ростка-2 9 голов запросов и 3 головы ключей и значений. Что изменится, если голов ключей и значений станет 9?

Кэш хранит ключи и значения, но не запросы: запрос нужен один раз — тому токену, который его задаёт. Окно в любом случае остаётся в 2 048 токенов. Растёт память на токен — с 11 520 до 34 560 чисел — и матрицы $W_K$ и $W_V$ в каждом блоке.

Что нужно большой модели

Кое-что, что сходит с рук маленькой модели, перестаёт быть безобидным, когда обучение идёт миллиарды токенов, а кое-какая защита почти ничего не стоит. Росток-2 добавляет две такие защиты и сохраняет одну от Ростка-1.

Одна строка, несколько документов

Обучение читает строки по 1 024 токена (в конце — по 2 048). Документы такого размера не бывают: средняя задача TinyGSM — 216 токенов, сказка TinyStories — 196, страница FineWeb-Edu — 1 070. Поэтому prepare.py записывает каждый источник одним длинным потоком токенов, где каждый документ открывается меткой <|endoftext|>, а train.py вырезает из потока строки там, где придётся. Обычно строка начинается посреди одного документа и захватывает начала следующих.

С одной причинной маской (глава 7) каждый токен может смотреть на всё, что было раньше в строке, — в том числе на документы, которые к нему не имеют никакого отношения. Вот одна такая строка: конец сказки, команда shell и начало веб-страницы.

Токены нарезаны собственным токенизатором Ростка-2. Нажмите на токен или на строку матрицы: закрашенные клетки в ней — те токены, на которые он может смотреть. Внизу — тот же подсчёт для настоящей строки обучения, по средней длине документа в каждом источнике.

Включите «Причинная маска» и нажмите на «·ls»: из 9 токенов, на которые он может смотреть, 6 — конец сказки. Не обращать внимания на такой шум модель научиться может, но это стоит усилий, а короткие документы делают дело хуже: в строке из 1 024 токенов задач TinyGSM около 80% пар, которые разрешает причинная маска, смотрят в чужую задачу.

Лечится это так: пронумеровать документы и пропускать внимание только внутри одного. train.py нумерует их одной строкой, (x == eot).cumsum(1), — бегущим счётом меток <|endoftext|>, так что каждая метка открывает новый документ; model.py превращает номера в маску (код — в конце раздела). На маленьких тестовых моделях прогон без маски закончился на 3,367 ната, с маской — на 3,342: лучше на 0,024, примерно как хорошая скорость обучения лучше вдвое завышенной. И стоит маска ничего: с torch.compile на Mac она не изменила скорость, а на видеокарте FlexAttention пропускает куски матрицы, закрытые целиком.

Потолок для логитов

На самом верху модели 32 768 логитов идут в softmax (глава 2). Ничто не ограничивает их величину, а у обучения есть причина их раздувать: больший логит у лёгкого токена — это более уверенная модель и меньшая ошибка. Убежавший логит делает модель самоуверенной, и тогда одна уверенная ошибка обходится огромной потерей. Росток-2 ставит над каждым логитом мягкий потолок:

$$\ell' = 30 \tanh\!\left(\frac{\ell}{30}\right).$$
Ведите пальцем по графику или двигайте ползунок. Пунктирная диагональ — логит как есть, толстая кривая — то, чем пользуется Росток-2, оранжевый отрезок — то, что снимает потолок.

Около нуля тангенс почти прямая: 5 превращается в 4,95, 10 — в 9,65, так что обычные логиты проходят почти нетронутыми. После 30 кривая загибается: 60 становится 28,9, 100 — 29,9, и ни один логит никогда не выходит из полосы от −30 до 30. Отсюда два следствия. Наклон, $1 - \tanh^2(\ell/30)$, равен 0,42 при 30 и 0,07 при 60: чем дальше убежал логит, тем слабее обучение может его толкать, и смысла раздувать логиты больше нет. А цена любого отдельного токена ограничена: даже самая уверенная ошибка не может стоить больше $2 \cdot 30 + \ln 32\,768 \approx 70{,}4$ ната.

На маленьких тестовых моделях потолок немного помог: 3,351 без него и 3,342 с ним. На первом замере модель без потолка даже чуть опережала, а окупился потолок к концу. Движок в вашем браузере применяет ту же функцию к каждому логиту, так что говорит там Росток-2 ровно так, как его учили.

QK-norm остаётся

Второе место, где числа могут убежать, — само внимание: $q \cdot k$ растёт вместе с длиной векторов. Росток-1 уже нормировал каждый запрос и ключ перед сравнением (QK-norm, глава 7), и Росток-2 это сохраняет. Вместе QK-norm и потолок охраняют оба softmax модели: тот, что внутри внимания, и тот, что на выходе. Вот маска и потолок в model.py:

def doc_mask(docs): """Dense (batch, 1, T, T) mask: a token sees the past of its own document only.""" T = docs.size(1) causal = torch.ones(T, T, dtype=torch.bool, device=docs.device).tril() return (causal & (docs[:, :, None] == docs[:, None, :]))[:, None] # ... def logits_of(self, x): logits = self.head(self.norm(x)).float() if self.cfg.softcap: logits = self.cfg.softcap * torch.tanh(logits / self.cfg.softcap) return logits

Маска — ровно матрица из виджета: причинный треугольник (tril) И «тот же номер документа». Потолок — одна строка после выходного слоя, перед функцией потерь.

Большая модель получает ремни безопасности до долгого обучения: внимание не выходит за пределы своего документа, и ни одна оценка — ни внутри внимания, ни на выходе — не может убежать.

Репетиции на маленьких копиях

Откуда известно, что маска даёт 0,024, а 0,02 — правильная скорость для Muon, ещё до долгого обучения? Не у большой модели же спрашивать. Строят её маленькую копию — прокси — и меняют по одной вещи за раз.

У прокси Ростка-2 8 блоков по 256, 4 головы запросов и 2 головы ключей и значений: 14,3 миллиона параметров, почти 60% из них — в таблице эмбеддингов, у которой по-прежнему 32 768 строк. Каждая прокси прочитала 100 миллионов токенов из уменьшенной копии корпуса в 1,4 миллиарда токенов — около часа на Mac; всё остальное — рецепт Ростка-2 в уменьшенном виде. Вот все кривые.

Ошибка прокси на проверке: шесть замеров по ходу каждого прогона и один в конце. «Разница» вычитает кривую того варианта, который оставил себе Росток-2.

Muon со скоростью 0,02 заканчивает на 3,342 — лучше, чем с 0,01 (3,353) и 0,04 (3,368). Для таблицы эмбеддингов, которую учит AdamW, 0,006 лучше и 0,003 (3,361), и 0,012 (3,347). Маска документов даёт 0,024, потолок — 0,008. Теперь переключитесь на «Сама ошибка»: кривые почти лежат друг на друге. Каждое решение здесь — это сотые и тысячные доли ната на кривой, которая падает с 4,8 до 3,3. В начале разрывы больше (на первом замере Muon со скоростью 0,04 отстаёт на 0,18) и потом в основном сокращаются; считается конец. Резкий спад после 80 миллионов токенов — собственный спуск прокси.

Две оговорки. Каждый вариант обучен один раз, так что мы не знаем, насколько сдвинуло бы результат другое случайное зерно: тысячным стоит доверять меньше, чем сотым. И прокси в девять раз меньше Ростка-2 и читает в девяносто раз меньше, так что лучшие для неё значения не обязаны быть лучшими для большой модели. Росток-2 просто их взял.

Расписание

В главе 10 расписание состояло из трёх частей: разогрев, плато, спуск. Росток-2 сохраняет форму и меняет пропорции:

  • Разогрев — 250 шагов, 131 миллион токенов: скорость обучения растёт от почти нуля до пика.
  • Плато — пиковая скорость до шага 13 732.
  • Спуск — последние 20%: 3 434 шага и 1,8 миллиарда токенов, за которые скорость линейно падает до нуля. Меняется и смесь (глава 14): больше кода, вопросов с ответами и математики, меньше веба и 3% разговоров в формате чата.

Строки по дороге удлиняются: 256 токенов первые 2% шагов, 512 до отметки 6%, 1 024 бо́льшую часть обучения и 2 048 только в последние 10%. Батч всё время — 524 288 токенов, так что в начале это 2 048 строк по 256, а в конце — 256 строк по 2 048. Длинные строки приберегают на конец, потому что цена внимания растёт с квадратом длины (глава 7) и потому что длинное прошлое полезнее всего модели, которая уже хорошо пишет. Вот две функции из train.py, которые всё это задают:

def lr_factor(step, total, warmup, cooldown): """Warm up, hold, then decay linearly to zero over the last `cooldown` share.""" if step < warmup: return (step + 1) / warmup decay_start = int(total * (1 - cooldown)) if step < decay_start: return 1.0 return max(0.0, (total - step) / (total - decay_start)) def context_at(step, total, args): """Short rows first; the full --long-context only in the last `long_frac` of the run.""" frac = step / max(1, total) if args.seq_warmup and frac < 0.02: return args.context // 4 if args.seq_warmup and frac < 0.06: return args.context // 2 if frac >= 1 - args.long_frac: return args.long_context return args.context

Для 17 166 шагов они дают строки по 256 токенов до шага 343, по 512 — до шага 1 029, по 1 024 — до шага 15 449 и по 2 048 — с шага 15 450; спуск начинается на шаге 13 732. Версия context_at у Ростка-1 удлиняла окно только в начале, а у Ростка-2 ещё и приберегает самые длинные строки на самый конец.

Контрольная точка перед спуском

На шаге 13 732, прямо перед тем как скорость начнёт падать, train.py сохраняет полную контрольную точку, ckpt_pre_cooldown.pt. В этом и состоит практический подарок расписания «разогрев, плато, спуск» (глава 10): в этот момент модель находится посреди обучения, а не в его конце. Если позже появятся новые данные, обучение можно продолжить с этой точки на полной скорости, как будто спуска не было, и снова остудить модель уже в новом конце. Готовая модель — не тупик.

Что лежит в полной контрольной точке

Итоговой точке достаточно весов. Точка перед спуском хранит ещё состояние обоих оптимизаторов (импульс Muon, скользящие средние AdamW), место загрузчика в каждом источнике (какой кусок в миллион токенов, в каком перемешанном порядке, как далеко внутри) и счётчики шагов и токенов. С --resume всё это возвращается, и продолжение читает ровно те токены, которые прочитало бы следующими; заново выставляются только скорости обучения, так что у продолжения могут быть свои.

Обучение

Сколько это работы? Обучение стоит примерно шесть операций на параметр на токен, $6ND$ (глава 9): две на прямой проход и четыре на обратный. Для Ростка-1 это $6 \times 17{,}3 \cdot 10^6 \times 330 \cdot 10^6 \approx 3{,}4 \cdot 10^{16}$, для Ростка-2 — $6 \times 125{,}1 \cdot 10^6 \times 9{,}0 \cdot 10^9 \approx 6{,}75 \cdot 10^{18}$, примерно в 200 раз больше. Поэтому Росток-2 не мог вырасти на ноутбуке, как Росток-1: он учился на одной видеокарте для дата-центров класса H100, около 9,5 часа.

Каждые 500 шагов train.py мерил ошибку на отложенном тексте из каждого из 31 источника и из разговоров. Вот всё обучение целиком.

Цветные линии — ошибка на проверке по группам источников (среднее по источникам группы). Чёрная — все источники на одном неизменном экзамене, с весами основной смеси. Полосы — длина строк, ромбы — сохранённые контрольные точки. Внизу — множитель скорости обучения или длина строки, посчитанные функциями самого train.py. Ведите по графику, чтобы прочитать значения.

Что говорят кривые:

  • Каждая группа улучшается до самого конца. Веб-текст — с 3,75 на шаге 500 до 2,94, код — с 2,26 до 1,47, математика — с 1,98 до 1,35. Кривые выполаживаются, но продолжают падать: 9 миллиардов токенов, 72 на параметр, не исчерпали того, чему может научиться модель такого размера.
  • Разговоры ждут спуска. Сначала их ошибка падает вместе с обычным английским, а потом стоит около 2,6. На шаге 13 732 смесь спуска приносит данные чата; к шагу 14 000 ошибка уже 1,59, а в конце — 1,43. В этих 268 шагах было около 4,2 миллиона токенов разговоров: слова модели были не в новинку, новым был формат.
  • Ошибка на обучении падает скачком в начале спуска: примерно с 2,51 до 2,23 за сотню шагов, когда скорость ещё на 98% от пика. Это не модель оседает на дно: сменилась смесь, а код, математику и чаты предсказывать легче, чем веб-страницы. Чёрная линия — один неизменный экзамен — почти не двигается: 2,581 на шаге 13 500, 2,578 на шаге 14 000 и 2,505 в конце спуска.
  • Включите «val_mix из журнала». Это число печатает train.py, и оно падает с 2,58 на шаге 13 500 до 2,30 на шаге 14 000. val_mix взвешивает источники по текущей смеси, так что на шаге 13 732 поменялся сам экзамен: лёгкие предметы получили больший вес, а в зачёт пошли и разговоры.
  • Длинные строки немного помогают. Когда на шаге 15 450 строки вырастают до 2 048 токенов, ошибка на обучении снова делает шаг вниз — с 2,21 до 2,12, если усреднить по тысяче шагов с каждой стороны: с длинным прошлым есть на что опереться, как в главе 10 было и у Ростка-1.

Теперь переключитесь на «По источникам». В конце задачи TinyGSM стоят 0,64 ната на токен, а веб-страницы DCLM — 3,37. Математику Росток-2 выучил не впятеро лучше английского: задача TinyGSM — короткая текстовая задача с решением на Python в одном неизменном стиле, как ещё два миллиона таких же, а веб-страница может быть о чём угодно. Ошибка измеряет не только то, насколько хорошо текст выучен, но и то, насколько он вообще предсказуем, так что сравнивать ошибки можно только на одном и том же тексте. Даже 1,34 на TinyStories с 1,554 Ростка-1 (глава 10) сравнивать нельзя: модели режут текст на разные токены, а ошибка на токен зависит от того, что такое токен.

Между шагами 13 500 и 14 000 val_mix, который пишет в журнал train.py, упал с 2,58 до 2,30. В чём главная причина?

На одном неизменном экзамене ошибка за эти шаги сдвинулась только с 2,581 до 2,578. Остальное падение — смена весов: код и математика, у которых ошибка низкая, в смеси спуска весят больше, веб-страницы — меньше, а разговоры, которые модель схватывает быстро, вообще начинают учитываться. До 2 048 токенов строки вырастают только на шаге 15 450.

Ошибка, упавшая за ночь, — это вопрос, а не ответ: сначала проверьте, не поменялись ли текст или экзамен.

Базовая модель

После 17 166 шагов у нас есть базовая модель — модель, которая только и умеет, что продолжать текст. Много ли она знает? Базовую модель просят выбирать, а не писать: к каждому вопросу теста прилагается несколько вариантов окончания, и eval.py выбирает то, которое модель считает самым вероятным после вопроса. acc сравнивает простые суммы логарифмов вероятностей, acc_norm делит каждую на длину окончания в символах, чтобы длинные окончания не проигрывали только из-за длины. Использованы все задания каждого теста.

ТестНаугадaccacc_norm
SciQ
вопрос по естествознанию после поясняющего абзаца
0,250,7990,746
ARC-Easy
школьные вопросы по естествознанию
0,250,5060,459
ARC-Challenge
вопросы, на которых ошибаются простые методы
0,250,2190,250
HellaSwag
самое правдоподобное продолжение бытовой сцены
0,250,3050,341
PIQA
какой из двух способов сделать что-то руками сработает
0,50,6450,638
WinoGrande
к кому или чему относится местоимение
0,50,5200,520
OpenBookQA
применить научный факт к новой ситуации
0,250,1800,304
BoolQ
да или нет по абзацу текста
0,50,5790,619

Читать таблицу надо рядом со столбцом «наугад». SciQ, ARC-Easy, PIQA и HellaSwag явно выше него: с абзацем перед глазами Росток-2 выбирает правильный ответ по естествознанию четыре раза из пяти, и какое-то бытовое чутьё на то, что за чем следует, у него есть. ARC-Challenge и WinoGrande — на уровне случайного: вопросы, специально собранные так, чтобы не помогали словесные ассоциации, и местоимения, для которых нужно настоящее понимание, ему не по силам. BoolQ выглядит выше случайного, но это не так: 2 033 из 3 270 ответов в нём — «да», так что, всегда отвечая «да», можно набрать 0,622.

Писать труднее, чем выбирать. Текстовые задачи GSM8K, записанные как начало функции на Python в стиле TinyGSM и проверенные запуском программы, которую он напишет, Росток-2 решает в 21,5% случаев — это стиль TinyGSM из его математической смеси. В MBPP и HumanEval, где нужна целая функция, проходящая тесты, у него 1,0% и 2,4%. При жадном декодировании он сваливается в петли: пишет один и тот же import двадцать раз или копирует функцию, которую только что написал. Сам проверочный стенд проверили на эталонных решениях: прошли 40 из 40.

А вопрос из начала главы? На «How do I list all files in a folder, including hidden ones?» готовая модель пишет: «Here is a list of all files that are hidden from view. How do I get the list of hidden files (with 0 size, 0 characters) when the folder is listed in the list in the admin console?» Она не отвечает, а продолжает ветку форума: в интернете за таким вопросом обычно следуют новые вопросы.

Базовая модель продолжает текст. Она очень многое знает о том, как выглядят тексты, и отвечает, только когда ответ — самое вероятное продолжение.

Росток сейчас

Вот Росток-2: готовая базовая модель с верхней ступеньки лесенки. Она отвечает с нашего сервера — там тот же движок, что считает остальные модели курса у вас в браузере, и тот же файл: 4 бита на вес, 78 МБ вместо 250 МБ, которые веса занимают в 16 битах, ценой 0,026 ната ошибки. Скачивать ничего не нужно, и ничего не сохраняется. Попробуйте пять затравок из начала главы: с новыми кубиками они каждый раз выходят по-другому. Росток-2 пишет сказки, объяснения, Python и сеансы shell — и по-прежнему превращает любой вопрос в ветку форума: он продолжает текст, а не отвечает. В следующей главе он научится разговаривать, думать перед ответом и запускать Python, когда арифметика становится трудной.

Главы

  1. 0 Знакомство
  2. 1 Считаем буквы
  3. 2 Мера удивления
  4. 3 Градиентный спуск
  5. 4 Обратное распространение
  6. 5 Эмбеддинги
  7. 6 Токены
  8. 7 Внимание
  9. 8 Трансформер
  10. 9 Корпус
  11. 10 Обучение
  12. 11 Выборка
  13. 12 Разговор
  14. 13 LoRA
  15. 14 Больше данных
  16. 15 Больше модели
    1. Как он рос
    2. Куда уходят параметры
    3. Ключей меньше, чем запросов
    4. Что нужно большой модели
    5. Репетиции на маленьких копиях
    6. Расписание
    7. Обучение
    8. Базовая модель
    9. Росток сейчас
  17. 16 Думать и Python
  18. 17 Что дальше