
Раскрученный проект nanochat обещает "за $100 обучите свой ChatGPT". В оригинале обучение на 8xH100 80Gb, но никто не мешает обучить его на домашнем и более слабом железе. Попробуем обучить LLM с нуля на статьях Хабра, чтобы посмотреть хватит ли там материала, чтобы модель хотя бы смогла связать пару слов.
Классические этапы обучения LLM:
pretrain - это base модель. Дают неразмеченный сырой текст, модель учится продолжать.
SFT - это instruct модель. Дают качественный, размеченный датасет.
RL, DPO - выравнивание модели, датасет с парами хороший-плохой ответ.
Обучение с нуля начинается со случайных параметров, на вход необученной модели подается неразмеченный текст и модель шаг за шагом пытается предсказать следующие токены на основе предыдущего контекста. Вначале модель осваивает простые паттерны, такие как орфография и пунктуация, после начинается выявление более сложных связей, где происходит аппроксимация не только коротких связей, но и полных документов, например, идея которая озвучена в 1 главе получается развитие в 3 главе, и такие длинные связи тоже начинают выявляться.
Цель настроить параметры так, чтобы модель могла уверенно продолжать текст, где начало, середина и конце связаны друг с другом. Упрощенно можно на это посмотреть так, из случайного распределения параметров начинают проявляться структуры, эти структуры означает что паттерны найдены и знание усвоено:
Анимация упрощенного процесса подбора параметров модели
Модель работает с токенами. Слова, перед тем как попадут в модель, разбиваются на токены, токены в свою очередь преобразуются в вектора, эти вектора можно назвать как "вектора сути". Такие вектора тоже обучаются, и после обучения начинают передавать смысл слова.
То, что имеет похожие смыслы или близкие сущности - они начинают располагаться всё ближе и ближе друг к другу в многомерном пространстве, формируя кластер семантической близости. Чем лучше обучена нейросеть, тем точнее будет их расположение в таком многомерном пространстве с огромной размерностью (размерность от 512 до 8192 и более). Это называют эмбеддингами.

Эмбеддинги несут суть слова в многомерном пространстве, а не само слово, и так как это вектор, с векторами можно проводить работать через геометрию пространства.
Например, есть Вектора "Король", "Мужчина", "Женщина". Каждый вектор содержит в себе какую-то определённую суть:
Вектор("Король") = [Мужчина] + [Монарх]Вектор("Мужчина") = [Мужчина] + [Человек]Вектор("Женщина") = [Женщина] + [Человек]
Если из Вектор("Король") вычесть Вектор("Мужчина"), получится чистый признак [Монарх]
Если пока нет готового понятия, его можно создавать:
Вектор("Король") - Вектор("Мужчина") + Вектор("Женщина") = Вектор("Королева")
По сути работа нейросети заключается в том, чтобы перемножать эмбеддинги с векторами положений слова, вычислять значимость слова в контексте, создавая новые вектора, новые вектора прогоняются дальше слой за слоем, всё усложняя признаки.
На выходе нейросети вычисленные вероятности для всех токенов нейросети, количество вероятностей равно vocab_size, обычно от 32к до 256к, но большинство их них будут иметь значение ниже 0.01 и только top-k токенов будут иметь какую-то значимость.
Нейросеть не выбирает какой токен из предложенных выбрать. Выбором занимается внешний от нейросети процесс - семплинг. Семплинг это не интеллектуальная сущность, тут нет накопленной статистики, нет обучения, и он предназначен чтобы добавить случайности и разнообразия в ответ, это делает модель более интересной, но выбор неудачного токена может увести даже большую модель в ошибочное направление.
Можно выделить основные параметры семплирования:
Greedy (Жадный поиск) - всегда берется самый вероятный токен, самый первый.
Top-K - отсекает хвосты, оставляя только N верхних по вероятности токенов.
Top-P - оставляет токены, пока их сумма вероятностей не достигает порога.
Temperature - масштабирует логиты до softmax, сглаживает или заостряет распределение.

