Хотел упростить мониторинг проектов и в отпуск — пришлось обучать свой LLM

от автора

Я работаю по ИП, поэтому не только пишу код, но и поддерживаю как DevOps свои проекты у заказчика. Эта история началась банально: я собирался в отпуск и хотел оптимизировать часть процессов, которые в повседневной жизни занимают время — чтобы не дёргать клиентов из-за вопросов по ошибкам, которые я мог не увидеть во время отдыха. Пусть локальная моделька сама разгребает типовое. Думал: запущу OpenClaw, подключу к локальной модели — и поеду спокойно

Через две недели — вместо отпуска — я сидел и обучал Qwen3 на Nvidia RTX 3090 24GB, потому что ни одно из существующих решений у меня не работало. Дальше расскажу, как до этого докатился — с цифрами, граблями и бенчмарками.

Точка входа: OpenClaw и боль с локальными моделями

Поставил OpenClaw — open-source агентный фреймворк. Если вы не знакомы с этим чудом, то простыми словами это прослойка между tool calling модели и скриптами (скиллами), которые может вызывать ваш ИИ. Плюс удобный планировщик типа cron — может по расписанию слать промт.

У меня несколько ПК, прокинул его на один с RTX 4080 16GB + 64GB DDR5 + Core i9. Задумка была такая: модель должна по расписанию через OpenClaw соединиться по SSH с удалённым сервером, пролистнуть docker ps, и если есть контейнеры, которые перезагружаются — взять оттуда логи, проанализировать и прислать в Телеграм с обоснованием «что это».

Я как и многие разрабы ведусь на хайп, читаю статьи, исследования. Это меня и подвело. В статьях — красивые цифры и «годнота», в реальности — мир влажных фантазий. Что произошло: я прогнал кучу моделей, начиная со старых gpt-oss до последнего qwen3.6, который мог себе позволить на ollama даже с очень низкой скоростью. И тут начало вылезать неприятное. Половина моих реальных кейсов локальные модели просто не вытягивали: tool calling через раз, галлюцинации в ответах, обрезанный output, путаница с аргументами функций.

В итоге картина простая: без серьёзных платных ИИ — а именно codex от OpenAI, который предлагается из коробки по oAuth, ну и Антропик (очень дорогой через консоль) — остальное мусор. Некая замануха: ты пробуешь локальные модельки для OpenClaw, обжигаешься, потом не страдаешь фигнёй и переезжаешь на платный. Что в свою очередь рождает массу сомнений — VPN (не только с нашей стороны), безопасность данных (а если я работаю с паспортными данными или ещё с чем). В итоге решил запилить сам.

Шаг 1: бенчмарк 16 локальных моделей

Слово «САМ» — оно очень смелое, потому что самому запилить с нуля модель типа Qwen — задача неподъёмная, поэтому я решил не придумывать велосипед, а взять готовый вариант и дообучить его до нужного состояния. Прежде чем пилить дообучение, надо понять, что вообще существует из локальных моделей и какая из них реально работает на моих задачах. Делаю бенчмарк из трёх реальных кейсов и замера скорости:

  • Code Review — даю заведомо багованный Java-код Jitsi-клиента (SQL injection, сравнение строк через ==, небезопасный Random, HTTP вместо HTTPS, ресурс-leak, пустой catch). Модель должна найти все баги и предложить fix. 0–10 баллов.

  • Финансовая аналитика — план погашения кредитов методом лавины с помесячной разбивкой на 12 месяцев. Проверяет математику и следование инструкциям. 0–10 баллов.

  • HTML страница — генерация визуального таймлайна погашения 8 кредитов. Проверяет вёрстку, адаптивный дизайн, обработку данных. 0–10 баллов.

Бонус за скорость 0–5 баллов: ≥100 t/s = 5, ≥60 = 4, ≥30 = 3, ≥15 = 2, >0 = 1.

Стенд: RTX 4080 16GB, 64GB DDR5, Core i9, Ollama 0.20.7. Прогнал все актуальные модели, которые помещаются:

#

Модель

Код

HTML

Кредит

Скорость

ИТОГО

t/s

Tool calling

1

🥇 gemma4:latest

10

10

9

5

34

108

2

🥈 phi4-mini:latest

10

10

8

5

33

208

3

