nanochat: свой ChatGPT от токенизатора до чата
Путеводитель по репозиторию Карпатого nanochat: что это, как устроен код, как один параметр --depth задаёт всю модель, полный перевод README и пошаговый запуск на машине ученика — 4× RTX 4080 без NVLink — с оценками времени для d12–d24.
Что это и для кого
Заголовок раздела «Что это и для кого»nanochat — репозиторий Андрея Карпатого (первая версия — октябрь 2025 года, лицензия MIT), где весь путь «своего ChatGPT» уложен в минимальный читаемый код: обучение токенизаторатокенизаторEN tokenizer → претрейнпредобучение, претрейнEN pretraining → SFTобучение с учителем на инструкциях (SFT)EN supervised fine-tuning (SFT) → необязательный RLобучение с подкреплениемEN reinforcement learning (RL) → оценка → разговор с моделью в терминале. Название — от его же nanoGPT, который покрывал только претрейн. Подзаголовок для цитирования: «The best ChatGPT that $100 can buy» — «лучший ChatGPT, какой можно купить за 100 долларов».
Главная идея — одна ручка сложности: --depth, число слоёв трансформера. Всё остальное — ширина, число голов,
learning rate, длительность обучения, weight decay — вычисляется из глубины по законам масштабирования. Модель
уровня GPT-2 по метрике CORE получается примерно при --depth=24…26 и на узле из 8× H100 обучается меньше чем за
2 часа.
Для кого: для ученика, прошедшего ступени 4–5 курса. Это «взрослая» версия того, что вы делали в уроках 4.6 и 5.6, и хорошая основа для выпускного проекта.
Как читать вместе с курсом
Заголовок раздела «Как читать вместе с курсом»- Сначала — урок 5.6.1 «MiniMind — LLM за 2 часа». Это китайский проект с той же идеей, и многие решения там объяснены по-русски. nanochat — англоязычный «старший брат».
- Перед запуском — уроки о распределённом обучении, Muon (nanochat обучает им матрицы) и практике на нескольких GPU, а также чек-лист инструментов из урока 4.6.5.
- Для разминки — лекции Карпатого, на которых nanochat вырос: токенизатор и воспроизведение GPT-2.
- Как проект — практикум претрейна и выпускной проект: nanochat годится и как база, и как эталон для сравнения.
Конвейер
Заголовок раздела «Конвейер»flowchart LR D["nanochat.dataset: шарды ClimbMix (parquet)"] --> T["scripts.tok_train: BPE на 32768 токенов"] T --> B["scripts.base_train: претрейн, --depth"] B --> E1["scripts.base_eval: CORE, bits per byte, примеры"] B --> S["scripts.chat_sft: SmolTalk + MMLU + GSM8K"] S --> E2["scripts.chat_eval: ARC, MMLU, GSM8K, HumanEval"] S --> R["scripts.chat_rl: RL на GSM8K (по желанию)"] S --> C["scripts.chat_cli: разговор в терминале"] R --> C
Карта репозитория
Заголовок раздела «Карта репозитория»| Файл | Что внутри | Урок курса | Важность |
|---|---|---|---|
runs/speedrun.sh | эталонный запуск: данные → токенизатор → претрейн d24 → SFT → оценка | 5.6, 10.1 | ★★★ начать отсюда |
runs/runcpu.sh | крошечный прогон на CPU или Mac (d6, контекст 512) | 5.6.2 | ★★ проверка, что всё работает |
runs/scaling_laws.sh, runs/miniseries.sh | эксперименты по законам масштабирования, серия моделей | 5.1.1 | ★ для исследования |
nanochat/gpt.py | модель GPT | 4.6, 5.2 | ★★★ |
nanochat/optim.py | Muon для матриц + AdamW для остального, в том числе распределённый вариант | 5.4.4 | ★★ |
nanochat/flash_attention.py | FlashAttention 3 с запасным путём через PyTorch SDPA | 5.7.2 | ★★ |
nanochat/fp8.py | обучение в FP8 (≈150 строк вместо torchao) | 5.4.1 | ★ не для RTX 4080 |
nanochat/tokenizer.py, scripts/tok_train.py | BPE: обучение на Rust (rustbpe), применение через tiktoken | 4.2.2 | ★★ |
nanochat/dataset.py, nanochat/dataloader.py | скачивание шардов, токенизирующий распределённый загрузчик | 5.1.2 | ★★ |
nanochat/loss_eval.py, nanochat/core_eval.py | метрики bits per byte и CORE | 7.3.1 | ★★ |
nanochat/engine.py, nanochat/execution.py | генерация с KV-кэшем, вызов Python как инструмента | 4.6.4 | ★★ |
scripts/chat_sft.py, tasks/* | SFT на смеси задач | 6.1.2 | ★★ |
scripts/chat_rl.py | упрощённый «GRPO» на GSM8K | 6.4.1 | ★ |
dev/LEADERBOARD.md, dev/LOG.md | правила рейтинга и журнал экспериментов | 10.1 | ★ полезно читать |
Ключевые идеи кода
Заголовок раздела «Ключевые идеи кода»Модель: что изменилось со времён GPT-2
Заголовок раздела «Модель: что изменилось со времён GPT-2»В шапке gpt.py Карпатый перечисляет отличия от классической GPT-2 (сравните с лекцией 9):
- позиционные эмбеддинги заменены вращательными (RoPEротационные позиционные эмбеддинги (RoPE)EN rotary position embedding (RoPE)RoPE предложен в Китае (Су Цзяньлинь и соавторы, компания Zhuiyi). Сейчас используется почти во всех LLM.);
- QK-norm: запросы и ключи нормализуются перед вниманием (QK-normQK-нормализация (QK-Norm)EN QK-NormНормализация (обычно RMSNorm) запросов и ключей перед скалярным произведением. Ограничивает рост attention-логитов; используется, например, в Qwen3.);
- веса эмбеддингов и
lm_headне связаны (в GPT-2 были связаны); - в MLP активация ReLU² (
F.relu(x).square()) вместо GELU; - RMSNormRMSNorm (нормализация по среднеквадратичному)EN root mean square layer normalization (RMSNorm)x / sqrt(mean(x²) + ε) · g — LayerNorm без вычитания среднего и без сдвига β (Zhang, Sennrich, 2019). Стандарт Llama, Qwen, DeepSeek, MiniMind. без обучаемых параметров, нормализация сразу после эмбеддингов, нет смещений в линейных слоях;
- поддержка GQAвнимание с группами запросов (GQA)EN grouped-query attention (GQA)Головы запросов делятся на G групп, у каждой группы своя пара K/V. G = H — это MHA, G = 1 — MQA. Стандарт Llama 3 и Qwen (Ainslie и др., 2023). (в
base_train.pyчисло KV-голов пока равно числу голов); - чередование окон внимания по шаблону
--window-pattern(по умолчаниюSSSL: три слоя с коротким окном в четверть контекста, один — с полным; последний слой всегда полный).
Плюс несколько приёмов из modded-nanogpt Келлера Джордана: «value embeddings» в чередующихся слоях, обучаемые скаляры на остаточном потоке, нулевая инициализация выходных проекций блоков. Словарь (32 768 токенов) дополняется до кратного 64 «ради эффективности» — тот же приём, что 50304 в лекции о GPT-2.
Одна ручка: —depth
Заголовок раздела «Одна ручка: —depth»Посмотрим, как scripts/base_train.py превращает глубину в целую конфигурацию:
- Ширина:
model_dim = depth × aspect_ratio(по умолчанию 64), округлённая вверх до кратного размеру головы (128). Число голов —model_dim / 128. - Сколько токенов учить: число «масштабных» параметров (матрицы трансформера +
lm_head) умножается на--target-param-data-ratio(в нашей версии по умолчанию 12). - Размер батча растёт с объёмом данных как (по статье Power Lines) относительно эталона d12 с батчем токенов и округляется до степени двойки.
- Learning rate умножается на , weight decay пересчитывается так, чтобы выдержать постоянным «время эпохи» из соответствующей статьи. Карпатый честно пишет в комментариях, что эти правила выведены для AdamW и для Muon приняты на веру.
- Накопление градиентов считается само:
total_batch_size / (device_batch_size × max_seq_len × число GPU). Поэтому--device-batch-size— чисто «скоростная» настройка, как и в лекции о GPT-2.
Мы посчитали, что получается для разных глубин (модель строится на «мета-устройстве», без памяти, тем же кодом, что
в base_train.py, при длине контекста 2048 и словаре 32 768):
--depth | ширина | головы | всего параметров | токенов при ratio 12 | батч, токенов | FLOPs обучения |
|---|---|---|---|---|---|---|
| 12 | 768 | 6 | 286 млн | 1,32 млрд | 524 288 | 1,0 · 10¹⁸ |
| 16 | 1024 | 8 | 537 млн | 2,82 млрд | 524 288 | 4,5 · 10¹⁸ |
| 20 | 1280 | 10 | 897 млн | 5,22 млрд | 1 048 576 | 1,5 · 10¹⁹ |
| 24 (ratio 8, как в speedrun) | 1536 | 12 | 1384 млн | 5,84 млрд | 1 048 576 | 2,8 · 10¹⁹ |
«Всего параметров» включает таблицы эмбеддингов, в том числе value embeddings, поэтому выглядит крупнее, чем
«масштабные» параметры (у d12 их 110 млн). FLOPs оценены функцией estimate_flops() из gpt.py — это только
обучение, без оценок по ходу.
Метрики: bits per byte и CORE
Заголовок раздела «Метрики: bits per byte и CORE»- Bits per byte (bpb) — loss на валидации, делённый не на число токенов, а на число байтов текста, который эти токены представляют. Так сравнение не зависит от размера словаря: можно менять токенизатор и честно сравнивать модели (bits per byteбит на байт (BPB)EN bits per byte (BPB)Loss, пересчитанный на байт исходного текста; в отличие от perplexity, его можно сравнивать между моделями с разными токенизаторами.).
- CORE — сводная метрика из статьи DCLM: 22 задачи вроде ARC и MMLU. У GPT-2 (1,6 млрд параметров) — 0,256525. «Время до GPT-2» — главная метрика рейтинга nanochat.
SFT, инструменты и RL
Заголовок раздела «SFT, инструменты и RL»После претрейна модель учат формату диалога спецтокенами <|user_start|>, <|assistant_start|> и другими, а также
вызову Python: <|python_start|> … <|python_end|>, результат возвращается между <|output_start|> и
<|output_end|>. Смесь для SFT: SmolTalk (около 460 тысяч диалогов), 3 эпохи вспомогательной части MMLU (учит
выбирать вариант ответа) и 4 эпохи GSM8K (учит математике и вызову инструмента). chat_rl.py — RL на GSM8K. Карпатый
берёт «GRPO» в кавычки: без KL-штрафа к эталонной модели, без отношения вероятностей и клиппинга PPO (обучение
строго on-policy), с нормировкой по токенам в стиле DAPO и преимуществом без деления на стандартное
отклонение — то есть почти REINFORCE. Сравните с уроками 6.4.1 и
6.4.3.
Перевод README
Заголовок раздела «Перевод README»Полный перевод README репозитория версии 92d63d4. Ссылки ведут на оригинал. Наши пояснения выделены курсивом.
nanochat
Заголовок раздела «nanochat»(В оригинале здесь две картинки: логотип nanochat и график законов масштабирования за январь 2026 года, файлы
dev/nanochat.png и dev/scaling_laws_jan26.png.)
nanochat — простейшая экспериментальная обвязка для обучения LLM. Она рассчитана на один узел с GPU, код минимален и
легко меняется, и он покрывает все основные этапы LLM: токенизацию, претрейн, дообучение, оценку и инференс.
Например, вы можете обучить собственную LLM уровня GPT-2 (обучение которой в 2019 году стоило около 43 000 долларов)
всего за 48 долларов (около 2 часов на узле с 8× H100), а потом поговорить с ней через простой CLI. На
спотовом инстансе общая стоимость может быть ближе к 15 долларам. В более общем виде nanochat из коробки настроен на
обучение целой мини-серии моделей, оптимальных по вычислениям, — достаточно повернуть одну ручку сложности:
--depth, число слоёв трансформера GPT (уровень GPT-2 получается примерно при глубине 26). Все остальные
гиперпараметры (ширина трансформера, число голов, поправки learning rate, длительность обучения, weight decay…)
вычисляются автоматически оптимальным образом.
С вопросами о репозитории я советую идти в DeepWiki от Devin/Cognition, на вкладку Discussions или в канал #nanochat в Discord.
Рейтинг «время до GPT-2»
Заголовок раздела «Рейтинг «время до GPT-2»»Сейчас основное внимание разработки — настройка претрейна, этапа, который требует больше всего вычислений. По примеру репозитория modded-nanogpt и чтобы поощрять прогресс и совместную работу, nanochat ведёт рейтинг «спидрана GPT-2»: это время по настенным часам, за которое модель nanochat обучается до уровня GPT-2 по метрике DCLM CORE. Скрипт runs/speedrun.sh всегда отражает эталонный способ обучить модель уровня GPT-2 и поговорить с ней. Текущий рейтинг:
| № | время, ч | val_bpb | CORE | Описание | Дата | Коммит | Участники |
|---|---|---|---|---|---|---|---|
| 0 | 168 | — | 0,2565 | исходный чекпоинт GPT-2 от OpenAI | 2019 | — | OpenAI |
| 1 | 3,04 | 0,74833 | 0,2585 | базовая линия d24, слегка переобученная | 29 янв. 2026 | 348fbb3 | @karpathy |
| 2 | 2,91 | 0,74504 | 0,2578 | d26, слегка недообученная, +fp8 | 2 февр. 2026 | a67eba3 | @karpathy |
| 3 | 2,76 | 0,74645 | 0,2602 | общий батч увеличен до 1 млн токенов | 5 февр. 2026 | 2c062aa | @karpathy |
| 4 | 2,02 | 0,71854 | 0,2571 | данные заменены на NVIDIA ClimbMix | 4 мар. 2026 | 324e69c | @ddudek @karpathy |
| 5 | 1,80 | 0,71808 | 0,2690 | autoresearch, раунд 1 | 9 мар. 2026 | 6ed7d1d | @karpathy |
| 6 | 1,65 | 0,71800 | 0,2626 | autoresearch, раунд 2 | 14 мар. 2026 | a825e63 | @karpathy |
(«Слегка переобученная» здесь значит «обучалась на большем числе токенов, чем оптимально по вычислениям», а не переобучение на обучающих данных.)
Главная метрика, которая нас интересует, — «время до GPT-2»: время по настенным часам, за которое модель обгоняет метрику CORE у GPT-2 (1,6B) на узле с 8× H100. CORE у GPT-2 — 0,256525. В 2019 году обучение GPT-2 стоило примерно 43 000 долларов, так что невероятно, что благодаря множеству улучшений во всём стеке за 7 лет мы можем сделать это настолько быстрее и гораздо дешевле 100 долларов (например, при нынешних ≈3 долларах за GPU в час узел 8× H100 стоит ≈24 доллара в час, так что 2 часа — это ≈48 долларов).
Подробнее о том, как читать рейтинг и как в него попасть, — в dev/LEADERBOARD.md.
Начало работы
Заголовок раздела «Начало работы»Установка
Заголовок раздела «Установка»nanochat использует для зависимостей uv. Установка:
uv sync --extra gpu # для CUDA (A100/H100 и т. д.)uv sync --extra cpu # (или) только CPU / MPSsource .venv/bin/activateДля разработки (добавляет pytest, matplotlib, ipykernel, transformers и т. д.):
uv sync --extra gpu --group devВоспроизвести GPT-2 и поговорить с ней
Заголовок раздела «Воспроизвести GPT-2 и поговорить с ней»Самое весёлое, что можно сделать, — обучить собственную GPT-2 и поговорить с ней. Весь конвейер для этого лежит в одном файле runs/speedrun.sh, рассчитанном на узел с 8× H100. Запустите новую машину 8× H100 у любимого провайдера (например, я пользуюсь Lambda, и она мне нравится) и запустите скрипт обучения:
bash runs/speedrun.shВозможно, стоит запускать его в сессии screen: это займёт около 1,5 часа. Когда всё закончится, с моделью можно
поговорить через CLI. Ещё раз убедитесь, что локальное виртуальное окружение uv активно (source .venv/bin/activate),
и общайтесь:
python -m scripts.chat_cliПопросите её написать рассказ или стихотворение. Попросите сказать, кто вы, — увидите галлюцинацию. Спросите, почему небо голубое. Или почему оно зелёное. Модель спидрана — это модель на 4e19 FLOPs, так что разговор с ней немного похож на разговор с ребёнком из детского сада :). Пример разговора с моделью спидрана:
Пользователь: Hello whats up?
Ассистент: Hello! How can I help you today?
Пользователь: Why is the sky blue?
Ассистент: The sky is blue due to an optical illusion called the Rayleigh Scattering of Light, which occurs when sunlight passes through tiny particles in the air. These particles are made up of tiny blue and violet particles that cause the light to bend in a particular way.
Пользователь: That’s cool. Write a poem about it.
Ассистент: In the twilight’s glow, the sky is blue,
A canvas of shimmering blue, where skies unfold,
A world of blue, where blue and more blue,
Are woven together in a tapestry so fine,
Where every hue seems to sing a story.
…
(Диалог оставлен на английском, как в оригинале: модель обучена на английских данных. В переводе: «Привет, как дела?» — «Привет! Чем могу помочь?» — «Почему небо голубое?» — «Небо голубое из-за оптической иллюзии под названием рэлеевское рассеяние света, которое происходит, когда солнечный свет проходит через крошечные частицы в воздухе. Эти частицы состоят из крошечных синих и фиолетовых частиц…» — физически это неверно, типичная уверенная галлюцинация маленькой модели. Затем стихотворение, в котором слово «blue» повторяется снова и снова.)
Ещё несколько замечаний:
- Код прекрасно работает и на узле Ampere 8× A100, только немного медленнее.
- Весь код прекрасно работает даже на одной GPU, если убрать
torchrun, и даёт почти идентичные результаты (код автоматически переключится на накопление градиентов), но ждать придётся в 8 раз дольше. - Если у ваших GPU меньше 80 ГБ памяти, придётся подстроить некоторые гиперпараметры, иначе возникнет OOM — нехватка
видеопамяти. Найдите в скриптах
--device-batch-sizeи уменьшайте его, пока всё не поместится: например, с 32 (по умолчанию) до 16, 8, 4, 2 или даже 1. Чтобы спуститься ниже, нужно лучше понимать, что вы делаете, и проявить изобретательность. - Большая часть кода — довольно обычный PyTorch, так что он должен работать на всём, что поддерживает PyTorch, — xpu, mps и т. д., но я лично не проверял все эти пути, так что возможны острые углы.
Исследования
Заголовок раздела «Исследования»Если вы исследователь и хотите помочь улучшить nanochat, вам будут интересны два скрипта: runs/scaling_laws.sh и runs/miniseries.sh. Документацию по ним см. в обсуждении Jan 7 miniseries v1. Для быстрых экспериментов (претрейн около 5 минут) мой любимый масштаб — 12-слойная модель (размера GPT-1), например так:
OMP_NUM_THREADS=1 torchrun --standalone --nproc_per_node=8 -m scripts.base_train -- \ --depth=12 \ --run="d12" \ --model-tag="d12" \ --core-metric-every=999999 \ --sample-every=-1 \ --save-every=-1 \Здесь используется wandb (имя прогона «d12»), метрика CORE считается только на последнем шаге, а промежуточные примеры и чекпоинты не создаются. Мне нравится менять что-нибудь в коде, перезапускать d12 (или d16 и т. д.) и смотреть, помогло ли, — в цикле. Чтобы понять, помог ли прогон, я слежу за графиками wandb:
val_bpb(loss на валидации в единицах bits per byte, не зависящих от размера словаря) как функция отstep,total_training_timeиtotal_training_flops.core_metric(метрика DCLM CORE).- Загрузка видеопамяти,
train/mfu(Model FLOPS utilization — доля пиковой производительности, которую реально использует модель),train/tok_per_sec(пропускная способность обучения).
Пример можно посмотреть здесь.
Важно понимать: nanochat написан и настроен вокруг одной ручки сложности — глубины трансформера. Это одно целое число
автоматически определяет все остальные гиперпараметры (ширину трансформера, число голов, поправки learning rate,
длительность обучения, weight decay…), так что обученная модель получается оптимальной по вычислениям. Идея в том,
что пользователю не нужно ни думать обо всём этом, ни настраивать: он просто просит модель поменьше или побольше
через --depth, и всё «просто работает». Перебирая глубину, вы получаете мини-серию nanochat — модели разных размеров,
оптимальные по вычислениям. Модель уровня GPT-2 (больше всего интересующая сейчас) с текущим кодом получается где-то
в диапазоне d24–d26. Но любые предлагаемые изменения репозитория должны быть достаточно принципиальными, чтобы
работать при любой глубине.
Запуск на CPU / MPS
Заголовок раздела «Запуск на CPU / MPS»Скрипт runs/runcpu.sh показывает очень простой пример запуска на CPU или Apple Silicon. Он резко уменьшает обучаемую LLM, чтобы обучение уложилось в разумное время — несколько десятков минут. Сильных результатов так не получить.
Точность / dtype
Заголовок раздела «Точность / dtype»nanochat не использует torch.amp.autocast. Вместо этого точность управляется явно одной глобальной переменной
COMPUTE_DTYPE (определена в nanochat/common.py). По умолчанию она выбирается автоматически по вашему железу:
| Железо | dtype по умолчанию | Почему |
|---|---|---|
| CUDA SM 80+ (A100, H100, …) | bfloat16 | родные тензорные ядра bf16 |
| CUDA SM ниже 80 (V100, T4, …) | float32 | bf16 нет; fp16 доступен через NANOCHAT_DTYPE=float16 (использует GradScaler) |
| CPU / MPS | float32 | безопасный вариант по умолчанию. На свежих macOS MPS нормально работает и с NANOCHAT_DTYPE=bfloat16 (≈на 25% меньше памяти при той же скорости) |
(RTX 4080 — это SM 8.9, то есть «SM 80+»: по умолчанию будет bfloat16.)
Значение по умолчанию можно переопределить переменной окружения NANOCHAT_DTYPE:
NANOCHAT_DTYPE=float32 python -m scripts.chat_cli -p "hello" # принудительно fp32NANOCHAT_DTYPE=bfloat16 torchrun --nproc_per_node=8 -m scripts.base_train # принудительно bf16Как это работает: веса модели хранятся в fp32 (ради точности оптимизатора), но наш собственный слой Linear
приводит их к COMPUTE_DTYPE во время прямого прохода. Эмбеддинги хранятся сразу в COMPUTE_DTYPE, чтобы сэкономить
память. Это даёт ту же выгоду смешанной точности, что и autocast, но с полным явным контролем над тем, что в какой
точности выполняется.
Примечание: обучение в float16 автоматически включает GradScaler в base_train.py, чтобы градиенты не
обнулялись из-за переполнения снизу (underflow). SFT это тоже поддерживает, а RL пока нет. Инференс в fp16 везде
работает нормально.
Руководства
Заголовок раздела «Руководства»Я опубликовал несколько руководств, в которых может найтись полезное, от новых к старым:
- 1 февраля 2026: Обгоняем GPT-2 гораздо дешевле 100 долларов — путь nanochat
- Jan 7 miniseries v1 — описание первой мини-серии моделей nanochat.
- Как добавлять nanochat новые способности, см. руководство: считаем буквы r в strawberry (и как добавлять способности вообще).
- 13 октября 2025: исходный пост о nanochat, где nanochat был представлен. Часть информации там устарела, а модель намного старше (и с худшими результатами), чем в текущей ветке master.
Структура файлов
Заголовок раздела «Структура файлов».├── LICENSE├── README.md├── dev│ ├── nanochat.png│ └── repackage_data_reference.py # Генерация шардов данных для претрейна├── nanochat│ ├── __init__.py # пустой│ ├── checkpoint_manager.py # Сохранение/загрузка чекпоинтов модели│ ├── common.py # Мелкие утилиты, удобства│ ├── core_eval.py # Оценка базовой модели по метрике CORE (статья DCLM)│ ├── dataloader.py # Токенизирующий распределённый загрузчик данных│ ├── dataset.py # Скачивание/чтение данных для претрейна│ ├── engine.py # Эффективный инференс модели с KV-кэшем│ ├── execution.py # Позволяет LLM выполнять код Python как инструмент│ ├── gpt.py # Трансформер GPT как nn.Module│ ├── loss_eval.py # Оценка в bits per byte (вместо loss)│ ├── optim.py # Оптимизатор AdamW + Muon, для 1 GPU и распределённый│ └── tokenizer.py # Обёртка BPE-токенизатора в стиле GPT-4├── pyproject.toml├── runs│ ├── miniseries.sh # Обучение мини-серии│ ├── runcpu.sh # Маленький пример запуска на CPU/MPS│ ├── scaling_laws.sh # Эксперименты по законам масштабирования│ └── speedrun.sh # Обучение nanochat d20 «за ~100 долларов»├── scripts│ ├── base_eval.py # Базовая модель: CORE, bits per byte, примеры│ ├── base_train.py # Базовая модель: обучение│ ├── chat_cli.py # Чат-модель: разговор через CLI│ ├── chat_eval.py # Чат-модель: задачи для оценки│ ├── chat_rl.py # Чат-модель: обучение с подкреплением│ ├── chat_sft.py # Чат-модель: SFT│ ├── infer_bench.py # Инференс: замеры задержки/пропускной способности/VRAM│ ├── tok_eval.py # Токенизатор: оценка степени сжатия│ └── tok_train.py # Токенизатор: обучение├── tasks│ ├── arc.py # Научные вопросы с вариантами ответа│ ├── common.py # TaskMixture | TaskSequence│ ├── gsm8k.py # 8 тысяч школьных задач по математике│ ├── humaneval.py # Название неточное; простая задача на код Python│ ├── mmlu.py # Вопросы с вариантами ответа по широкому кругу тем│ └── smoltalk.py # Сборный набор данных SmolTalk от HF├── tests│ ├── test_attention_fallback.py # Запасной путь внимания FA3/SDPA│ ├── test_engine.py # Движок инференса, KV-кэш│ ├── test_execution.py # Выполнение кода в песочнице│ ├── test_optim.py # Оптимизатор MuonAdamW (нужна GPU)│ ├── test_tasks.py # Нарезка задач, смеси, HubDataset│ └── test_tokenizer.py # Проверки BPE туда-обратно, отрисовка чата└── uv.lock(Комментарий у speedrun.sh в оригинале устарел: сейчас скрипт обучает d24, а не d20. Файлы fp8.py и
flash_attention.py в схеме не указаны, хотя в репозитории есть.)
Как участвовать
Заголовок раздела «Как участвовать»Цель nanochat — продвинуть передний край микромоделей, с которыми можно работать от начала до конца при бюджете меньше 1000 долларов. Доступность — это общая стоимость, но и когнитивная сложность: nanochat — не исчерпывающе настраиваемый LLM-«фреймворк»; в коде нет гигантских объектов конфигурации, фабрик моделей или монстров из if-then- else. Это единая, связная, минимальная, читаемая, легко изменяемая, максимально пригодная для форков «сильная базовая линия», которая проходит путь от начала до конца и выдаёт модель ChatGPT, с которой можно поговорить. Сейчас мне лично интереснее всего сокращать время до уровня GPT-2 (то есть до метрики CORE выше 0,256525). Сейчас это занимает около 1,5 часа (было 3), но, улучшая этап претрейна, его можно сократить ещё.
Текущая политика по ИИ: раскрытие. Отправляя PR, укажите, какие части написаны с существенным участием LLM и какие вы не писали сами или не до конца понимаете.
Благодарности
Заголовок раздела «Благодарности»- Название (nanochat) происходит от моего более раннего проекта nanoGPT, который покрывал только претрейн.
- nanochat также вдохновлён modded-nanoGPT, который превратил nanoGPT в игру с понятными метриками и рейтингом, и заимствует оттуда многие идеи и часть реализации претрейна.
- Спасибо HuggingFace за fineweb и smoltalk.
- Спасибо Lambda за вычисления для разработки проекта.
- Спасибо главному «заклинателю LLM» 🧙♂️ Алеку Рэдфорду за советы и наставления.
- Спасибо «царице репозитория» Софи @svlandeg за помощь с issues, pull requests и обсуждениями nanochat.
Как цитировать
Заголовок раздела «Как цитировать»Если nanochat пригодился вам в исследовании, цитируйте просто так:
@misc{nanochat, author = {Andrej Karpathy}, title = {nanochat: The best ChatGPT that \$100 can buy}, year = {2025}, publisher = {GitHub}, url = {https://github.com/karpathy/nanochat}}Лицензия
Заголовок раздела «Лицензия»MIT
🖥 На вашей машине: 4× RTX 4080, без NVLink
Заголовок раздела «🖥 На вашей машине: 4× RTX 4080, без NVLink»Дальше — наши рекомендации, а не текст README. Главное: d24 «уровня GPT-2» на вашей машине — это дни, а не полтора часа. Начинайте с d12, потом d16 или d20.
Сколько это займёт
Заголовок раздела «Сколько это займёт»Время обучения ≈ FLOPs / (число GPU × реальная производительность одной GPU). Пиковая BF16-производительность RTX 4080 по спецификации NVIDIA — порядка 100 TFLOPS на плотных матрицах, а реально модель использует обычно 30–50% пика (это и есть MFU). Возьмём 30–50 TFLOPS на карту:
| Модель | FLOPs | 4 GPU по 30 TFLOPS | 4 GPU по 50 TFLOPS |
|---|---|---|---|
| d12 | 1,0 · 10¹⁸ | ≈2,3 ч | ≈1,4 ч |
| d16 | 4,5 · 10¹⁸ | ≈10 ч | ≈6 ч |
| d20 | 1,5 · 10¹⁹ | ≈35 ч | ≈21 ч |
| d24 (ratio 8) | 2,8 · 10¹⁹ | ≈65 ч | ≈39 ч |
Это оценка снизу: сюда не входят оценки CORE и bpb по ходу обучения, загрузка данных и SFT. Точное число вы узнаете
по tok/sec в логе за первые минуты: время ≈ (токенов из лога «Total number of training tokens») / (tok/sec).
Пошагово
Заголовок раздела «Пошагово»-
Окружение. Нужны свежий драйвер NVIDIA с поддержкой CUDA 12.8 (проверьте
nvidia-smi), git и интернет.Окно терминала git clone https://github.com/karpathy/nanochat.git && cd nanochatcommand -v uv || curl -LsSf https://astral.sh/uv/install.sh | sh # менеджер пакетов uvuv venv && uv sync --extra gpu # PyTorch 2.9.1 под CUDA 12.8 и остальные зависимостиsource .venv/bin/activateexport OMP_NUM_THREADS=1export NANOCHAT_BASE_DIR="$HOME/.cache/nanochat" # здесь будут данные, токенизатор и чекпоинты -
Проверка, что всё живо. Перед многочасовым запуском прогоните маленький вариант на одной карте, по образцу
runs/runcpu.sh, но на CUDA:--depth=6 --max-seq-len=512 --window-pattern=L --num-iterations=200. Если он прошёл и loss падает, окружение в порядке. -
Данные и токенизатор. Один шард — около 100 МБ сжатого текста (≈250 млн символов). Из комментариев
speedrun.sh(≈150 шардов на ≈5,8 млрд токенов для d24) выходит ≈40 млн токенов на шард. Значит, для d12 хватит ≈40–50 шардов, для d16 — ≈80, для d20 — ≈150 (с запасом):Окно терминала python -m nanochat.dataset -n 8 # первые ≈2 млрд символов — для обучения токенизатораpython -m nanochat.dataset -n 50 & # остальное докачивается в фоне (для d12)python -m scripts.tok_train # BPE на 32768 токеновpython -m scripts.tok_eval # степень сжатияwaitДанные скачиваются с Hugging Face (
karpathy/climbmix-400b-shuffle); адрес задан вnanochat/dataset.py. -
Претрейн d12 на 4 картах.
--device-batch-sizeподберите под 32 ГБ: начните с 16 и уменьшайте до 8 при OOM. Накопление градиентов посчитается само: для d12 при 16 это 524 288 / (16 × 2048 × 4) = 4 микрошага.Окно терминала torchrun --standalone --nproc_per_node=4 -m scripts.base_train -- \--depth=12 --device-batch-size=16 --run=dummytorchrun --standalone --nproc_per_node=4 -m scripts.base_eval -- --device-batch-size=16Флаг
--fp8не используйте: в справке скрипта он помечен «requires H100+ GPU». Без него обучение идёт в bf16. -
Проверьте внимание. В начале лога
base_trainпишет либо «Using Flash Attention 3», либо предупреждение о переходе на PyTorch SDPA. Комментарий вflash_attention.pyговорит, что ядра FA3 собраны в том числе для Ada (sm89), то есть для RTX 4080, — но если увидите предупреждение, перезапустите с--window-pattern L: SDPA не поддерживает скользящие окна, и без этого флага загрузка GPU будет, по словам автора, «ужасной». -
SFT, оценка, разговор.
Окно терминала torchrun --standalone --nproc_per_node=4 -m scripts.chat_sft -- --run=dummy # размер батча возьмёт из претрейнаtorchrun --standalone --nproc_per_node=4 -m scripts.chat_eval -- -i sftpython -m scripts.chat_cli -p "Why is the sky blue?"По желанию — RL на GSM8K:
torchrun --standalone --nproc_per_node=4 -m scripts.chat_rl, затемpython -m scripts.chat_cli -i rl. -
Дальше — больше. Повторите с
--depth=16или--depth=20(для d20 начните с--device-batch-size=8: батч там 1 048 576 токенов, это 16 микрошагов). Сравнитеval_bpbи CORE — это ваша собственная мини-серия.
Связь без NVLink
Заголовок раздела «Связь без NVLink»Карты общаются через PCIe 4.0 и память процессора, а не напрямую. Для nanochat это не страшно: градиенты синхронизируются один раз за шаг оптимизатора, а при накоплении градиентов шаг длится секунды (для d20 — порядка десятков секунд), и обмен занимает малую долю этого времени. Две практические вещи:
- у GeForce прямой обмен P2P обычно запрещён драйвером — проверьте
nvidia-smi topo -p2p r(подробно в уроке 5.7.4); - если запуск на нескольких картах зависает на первом шаге, попробуйте
NCCL_P2P_DISABLE=1 torchrun …, как в уроке 5.4.3.
Что ждать от результата
Заголовок раздела «Что ждать от результата»d12 — модель размера GPT-1. Она связно пишет по-английски, но знает мало и галлюцинирует охотно — как модель
спидрана из README, только слабее. Обучена она на английских данных, поэтому по-русски почти не говорит. Для
русскоязычного проекта нужно менять данные (и переобучать токенизатор). Это хорошая тема для
выпускного проекта: сравните, сколько токенов на символ даёт
токенизатор nanochat на русском до и после переобучения (scripts/tok_eval.py).
Что читать дальше
Заголовок раздела «Что читать дальше»- Обсуждение «Beating GPT-2 for <<$100: the nanochat journey» — история оптимизаций за январь 2026 года.
- Руководство о буквах r в strawberry — как добавлять модели новые умения через данные SFT. Хорошее упражнение после ступени 6.
dev/LOG.md— журнал экспериментов: что пробовали, что помогло, что нет. Отличный образец журнала экспериментовжурнал экспериментовEN experiment logЗапись каждого запуска — дата, коммит, конфиг, гипотеза, результат, вывод. Из него потом пишется отчёт. для вашего проекта.- modded-nanogpt — откуда пришли Muon и многие приёмы претрейна.
- В курсе: MiniMind — тот же путь на китайском проекте, и microgpt — тот же алгоритм в 200 строках чистого Python.
Словарик
Заголовок раздела «Словарик»| EN | RU |
|---|---|
| harness | обвязка, «стенд» для экспериментов |
| speedrun, leaderboard | спидран (гонка на время), рейтинг |
| depth, aspect ratio | глубина (число слоёв), отношение ширины к глубине |
| compute-optimal, overtrained, undertrained | оптимальная по вычислениям, обученная дольше / меньше оптимума |
| tokens:params ratio | отношение числа токенов к числу параметров |
| bits per byte (bpb) | биты на байт — loss, не зависящий от словаря |
| CORE metric | сводная метрика CORE (статья DCLM) |
| MFU (model FLOPS utilization) | доля пиковой производительности, которую использует модель |
| device batch size, gradient accumulation | микробатч на одну GPU, накопление градиентов |
| sliding window attention | внимание со скользящим окном |
| value embeddings | эмбеддинги значений (приём из modded-nanogpt) |
| spot instance | спотовый (прерываемый) облачный сервер |
| OOM (out of memory) | нехватка видеопамяти |