Когда pretrain создан, он умеет только продолжать фразы и нужно научить его следовать инструкциям. Модели показывают как выглядят инструкции, шаблон, структуру, служебные токены для разделения user и assistant, чтобы модель могла правильно отделять свои ответы от запросов пользователя.
Всё это делается на основе множества размеченных примеров, из которых будут выявляться паттерны. Происходит процесс подбора параметров так, чтобы шаблон сходился.

После того как модель научилась следовать шаблону, можно её дополнительно настроить или выровнять, усилить сигнал хороших ответов и ослабить у плохих. Для этого нужен датасет где будет пара хороший-плохой ответы, и по ходу дообучения расхождение между этими парами должно усиливаться.

Обучать будем на nanochat, это позволит создать современную LLM без погружения в конфигурации, настройки, подбор параметров обучения.
Nanochat от Andrej Karpathy построен по принципу "укажите 1 параметр, остальные будут подобраны автоматически" - нужно указать depth или количество слоев трансформера. Остальные параметры будут подобраны согласно закону Шиншиллы.
В nanochat свежие подходы к обучению, вместо AdamW используют гибрид с более лучшим Muon, вместо GELU более качественный SwiGLU, пробуют различные новые архитектурные ращения вроде PLE, обучение в fp8, компиляция torch.compile triton ядер для ускорения в 2 раза и т.д.
Возможно, вместо adamw+muon можно перейти на новый оптимизатор gefen, который заявляет, что можно сэкономить до x8 памяти: https://github.com/ndvbd/Gefen
Размер модели d16 составляет 0.5B, но эффективных ~0.25B, остальное это PLE.
Архитектура PLE (Per-Layer Embeddings), это к каждому, или через один, слою добавляется слои эмбеддингов, которые по сути является ассоциативной памятью. Этот слой обучают вместе с моделью, но он почти не требует FLOPs ни на обучение, ни на инференс. Идея в чем-то похожа на MoE, только тут получается статичный слой PLE, который увеличивает емкость данных у модели в 2 раза, не тратя ресурсы на вычисления.
В Gemma4 есть 2 модели E2B и E4B, которые работают на этой архитектуре. В теории Gemma4 E4B обучается и запускается как 4B модель, но работает как "умная" 8B модель.
Исследователи из DeepMind в 2022 году представили работу Chinchilla Scaling Laws (Закон Шиншиллы), в которой вывели эмпирические правила, что на 1 параметр модели нужно 20-25 токенов обучения. Так же они внесли корректировки в Warmup и пиковый LR.
В nanochat из этого scaling laws исключаются эмбеддинги, поэтому в nanochat, где эмбединги PLE занимают почти половину модели, Chinchilla ratio составляет 12. Другие параметры будут вычислены на основе этих факторов.
Например, вычисление общего размера батча:
Где B - оптимальный размер батча, а D - размер датасета. Степень 0.383 тоже выведена эмпирически DeepMind и OpenAI, отвечающая на вопрос как сильно надо увеличить размер батча, если увеличивается объем датасета, чтобы обучение шло оптимально.
Для d16 размер батча оптимален в 512к, для d24 будет 1кк. Вместить такой размер физически на GPU тяжело, чтобы это компенсировать используют Gradient accumulation.
На домашнем GPU физический размер батча будет где-то в диапазоне 4-16, но за счет накапливания через Gradient accumulation, итоговый батч будет иметь нужный размер. И математически это почти равнозначно физическому батчу.
Nanochat рассчитан на работу с серверными картами уровня H100 и выше, потому что тут используется flash-attention 3, который ускоряет обучение и тратит меньше памяти. FA3 работает на архитектурах Blackwell и Hopper.
Я буду обучать на 5090, и это как раз архитектура Blackwell, но в ходе обучения возникла первая проблема, расход памяти был слишком большой, из-за снижалась скорость обучения, MFU показывал не эффективное использование карты.
Проблема в том, что в nanochat используется скользящее окно внимания SSSL (S - 1/4 от полного внимания, L - полное), это ускоряет обучение и экономит память. SSSL эффективно только в fa3, для этого паттерна нужно оптимально маскировать урезанное внимание, чтобы не тратить полную память и вычисления на операция с такими слоями.
Изучение вопроса как работает FA3 привело к тому, что тут нужны физические блоки тензорной памяти TMEM, если их нет, будет откат на старые регистры HMMA (уровень Volta/Ampere), которые не могут оптимально работать с SSSL и тратят лишнюю память и время на обработку, что приводит к тому, что скорость обучения падает. Оказалось, что TMEM физически присутствует только в корпоративном сегменте Blackwell, на картах типа B200, на домашних картах этих блоков нет. Подробнее про fa3 и отсутствие TMEM на 5090: https://gist.github.com/solatticus/aab6ec3a0436748b021cbbdd12e8c739
В итоге на домашних GPU придется откатиться на SDPA, где под капотом используется FA2 (в Linux, под Windows скорее всего придется установить fa2 вручную), который не может нормально работать с SSSL. FA2 это тоже быстро, но не так эффективно как FA3/FA4, и придётся переключиться на полный L.
Возможно, есть какой-то эффективный способ обучать со скользящим окном внимания на потребительских GPU, вроде FlexAttention. Ещё какая-то работа представлена тут: https://github.com/MaxLSB/flash-attn2
Под Windows нужно установить pip install triton-windows - это позволит запустить обучение и скомпилировать triton ядра, но вопрос скорости остается открытым.
Под Linux скорость обучения выше, чем под Windows. В чем именно проблема, не понятно. Возможно проблема моей сборки, возможно медленный fa2 под Windows, или из-за Win10, а на Win11 будет нормально.
Под Windows скорость 85к t/s:

Утилизация GPU 100%, что с учетом скорости, не эффективно:

Под Linux 132к t/s, на ~60% выше при той же утилизации 100%:

В обоих случаях nvidia-smi -i 0 -pl 450, чтобы GPU оставалась тихой во время работы, это снижает эффективность всего на ~7%, но шум вентиляторов тише в 2 раза.

Обучение начинается с обучения токенизатора. В качестве дапасета IlyaGusev/habr.
Обучение токенизатора проходит на тех же статьях, он ищет зависимости языка, выявляет частые слоги, корни слов, различные сочетания символов, создает словарь, чтобы эффективно сжимать русский текст в токены.

После обучения токенизатора, можно подсчитать точное число токенов Хабра, получилось на 302к статей всего 508M токенов.

Комментарии это ещё +0.6B. Но их не будем учитывать, так как обучение на статьях, хотя комментарии могли бы внести живости в общение модели и расширить её знания.

Скрипт обучения рассчитан на 80Гб VRAM, по умолчанию batch-size равен 32, чтобы влезть в меньший объем VRAM, нужно его снизить. Если памяти совсем не хватает, можно включить Gradient checkpointing, это кардинально снизить расход памяти.
Обучать будем в fp8, размер d16, для размера 0.5B нужен датасет в 6-10B, у нас есть только 508M. Вместо SSSL используем L, сохраняем чекпоинт каждые 500 шагов, размер физического батча 16 или меньше, чтобы влезало в VRAM.
python -m scripts.base_train --depth=16 --fp8 --window-pattern L --save-every=500 --device-batch-size 16
После запуска, скрипт произведет все необходимые расчёты по автоматическому выставлению параметров. Размер d16 составляет 0.5B (размер PLE указан как value_embeds и составляет 0.25B, размер матриц трансформера 0.19B и общий размер 0.5B), суммарный размер батча 512к, количество голов внимания 8.
С учетом Закона Шиншиллы и PLE скрипт рассчитал, что для d16 нужно 2.8B токенов, чтобы достигнуть это число обучение будет в несколько эпох, пока не будет пройдено заданное количество токенов. Из-за множества эпох возможно переобучение (overfitting).

