Андрей Карпаты, легенда мира AI и один из создателей OpenAI и Tesla Autopilot, снова встряхнул сообщество. Он выложил nanochat — проект, который можно назвать одним из самых полезных репозиториев за последнее время.
В чем суть? Это полный конвейер для создания LLM-клона ChatGPT с нуля. Всего 8000 строк чистого, минималистичного кода, который демистифицирует весь процесс: от обучения токенизатора и предобучения модели до файнтюнинга и даже обучения с подкреплением (RL).
Карпаты делает "спидран": сборку языковой модели, которую можно реализовать за $100 и примерно 4 часа работы на облачном сервере с 8xH100 GPU.
Мы разберем проект nanochat по винтикам и поймем, что происходит на каждом этапе.
Карпаты арендует мощный сервер (например, в Lambda GPU Cloud) с восемью видеокартами H100. Такая машина стоит примерно $24 в час. Бюджет ограничен сотней долларов. Это значит, что есть чуть больше 4 часов, чтобы из хаоса интернет-текстов родилась маленькая языковая модель.
[!NOTE] Философия nanochat Весь репозиторий пронизан идеей минимализма. Карпаты сознательно избегает громоздких фреймворков и лишних зависимостей. Его цель — создать понятную, связную и легко модифицируемую кодовую базу, которую можно изучать, форкать и использовать как основу для собственных исследований.
В репозитории есть скрипт speedrun.sh, который автоматизирует весь процесс от А до Я. Но мы же здесь не для того, чтобы просто нажать кнопку, верно? Мы пройдем по этому скрипту шаг за шагом, комментируя каждую деталь.
Итак, мы залогинились на наш свежеарендованный сервер. Время пошло.
uv и RustПервым делом — клонируем репозиторий и настраиваем рабочее окружение.
git clone https://github.com/karpathy/nanochat.git
cd nanochat
Дальше начинается интересное. Карпаты использует не pip или conda, а uv.
# Устанавливаем uv, если его еще нет
command -v uv &> /dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
# Создаем локальное виртуальное окружение .venv
[ -d ".venv" ] || uv venv
# Устанавливаем зависимости
uv sync
# Активируем окружение
source .venv/bin/activate
[!INFO] Что за
uv?uv— новый и чертовски быстрый менеджер пакетов и виртуальных окружений для Python, написанный на Rust. Он позиционируется как заменаpipиvenv, и его главное преимущество — скорость.
Следующий этап может удивить: нам понадобится Rust.
# Устанавливаем Rust и его пакетный менеджер Cargo
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
# Собираем кастомный токенизатор на Rust
uv run maturin develop --release --manifest-path rustbpe/Cargo.toml
Зачем здесь Rust? Ответ прост: для обучения токенизатора. Карпаты отмечает, что его предыдущая реализация на чистом Python (minbpe) оказалась слишком медленной, а готовые решения от Hugging Face — излишне громоздкими и запутанными. В мире, где скорость — наше всё, кастомный токенизатор на Rust — это оправданный шаг. Утилита maturin как раз и служит мостом, позволяя легко собирать Rust-код в Python-модуль.
На этом подготовка завершена. Наше окружение готово к созданию мозга нашей будущей модели.
Прежде чем модель сможет "читать" текст, ей нужен словарь. Токенизатор — это и есть этот словарь. Он разбивает текст на осмысленные кусочки (токены) и переводит их в числа, понятные машине.
Сначала — данные
Чтобы обучить токенизатор, а затем и саму модель, нам нужны данные. Много данных. Карпаты использует датасет FineWeb-EDU — это гигантская коллекция отфильтрованных веб-страниц образовательного характера.
Чтобы не тащить тяжеловесную библиотеку huggingface/datasets, Карпаты сделал простую вещь: он перепаковал датасет в удобные для стриминга "шарды" и выложил их на Hugging Face Hub. Каждый шард — это parquet-файл, сжатый gzip, размером около 100 МБ.
Для модели с глубиной 20 (d20) нам понадобится 240 таких шардов. Это примерно 24 ГБ данных. На облачном сервере они скачиваются довольно быстро.
# Запускаем скачивание 240 шардов данных.
# По умолчанию они попадут в ~/.cache/nanochat
python -m nanochat.dataset -n 240
[!TIP] В скрипте
speedrun.shКарпаты хитро запускает скачивание 240 шардов в фоновом режиме, в то время как обучение токенизатора начинается на первых 8 уже скачанных шардах. Это отличный пример оптимизации времени!
Запускаем обучение
Как только первые данные на месте, мы можем обучить наш словарь. Мы будем создавать словарь размером 65,536 токенов (2**16). Обучение пройдет на первых 2 миллиардах символов и займет всего около минуты.
python -m scripts.tok_train --max_chars=2000000000
python -m scripts.tok_eval
После обучения скрипт tok_eval выдает интересный отчет. Главный показатель — коэффициент сжатия (compression ratio). Он показывает, сколько в среднем символов исходного текста "упаковывается" в один токен. У нас он будет в районе 4.8.
Карпаты также сравнивает этот свежеобученный токенизатор с эталонными от OpenAI: GPT-2 и GPT-4.
Наш собственный, кастомный и эффективный словарь готов. Модель почти готова начать учиться.
Это самый важный и вычислительно дорогой этап. Здесь LLM будет впитывать в себя знания из гигабайтов текста. Процесс прост по своей сути: модель учится предсказывать следующее слово (точнее, токен) в последовательности. Именно так она изучает грамматику, факты, логику и обретает "знания о мире".
Последние приготовления
Перед запуском нам нужен еще один компонент — "eval bundle". Это набор данных для оценки метрики CORE, которая измеряет "общую эрудицию" модели на 22 различных задачах (ответов на вопросы, логических задач и т.д.).
# Скачиваем и распаковываем eval_bundle
curl -L -o eval_bundle.zip https://karpathy-public.s3.us-west-2.amazonaws.com/eval_bundle.zip
unzip -q eval_bundle.zip
rm eval_bundle.zip
mv eval_bundle "$HOME/.cache/nanochat"
Для визуализации процесса обучения можно использовать wandb (Weights & Biases). Это позволит в реальном времени следить за графиками потерь и метрик.
# Если еще не сделали, залогиньтесь в wandb
wandb login
Запускаем!
Теперь все готово для запуска самой тяжелой артиллерии. Мы запускаем скрипт base_train.py на всех 8 GPU. Мы будем обучать модель-трансформер с 20 слоями (depth=20).
torchrun --standalone --nproc_per_node=8 -m scripts.base_train -- --depth=20
[!NOTE] Если вы настроили
wandb, добавьте флаг--run=speedrun(или любое другое имя), чтобы логи отправлялись в ваш проект.
После запуска в консоли появится много информации:
Vocab size: 65,536
num_layers: 20
model_dim: 1280
...
Number of parameters: 560,988,160
...
Total number of training tokens: 11,219,763,200
Tokens : Params ratio: 20.00
Total training FLOPs estimate: 3.917670e+19
...
step 00001/21400 (0.00%) | loss: 10.808654 | ... | tok/sec: 807,569 | mfu: 35.64 | ...
step 00002/21400 (0.01%) | loss: 10.179083 | ... | tok/sec: 1,110,094 | mfu: 48.99 | ...
...
Что значат все эти цифры?
tok/sec — это скорость обработки токенов в секунду. mfu (Model FLOPs Utilization) — это процент утилизации теоретической вычислительной мощности GPU. Значение в районе 50% — это очень хороший показатель.Теперь остается только ждать. Процесс займет около 3 часов. В это время 8 видеокарт H100 будут трудиться на полную, а мы можем наблюдать за магией в wandb. График loss (ошибки) должен плавно снижаться, а метрика CORE — расти. Это значит, что модель становится умнее.
Первые результаты
По завершении предобучения наша модель — это, по сути, очень навороченный автокомплит. Она знает факты, но еще не умеет общаться в формате диалога. Мы можем проверить ее базовые знания:
# Запускаем скрипты для оценки потерь и метрик
torchrun --standalone --nproc_per_node=8 -m scripts.base_loss
torchrun --standalone --nproc_per_node=8 -m scripts.base_eval
Модель достигает метрики CORE около 0.22. Для сравнения, это чуть лучше, чем GPT-2 Large (0.21), но немного хуже, чем GPT-2 XL (0.26).
Проверим на простых запросах:
The capital of France is -> Paris. (Знает!)The chemical symbol of gold is -> Au. (Знает!)The planets of the solar system are: -> Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, (Знает!)If 5*x + 3 = 13, then x is -> a positive integer. (Математику пока не осилила, но поняла, что речь о числах).Неплохо для модели, на обучение которой ушло $72. Но это еще не чат-бот. Чтобы научить ее диалогу, мы переходим к следующим этапам.
Модель уже выучила слова и факты, но пока не умеет вести диалог. Этап midtraining (промежуточное дообучение) — это как отправить её в школу, где её научат общаться, решать задачки и пользоваться инструментами.
Этот этап гораздо быстрее предобучения и занимает всего около 8 минут. Здесь мы дообучаем нашу базовую модель на новом типе данных — на диалогах. Алгоритмически это то же самое обучение, но цели у него другие.
Чему мы учим модель на этом этапе?
<|user_start|>, <|assistant_start|> и <|assistant_end|>. Она учится понимать структуру "вопрос-ответ".<|python_start|> и <|python_end|>. Модель учится "заворачивать" математические выражения в эти теги, чтобы внешний интерпретатор мог их вычислить. Это необходимо для решения математических задач из датасета GSM8K.Запускаем Midtraining
Процесс запускается одной командой:
torchrun --standalone --nproc_per_node=8 -m scripts.mid_train
После завершения этого короткого, но важного этапа, модель превращается из простого автокомплита в полноценного чат-ассистента. Теперь мы можем оценить ее производительность на специализированных "чатовых" бенчмарках.
torchrun --standalone --nproc_per_node=8 -m scripts.chat_eval -- -i mid
Флаг -i mid указывает скрипту, что нужно оценивать модель после этапа midtraining. Результаты будут примерно такими:
Показатели пока скромные, но мы видим, что модель уже приобрела новые навыки. Чтобы "затянуть гайки" и еще немного улучшить ее производительность, переходим к следующему этапу.
SFT (контролируемое дообучение) — это еще один короткий раунд файнтюнинга на диалогах. Он длится около 7 минут и служит для финальной "полировки" модели.
В чем отличие от Midtraining?
Запускаем SFT и последующую оценку:
torchrun --standalone --nproc_per_node=8 -m scripts.chat_sft
torchrun --standalone --nproc_per_node=8 -m scripts.chat_eval -- -i sft
Результаты снова немного подрастают:
Время поговорить!
Наконец-то! Наша модель полностью готова к диалогу. Мы можем пообщаться с ней прямо в терминале или через удобный веб-интерфейс.
# Вариант 1: Чат в командной строке
python -m scripts.chat_cli
# Вариант 2: Веб-интерфейс в стиле ChatGPT
python -m scripts.chat_web
При запуске chat_web скрипт поднимет веб-сервер. Чтобы получить доступ, нужно в браузере ввести публичный IP-адрес вашего сервера и порт 8000, например: http://209.20.xxx.xxx:8000/.
И вот он, результат трудов:
Конечно, такая модель не выиграет конкурс поэзии или олимпиаду по физике. Но сам факт, что можно с нуля создать вполне сносного собеседника, потратив меньше $100 и 4 часов, впечатляет.
Последний этап, который в speedrun.sh по умолчанию закомментирован, — это обучение с подкреплением (Reinforcement Learning). В больших моделях RLHF (RL from Human Feedback) используется для тонкой настройки и согласования ответов модели с человеческими предпочтениями.
В нашем масштабе это не так критично. Но есть одна область, где RL может дать заметный прирост, — это задачи с четким и объективным критерием правильности. Например, математические задачи из GSM8K. Ответ либо правильный, либо нет.
Скрипт chat_rl запускает простой цикл RL:
Этот процесс "натаскивает" модель на генерацию правильных ответов.
# Запускаем RL-обучение
torchrun --standalone --nproc_per_node=8 -m scripts.chat_rl
# Оцениваем результат только на GSM8K
torchrun --standalone --nproc_per_node=8 -m scripts.chat_eval -- -i rl -a GSM8K
Процесс RL довольно медленный, так как модель получает очень мало "битов" информации (только 1 или 0 на целое решение). По умолчанию он занимает около 1.5 часов. Но результат заметен: точность на GSM8K может вырасти еще, например, с 4.5% до 7.5%.
Карпаты отмечает, что этот этап пока не очень хорошо настроен и создает модель, "заточенную" под математику, а не универсального чат-бота. Поэтому он и является опциональным.
По завершении всех этапов в директории проекта появляется файл report.md. Это своего рода "табель успеваемости" спидрана, содержащий всю ключевую информацию о запуске и итоговые метрики.
Отчет начинается с раздела, который показывает, насколько минималистичен сам проект:
Всего 8 тысяч строк кода и минимум зависимостей для создания целого ChatGPT-клона!
Но самое интересное — это итоговая таблица с метриками на каждом этапе.
| Metric | BASE | MID | SFT | RL |
|---|---|---|---|---|
| CORE | 0.2219 | - | - | - |
| ARC-Challenge | - | 0.2875 | 0.2807 | - |
| ARC-Easy | - | 0.3561 | 0.3876 | - |
| GSM8K | - | 0.0250 | 0.0455 | 0.0758 |
| HumanEval | - | 0.0671 | 0.0854 | - |
| MMLU | - | 0.3111 | 0.3151 | - |
| ChatCORE | - | 0.0730 | 0.0884 | - |
Что здесь видно:
midtraining модель уже показывает базовые способности в решении задач (MMLU, GSM8K, HumanEval). После SFT эти показатели еще немного подрастают.И главный итог: Total wall clock time: 3h51m
С учетом стоимости сервера $24/час, общие затраты составили (3 + 51/60) * 24 = $92.4.
Самое прекрасное в nanochat то, что Карпаты дает в руки не просто инструмент, а целую песочницу для экспериментов. Вы можете менять все: токенизатор, данные для обучения, гиперпараметры.
Самый простой способ получить более мощную модель — увеличить ее глубину. Это делается с помощью одного-единственного параметра --depth на этапе base_train. Код устроен так, что все остальные параметры (ширина слоев, learning rate и т.д.) подстроятся автоматически.
Например, чтобы получить модель уровня GPT-2 (CORE-метрика ~0.25), можно попробовать depth=26. Но для этого потребуется больше данных, и, что важнее, придется уменьшить размер батча на каждом устройстве, чтобы не словить ошибку нехватки видеопамяти (OOM).
Пример для обучения модели d26 (~12 часов, ~$300):
# Уменьшаем device_batch_size с 32 до 16
torchrun --standalone --nproc_per_node=8 -m scripts.base_train -- --depth=26 --device_batch_size=16
Код автоматически поймет, что для достижения целевого общего размера батча ему теперь нужно делать 2 шага градиентной аккумуляции вместо одного.
Пример для обучения модели d30:
# Уменьшаем device_batch_size еще сильнее
torchrun --standalone --nproc_per_node=8 -m scripts.base_train -- --depth=30 --device_batch_size=8
Не бойтесь читать и изменять код. Он написан предельно чисто и хорошо прокомментирован.
Ваша поддержка — это энергия для новых статей и проектов. Спасибо, что читаете!
В итоге, nanochat — это мощнейший образовательный инструмент, который снимает завесу тайны с процесса создания больших языковых моделей. Он демократизирует доступ к технологиям, которые еще вчера казались уделом мегакорпораций.
Проект Карпаты доказывает, что для серьезных экспериментов и получения работающих моделей не всегда нужны многомиллионные бюджеты. Он дает в руки сообществу "сильный базовый" стек (strong baseline), который можно использовать как отправную точку для собственных исследований, обучения и создания новых продуктов.
Экспериментируйте!