🥉 deepseek-r1-tool-calling:14b

10

10

9

4

33

61

4

qwen3:8b

9

10

8

5

32

102

5

deepseek-r1:14b

10

9

9

4

32

65

6

phi4:latest

8

10

9

4

31

67

7

lfm2:latest

8

10

9

4

31

70

8

glm-4.7-flash:latest

9

10

9

2

30

20

9

devstral:latest

8

10

9

1

28

14

10

qwen3:30b-a3b

9

9

7

2

27

25

11

mistral-small:latest

8

9

9

1

27

14

12

nemotron-3-nano:latest

8

10

7

2

27

21

13

gpt-oss:latest

9

10

2

5

26

123

14

qwen3:32b

timeout

1

1

5

Но не всё так гладко. Для агентной работы (а я хотел именно агента, не просто chat) обязательно нужен tool calling. Из 14 протестированных это умели только 7. И из них в категории «10/10 на коде + быстрая» — только gemma4.

Шаг 2: первая попытка дообучения, и почему 4080 16GB — мало

Решил: беру gemma4:latest как базу, дообучаю QLoRA-лорой на своих DevOps-кейсах, получаю свой домашний AI-ассистент.

На бумаге норм. На практике — не помещается в 16GB VRAM 4080. QLoRA с активациями + градиентами в 4-битном квантовании требует ~22–24 GB. Раздать на CPU offload — обучение шло бы неделями, с потерей всего смысла «локальное дообучение». Простыми словами: запустить уже обученную LLM — она отлично работает и помещается в 16GB, а вот дообучать и формировать адаптеры — места уже не хватает.

Опустился на qwen3:8b. 8B модель реально влезает в 4080, обучение проходит за ночь. Прогнал свой первый датасет (~1000 hand-crafted DevOps-трейсов), получил oni:v3. Запустил.

И вот тут второе расстройство: 8B мало для агентной работы. На простых задачах работает, на multi-step типа «отрефактори этот docker-compose с учётом всех 12 требований» — теряет нить. Не хватает параметров, чтобы держать контекст и принимать сложные решения.

Я мог:

  • Смириться с 8B и считать «лучше что есть».

  • Снять VM с большим объёмом памяти на GPU, дообучить нормальную модель, потом смержить и запустить на 4080, как я и хотел.

  • Купить ещё одну видеокарту.

Выбрал вариант 2.

Шаг 3: VM с RTX 3090 и обучение Qwen3-14B

Снял на ночь VM с RTX 3090 24GB. Снова сделал бенчмарк, добавив простой тест на создание файла на сервере. Тут уже выиграл ранее пропущенный мной Qwen3-14B. На 24 гигах помещается с QLoRA r=64 — хороший баланс «достаточно большая, чтобы тянуть агентные задачи» + «обучение проходит за разумное время».

Попросил Claude запилить сиды на дырявые моменты в шагах тестирования. Перетащил датасет, запустил обучение. Через ~6 часов получил oni:v6. Стало заметно лучше: tool calling стабильный, контекст держится, multi-step задачи решает.

Понимая, что OpenClaw уже может что-то типа «закинь мне с frontend-контейнера последние логи с ошибками за 3 часа», я решил отказаться от достойного по цене intelion.cloud VM (мы с ним прожили 4 дня за 44 рубля в час, дешевле и качественнее не нашёл) и купил себе домой с авито старенький Dell с 3090. Теперь обучение полностью дома, никаких VM, никаких облачных зависимостей. Точно соответствует моему изначальному принципу: ничего не должно зависеть от VPN или зарубежной инфраструктуры.

Шаг 4: качество датасета — главное, что я недооценивал

На oni:v6 я провёл сравнение head-to-head с базовым qwen3.6:27b (более крупная свежая модель из коробки) на конкретной задаче — multi-file scaffold TODO web app: backend Django REST, frontend Vue+Vite, docker-compose, README. Промт был такой: «Создай мне приложение: TODO на фронте Vue, бэк на Django, и подними мне на локалке в docker-compose».

Получилось интересно:

qwen3.6:27b (cloud)

oni:v6 (local 4080)

Время

3 мин 54 с

49 с

Шагов агента

21 (max=20, упёрся)

19

Файлов создано

15 (но docker-compose.yml ✗)

14 (compose.yml ✓)