По умолчанию в nanochat максимальная длина последовательности 2048 токенов. Но я выставил её в 512, чтобы сэкономить память, и тут же столкнулся с первой проблемой обучения.
Обучение шло не плохо, loss уверенно снижался, но эпохи слишком быстро сменяли друг друга.
По расчётам должно было быть 5.5 эпох, но первые эпохи прошли слишком быстро, а потом пошли эпохи 6, 7, 8, чего быть не должно. Вернувшись к анализу кода, и внимательнее посмотрев на предупреждение в файле dataloader.py о том, что "Original Streams tokens" отключен, теперь вместо него BOS-aligned bestfit который "~35% tokens cropped at T=2048", стало понятно, что датасет кропается по какому-то алгоритму.
Старый способ создавал плоский буфер токенов из 100% датасета, который шли друг за другом, в таком режиме обрезки старой статьи попадали в новую, получалось много шумных данных. Вместо этого новый алгоритм ищет статьи которые влезают в заданную последовательность, если таких нет, берутся длинные и обрезаются, обрезки выбрасываются.
При bestfit модель видит более чистые данные, каждое начало это BOS токен и начало документа, структура и весь контекст статьи в одном месте. Но при размере контекста в 512 токенов, хвосты выбрасываются и это составляют львиную долю датасета.
В итоге в обучение попало только 170M вместо 508M при ctx 512.
Вернемся к стандартному значению в 2048. При таком варианте из датасете берется 370M. Потери в районе обещанных 35%, поэтому попробуем обучить так.
Численным показателем хода обучения выступает функция потерь cross-entropy loss. Модель выдает распределение вероятностей по всему словарю, настоящий следующий токен известен из обучающих данных, и кросс-энтропия измеряет расстояние между тем, что выдает модель и тем, что должно быть. На первых шагах loss максимальный и вычисляется как натуральный логаритм от размера словаря, в нашем случае это 32к, все 32к токенов имеют равную вероятность.
Базовые паттерны будут очень быстро усваивается, поэтому дойти до loss 6 будет просто, в районе 3 будут усвоены все основные принципы языка, орфография, пунктуация, опуститься ниже 3 будет сложно, так как там будут выявляться уже какие-то конкретные факты, тонкости.
Размер d16, fp8, 5090, датасет 370M с BOS bestfit, целевое количество токенов 2.8B, ожидаемое время обучения 6 часов. Размер контекста в 2048, хвосты длинных статей обрезаются, 63% датасета. Обучение потребует ~9 эпох.

loss 10.39. Шаг 1. Loss совпадает с расчётным, все 32к токена равновероятны при выборе моделью. Посмотрим как это выглядит.
Токены просто хаотично слипаются, есть какие-то слова, но это либо случайность, либо целый токен слова, текст представляет собой неразборчивое месиво:

loss 6. Шаг 40. Происходит быстрое снижение с 10 до 6. Базовые паттерны языка выявляются, смысла в фразах нет, но теперь это уже не мешанина токенов:

loss 3.3. Шаг 500. Примерно 10% обучения, эпоха ещё не пройдена. До loss 3 добраться просто, нейросеть быстро аппроксимирует язык и PPL равняется 20, у модели есть 20 вариантов следующих токенов, которые она считает подходящими.

PPL 20 означает, что ответы уже начинают походить на настоящие, модель выучила частоту токенов, простые сочетания, базовую грамматику, в фразах начинает проявляться смысл:

loss 2.6. Шаг 3000. 55% обучения. 2 эпохи. Опуститься ниже loss 3 занимает много времени.
Тут можно заметить bpb 0.56, который я пока не учитываю.

loss ниже 3 изучает семантические связи, длинные зависимости, набор фактических знаний. PPL опустился до 13, текст становится более связным:

loss 1.8. Шаг 5376. bpb 0.7, 9 эпох. Пройдено 2.8B токенов, цель достигнута.

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

Проверим, как модель усвоила тонкости языка. Если начать как мужской, то модель продолжает писать в мужском роде, если начать как женский, то модель следует этому:

Списки стандартный, буквенный, римский и ненумерованный. У модели не получилось аппроксимировать этот навык:

И это первое проявление проблемы fp8 против bf16. Точнее их было больше, но модель постепенно, но не идеально, выправляла их по ходу обучения.
Для сравнения fp8 vs bf16 пример старого чекпоинта d16, который обучался только на статьях Хабра, но обучался в bf16. Такой чекпоинт не только вывел как правильно составлять все варианты списков, но и усвоил последовательность планет до Марса:

Но в целом pretrain обучился и в fp8, и возможно ему просто не хватило токенов.
Чтобы увеличить количество токенов, и прежде чем отказываться от BOS bestfit, попробуем обучить модель в 8192. Это увеличивает до 450M токенов, примерно 85%.
Обучать сразу в 8192 не рекомендуется, так как стабильность хуже, матрица внимания слишком большая на первых шагах из-за чего сходимость будет плохая.
Скорость обучения просела в 2 раза (и дополнительно от того, что под Win10):

На 500 шагах loss упал до 3.2, в целом работает:

Качество для loss 3 имеет слишком много зацикливаний и повторений:


Поэтому попробуем вариант поэтапного обучения. Можно взять предыдущий чекпоинт в контексте 2048, который прошел 5376 шагов и дообучить его как Continued Pre-Training (CPT). Если немного модифицировать код nanochat, можно продолжать обучение не с конкретного шага, а инициировать новое обучение, но не с нуля, а весами с другого шага.
python -m scripts.base_train --depth=16 --fp8 --window-pattern L --save-every=500 --device-batch-size 4 --max-seq-len 8192 --model-tag d16-8192 --load-weights-from 5376
Модель уже видела 370M токенов, поэтому сразу loss 3.1 и быстро падает до 2.3, адаптация к 8192 проходит на первых шагах нормально:

А дальше loss начал плавно расти до 2.7 и не собирался останавливаться. Модель обучается слишком большим шагом LR и вылетает из своего минимума.

Нужно снизить LR, увеличить warmup:
python -m scripts.base_train --depth=16 --fp8 --window-pattern L --save-every=500 --device-batch-size 4 --max-seq-len 8192 --model-tag d16-8192 --load-weights-from 500 --matrix-lr 0.005 --embedding-lr 0.075 --unembedding-lr 0.002 --scalar-lr 0.125 --warmup-steps 200
После этого loss снижается, в ходе адаптации плавает от 2.4 до 2.1. Итоговый loss стал ниже даже 1.7, у 2048 версии было 1.8. Но при этом val bpb остался тем же, стал 0.71, а был 0.70:

И это странно, если посмотреть лог, то минимальный bpb составлял 0.55, но в конце обучения он вырос до 0.7, хотя loss упал:

Всё это указывает на переобучение. Для CPT надо было задать меньше эпох, так как в обучение добавилось только 80M дополнительных токенов, но чекпоинт на 2048 имел аналогичные показатели, минимальный был 0.55, а итоговый 0.70. Так что проблема была и с первым чекпоинтом.
Не сохранилась 0.55 версия, но можно сравнить bpb 0.65 vs 0.71:

Версия 0.65 дает правильный код и структуру ответа, версия на 0.71 зацикливается. Видимо на 0.55 было бы ещё лучше. Переобучение ощутимо снижает качество, но оба чекпоинты были переобучены, так как val bpb был 0.7, потенциал датасета не раскрыт.
Простая и быстрая оценка LLM это показатель перплексии PPL.
PPL показывает степень уверенности модели в её ответе. PPL равный 10 означает, что у модели на выбор 10 вариантов, и она не уверена какой из них лучше, модель колеблется между этими 10 вариантами. Loss и PPL математически связаны:
Loss 3 дает PPL равную 2^3 = 8, loss 1.8 = PPL 3.5. Если Loss снижается, а val Loss растет, значит модель от обобщения перешла к запоминанию, модель переобучается (overfitting).
В nanochat для валидации используют BPB, более универсальную метрику, которую можно сравнивать на разных датасетах и токенизаторах. BPB не зависит от того, токенизатор разбил слово на 3 токена или на 1 токен, так как нормализует значение. BPB - сколько бит информации требуется модели, чтобы закодировать один байт текста:
Пока продолжим с тем, что получилось. Pretrain умеет только продолжать текст, чтобы раскрыть этот потенциал нужно дообучение на другом датасете, который покажет модели:
Шаблон Assistant - User
Примеры вопросов и качественных ответов
Примеры где нужно останавливать генерацию, чтобы не генерировать до отсечки
Найти хороший датасет не тривиальная задача. Я нашел несколько вариантов:
Возможно, имеет смысл создать SFT датасет по статьям хабра в формате вопрос-ответ, чтобы знания у модели лучше усвоились. Например, создать что-то такое, для каждой статьи создать от 2 до 10 ответов, это позволит научиться отвечать на вопрос не целой статьей, а коротким ответом, и позволит лучше усвоить знания из статей:

