Воспроизводим статьи
Как дорасти от «понимаю статью» до «умею»: выбрать посильную работу, выписать паспорт постановки, собрать минимальную версию, честно сравнить свои числа с числами статьи и написать отчёт — на примере гроккинга (Power et al., 2022) и списка из восьми статей, посильных на 4×RTX 4080.
Зачем это нужно
Заголовок раздела «Зачем это нужно»В уроке 9.2.2 вы научились читать статьи в три прохода. Третий проход Кешав описывает как «мысленно переизобрести статью». Для ML-статьи лучший третий проход — переизобрести её на самом деле: написать код, обучить, получить свои числа. Это и есть воспроизведение статьивоспроизведение статьиEN paper reproductionПовторить результат статьи своими руками: выписать постановку, собрать минимальную версию, сравнить свои числа с числами статьи с учётом разброса и объяснить расхождения в отчёте..
Почему это лучший способ дорасти от «понимаю» до «умею»:
- Всплывают все детали, которых нет в тексте. Как разбили данные, чем инициализировали веса, на какие параметры действует weight decay, считали ли точность по всему набору. Пока вы читаете, эти вопросы не возникают. Когда код не сходится с рисунком статьи, вы находите ответы сами — и запоминаете их навсегда.
- Это лучшее доказательство навыка. Отчёт «повторил рисунок 1 статьи X, вот код, вот мои кривые по трём seed, вот что не совпало и почему» говорит работодателю больше, чем сертификат. Как оформить такой проект в портфолио — в уроке 11.3.6.
- Так работает наука. Обе истории из урока 9.2.2 — «aha moment» в R1-Zero и простой базлайн против DeepSeek-OCR — начались с проверки чужого результата своими руками. А Карпатый построил вокруг воспроизведения целую лекцию — «Let’s reproduce GPT-2 (124M)».
В этом уроке — метод из семи шагов, полный разбор статьи о гроккинге и список статей, которые реально повторить на вашей машине. Домашняя работа модуля — воспроизвести гроккинг самим.
Метод в семи шагах
Заголовок раздела «Метод в семи шагах»flowchart TD
S1["1 · Выбрать статью<br/>вычисления, данные, код"] --> S2["2 · Прочитать в 3 прохода<br/>с прицелом на детали"]
S2 --> S3["3 · Паспорт постановки<br/>значение, источник, уверенность"]
S3 --> S4["4 · Минимальная версия<br/>самый маленький опыт с эффектом"]
S4 --> S5["5 · Сравнить числа<br/>несколько seed, интервал"]
S5 --> D{"Совпало?"}
D -->|"нет"| S6["6 · Расхождения<br/>одно изменение за раз"]
S6 --> S4
D -->|"да или понятно, почему нет"| S7["7 · Отчёт"]Шаг 1. Выбрать посильную статью
Заголовок раздела «Шаг 1. Выбрать посильную статью»Первая ошибка новичка — взять статью, которую невозможно повторить: модель на 70B, закрытые данные, тысячи GPU-часов. Проверьте четыре вещи до того, как потратите вечер на чтение.
| Критерий | Что проверить | Красный флаг |
|---|---|---|
| Вычисления | FLOPs по формуле и время на вашей машине | больше недели даже в уменьшенном масштабе |
| Данные | открыты ли, сколько весят, нужна ли разметка | «внутренний датасет компании» |
| Код | есть ли код авторов, есть ли независимые воспроизведения | ни кода, ни гиперпараметров в приложении |
| Проверяемый результат | рисунок или число, которое можно сравнить | только «модель стала лучше» без таблиц |
Оценку времени вы уже умеете делать с урока 5.1.1:
Здесь — число параметров, — число токенов, которые модель увидит за обучение, MFUутилизация FLOPs модели (MFU)EN model FLOPs utilization (MFU)Доля пиковой производительности железа, которая уходит на полезные вычисления модели. Для больших распределённых обучений типичны 30–50 %. — доля пиковой скорости, которую реально удаётся получить. Два примера на крайних точках:
- Гроккинг. В коде авторов модель на 422 тысячи параметров, шагов по 512 примеров, у каждого на входе
около 6 токенов (
<eos> a ∘ b = c): FLOPs. Даже при MFU 5% (крошечная модель почти всё время ждёт запуска ядер) это с на одной RTX 4080 в FP32. Минуты. - GPT-2 124M на 10 млрд токенов. . На четырёх картах в BF16 (пик 97.5 TFLOPS) при MFU 30% — с, почти 18 часов. Ночь и ещё день — тоже посильно, но уже проект, а не вечер.
Начинайте со статей «на минуты и часы». Первое воспроизведение учит методу, и лучше, если цикл «запустил → увидел расхождение → поправил» занимает минуты, а не сутки.
Шаг 2. Прочитать в три прохода — с прицелом на детали
Заголовок раздела «Шаг 2. Прочитать в три прохода — с прицелом на детали»Читаете так же, как в уроке 9.2.2, но на втором и третьем проходе ищете то, что нужно для кода:
- Приложение (Appendix). Гиперпараметры почти всегда там, а не в основном тексте.
- Подписи к рисункам. «Mean over 3 seeds», «50% of data», «with weight decay 1» часто есть только в подписи.
- Сноски и раздел про оценку. Какая именно метрика, на каком наборе, сколько прогонов.
- Код авторов — часть статьи. Читайте его как ещё одно приложение: значения по умолчанию в
argparse, скрипты запуска, конфиги. Но помните: значение по умолчанию в коде ≠ значение в опыте статьи. Скрипт запуска или конфиг могли его переопределить.
Шаг 3. Паспорт постановки
Заголовок раздела «Шаг 3. Паспорт постановки»Всё найденное сведите в одну таблицу — воспроизводимостьвоспроизводимостьEN reproducibilityВозможность получить тот же результат, повторно запустив тот же код на тех же данных. начинается с неё. У каждой строки три поля: значение, где нашли (страница, рисунок, файл и строка кода) и уверенность: «в тексте статьи», «в коде авторов», «в чужом воспроизведении», «наша догадка». Пустая ячейка «где нашли» — это кандидат номер один в причины будущего расхождения.
Минимальный набор строк: задача и данные, разбиение train/val, архитектура и число параметров, инициализация, оптимизатор и все его параметры, расписание learning rate, размер батча и число шагов, регуляризация, метрика и протокол оценки, число seed, железо и время.
Шаг 4. Минимальная версия
Заголовок раздела «Шаг 4. Минимальная версия»Не пытайтесь сразу повторить всё. Найдите самый маленький опыт, в котором эффект ещё виден: меньше модель, меньше данных, проще задача. Каждое упрощение запишите в паспорт отдельной колонкой «у меня» с причиной. Именно этот список отличий потом объяснит половину расхождений.
Порядок сборки — как в рецепте Карпатого из урока 10.1.1: проверить данные глазами, переобучить один крошечный батч до нулевого loss, сравнить с тривиальным базлайномбазовое решение, бейзлайнEN baselineПростейшая модель (среднее, самый частый класс), с которой сравнивают всё остальное. и только потом запускать полный прогон.
Шаг 5. Сравнить числа
Заголовок раздела «Шаг 5. Сравнить числа»Сравнивать можно на трёх уровнях, и в отчёте надо честно сказать, какой достигнут:
| Уровень | Что совпадает | Когда достаточно |
|---|---|---|
| Качественное | форма эффекта: кривая, знак, порядок величин | новое явление, другая постановка, меньший масштаб |
| Количественное | число статьи внутри интервала по вашим seed с допуском | та же постановка, тот же масштаб |
| Точное | до бита | только тот же код, данные, версии библиотек и железо |
Для количественного сравнения нужны несколько seed: 3 — минимум, 5 — лучше. Инструменты — из уроков 1.3.4 и 11.1.3: среднее, стандартная ошибка и 95%-интервал. При 3 seed множитель не 1.96, а квантиль распределения Стьюдента 4.30: по трём точкам разброс оценивается плохо, и интервал честно шире. Пример: точности дают интервал . Число статьи 0.93 внутри — совпало. Число 0.95 снаружи — не совпало, ищем причину. Это правило вы закодируете в задании ниже.
Шаг 6. Разобраться в расхождениях
Заголовок раздела «Шаг 6. Разобраться в расхождениях»Расхождение — не провал, а самая полезная часть работы. Проверяйте подозреваемых в порядке «дёшево проверить, часто виноват»:
| Подозреваемый | Типичный пример |
|---|---|
| Данные и разбиение | другая доля train, утечка пар из train в val, другая предобработка |
| Скрытые гиперпараметры | weight decay применяется ко всем параметрам или только к матрицам; β₂ Adam 0.98 вместо 0.999 |
| Детали реализации | инициализация, место LayerNorm, с какой позиции читается ответ |
| Протокол оценки | точность по батчу вместо всего набора, train-режим вместо eval |
| Разброс | один seed против среднего по трём |
| Бюджет | вы остановились на 6 тысячах шагов, авторы шли до 100 тысяч |
| Численные эффекты | TF32 и BF16 против FP32, недетерминированные ядра GPU |
Метод — абляцияабляционный эксперимент, абляцияEN ablation studyУбрать или заменить один компонент и сравнить с полной моделью, чтобы измерить вклад этого компонента. наоборот: одно изменение за раз. Если есть код авторов, запустите его и двигайтесь от него к своему по одному отличию, пока числа не разойдутся. Это бинарный поиск по различиям, и он почти всегда находит виноватого за несколько прогонов.
Шаг 7. Написать отчёт
Заголовок раздела «Шаг 7. Написать отчёт»Отчёт короче статьи, но строже: в нём нельзя смешивать то, что написано в статье, и то, что вы предполагаете. Шаблон из домашней работы: что воспроизводим (утверждение одной фразой и номер рисунка), паспорт «статья против меня» с причиной каждого отличия, таблица результатов по seed, что совпало, что нет, почему (гипотезы и как проверили), что делать дальше. Публикуйте его вместе с кодом: README в репозитории на GitHub, пост на Хабре или Чжиху.
Разбор: гроккинг
Заголовок раздела «Разбор: гроккинг»Статья за первый проход
Заголовок раздела «Статья за первый проход»Grokking: Generalization Beyond Overfitting on Small Algorithmic Datasets — Алетея Пауэр, Юрий Бурда, Харри Эдвардс, Игорь Бабушкин и Ведант Мисра из OpenAI. Впервые её показали на воркшопе MATH-AI конференции ICLR 2021, на arXiv она вышла в январе 2022 года (arXiv:2201.02177).
Задача. Таблицы бинарных операций по модулю простого числа 97: сложение, вычитание, деление, многочлены вроде , композиция перестановок. Это модульная арифметикамодульная арифметикаEN modular arithmeticАрифметика остатков: (a + b) mod p — остаток от деления суммы на p, как часы с p делениями. Таблицы таких операций — «маленькие алгоритмические датасеты» из статьи о гроккинге.: — остаток от деления суммы на , как стрелка на часах с делениями. Часть клеток таблицы дают модели, остальные — валидация. Ответ нельзя «подсмотреть»: чтобы заполнить пустые клетки, модель должна понять операцию.
Главное наблюдение — гроккинггроккингEN grokkingЗапоздалое обобщение: модель давно выучила train (точность ≈ 100%), на валидации долго держится на уровне угадывания, а потом точность на валидации резко растёт почти до 100%. Описан в статье Power et al. (2022) на модульной арифметике; weight decay сильно ускоряет переход. В китайских текстах часто пишут просто Grokking.. Маленький трансформер быстро запоминает обучающие клетки: точность на train около 100%, на валидации — угадывание. Классический совет — остановить обучение, ведь началось переобучениепереобучениеEN overfitting. Если же продолжать, то намного позже точность на валидации резко поднимается почти до 100%. Обобщение приходит далеко за точкой переобучения — отсюда название статьи.
Ещё два результата, ради которых стоит читать дальше: чем меньше доля данных в train, тем дольше приходится оптимизировать до обобщения, а среди вмешательств, которые пробовали авторы, weight decay заметнее всего ускорял обобщение (сверьте по рисунку с вмешательствами в оригинале).
Паспорт постановки
Заголовок раздела «Паспорт постановки»Паспорт ниже собран по коду авторов openai/grok и открытым воспроизведениям; что нужно сверить с текстом статьи, так и помечено.
| Что | Значение | Где нашли |
|---|---|---|
| Задача | ; сложение, вычитание, деление, многочлены, перестановки | grok/data.py |
| Рисунок 1 | деление , половина таблицы в train | открытые воспроизведения teddykoker/grokking и danielmamay/grokking; сверьте по оригиналу |
| Модель | трансформер-декодер: 2 слоя, ширина 128, 4 головы; по нашему подсчёту 422K параметров | grok/training.py, значения по умолчанию |
| Оптимизатор | AdamW, lr , , линейный разогрев 10 шагов | grok/training.py |
| Weight decay | в коде по умолчанию 0; в danielmamay/grokking и у Nanda et al. — 1 | сверьте по оригиналу, какой стоял в главных опытах |
| Батч | min(512, половина train) | grok/data.py |
| Шагов | до | grok/training.py |
| Метрика | точность на всех клетках, не попавших в train | grok/training.py |
Строка про weight decay — учебный пример ловушки из шага 2: значение по умолчанию в коде (0) почти наверняка не то, с которым получены главные кривые. Воспроизведения берут 1. Пока не сверено с текстом, в отчёте это «уверенность: чужое воспроизведение».
Минимальная версия: что мы упростили
Заголовок раздела «Минимальная версия: что мы упростили»Цель — увидеть эффект на CPU за минуту. Поэтому пять сознательных отличий:
| Отличие | Статья (код) | У нас | Зачем |
|---|---|---|---|
| Операция | деление на рис. 1 | сложение | грокает быстрее |
| Модель | трансформер 422K | MLP: эмбеддинги и → 128 → 97, 24K параметров | шаг на CPU в десятки раз дешевле |
| Доля train | 50% | 30% | задержка обобщения заметнее |
| Батч | 512 | весь train, 2822 пары | детерминированно и проще |
| lr и wd | и (по воспроизведениям) 1 | и 2 | быстрее весь процесс |
Почему именно weight decay так важен? В AdamWAdamWEN AdamW (Adam with decoupled weight decay)Adam, в котором затухание весов применяется отдельно от адаптивного шага. Оптимизатор по умолчанию для обучения LLM. он «развязан» с градиентом (урок 1.4.2): на каждом шаге веса умножаются на . При и это 0.994, и за 1000 шагов без градиента вес сжался бы в раза. Запоминающее решение требует больших весов, обобщающее обходится маленькими. Weight decayзатухание весов (weight decay)EN weight decayНа каждом шаге веса умножаются на (1 − ηλ) — «притягиваются» к нулю. Для обычного SGD равносильно L2-регуляризации, для Adam — нет. всё время давит в сторону маленьких — и в конце концов выдавливает модель из запоминания в обобщение.
Результаты
Заголовок раздела «Результаты»Все прогоны — на одном ядре CPU, код из эталона домашней работы. Шаги измерены с точностью до шага записи журнала (25 или 50).
| Настройка | seed | train ≥ 0.99 | val ≥ 0.95 | во сколько раз позже | время |
|---|---|---|---|---|---|
| MLP, доля 0.3, wd 2 | 0 | 375 | 1725 | 4.6 | 45 с |
| MLP, доля 0.3, wd 2 | 1 | 425 | 2700 | 6.4 | 51 с |
| MLP, доля 0.3, wd 2 | 2 | 425 | 1825 | 4.3 | 60 с |
| MLP, доля 0.3, wd 1 | 0 | 200 | 7100 | 35.5 | 109 с |
| MLP, доля 0.3, wd 1 | 1 | 200 | 8300 | 41.5 | 84 с |
| трансформер, 1 слой, , , доля 0.5, wd 1 | 0 | 100 | 1500 | 15 | 103 с (1800 шагов) |
Как выглядит прогон с wd 1 и seed 0:
| Шаг | 0 | 200 | 1000 | 2000 | 3000 | 5000 | 6000 | 7000 | 8000 |
|---|---|---|---|---|---|---|---|---|---|
| train accuracy | 0.01 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| val accuracy | 0.01 | 0.001 | 0.001 | 0.005 | 0.006 | 0.043 | 0.22 | 0.92 | 1.00 |
| val loss | 4.6 | 11.0 | 19.5 | 23.8 | 20.8 | 12.0 | 5.5 | 0.26 | 0.00 |
Три тысячи шагов модель знает train наизусть и ниже угадывания на val: 0.001 при случайных . Val loss тем временем растёт с 4.6 до 23.8. Причина: чтобы train loss стремился к нулю, логиты становятся огромными, и на незнакомых парах модель уверенно называет неправильный ответ, а cross-entropyперекрёстная энтропияEN cross-entropy за уверенную ошибку растёт неограниченно. Затем weight decay берёт своё: val loss начинает падать задолго до скачка точности, и между шагами 6000 и 7000 val accuracy взлетает с 0.22 до 0.92.
Что совпало, что нет и почему
Заголовок раздела «Что совпало, что нет и почему»Совпало качественно. Во всех шести прогонах train выучен за 100–425 шагов, обобщение приходит в 4–40 раз позже, а weight decay управляет задержкой: wd 2 даёт гроккинг на шагах 1725–2700, wd 1 — на 7100–8300, то есть в 3–4 раза позже.
Не совпало — и это полезно записать.
- Масштаб времени. У нас тысячи шагов, в статье бюджет до . Постановки разные, поэтому номера шагов напрямую не сравниваем: это качественное воспроизведение.
- Трансформер сразу угадывает половину val. При доле 0.5 точность на val застывает на 0.48 задолго до гроккинга. Гипотеза: модель выучила коммутативность . Проверка заняла минуту: среди пар val, у которых зеркальная пара есть в train, модель права в 99.8% случаев, среди остальных — в 0.6%. MLP так не умеет: у него и входят в разные веса первого слоя.
- Разброс огромный. Одна и та же настройка с тремя seed даёт 1725, 2700 и 1825 шагов. В черновом скрипте в train попадал на один пример больше (округление вместо отбрасывания дробной части) — и шаг гроккинга для seed 0 сдвинулся с 1725 на 1850. Один прогон не доказывает ничего, сравнение настроек — только по нескольким seed.
- Кривые трансформера шумные. При lr train accuracy периодически проваливается до 0.3–0.5, у MLP кривые гладкие. Гипотеза — слишком большие шаги Adam при почти нулевом loss. В отчёте: «не проверено».
Почему так происходит. Нил Нанда и соавторы разобрали обученную модель по весам (методы урока 9.3.1): сеть складывает по модулю через тригонометрию — числа становятся углами на окружности, сложение — поворотом. Этот механизм строится ещё во время «плато», а weight decay вычищает запоминание. Группа Цзымина Лю (Omnigrok) смотрит на норму весов: запоминающее решение находится при большой норме, обобщающее — при меньшей, и weight decay медленно ведёт туда. Пошаговый разбор — в наборе 1.5.2 курса ARENA (путеводитель).
Восемь статей, посильных на 4×RTX 4080
Заголовок раздела «Восемь статей, посильных на 4×RTX 4080»Все номера arXiv — из материалов нашего курса, рядом указан урок, где статья разобрана. Время — оценка по порядку величины по формуле из шага 1 и нашему опыту, а не замер; проверьте на своей машине на коротком прогоне.
| Статья | Что воспроизвести | Оценка времени | Урок |
|---|---|---|---|
| ResNet, arXiv:1512.03385 | CIFAR-10: «обычные» сети на 20 и 56 слоёв (у 56 ошибка выше даже на train) и остаточные той же глубины | ≈ час на модель, 4 модели параллельно — вечер | 3.4.2 |
| FlashAttention, arXiv:2205.14135 | время и память внимания (прямой + обратный проход) от длины последовательности: обычное против FlashAttention | вечер на одной карте | 5.7.2 |
| Speculative Decoding, arXiv:2211.17192 | измерить долю принятия для пары «маленькая + большая модель» и сравнить ускорение с формулой | вечер на одной карте | 7.1.2 |
| LoRA, arXiv:2106.09685 | одна задача GLUE с моделью уровня RoBERTa-base: полное дообучение против LoRA с рангами 1, 4, 8, 64 — проверить утверждение статьи, что маленького ранга хватает | 1–2 вечера, ранги на разных картах | 6.2.1 |
| TinyStories, arXiv:2305.07759 | модели на единицы и десятки миллионов параметров пишут связные сказки; оценка — LLM-судья из урока 7.3.2 | часы на модель | 5.6.2 |
| Chinchilla, arXiv:2203.15556 | мини-IsoFLOP: 3 бюджета (, , ) × 6 размеров модели, оптимальный размер для каждого бюджета | ≈ FLOPs: 2 ч при MFU 30%, реально — полдня | 5.1.1 |
| DPO, arXiv:2305.18290 | опыт с управляемой тональностью на отзывах IMDb: награда классификатора против KL от референса для DPO (модель — сверьте по статье) | день | 6.3.3 |
| Understanding R1-Zero-Like Training (Dr. GRPO), arXiv:2503.20783 | смещение GRPO по длине: по статье, длина неверных ответов растёт у GRPO и не растёт у Dr. GRPO; повторить на маленькой базовой модели в verl | 1–2 дня на 4 картах | 6.4.3 |
Порядок — от простого к сложному. ResNet и FlashAttention повторяются почти наверняка, и на них удобно отточить метод. Последняя строка — сама по себе статья-проверка чужого результата: воспроизводя её, вы проверяете проверяющих.
Практика
Заголовок раздела «Практика»Задание в браузере: бюджет и вердикт
Заголовок раздела «Задание в браузере: бюджет и вердикт»Соберите два инструмента, которые пригодятся в каждом воспроизведении: оценку «посильно ли это» по и правилу 16 байт на параметр для AdamW (урок 5.4.1) и вердикт «совпало ли с статьёй» по интервалу из нескольких seed.
"""Бюджет воспроизведения и вердикт «совпало ли с статьёй»."""
import numpy as np
# Квантили распределения Стьюдента t(0.975, df) для 95%-интервала по n = df + 1 seed. Готово.
T975 = {1: 12.706, 2: 4.303, 3: 3.182, 4: 2.776, 5: 2.571, 6: 2.447, 7: 2.365, 8: 2.306, 9: 2.262, 10: 2.228,
11: 2.201, 12: 2.179, 13: 2.160, 14: 2.145, 15: 2.131, 16: 2.120, 17: 2.110, 18: 2.101, 19: 2.093, 20: 2.086}
def t_quantile(df):
"""Множитель для 95%-интервала при df степенях свободы (при df > 20 — почти нормальные 1.96). Готово."""
return T975.get(df, 1.96)
def train_flops(n_params, n_tokens):
"""FLOPs обучения по правилу 6·N·D (урок 5.1.1): прямой проход 2·N·D, обратный 4·N·D."""
# TODO
...
def gpu_hours(flops, peak_tflops, mfu, n_gpus=1):
"""Часы реального времени: FLOPs / (n_gpus · пик · MFU), пик в TFLOPS (10^12 FLOP/с)."""
# TODO
...
def fits_on_gpu(n_params, gpu_mem_gb, bytes_per_param=16, reserve=0.2):
"""Влезет ли обучение с AdamW на одну карту: n_params · bytes_per_param (в ГБ = 10^9 байт)
не больше доли (1 − reserve) памяти карты; reserve — запас на активации и фрагментацию."""
# TODO
...
def seed_interval(values):
"""(среднее, нижняя граница, верхняя граница) 95%-интервала среднего по seed:
mean ± t · SE, где SE = std(ddof=1) / √n, t = t_quantile(n − 1). Меньше двух значений — ValueError."""
# TODO
...
def verdict(my_values, paper_value, rel_tol=0.0):
"""Сравнение с числом из статьи. Интервал seed_interval расширяем на rel_tol · |paper_value| в обе стороны.
Число статьи внутри → "совпало"; иначе "выше статьи", если наше среднее больше, или "ниже статьи"."""
# TODO
...
if __name__ == "__main__":
# Гроккинг по коду авторов: 422K параметров, батч 512 примеров × ~6 токенов («<eos> a ∘ b = c»), 10^5 шагов
c = train_flops(422_241, 512 * 6 * 100_000)
print("гроккинг, FLOPs:", c)
print(seed_interval([0.90, 0.92, 0.91]))
Домашняя работа: воспроизводим гроккинг
Заголовок раздела «Домашняя работа: воспроизводим гроккинг»Проверь себя
Заголовок раздела «Проверь себя»- Воспроизведение — это третий проход чтения, доведённый до кода: выбрать посильное, выписать паспорт, собрать минимальную версию, сравнить по нескольким seed, разобрать расхождения, написать отчёт.
- Посильность считается заранее: и . Гроккинг — минуты, GPT-2 124M — около суток на четырёх картах.
- Код авторов — часть статьи, но значения по умолчанию в нём не обязательно те, что в опытах статьи.
- Гроккинг воспроизводится качественно за минуту на CPU: train выучен к шагу ≈ 400, обобщение — к шагу ≈ 2000 при wd 2 и ≈ 7500 при wd 1. Разброс между seed в 1.6 раза — один прогон ничего не доказывает.
- Расхождения — самая ценная часть отчёта, если у каждого есть гипотеза и проверка (или честное «не проверено»).
Дальше: «Вклад в open source» — следующий шаг после воспроизведения: найти в чужом репозитории баг или недостающую функцию и прислать исправление.