Честность отчёта

❌ сфабриковал docker-compose.yml

✅ отчёт совпадает с диском

И вот это уже серьёзно. qwen3.6:27b в финальном отчёте написал «успешно создал TODO web app» и привёл содержимое docker-compose.yml — красивый YAML, четыре строки описания сервисов, volumes, ports. А файла на диске нет. Вызова write_file в логе агента не было. Модель в финальном ответе сфабриковала результат, которого не существует.

Это так называемый «phantom final answer» — когда модель описывает то, чего не делала. В продакшене это убивает доверие: ты думаешь «всё готово», идёшь в папку, делаешь docker compose up — и получаешь «no such file». Если ошибку видно сразу — её можно починить. А если ассистент врёт, что её нет — починить нельзя.

Мой oni:v6 наоборот: знал Django хуже (забыл cors_headers, settings.py был куцый), но не врал про результаты. Что записал — про то и отчитался. Плюс в 4.8 раза быстрее на 4080, чем 27B на 3090 через тоннель.

Когда я давал тест, я не ждал от своей модели, что она запилит хоть что-то — мне просто нужен был честный ответ или решение на уровне песочницы. Я и до этого видел, как модельки на локалке при разработке простого приложения путаются в роутинг-файлах. То есть тест был не про «вот посмотрите, модель 14b побеждает 27b» — это бред — а про разницу в подходе. qwen3.6:27b в это сравнение попал как рекомендация от друга-ИИшника со словами «там tool-calling проработан лучше».

Так вот, для агента честность — главное. Для LLM-чата — наоборот, 27B будет полным, развёрнутым. Но для self-running агента, который реально выполняет задачи и должен честно отчитываться — фантюненный 14B работает лучше базового 27B.

Шаг 5: текущее состояние — base-7.v2

Ниже я вложил плод моих страданий. Повёлся на полный набор сидов от Magicoder, которые лежат на HF — там много обучающих датасетов, но они общие и ломают логику маленького агента. Поэтому следующую статью я хотел написать как раз про дистилляцию данных с общих ресурсов под агента — это отдельная тема.

За неделю мой датасет вырос:

  • 180 hand-written seed templates в YAML (типовые DevOps-задачи)

  • 14 generation templates (превращают seed → multi-turn agent trace)

  • 45 hand-curated «эталонных» полных трейсов (для самых сложных паттернов: anti-pattern fix, honest failure, multi-file scaffold)

  • = ~1900 трейсов после paraphrase-вариаций

Текущий чемпион — oni:base-7.v2, обученный на этом датасете с 2 эпохами Unsloth QLoRA r=64 на RTX 3090. Метрики:

Stage 1 SSH (22 теста):           15/22  (68%)Realworld stepped (5 этапов):     4/5    (80%, честный fail на 5-м)Phase 3 multi-file scaffold:      работаетСкорость inference:               25-30 t/s на 3090

Это не идеально. Multi-step SSH chains (port-forward, scp с проверкой, обратный туннель) проседают. Реальный nginx через SSH+sudo тоже тяжело. До production-ready ещё доводить — но базовый агент уже даёт пользу. Это значит, что не «два раза я сделал ничего», а «выполняю задачи, но говорю, если у меня не выходит» (уровень джуна: не получилось — сказал). Что я считаю прорывом!

Самый загадочный баг по дороге: Qwen3 thinking mode

Один баг сожрал у меня 3 ночи — расскажу подробно, потому что ловушка типичная для тех, кто фантюнит.

Модель обучилась хорошо (eval_loss 0.4), но в Ollama выдавала 2/22 на бенчмарке. Сижу, смотрю на вывод: модель пишет осмысленно, но не выполняет инструкции. Адаптер кривой? Шаблон чата сломан?

Оказалось — Qwen3 family по умолчанию пишет в thinking-поле, а не в response. То есть весь num_predict уходит в <think>...</think> блок, в response — пусто. Модель «думает», но не «отвечает».

Решение — TEMPLATE override в Modelfile с предзакрытым <think></think>:

TEMPLATE """{{- if .System }}<|im_start|>system{{ .System }}<|im_end|>{{ end }}{{- range .Messages }}<|im_start|>{{ .Role }}{{ .Content }}<|im_end|>{{ end }}<|im_start|>assistant<think></think>"""