saiga_preferences это DPO датасет, но если взять только chosen, и игнорировать rejected, то получиться SFT. Но это маленький датасет на 30к записей, который не включает в себя многие сценарии. Дообучение на нем займет не много времени, как проверка концепции подойдет.
Я не сохранял промежуточные чекпоинты, поэтому версии с bpb 0.55 не осталось. Попробуем дообучить 0.71 версию, посмотрим, что получиться с переобученным pretrain. Запускаем SFT и смотрим, что показатели loss тут отличаются. Они ниже 1, но это нормально, так как датасет маленький, и показатель val bpb не показывает переобучение:

Свободный режим общения с моделью. Уже не плохо, модель ведет диалог, ответы короткие:

Подберет занятия, чтобы развеять скуку (конечно, если вам подходит вариант с путешествиями по времени или в космосе):

Если задавать вопросы, то ответы уже становиться длинными, но модель научилась выполнять инструкции и отвечать на вопросы по теме:

У pretrain размер генерации без остановки доходил до лимита в 8к, теперь же модель останавливается сама:

Можно попробовать написать статью, так как обучение было на статьях, то она не должна потерять навык генерировать очень длинный текст, сохраняя оформление:

Попытка написать стих. Всплывает проблема зацикливания:

Как концепция - всё работает.
В датасете DPO есть хорошие и отвергнутые ответы, увеличивая разрыв между ними, можно выровнять веса модели так, чтобы у неё был перевес в нужную сторону.
Тут сталкиваемся с той же проблемой обучения. Вначале у DPO loss всегда равен ~0.693, PPL 2 - cлучайное угадывание между 2 вариантами. При нормальном обучении DPO loss должен плавно снижаться, показывая, что модель учиться выбирать хорошие варианты. И вначале всё идет не плохо, loss снижается:

Но после 25% loss резко растет выше 0.69, что в целом не нормально для DPO:

Попробуем тот же способ как выше, снизим LR:
python -m scripts.chat_dpo --model-tag d16-8192 --beta 0.1 --dataset saiga_preferences --matrix-lr 0.00001 --embedding-lr 0.00001 --unembedding-lr 0.00001
Теперь нормально, loss плавал от 0.33 до 0.56, но не выходил выше 0.69:

Посмотрим, дало ли это хоть что-то.
Начало генерации в обоих случая одинаковое, перегенерирован только последний ответ:

Так как SFT версия получилась не достаточно хорошей, сложно сказать это случайность или действительно какой-то значимый эффект есть. Ответ стал лучше, DPO версия хотя бы сгенерировала код, а не впала в рассуждения, но код не того качества, как ожидалось.
Первые попытки не стали полным провалом, но было видно, что они работают хуже, чем могли бы, и это заставляет искать другие стратегии обучения. Проверим несколько гипотез на микро модели размером d8, сравним загрузку датасета по стратегии bestfit с 100% flat, fp8 vs bf16.
У d8 размер 124M (как у базовой GPT-2 Small 124M). Для обучения 124M с учетом PLE нужно как раз 0.5B, что совпадает с размером датасета Хабра без bestfit.
Скрипт показывает, что обучение влезает в ~1 эпоху:

d8 это маленькая модель, модели такого размера почти сразу скатываются в повторы и зацикливание:

Поэтому сразу добавим штраф за повтор в размере 1.1, ответы буду глупее чем на d16, так как размер тут совсем крохотный, хотя и d16 очень маленькая.
1 вариант. Размер d8, обучение fp8, контекст 2048 с 100% загрузкой датасета:

bpb 0.61:

2 вариант. Размер d8, обучение fp8, контекст 8192 с bestfit:

bpb 0.58:


3 вариант. Размер d8, обучение bf16, контекст 2048 с 100%:

