План выпускного проекта
Техзадание выпускного проекта — от своего токенизатора и претрейна через SFT и GRPO до квантования, сервера и агента с инструментом; варианты для CPU, одной GPU, облака и 4×GPU, бюджет по этапам и проверяемые критерии Simple/Medium/Strong/Boss.
Зачем это нужно
Заголовок раздела «Зачем это нужно»За девять ступеней вы по отдельности собрали все детали современной LLM: токенизатор (4.2), трансформер (4.6), претрейн (5.6), SFT (6.1), GRPO (6.4), квантование и локальный запуск (7.2), оценку (7.3), агента с инструментами (8.3), а перед релизом — проверку безопасности и чек-лист ответственного релиза (9.3.4–9.3.5). Но инженера отличает не знание деталей, а умение собрать их в работающую систему и честно сказать, что получилось.
выпускной проектвыпускной проектEN capstone projectИтоговый проект, который собирает навыки всего курса в одну работающую систему. В Китае сокращённо «бишэ» (выпускной проект). — это ровно такая сборка. Вы пройдёте полный цикл, который проходят DeepSeek и Qwen, только в масштабе, доступном одному человеку: своя модель на десятки миллионов параметров вместо сотен миллиардов. Принципы те же. Китайский учебный проект MiniMind показывает, что это реально: модель на 64M параметров «за 2 часа и 3 юаня» аренды одной видеокарты (урок 5.6).
Результат — проект для портфолио: репозиторий, который запускается одной командой, модель, которой можно задать вопрос через API, и технический отчёт (следующий урок). Этот урок — техзадание: что строим, в каком порядке, как проверяем и сколько это стоит.
Что вы построите
Заголовок раздела «Что вы построите»Маленький русскоязычный ассистент-решатель. Модель отвечает на простые вопросы и решает текстовые задачи на арифметику. Когда считать в уме трудно, она вызывает калькулятор. Задача выбрана не случайно: у неё есть проверяемый ответ (число), а значит, есть награда для GRPO и честная автоматическая оценка.
flowchart LR D["Корпус: русский текст + немного китайского"] --> T["1. Токенизатор BPE"] T --> P["2. Претрейн"] P --> S["3. SFT: диалоги, решения, вызовы инструмента"] S --> G["4. GRPO: награда за верный ответ"] G --> Q["5. Квантование INT8 / INT4"] Q --> V["6. Сервер /v1/chat/completions"] V --> A["7. Агент + калькулятор"] E["Eval-набор: заморожен с первого дня"] -.-> P E -.-> S E -.-> G E -.-> Q E -.-> A
Пунктир на схеме — главное правило проекта. Eval-набор вы собираете и замораживаете до начала обучения (урок 7.3.2). Каждый этап заканчивается прогоном одного и того же набора. Так вы увидите, что дал каждый шаг, и не подгоните данные под тест.
Техзадание по этапам
Заголовок раздела «Техзадание по этапам»| Этап | Что сделать | Артефакт | Как проверить | Уроки и ДЗ |
|---|---|---|---|---|
| 1. токенизатортокенизаторEN tokenizer | byte-level BPE на своём корпусе, словарь 4096–32 000 | tokenizer.json | сжатие (символов на токен) на отложенном русском тексте; сравнение с токенизатором Qwen | 4.2 |
| 2. Претрейнпредобучение, претрейнEN pretraining | трансформер с RMSNorm, RoPE, SwiGLU, GQA (как в 5.6) | чекпоинт, loss.png | начальный loss ≈ ; val loss ниже биграммной линии; осмысленная генерация | 5.6, s5-hw02-tiny-llm-pretrain |
| 3. обучение с учителем на инструкцияхобучение с учителем на инструкциях (SFT)EN supervised fine-tuning (SFT) | chat-шаблон, loss только на ответах; данные: диалоги, решения задач с рассуждением, примеры вызова калькулятора | чекпоинт | доля ответов в нужном формате (Ответ: N, корректный <tool_call>) | 6.1 |
| 4. GRPO | группа из ответов, награда = верный ответ + формат, KL к SFT-модели | чекпоинт, графики награды и длины ответа | точность на отложенных задачах до/после; pass@1 по 4–8 сэмплам | 6.4, s6-hw02-grpo |
| 5. квантованиеквантованиеEN quantization | INT8 и INT4 по группам своим кодом или GGUF через llama.cpp | файлы моделей | размер, скорость (токенов/с), потеря точности на eval в п.п. | 7.2, s7-hw01-quantization |
| 6. Сервер | OpenAI-совместимый /v1/chat/completions (свой на Python или llama-server / vLLM) | serve.py или команда запуска | запрос через curl и клиент openai; токенов/с | 7.1, 7.2, 8.1 |
| 7. агентагентEN agentВ китайском встречается и «дайли» («посредник»), но в AI-контексте стандарт — «чжинэнти» (буквально «интеллектуальное тело»). | цикл «думай → действуй → наблюдай», инструмент calculator через вызов инструментоввызов инструментовEN tool calling / function calling | agent.py | доля решённых задач с инструментом и без; pass^k на 3 прогонах | 8.3, s8-hw02-agent |
Четыре оговорки, которые сэкономят вам недели:
- Задача для GRPO должна быть по силам модели после SFT. В GRPO преимущество ответа — его награда минус средняя награда группы. Если модель не решает задачу ни разу из 8 попыток, все награды нули, преимущества нули, и учиться не на чем. Начните с того, где pass@8 после SFT хотя бы 20–30%: сложение и вычитание двузначных чисел, задачи в одно-два действия. Сложность поднимайте постепенно — от простого к сложному, как в школьной программе (это называют curriculum learning).
- Маленькая модель плохо считает «в уме» — и это хорошо. Умножение четырёхзначных чисел модель на 25M параметров почти никогда не сделает сама, а с калькулятором — легко. Разница «с инструментом / без» будет вашим самым наглядным результатом.
- Свой формат вызова инструмента. Используйте те же теги, что Qwen и MiniMind:
<tool_call>{"name": "calculator", "arguments": {"expr": "37*48"}}</tool_call>, ответ инструмента — в<tool_response>. Добавьте эти теги как спецтокеныспециальный токен (спецтокен)EN special tokenУправляющий токен, а не кусок текста — начало/конец текста (BOS/EOS), заполнитель для батча (PAD), разметка ролей в чате (например, <|im_start|> у Qwen). на этапе 1, иначе токенизатор разобьёт их на куски. - Квантование своей архитектуры. Конвертер llama.cpp понимает только поддерживаемые архитектуры. Если ваша модель совпадает с Llama/Qwen по устройству и именам весов, GGUF сработает; если нет — квантуйте своим кодом из ДЗ s7-hw01. Для первого прохода проще второй путь: он не требует перекладывать веса в формат Hugging Face.
Стартовый репозиторий
Заголовок раздела «Стартовый репозиторий»Чтобы не начинать с пустой папки, в курсе есть стартер — домашняя работа s10-capstone в папке
homework/s10-capstone/. В нём уже готовы конфиги трёх масштабов, скрипты всех этапов, связь между ними, сервер
на стандартной библиотеке и автотесты. Вам остаётся заполнить функции и методы с raise NotImplementedError.
Их около 70, но почти все вы уже писали в домашних работах s4-hw01, s5-hw02, s6-hw02, s7-hw01 и s8-hw02.
В докстринге каждой функции написано, откуда её взять, какая там формула и какой ожидается пример.
Как этапы плана привязаны к файлам стартера:
| Этап плана | Файл в code/ | Что заполнить | Проверка тестами |
|---|---|---|---|
| Данные и eval-набор | data.py | чтение корпуса, разбиение по документам, упаковка, get_batch, ChatML, маска SFT | test_units.py, test_pretrain.py |
| 1. Токенизатор | tokenizer.py | BPE из s4-hw01 на списке документов, спецтокены, сжатие | test_units.py -k tokenizer |
| 2. Претрейн | model.py, pretrain.py | модель из s5-hw02 + KV-кэш и generate; цикл обучения | test_pretrain.py |
| 3. SFT | sft.py | примеры из диалогов, цикл SFT | test_posttrain.py -k sft |
| 4. GRPO | grpo.py | кирпичики из s6-hw02, log π ответа, rollout, шаг обновления | test_posttrain.py -k grpo |
| 5. Квантование | export.py | INT8 и INT4 по группам из s7-hw01 | test_units.py -k quant |
| 6. Сервер | serve.py | генерация ответа и обработчик /v1/chat/completions | test_pipeline.py -k server |
| 7. Агент | agent.py | калькулятор, разбор вызовов, цикл ReAct из s8-hw02 | test_pipeline.py -k agent |
| Оценка | eval.py | формат ответа, интервалы, pass@k, pass^k, контаминация | test_units.py -k metrics |
Конфиги лежат в configs/: xs.yaml (CPU, минуты), m.yaml (одна видеокарта, часы), xl.yaml (4 карты, ночь)
и tiny.yaml для автотестов. Весь конвейер запускается одной командой из корня репозитория:
bash homework/s10-capstone/run_all.sh configs/xs.yaml # этапы по очереди, сервер — фоновым процессомpython homework/s10-capstone/code/pipeline.py --config homework/s10-capstone/configs/xs.yaml # то же одним процессом (Windows)Каждый этап пишет homework/s10-capstone/artifacts/<запуск>/<этап>/metrics.json, а последний собирает их в общий
metrics.json с ключами из раздела «Критерии» ниже.
Уровни здесь двух видов, и их важно не путать:
- Уровни автогрейдера проверяют код.
python homework/check.py s10-capstoneгоняет весь конвейер на крошечной конфигурацииtinyна CPU за пару минут. Simple — интерфейсы и маленькие юниты, Medium — токенизатор и мини-претрейн (loss падает), Strong — SFT и шаг GRPO, Boss — весь конвейер до сервера и агента. Качество модели тесты не оценивают. - Уровни проекта проверяют результат: числа вашего прогона против порогов из раздела «Критерии». Их печатает
python homework/s10-capstone/code/eval.py --levels homework/s10-capstone/artifacts/xs/metrics.json.
Пройденный Boss автогрейдера значит, что конвейер собран правильно. Уровень проекта зависит от масштаба: на XS он честно «не пройден», Simple и Medium достижимы на M.
Откуда взять код и данные
Заголовок раздела «Откуда взять код и данные»С нуля писать почти ничего не нужно: каждый этап вы уже делали в уроке или домашней работе. Выпускной проект — это
сборка готовых кусков в один конвейер. Переносите свои решения (или эталоны из solutions/) в заготовки
стартера homework/s10-capstone/code/ — или в папку capstone/src/, если собираете репозиторий сами:
| Этап | Что взять за основу | Что дописать |
|---|---|---|
| Данные для претрейна | урок 5.6.2, «Шаг 1»: FineWeb-2 rus_Cyrl через datasets, очистка из урока 5.1.2, формат corpus.jsonl | ничего — тот же корпус |
| 1. Токенизатор | урок 5.6.2, «Шаг 2» (byte-level BPE на tokenizers) или ваш homework/s4-hw01-bpe-tokenizer/code/bpe.py | спецтокены <tool_call>, </tool_call>, <tool_response>, </tool_response> и токены chat-шаблона |
| 2. Претрейн | model.py и train.py из урока 5.6.2 (или ДЗ s5-hw02-tiny-llm-pretrain: model.py, train.py, data.py) | только конфиг; для 4 карт — DDP-правки из того же урока |
| 3. SFT | маска loss «только ответы ассистента» из урока 6.1.2 (браузерное задание 6-1-sft-loss-mask) и тот же цикл обучения | сборку диалогов из задач генератора ниже |
| 4. GRPO | group_advantages, kl_k3, grpo_loss из ДЗ s6-hw02-grpo/code/grpo.py; цикл — урок 6.4.5; генерация с KV-кэшем — урок 4.6.4 | функцию награды reward из генератора ниже |
| 5. Квантование | quantize_model и QuantLinear из ДЗ s7-hw01-quantization/code/qlinear.py | замер размера, скорости и точности на eval_v1 |
| 6. Сервер | mock_server.py из практики урока 8.1.1 — готовый OpenAI-совместимый сервер на стандартной библиотеке | заменить строку с фейковым ответом на генерацию вашей модели |
| 7. Агент | Agent, ToolRegistry, calculator, parse_tool_calls из ДЗ s8-hw02-agent (понимает теги <tool_call> как у Qwen) | подключить к своему серверу из этапа 6 |
| Оценка | exact match и pass@k (урок 7.3.2, задание 7-3-pass-at-k), интервалы (урок 7.3.1), n-граммная проверка контаминации (урок 7.3.1) | run_eval.py, который пишет числа в metrics.json |
Задачи для eval, SFT и GRPO нигде скачивать не нужно: их генерирует скрипт с фиксированным seed. Он сразу исключает eval-вопросы из обучающих и даёт функцию награды:
# data/make_tasks.py — арифметические задачи для eval, SFT и GRPO + функция наградыimport jsonimport randomimport re
NAMES = [("Аня", "а"), ("Борис", ""), ("Вера", "а"), ("Глеб", "")] # имя и окончание глаголаITEMS = [("тетради", "тетрадей"), ("ручки", "ручек"), ("яблока", "яблок")] # «2–4 …» и «5–9 …»
def make_task(rng, kind): """Одна задача: вопрос, числовой ответ, тип и эталонное решение для SFT.""" if kind == "one_step": # одно действие, числа до 100 a, b, op = rng.randint(10, 99), rng.randint(10, 99), rng.choice("+-") ans = a + b if op == "+" else a - b q, sol = f"Сколько будет {a} {op} {b}?", f"{a} {op} {b} = {ans}." elif kind == "word": # текстовая задача в два действия (name, end), (few, many) = rng.choice(NAMES), rng.choice(ITEMS) n, price = rng.randint(2, 9), rng.randint(5, 99) item = few if n < 5 else many # 3 яблока, но 7 яблок ans = 1000 - n * price q = f"{name} купил{end} {n} {item} по {price} руб. и заплатил{end} 1000 руб. Сколько сдачи?" sol = f"{n} · {price} = {n * price}; 1000 − {n * price} = {ans}." else: # большие числа — нужен калькулятор a, b = rng.randint(1000, 9999), rng.randint(100, 999) ans = a * b q = f"Сколько будет {a} * {b}?" sol = '<tool_call>{"name": "calculator", "arguments": {"expr": "%d*%d"}}</tool_call>' % (a, b) return {"q": q, "answer": ans, "type": kind, "solution": sol}
def make_set(seed, n, exclude=frozenset()): """n уникальных задач с фиксированным seed; вопросы из exclude (eval) пропускаем.""" rng, kinds = random.Random(seed), ["one_step", "word", "big"] out, seen = [], set(exclude) while len(out) < n: t = make_task(rng, kinds[len(out) % 3]) # типы по кругу — набор сбалансирован if t["q"] not in seen: # ни дублей, ни пересечения с eval seen.add(t["q"]) out.append(t) return out
def reward(text, answer): """Награда для GRPO и проверка в eval: 1.0, если последнее «Ответ: N» совпало с правильным числом.""" found = re.findall(r"Ответ:\s*(-?\d+)", text) # все числа после «Ответ:» return 1.0 if found and int(found[-1]) == answer else 0.0
if __name__ == "__main__": eval_set = make_set(seed=2026, n=300) # замораживаем: seed больше не меняем eval_q = frozenset(t["q"] for t in eval_set) train_set = make_set(seed=1, n=20_000, exclude=eval_q) for path, rows in [("eval_v1.jsonl", eval_set), ("train_tasks.jsonl", train_set)]: with open(path, "w", encoding="utf-8") as f: f.writelines(json.dumps(t, ensure_ascii=False) + "\n" for t in rows) print("eval:", len(eval_set), "| train:", len(train_set), "| пересечений:", len(eval_q & {t["q"] for t in train_set})) for t in eval_set[:3]: print(f'{t["type"]:8} | {t["q"]} → {t["answer"]}') print(reward("1000 − 440 = 560. Ответ: 560", 560), reward("Ответ: 561", 560), reward("не знаю", 560))eval: 300 | train: 20000 | пересечений: 0one_step | Сколько будет 25 + 50? → 75word | Борис купил 8 яблок по 78 руб. и заплатил 1000 руб. Сколько сдачи? → 376big | Сколько будет 9975 * 962? → 95959501.0 0.0 0.0Как из задачи получается SFT-пример: вопрос — реплика пользователя, поле solution плюс строка Ответ: N —
реплика ассистента. Для задач типа big ассистент сначала просит калькулятор, ваш код подставляет ответ инструмента,
и только потом модель пишет ответ. В разметке ChatML (урок 6.1.2) один такой диалог выглядит так:
<|im_start|>userСколько будет 9975 * 962?<|im_end|><|im_start|>assistant<tool_call>{"name": "calculator", "arguments": {"expr": "9975*962"}}</tool_call><|im_end|><|im_start|>tool<tool_response>9595950</tool_response><|im_end|><|im_start|>assistantОтвет: 9595950<|im_end|>Loss считается только на репликах assistant — строку <tool_response> пишет среда, а не модель (урок 8.3.4).
Формат роли tool выбираете вы; главное — один и тот же в SFT, GRPO и агенте. Для GRPO берите из train_tasks.jsonl
только вопросы и ответы: решение модель должна найти сама, а reward проверит последнее Ответ: N.
Варианты по железу
Заголовок раздела «Варианты по железу»| CPU (ноутбук) | Одна GPU 8–24 ГБ | Облако | Ваша машина, 4×GPU | |
|---|---|---|---|---|
| Модель | S из урока 5.6: 11.9M | M из урока 5.6: 25.8M (или своя на 64M, как minimind-3) | как «одна GPU» | 100–250M, плотная или MoE |
| Токенов претрейна | десятки миллионов | 0.5–1.5 млрд | 0.5–1.5 млрд | несколько миллиардов |
| GRPO | группа 4, сотни шагов, короткие ответы | группа 8, 500+ шагов | как «одна GPU» | генерация на 1 карте (через vLLM, если архитектура совместима с Llama/Qwen), обучение на 3 |
| Время на этапы 2–4 с отладкой и повторными запусками | вечера недели | 1–2 дня | 1–2 дня аренды | 1–2 ночи |
| Уровень, на который реально выйти | Simple, Medium | Medium, Strong | Medium, Strong | Strong, Boss |
Облако из России: Yandex DataSphere (есть пробный грант), Kaggle (около 30 GPU-часов в неделю, нужна верификация телефона), Colab. Лимиты и условия меняются — проверяйте перед стартом. «3 юаня» в MiniMind — это аренда одной RTX 3090 примерно на 2.3 часа претрейна и SFT, по 1.3 юаня в час (урок 5.6).
Бюджет по этапам
Заголовок раздела «Бюджет по этапам»Вычислительная стоимость считается по формуле из урока 5.1: обучение на токенах стоит FLOPs. Генерация одного токена — примерно FLOPs (только прямой проход). Посчитаем все этапы для конфигурации M на одной GPU:
def stage_flops(n_params, pretrain_tokens, sft_tokens, grpo_steps, prompts_per_step, group, gen_len): """FLOPs по этапам выпускного проекта (оценка «по порядку величины»).""" pre = 6 * n_params * pretrain_tokens # прямой + обратный проход: 6·N·D sft = 6 * n_params * sft_tokens rollout_tokens = grpo_steps * prompts_per_step * group * gen_len grpo = (2 + 2 + 6) * n_params * rollout_tokens # генерация + референс-модель + обновление return {"pretrain": pre, "SFT": sft, "GRPO": grpo}, rollout_tokens
# Конфигурация M из урока 5.6 (25.8M параметров), одна GPUflops, rollout = stage_flops(25.8e6, pretrain_tokens=0.5e9, sft_tokens=20e6, grpo_steps=500, prompts_per_step=32, group=8, gen_len=64)total = sum(flops.values())for name, f in flops.items(): print(f"{name:8} {f:8.1e} FLOPs {100 * f / total:5.1f}%")print(f"сгенерировать для GRPO: {rollout / 1e6:.1f}M токенов")
# Время: FLOPs делим на реальную скорость; генерацию — на токены в секундуtflops_eff = 20e12 # реальная скорость обучения на вашей GPU (замерьте, урок 5.6)gen_tok_s = 3000 # скорость генерации батчем (замерьте, урок 7.1)train_h = (flops["pretrain"] + flops["SFT"]) / tflops_eff / 3600gen_h = rollout / gen_tok_s / 3600print(f"pretrain + SFT ≈ {train_h:.1f} ч, генерация для GRPO ≈ {gen_h:.1f} ч")pretrain 7.7e+16 FLOPs 93.7%SFT 3.1e+15 FLOPs 3.7%GRPO 2.1e+15 FLOPs 2.6%сгенерировать для GRPO: 8.2M токеновpretrain + SFT ≈ 1.1 ч, генерация для GRPO ≈ 0.8 чДва вывода. Первый: почти все FLOPs уходят на претрейн, как и у больших моделей — у DeepSeek-V3 пост-трейнинг занял 5 тыс. GPU-часов из 2.788 млн. Второй: GRPO по FLOPs копеечный, но по времени сравним с претрейном. Генерация идёт последовательно, по одному токену за шаг, и на каждом шаге видеокарта почти простаивает: у больших моделей она ждёт чтения весов из памяти (урок 7.1), у крошечных — накладных расходов на запуск каждого шага. Поэтому в больших RL-системах генерацию отдают быстрому движку (vLLM, SGLang), а у вас на 4 картах — отдельной карте.
Числа tflops_eff и gen_tok_s — условные. На первой неделе замерьте свои и пересчитайте план.
Вехи и расписание
Заголовок раздела «Вехи и расписание»Модуль рассчитан примерно на 30 часов. Разбейте их на вехивеха (milestone)EN milestoneПромежуточная цель с проверяемым результатом — артефактом или числом. Проект без вех трудно довести до конца. — промежуточные цели с проверяемым результатом. Для каждой заранее запишите определение готовности (definition of done): какой артефакт появится и какое число он должен показать.
| Неделя | Веха | Готово, когда… |
|---|---|---|
| 1 | Скелет и данные | репозиторий, корпус, eval-набор v1 заморожен; весь конвейер XS проходит от начала до конца |
| 2 | Токенизатор и претрейн | val loss ниже биграммной линии, график loss без всплесков, генерация из русских слов |
| 3 | SFT и скрипт оценки (harness) | доля ответов в нужном формате ≥ 80%; run_eval.py считает точность и печатает интервалы |
| 4 | GRPO | точность на отложенных задачах выросла; графики награды и средней длины ответа |
| 5 | Квантование, сервер, агент | INT4-модель отвечает через curl; агент с калькулятором решает задачи, недоступные модели без него |
| 6 | Отчёт и защита | отчёт по шаблону урока 10.1.2, 10-минутный доклад с живой демонстрацией |
-
Заведите журнал экспериментов (журнал экспериментовжурнал экспериментовEN experiment logЗапись каждого запуска — дата, коммит, конфиг, гипотеза, результат, вывод. Из него потом пишется отчёт.): дата, коммит, конфиг, гипотеза, результат, вывод. Одна строка на запуск. Через месяц вы не вспомните, почему у запуска
run-17былlr=6e-4, а отчёт пишется именно из этого журнала. -
Фиксируйте случайность.
torch.manual_seed, seed для данных, версия библиотек вrequirements.txt(воспроизводимостьвоспроизводимостьEN reproducibilityВозможность получить тот же результат, повторно запустив тот же код на тех же данных.). Результат, который нельзя повторить, нельзя и защитить. -
Каждый этап пишет
metrics.json. Это контракт между обучением, оценкой и отчётом: графики и таблицы отчёта строятся из этих файлов, а не переписываются руками.
Как те же правила устроены в команде, где прогонов сотни, — папка на каждый прогон с конфигом и git-хэшем, трекеры, лабораторный журнал и сравнение по нескольким seed — в уроке 11.1.3.
Структура репозитория:
capstone/├── configs/ # xs.yaml, m.yaml — все гиперпараметры, никаких «магических чисел» в коде├── data/ # prepare.py; сами данные — в .gitignore├── eval/│ ├── eval_v1.jsonl # заморожен в первую неделю│ └── run_eval.py # точность, exact match, pass@k, интервалы (урок 7.3)├── src/ # tokenizer.py, model.py, train_pretrain.py, train_sft.py,│ # train_grpo.py, quantize.py, serve.py, agent.py├── runs/<этап>/ # metrics.json, loss.png, конфиг запуска├── report/ # отчёт и рисунки (урок 10.1.2)├── LOG.md # журнал экспериментов└── Makefile # make all: от данных до агента одной командойMakefile — файл для утилиты make, которая запускает команды этапов по порядку. Если make вам незнаком, начните
с обычного bash-скрипта — он делает то же самое (имена файлов — ваши из src/):
#!/usr/bin/env bash# run_all.sh — весь конвейер одной командой: bash run_all.sh configs/xs.yamlset -e # остановиться на первой же ошибке, а не идти дальшеCFG=$1 # путь к конфигу — первый аргумент командыpython data/make_tasks.py # задачи для eval, SFT и GRPOpython src/tokenizer.py --config $CFGpython src/train_pretrain.py --config $CFGpython src/train_sft.py --config $CFGpython src/train_grpo.py --config $CFGpython src/quantize.py --config $CFGpython eval/run_eval.py --config $CFG # пишет runs/*/metrics.jsonАргумент --config читают модулем argparse (урок 0.2.4), а YAML-файл — библиотекой pyyaml
(pip install pyyaml): yaml.safe_load(open(path)) вернёт обычный словарь. В уроке 5.6.2 гиперпараметры записаны
прямо в начале train.py — перенесите их в YAML, чтобы XS, M и XL отличались только файлом конфига.
Критерии Simple / Medium / Strong / Boss
Заголовок раздела «Критерии Simple / Medium / Strong / Boss»Уровни устроены лестницей, как в домашних заданиях курса и у Ли Хунъи: следующий засчитывается, только если пройден предыдущий. Все критерии — проверяемые числа, а не «модель стала лучше».
| Уровень | Что нужно | Пороги |
|---|---|---|
| Simple | Все 7 этапов работают одной командой, хотя бы на крошечной модели | SFT: ≥ 80% ответов в формате; GRPO: +10 п.п. точности на отложенных задачах; квантование: потеря ≤ 3 п.п.; агент решает ≥ 50% задач |
| Medium | Настоящий масштаб для вашего железа | претрейн ≥ 0.5 млрд токенов; GRPO: +15 п.п.; агент с калькулятором лучше агента без него на ≥ 20 п.п.; eval-набор ≥ 200 задач |
| Strong | Исследование, а не только сборка | ≥ 2 абляцииабляционный эксперимент, абляцияEN ablation studyУбрать или заменить один компонент и сравнить с полной моделью, чтобы измерить вклад этого компонента. (например, награда за формат: есть/нет; группа 4 или 8); 95% интервалы у всех ключевых чисел; проверка контаминации train/eval; отчёт ≥ 6 страниц |
| Boss | Масштаб и надёжность | обучение на ≥ 2 GPU (DDP/FSDP); модель ≥ 100M параметров (или MoE); агент: pass^3 ≥ 0.5; публичная защита с живой демонстрацией |
Пороги проверяются автоматически. Пример самопроверки по metrics.json:
import json
# Пороги уровней: (метрика, условие, значение). Всё, что ниже, — проверяемые числа из metrics.json.LEVELS = { "Simple": [("pipeline_ok", "==", True), # все 7 этапов запускаются одной командой ("sft_format_rate", ">=", 0.80), # доля ответов в нужном формате ("grpo_gain_pp", ">=", 10), # прирост точности после GRPO, п.п. ("quant_drop_pp", "<=", 3), # потеря точности после квантования, п.п. ("agent_success", ">=", 0.50)], # доля задач, решённых агентом с инструментом "Medium": [("pretrain_tokens_b", ">=", 0.5), ("grpo_gain_pp", ">=", 15), ("tool_gain_pp", ">=", 20), # агент с калькулятором vs без него ("eval_size", ">=", 200)], "Strong": [("n_ablations", ">=", 2), ("has_confidence_intervals", "==", True), ("contamination_checked", "==", True), ("report_pages", ">=", 6)], "Boss": [("n_gpus", ">=", 2), ("params_m", ">=", 100), ("agent_pass_hat_3", ">=", 0.5), # pass^3: агент надёжен, а не «иногда» ("defense_done", "==", True)],}OPS = {"==": lambda a, b: a == b, ">=": lambda a, b: a >= b, "<=": lambda a, b: a <= b}
def level(metrics): """Уровни идут лестницей: Medium засчитывается, только если пройден Simple, и т. д.""" achieved = "не пройден" for name, rules in LEVELS.items(): failed = [f"{k} {op} {v} (у вас {metrics.get(k)})" for k, op, v in rules if k not in metrics or not OPS[op](metrics[k], v)] if failed: print(f"{name}: не хватает → " + "; ".join(failed)) break achieved = name return achieved
metrics = json.loads("""{ "pipeline_ok": true, "sft_format_rate": 0.91, "grpo_gain_pp": 17.5, "quant_drop_pp": 1.2, "agent_success": 0.64, "pretrain_tokens_b": 1.3, "tool_gain_pp": 38.0, "eval_size": 300, "n_ablations": 1, "has_confidence_intervals": true, "contamination_checked": true, "report_pages": 7}""")print("Ваш уровень:", level(metrics))Strong: не хватает → n_ablations >= 2 (у вас 1)Ваш уровень: MediumПрактика: первая неделя
Заголовок раздела «Практика: первая неделя»- Возьмите стартер
homework/s10-capstone/(или создайте репозиторий по структуре выше) и заведитеLOG.mdс первой записью: «цель проекта, железо, выбранный уровень». - Соберите
eval_v1.jsonl: 200–300 задач трёх типов — арифметика в одно действие, текстовые задачи в два-три действия, задачи с большими числами (для калькулятора). Ответы — числа. Сгенерируйте их скриптомmake_tasks.pyиз раздела «Откуда взять код и данные» (seed фиксирован) и сразу проверьте пересечение с обучающими данными 8-граммами (код из урока 7.3.1). Осторожно с шаблонами: если обучающие задачи для SFT и GRPO делает тот же генератор, длинные n-граммы шаблона совпадут у всех задач. Поэтому сохраните множество eval-задач (шаблон + числа) и исключайте их при генерации обучающих данных. - Пересчитайте бюджет
stage_flopsдля своей конфигурации и железа, запишите вLOG.md. - Прогоните конвейер на конфигурации XS: 200 шагов претрейна, 100 шагов SFT, 20 шагов GRPO, INT8, сервер, один
вызов агента. Качество не важно — важно, что
make allдоходит до конца. В стартере этоbash homework/s10-capstone/run_all.sh configs/xs.yaml, а перед этим —python homework/check.py s10-capstoneхотя бы до уровня Boss. - Запишите в
metrics.jsonпервые числа и прогоните самопроверку уровней (в стартере —python homework/s10-capstone/code/eval.py --levels homework/s10-capstone/artifacts/xs/metrics.json).
Проверь себя
Заголовок раздела «Проверь себя»- Выпускной проект — 7 этапов: токенизатор → претрейн → SFT → GRPO → квантование → сервер → агент. У каждого есть артефакт, метрика и урок, где вы это уже делали.
- Eval-набор замораживается в первую неделю. Через него проходит каждый этап.
- Масштаб выбирайте под железо, но сначала прогоните весь конвейер на крошечной модели.
- Задача для GRPO и агента должна быть проверяемой и посильной: арифметика с числовым ответом и калькулятор.
- Уровни Simple → Boss задаются числами из
metrics.jsonи проверяются скриптом.
Если сервер и агента выпускного проекта вы будете собирать вместе с ИИ-агентом для кода, начните со спецификаций: как превратить план проекта в требования, сценарии и задачи, которые агент выполняет по одной, — в программе SDD, её выпускной проект — урок SDD.4.4.
Дальше: как превратить журнал экспериментов и metrics.json в технический отчёт по образцу DeepSeek и Qwen
и как подготовиться к защите.