Без этого: 2/22. С этим: 15/22. Один параметр в Modelfile = разница в 13 баллов на бенчмарке.

Если будете фантюнить Qwen3 family — обязательно проверьте этот Modelfile-фикс. Через Ollama API параметр называется think: false:

requests.post(f"{OLLAMA_URL}/api/generate", json={    "model": "oni:base-7.v2",    "prompt": prompt,    "stream": False,    "think": False,           # ← критично для Qwen3 family    "options": {"num_ctx": 16384, "num_predict": 4000}})

Что выложил в open source

Весь датасет, скрипты, бенчмарки, методология — лежит на GitHub:

👉 github.com/makarsuperstar/oni-devops-traces

Что там:

  • data/own_anchors/train.jsonl — 1867 hand-crafted multi-turn agent трейсов в JSONL формате

  • data/distilled_* — distilled subsets (распилка из Magicoder через локальный teacher gemma4:31b — об этом в следующей статье)

  • scripts/distill.py — скрипт дистилляции (~250 строк, только requests + pyyaml)

  • scripts/run_benchmark.py — composite scoring 8 метрик format compliance

  • meta/few_shot_reference.jsonl — 5 эталонных трейсов для few-shot prompting

  • meta/benchmark_results.md — полный отчёт teacher-бенчмарка

  • DATASET_FORMAT.md — спецификация формата

  • REPRODUCE.md — пошаговый гайд как пересобрать с нуля

Что я бы сделал иначе с самого начала

Сразу пошёл бы на 14B+ базу. Время, потраченное на qwen3:8b в попытках «уместить в 4080» — потеряно зря. Если задача агентная (multi-step + tool calling), 8B параметров просто мало. Лучше снять VM на ночь, чем месяц мучиться на маленькой модели.

Не начинал бы с HuggingFace mix raw. Это разрушило 3 итерации. Если фантюнишь агента — данные должны быть в формате агента (multi-turn). Иначе модель учится противоречию между single-turn и multi-turn.

TEMPLATE override в Modelfile — с первого дня. 3 ночи на диагностику «обучилось, но не работает» — этого можно было избежать одной строкой.

Сразу делал бы composite scoring. Я долго смотрел на loss + субъективную оценку. Когда добавил композитный 8-метричный scoring (json parses, has messages, tool call present, final answer present, verification before final и т.д.) — стало возможно сравнивать модели объективно.

Не игнорировал бы баланс датасета. Распределение трейсов по seed-ам у меня было от 1 до 30. Это означало 30× перекос. Из-за него base-8 начал hallucinate success на realworld step. Урок: кап на seed — обязателен.

Что дальше

  • Distillation pipeline — расширяем датасет через локальный teacher LLM (без облачных API), переоборудуем существующие открытые датасеты в наш agent-формат. Уже выложено distilled_bash_pipes (243 трейса) и distilled_django (199 трейсов). В очереди — express, microservices, ssh, docker_advanced, kubernetes, ci_cd, postgres. Подробнее в следующей статье.

  • Train base-10 на сбалансированных + distilled данных. Цель — Stage 1 SSH 19+/22, realworld 5/5 honest.

  • Telegram-бот @oni_devops_bot для публичного demo (через 2–3 недели, когда модель станет «не стыдно показывать»). Free 10 запросов/день.

  • OpenClaw integration — ради чего всё начиналось. Конфиг + Modelfile в репо, любой может подключить свою oni к openclaw-серверу.

Если интересно следить

Я веду building-in-public канал с короткими заметками про процесс, грабли, числа.

Репо с данными и скриптами: github.com/makarsuperstar/oni-devops-traces

В следующей статье — детально про SeedBuilder pipeline (как мы локально дистиллируем существующие датасеты в agent format) и сравнение 4 кандидатов на teacher-модель: Qwen 2.5 Coder vs Qwen 3.6 vs Gemma 4 vs DeepSeek Coder V2.

Если делаете что-то похожее, или хотите помочь с PR (например, добавить distillation из своего домена — Terraform, Ansible, Kubernetes-specific) — буду рад. Или просто скачайте JSONL и расскажите как он повёл себя у вас в SFT — фидбек ценнее звёзд.

— makarsuperstar

ссылка на оригинал статьи https://habr.com/ru/articles/1033128/