bpb 0.61:

4 вариант. Размер d8, обучение bf16, контекст 8192 с bestfit:

bpb 0.58:

Ответы моделей слабые, но даже так можно сделать какие-то выводы:
даже при одинаковых loss и bpb связность ответа отличается.
ответы bf16 лучше согласованы
разница между bestfit и 100% загрузкой незначительная
Обучать в fp8 сложно, когда bf16 это тоже самое, что fp32 для обучения, то переход от bf16 к fp8 снижает диапазон в 4 раза и требует различных хаков с масштабированием. Для таких маленьких моделей это приводит к тому, что модели не имеют достаточно простора, чтобы сделать обобщение быстро и переобучаются раньше, чем обобщают. Требуется определенный опыт для обучения в fp8 и в условиях ограниченного датасета это не лучший вариант.
Что касается загрузки датасета, показатели loss и bpb - это усреднение ошибки на млн токенах, они не показывают качество усвоенных данных. И потеря 37% критична на таком размере, поэтому имеет смысл переключиться на полную загрузку. Как вариант, можно попробовать разбивать статьи длиннее T=2048 по абзацам, чтобы лучше влезать в besfit.
Попробуем всё переделать. Обучение в bf16, ctx 2048, непрерывный 100% датасет.
На половине обучения bpb 0.54:

Обучение ещё не завершено, но pretrain уже ведет себя стабильнее:

Модель начала усваивать списки, значит хватает диапазона, чтобы извлекать более сложные признаки и паттерны, чем это получалось в fp8:

На 4500 шагу bpb остается 0.54, модель не переобучается и продолжает выявлять паттерны:

К этому шагу модель освоила простой перевод, чего версия на fp8 не смогла сделать:

Более сложные фразы удаются:

Обучение завершено на val bpb 0.55, loss 2.12. Пройдено 2.8B без bestfit, плоским списком с 100% загрузкой всех токенов. Переобучение минимальное, val bpb близок к лучшему значению.

pretrain усвоил порядок планет до Нептуна:

Свободный чат:

Вопрос-ответ:

Генерация простого кода:

Написать корректный json это тоже навык:

Модель не может знать как чинить кран на кухне, и что вообще такое кран на кухне, так как такой информации не было на Хабре.

Но зато смогла каким-то образом понять, что вообще такое "Кухня":

У модели нет знаний из Wiki, встречая незнакомые сущности, навык галлюцинаций выкручивается на 500%:

Но знает эмодзи, так как они встречались:

Сложность со следованием какой-то точной инструкции, например "не писать комментарии", просить перечислить точное количество чего-то, написать сложный код и тому подобное.

Более простые задания с небольшой конкретикой даются лучше:

Если pretrain мог переводить с помощью правильно составленного запроса, то SFT версия почти не умеет, так как не было или мало примеров такого сценария в SFT датасете. Вместо перевода начинает общение с текстом, при этом понимает смысл текста на английском.

Но простые примеры иногда получаются:

Как концепция - работает, но можно лучше. Если пройтись циклом дообучения:
Saiga SFT → Habr SFT → Vikhr SFT → Saiga DPO
Можно получить более интересную модель:

Модель habrGPT 0.5B научилась связно говорить, соблюдать орфографию, пунктуацию, давать ответы на вопросы, писать статьи, выполнять задания, генерировать простой код, писать что-то похожее на стихи, немного переводить, несмотря на то, что английского языка было мало.
Первая GPT-2 124M обучалась на 10B токенов, Gemma3 1B обучалась на 2T (2048B) токенов. Датасет Хабра содержит всего 508M (0.49B) узкоспециализированных токенов и всё равно что-то получилось.
Возможно, вместо 0.5B стоит называть эту модель E0.25B, хоть тут и есть 0.5B, но вместо трансформных +0.25B тут легковесные +0.25B PLE и на сколько они хороши и компенсируются качество непонятно. Возможно, честные 0.5B были бы даже лучше.
Обучение в fp8 частично провалилось, частично удалось. Обучить такую маленькую модель в fp8 можно и какой-то результат достигнут, но результат хуже чем на bf16.