Перейти к содержимому
ПереводыАндрей Карпатый

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, и хорошая основа для выпускного проекта.

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
Весь путь nanochat; скрипт runs/speedrun.sh запускает его целиком (кроме RL)
ФайлЧто внутриУрок курсаВажность
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модель GPT4.6, 5.2★★★
nanochat/optim.pyMuon для матриц + AdamW для остального, в том числе распределённый вариант5.4.4★★
nanochat/flash_attention.pyFlashAttention 3 с запасным путём через PyTorch SDPA5.7.2★★
nanochat/fp8.pyобучение в FP8 (≈150 строк вместо torchao)5.4.1★ не для RTX 4080
nanochat/tokenizer.py, scripts/tok_train.pyBPE: обучение на Rust (rustbpe), применение через tiktoken4.2.2★★
nanochat/dataset.py, nanochat/dataloader.pyскачивание шардов, токенизирующий распределённый загрузчик5.1.2★★
nanochat/loss_eval.py, nanochat/core_eval.pyметрики bits per byte и CORE7.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» на GSM8K6.4.1★
dev/LEADERBOARD.md, dev/LOG.mdправила рейтинга и журнал экспериментов10.1★ полезно читать

В шапке 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.

Посмотрим, как scripts/base_train.py превращает глубину в целую конфигурацию:

  1. Ширина: model_dim = depth × aspect_ratio (по умолчанию 64), округлённая вверх до кратного размеру головы (128). Число голов — model_dim / 128.
  2. Сколько токенов учить: число «масштабных» параметров (матрицы трансформера + lm_head) умножается на --target-param-data-ratio (в нашей версии по умолчанию 12).
  3. Размер батча растёт с объёмом данных как D0,383D^{0{,}383} (по статье Power Lines) относительно эталона d12 с батчем 2192^{19} токенов и округляется до степени двойки.
  4. Learning rate умножается на B/Bref\sqrt{B / B_\text{ref}}, weight decay пересчитывается так, чтобы выдержать постоянным «время эпохи» из соответствующей статьи. Карпатый честно пишет в комментариях, что эти правила выведены для AdamW и для Muon приняты на веру.
  5. Накопление градиентов считается само: 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 обучения
127686286 млн1,32 млрд524 2881,0 · 10¹⁸
1610248537 млн2,82 млрд524 2884,5 · 10¹⁸
20128010897 млн5,22 млрд1 048 5761,5 · 10¹⁹
24 (ratio 8, как в speedrun)1536121384 млн5,84 млрд1 048 5762,8 · 10¹⁹

«Всего параметров» включает таблицы эмбеддингов, в том числе value embeddings, поэтому выглядит крупнее, чем «масштабные» параметры (у d12 их 110 млн). FLOPs оценены функцией estimate_flops() из gpt.py — это только обучение, без оценок по ходу.

  • 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.

После претрейна модель учат формату диалога спецтокенами <|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 и преимуществом r−μr - \mu без деления на стандартное отклонение — то есть почти REINFORCE. Сравните с уроками 6.4.1 и 6.4.3.


Полный перевод README репозитория версии 92d63d4. Ссылки ведут на оригинал. Наши пояснения выделены курсивом.

