Переход на открытую модель - это миграция, а не правка конфига
Перевод автоматизации с платного API на свою open-weight модель кажется заменой одной строки. Рассказываю, по какому плану я переезжаю, чтобы сэкономить и не сломать продакшен.
Pavel Duglas
AI Automation & MVP Architect
Раз в пару недель мне пишет клиент примерно одно и то же: «Вышла новая открытая модель, влезает в одну видеокарту, по бенчмаркам почти как то, за что мы платим. Может, просто переключимся?» Честный ответ: да, часто можно. Но обычно это делают так: меняют base URL, подставляют новое имя модели, гоняют три тестовых prompt, видят приличный ответ и деплоят. Через две недели пайплайн извлечения начинает терять поля, классификатор плывёт, и никто не связывает это с переездом. Ответы же выглядели нормально.
Я отношусь к переносу задачи с закрытого API на open-weight модель так же, как к миграции базы данных. Есть состояние «до», состояние «после», способ доказать, что они эквивалентны, и путь назад. Ниже план, по которому я работаю на реальных проектах.
Почему все сейчас переезжают
Причины настоящие, и дело не только в деньгах:
- Стоимость на объёме. Узкая задача, которая крутится миллионы раз в месяц, - именно там счёт за API начинает по-настоящему болеть.
- Контроль над данными. Многие клиенты не могут отправлять договоры, медицинские записи или внутренние тикеты третьей стороне. У российских компаний это ещё и упирается в 152-ФЗ и требования не выносить персональные данные за периметр.
- Стабильность. Модель на своём сервере никто не обновит молча, не снимет с поддержки и не порежет по rate limit в ваш самый загруженный час.
- Задержка. Маленькая модель рядом с воркерами на коротких запросах легко обгоняет большую удалённую.
А вот недостаточные причины: скриншот лидерборда, пост про токены в секунду или смутное ощущение, что open source как-то правильнее. Это повод поставить эксперимент, а не мигрировать.
Шаг 1. Выберите одну задачу, а не всю систему
Не надо мигрировать «весь AI». Мигрируйте одну конкретную задачу. Хорошие кандидаты для первого раза совпадают по трём признакам:
- Большой объём. Если задача запускается 2 000 раз в месяц, экономия никогда не окупит потраченное время.
- Узкая постановка. Классификация, извлечение полей, маршрутизация, дедупликация, короткие переписывания. Всё, где есть понятный правильный ответ.
- Измеримый результат. Корректность можно проверить автоматически или дешёвой ручной проверкой.
Худшие кандидаты: агентные циклы с кучей инструментов, рассуждения над длинными грязными документами и всё клиентское, где важен тон, а что такое «хорошо», никто так и не сформулировал. Это пока оставьте на frontier API. Роутер моделей может отправлять туда сложные 10% запросов, а простые 90% пускать на ваш сервер.
Шаг 2. Eval-набор собирайте из продакшена, а не из головы
Главная ошибка - тестировать на prompt, которые вы написали руками. Ваши примеры чистые и аккуратные. Продакшен таким не бывает.
Что делаю я:
- Выгружаю из логов от 300 до 1 000 реальных запросов, с разбивкой по типам. Обязательно беру странные: пустые поля, смесь русского и английского, огромные входы, откровенный мусор.
- Рядом с каждым входом сохраняю ответ текущей модели. Это базовая линия, а не эталон.
- Прошу человека разметить 100-200 примеров как верные или неверные для обеих моделей. Именно здесь обычно выясняется, что текущая модель тоже ошибалась в 6% случаев.
- Для каждой задачи фиксирую метрику: точное совпадение извлечённых полей, точность меток для классификатора, долю валидного JSON для структурированного вывода.
На выходе получается нормальная сравнительная таблица вместо ощущений. «Новая модель: 94,1% точности по полям, старая: 95,3%, валидный JSON: 99,8% против 100%» - по этому можно принять решение. «Вроде примерно так же» - нельзя.
Шаг 3. Prompt придётся переносить
Prompt не переносятся между семействами моделей один в один. Вот что меняется при переезде.
Chat template и системный prompt
Открытые модели обучены на конкретном chat template. Если сервер применяет не тот шаблон, качество падает так, что кажется, будто модель просто глупая. Используйте шаблон, который идёт вместе с моделью, и один раз проверьте глазами итоговую последовательность токенов. Кроме того, небольшие модели слабее следуют системному prompt, поэтому важные инструкции, спрятанные в system, иногда стоит перенести ближе к самой задаче.
Структурированный вывод
Именно здесь ломается большинство моих миграций. Frontier API с JSON-режимом почти никогда не возвращает невалидный JSON. Маленькая открытая модель будет это делать в 1-3% случаев, а на пайплайне в 50 000 вызовов в день это очень много битых записей.
Лечится это не строгим prompt, а constrained decoding. Движки вроде vLLM и llama.cpp умеют принудительно ограничивать вывод JSON-схемой или грамматикой, и невалидный ответ становится невозможным на уровне токенов. Для всех задач извлечения я включаю это по умолчанию. Валидатор схемы при этом оставляю: валидный JSON вполне может содержать выдуманное значение.
Токенизаторы
У разных токенизаторов один и тот же текст может занимать на 10-30% больше токенов. Для русского текста разница бывает особенно заметной. Это ломает бюджеты контекста, которые вы аккуратно подбирали, а если чанкинг считает токены старым токенизатором, вход будет молча обрезаться. Пересчитайте всё новым токенизатором и поправьте размеры чанков.
Few-shot примеры
Маленькие модели опираются на примеры гораздо сильнее, чем frontier. Два-три хороших примера на сложные случаи часто закрывают половину разрыва в точности. Но они съедают контекст, так что измеряйте оба эффекта.
Шаг 4. Меряйте под реальной нагрузкой
Цифра «100 токенов в секунду на домашней видеокарте» почти всегда про генерацию одного запроса. Ваш пайплайн не обрабатывает один запрос. Он обрабатывает сорок параллельных воркеров в 9 утра, когда очередь забивается.
Что нужно измерить до решения:
- Пропускную способность на пиковой конкурентности в запросах в минуту, а не в токенах в секунду.
- p95 задержки, а не среднее. Очереди убивает хвост.
- Поведение на тех длинах контекста, которые у вас реально бывают. Длинные prompt съедают память и уменьшают число запросов, которые помещаются на карту одновременно.
- Влияние квантизации на точность. 4-битная модель влезает в железо подешевле, но прогоняйте eval-набор именно на той сборке, которую будете деплоить. Я видел, как 4-битная версия теряла два-три пункта на извлечении и при этом никак не проседала на классификации.
Используйте нормальный serving-движок с батчингом. Самописный скрипт-обёртка вокруг модели выдаст лишь малую долю того, что та же карта даёт за батчинг-сервером.
Шаг 5. Посчитайте экономику честно
Условный пример, похожий на то, что я часто вижу. Задача извлечения: 2 миллиона запросов в месяц, около 1 500 входных и 200 выходных токенов на запрос. По ценам API среднего уровня это может выйти примерно в $2 000 в месяц. Арендованная GPU, которая вытянет такую нагрузку, стоит где-то $1 000-1 200 в месяц при работе 24/7.
Выглядит как очевидная победа. Теперь добавим то, о чём обычно забывают:
- Простой. Если нагрузка неравномерная, вы платите за карту и в 3 часа ночи. Если только не построите автоскейлинг, а это отдельный проект.
- Резервирование. Одна GPU - это одна точка отказа. Две GPU съедают половину экономии.
- Время на эксплуатацию. Обновления драйверов, падения по памяти, мониторинг, чьи-то ночные алерты. Даже четыре часа в месяц сеньорского времени стоят денег.
- Счёт за резервный API. Он у вас останется для пиков и аварий.
В итоге реальная экономия в этом примере может составить 20-30%, а не 50%. На таком объёме это всё равно имеет смысл, а аргумент про контроль данных может оказаться важнее денег. На объёме в десять раз меньше это почти никогда не окупается. Моё грубое правило: если задача стоит на API меньше $500 в месяц, не поднимайте свой сервер только ради экономии.
Шаг 6. Выкатывайте как миграцию
Сначала shadow-режим
Отправляйте продакшен-трафик в обе модели, но используйте только ответ старой. Логируйте оба ответа и автоматически сравнивайте их по метрике из шага 2. Держите так неделю, обязательно захватив пиковые дни. Shadow-трафик находит типы входов, которые ваш eval-набор пропустил.
Поэтапное переключение
Переводите на новую модель 5%, потом 25%, потом 100% трафика. Следите за теми же метриками и за сигналами ниже по цепочке: как часто люди правят результат, сколько записей не проходит валидацию, сколько ретраев вы генерируете.
Оставьте путь назад
Путь через API остаётся в коде под флагом минимум на месяц. Настройте автоматический fallback: если ваш сервер не ответил по таймауту или выдал невалидный результат после повторной попытки с ограничением, запрос уходит в API. Тогда ваш бюджет ошибок превращается в строчку расходов, а не в аварию.
Зафиксируйте всё
Закрепите конкретный файл весов по хэшу, квантизацию, версию serving-движка и chat template. Считайте всё это вместе с prompt одним артефактом сборки. Весь смысл своего хостинга в том, что ничего не меняется, пока вы сами этого не захотите. Не выбрасывайте это преимущество, подтягивая «latest» при каждом деплое.
Чек-лист, который я прохожу перед тем, как сказать «да»
- Выбрана одна задача: большой объём, узкая постановка, измеримый результат
- Eval-набор собран из реальных логов и размечен человеком
- Prompt перенесён: правильный chat template, добавлены примеры, токены пересчитаны
- Для структурированного вывода включён constrained decoding, валидатор на месте
- Пропускная способность и p95 замерены на реальной конкурентности и на той самой квантизованной сборке
- Экономика посчитана с учётом простоя, резервирования, эксплуатации и резервного API
- Неделя в shadow-режиме, потом поэтапное переключение
- Fallback на API подключён и проверен по-настоящему: GPU-сервер выключали руками
- Веса, движок и шаблон зафиксированы и версионированы
Открытые модели сейчас действительно хороши, и для большой части задач автоматизации это правильный выбор. Но между «она ответила на мой тестовый prompt» и «она переваривает 50 000 грязных продакшен-запросов в день» лежит ровно та работа, из которой и состоит миграция. Сделайте её один раз как следует, и следующий переезд станет рутинной задачей, а не лотереей.
Вопросы и ответы
Можно ли просто поменять base URL, если сервер модели совместим с OpenAI API?
Технически да, код заработает. Но совместимость протокола не означает совместимость поведения. Другой chat template, другой токенизатор, другая склонность ломать JSON. Совместимый API экономит время на интеграции, но eval-набор, shadow-режим и поэтапный rollout всё равно нужны.
С какого объёма имеет смысл поднимать свою модель ради экономии?
Мой ориентир: если задача обходится на API дешевле $500 в месяц, переезжать только ради денег не стоит. Аренда GPU, резервирование, простой и время на эксплуатацию съедят разницу. Исключение - когда данные в принципе нельзя отправлять третьей стороне. Тогда вопрос не в экономии, а в том, можно ли вообще использовать модель.
Что делать, если открытая модель проигрывает текущей пару процентов точности?
Сначала попробуйте закрыть разрыв: добавить few-shot примеры на сложные случаи, проверить chat template, взять менее агрессивную квантизацию. Если разрыв остаётся, решайте на уровне бизнеса: сколько стоит каждая ошибка и сколько вы экономите. Часто разумный вариант - роутер, который отправляет сложные или неуверенные запросы в API, а основную массу обрабатывает своей моделью.
Похожие статьи
Сделаю под ключ
Соберу ИИ-агента под реальную задачу
С инструментами, памятью и логами, чтобы он работал в проде, а не только в демо.
от 70 000 ₽ · 1-2 недели