(В оригинале здесь две картинки: логотип 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.

Сейчас основное внимание разработки — настройка претрейна, этапа, который требует больше всего вычислений. По примеру репозитория modded-nanogpt и чтобы поощрять прогресс и совместную работу, nanochat ведёт рейтинг «спидрана GPT-2»: это время по настенным часам, за которое модель nanochat обучается до уровня GPT-2 по метрике DCLM CORE. Скрипт runs/speedrun.sh всегда отражает эталонный способ обучить модель уровня GPT-2 и поговорить с ней. Текущий рейтинг:

№время, чval_bpbCOREОписаниеДатаКоммитУчастники
0168—0,2565исходный чекпоинт GPT-2 от OpenAI2019—OpenAI
13,040,748330,2585базовая линия d24, слегка переобученная29 янв. 2026348fbb3@karpathy
22,910,745040,2578d26, слегка недообученная, +fp82 февр. 2026a67eba3@karpathy
32,760,746450,2602общий батч увеличен до 1 млн токенов5 февр. 20262c062aa@karpathy
42,020,718540,2571данные заменены на NVIDIA ClimbMix4 мар. 2026324e69c@ddudek @karpathy
51,800,718080,2690autoresearch, раунд 19 мар. 20266ed7d1d@karpathy
61,650,718000,2626autoresearch, раунд 214 мар. 2026a825e63@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 / MPS
source .venv/bin/activate

Для разработки (добавляет pytest, matplotlib, ipykernel, transformers и т. д.):

Окно терминала
uv sync --extra gpu --group dev

Самое весёлое, что можно сделать, — обучить собственную 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:

  1. val_bpb (loss на валидации в единицах bits per byte, не зависящих от размера словаря) как функция от step, total_training_time и total_training_flops.
  2. core_metric (метрика DCLM CORE).
  3. Загрузка видеопамяти, train/mfu (Model FLOPS utilization — доля пиковой производительности, которую реально использует модель), train/tok_per_sec (пропускная способность обучения).

Пример можно посмотреть здесь.

Важно понимать: nanochat написан и настроен вокруг одной ручки сложности — глубины трансформера. Это одно целое число автоматически определяет все остальные гиперпараметры (ширину трансформера, число голов, поправки learning rate, длительность обучения, weight decay…), так что обученная модель получается оптимальной по вычислениям. Идея в том, что пользователю не нужно ни думать обо всём этом, ни настраивать: он просто просит модель поменьше или побольше через --depth, и всё «просто работает». Перебирая глубину, вы получаете мини-серию nanochat — модели разных размеров, оптимальные по вычислениям. Модель уровня GPT-2 (больше всего интересующая сейчас) с текущим кодом получается где-то в диапазоне d24–d26. Но любые предлагаемые изменения репозитория должны быть достаточно принципиальными, чтобы работать при любой глубине.

Скрипт runs/runcpu.sh показывает очень простой пример запуска на CPU или Apple Silicon. Он резко уменьшает обучаемую LLM, чтобы обучение уложилось в разумное время — несколько десятков минут. Сильных результатов так не получить.

nanochat не использует torch.amp.autocast. Вместо этого точность управляется явно одной глобальной переменной COMPUTE_DTYPE (определена в nanochat/common.py). По умолчанию она выбирается автоматически по вашему железу:

Железоdtype по умолчаниюПочему
CUDA SM 80+ (A100, H100, …)bfloat16родные тензорные ядра bf16
CUDA SM ниже 80 (V100, T4, …)float32bf16 нет; fp16 доступен через NANOCHAT_DTYPE=float16 (использует GradScaler)
CPU / MPSfloat32безопасный вариант по умолчанию. На свежих 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" # принудительно fp32
NANOCHAT_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 везде работает нормально.

Я опубликовал несколько руководств, в которых может найтись полезное, от новых к старым:

.
├── 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


Дальше — наши рекомендации, а не текст README. Главное: d24 «уровня GPT-2» на вашей машине — это дни, а не полтора часа. Начинайте с d12, потом d16 или d20.

Время обучения ≈ FLOPs / (число GPU × реальная производительность одной GPU). Пиковая BF16-производительность RTX 4080 по спецификации NVIDIA — порядка 100 TFLOPS на плотных матрицах, а реально модель использует обычно 30–50% пика (это и есть MFU). Возьмём 30–50 TFLOPS на карту:

МодельFLOPs4 GPU по 30 TFLOPS4 GPU по 50 TFLOPS
d121,0 · 10¹⁸≈2,3 ч≈1,4 ч
d164,5 · 10¹⁸≈10 ч≈6 ч
d201,5 · 10¹⁹≈35 ч≈21 ч
d24 (ratio 8)2,8 · 10¹⁹≈65 ч≈39 ч

Это оценка снизу: сюда не входят оценки CORE и bpb по ходу обучения, загрузка данных и SFT. Точное число вы узнаете по tok/sec в логе за первые минуты: время ≈ (токенов из лога «Total number of training tokens») / (tok/sec).

  1. Окружение. Нужны свежий драйвер NVIDIA с поддержкой CUDA 12.8 (проверьте nvidia-smi), git и интернет.

    Окно терминала
    git clone https://github.com/karpathy/nanochat.git && cd nanochat
    command -v uv || curl -LsSf https://astral.sh/uv/install.sh | sh # менеджер пакетов uv
    uv venv && uv sync --extra gpu # PyTorch 2.9.1 под CUDA 12.8 и остальные зависимости
    source .venv/bin/activate
    export OMP_NUM_THREADS=1
    export NANOCHAT_BASE_DIR="$HOME/.cache/nanochat" # здесь будут данные, токенизатор и чекпоинты
  2. Проверка, что всё живо. Перед многочасовым запуском прогоните маленький вариант на одной карте, по образцу runs/runcpu.sh, но на CUDA: --depth=6 --max-seq-len=512 --window-pattern=L --num-iterations=200. Если он прошёл и loss падает, окружение в порядке.

  3. Данные и токенизатор. Один шард — около 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.

  4. Претрейн 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=dummy
    torchrun --standalone --nproc_per_node=4 -m scripts.base_eval -- --device-batch-size=16

    Флаг --fp8 не используйте: в справке скрипта он помечен «requires H100+ GPU». Без него обучение идёт в bf16.

  5. Проверьте внимание. В начале лога base_train пишет либо «Using Flash Attention 3», либо предупреждение о переходе на PyTorch SDPA. Комментарий в flash_attention.py говорит, что ядра FA3 собраны в том числе для Ada (sm89), то есть для RTX 4080, — но если увидите предупреждение, перезапустите с --window-pattern L: SDPA не поддерживает скользящие окна, и без этого флага загрузка GPU будет, по словам автора, «ужасной».

  6. SFT, оценка, разговор.

    Окно терминала
    torchrun --standalone --nproc_per_node=4 -m scripts.chat_sft -- --run=dummy # размер батча возьмёт из претрейна
    torchrun --standalone --nproc_per_node=4 -m scripts.chat_eval -- -i sft
    python -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.

  7. Дальше — больше. Повторите с --depth=16 или --depth=20 (для d20 начните с --device-batch-size=8: батч там 1 048 576 токенов, это 16 микрошагов). Сравните val_bpb и CORE — это ваша собственная мини-серия.

Карты общаются через 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.
ENRU
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)нехватка видеопамяти