Своя модель
дороже API
Как я поднял локальную модель прямо на сервере, чтобы не платить за облако — и почему через сутки снёс её обратно, хотя она работала.
Решение напрашивалось: поставить между сторожем и Telegram умного фильтра, который отделит «это мы сами» от «это правда чужой». Маленькая классификация — на вход описание события, на выход одно слово: реальная угроза или ложняк. Ровно та работа, под которую языковая модель создана.
И тут была развилка. Можно дёрнуть облачный API — пара копеек, минута настройки. А можно поднять модель прямо на сервере: никакого внешнего провайдера, ничего не уходит наружу, и — как тогда казалось — бесплатно. У меня было требование держать как можно больше на своём железе, плюс соблазн сэкономить: фильтр — первая ласточка, за ним маячил целый выводок фоновых агентов. Сторож, разбор автоматических банов от CrowdSec, ещё с десяток мелких задач. Если у всех будет один общий локальный «мозг» — зачем вообще платить облаку?
Через сутки я этот локальный мозг снёс и вернулся на облачный API. Не потому что он не завёлся — он работал, и работал правильно. А потому что «бесплатно» на общем сервере оказалось самой дорогой опцией из всех. Вот эта история целиком — что ставил, на какие грабли наступил, и почему в итоге арифметика «экономии» перевернулась.
Что я поднимал
Связка простая и популярная: Ollama (это менеджер локальных моделей — качаешь модель одной командой, он поднимает её как локальный сервис, к которому ходишь по сети) плюс модель от Google семейства Gemma 4.
Сначала взял лёгкую версию — около 4 миллиардов параметров. Завелась, отвечала за 7–12 секунд. Но мне хотелось запаса по качеству на будущие задачи, и я переехал на старшую — 26-миллиардную, с архитектурой MoE. Сразу оговорюсь, чтобы не строить интригу на пустом месте: для моей задачки — выбор из двух вариантов — это был перебор, младшая модель справлялась не хуже. Старшую я брал на вырост, под будущий парк агентов, который так и не случился. Так что да, оверкилл — но именно на нём и вылезли все интересные грабли.
Тут стоит пояснить, потому что выбор был осознанный. MoE (mixture of experts, «смесь экспертов») — это когда в модели формально 26 миллиардов параметров, но на каждое слово работает не вся сеть, а маленькая её часть, около 4 миллиардов. Идея красивая: платишь за качество большой модели, а считаешь по цене маленькой. На практике, как выяснилось, «по цене маленькой» — это про вычисления, а не про память: все 26 миллиардов всё равно должны лежать в оперативке целиком. 17 гигабайт на диске. В памяти модель держалась постоянно — около 18 гигабайт, — а в момент ответа раздувалась до 23,7 гигабайта: на время вычисления она разворачивает поверх себя временные буферы. Запомните оба числа, к ним вернёмся — память тут и есть главный герой развязки.
Где пришлось повозиться
Прежде чем фильтр заработал как надо, я собрал три грабли. Рассказываю не для красоты — на каждую я потратил время, и каждая повторится у любого, кто пойдёт этим путём.
Первая: модель молчала. Шлю запрос — в ответ пусто. Не ошибка, не таймаут, просто пустой ответ с пометкой «закончились токены». Оказалось, современные модели по умолчанию сначала «думают» про себя, и только потом отвечают. Я выдал ей крошечный бюджет на ответ, она весь его потратила на невидимое мне размышление, и до собственно ответа дело не дошло.
Вторая: модель дралась сама с собой. Сервер — 12 ядер. Я выделил рантайму половину, 6 ядер, через системные лимиты. А Ollama эти лимиты проигнорировала и запустила 12 рабочих потоков — по числу всех ядер в системе. В итоге 12 потоков дрались за 6 ядер, и это замедляло работу. Когда я вручную сказал «ровно 6 потоков», скорость обработки промпта выросла на 43%.
Третья — самая ехидная: модель будила сама себя. Помните, фильтр работал внутри сторожа, который ловит скачки нагрузки? А языковая модель на процессоре во время ответа сама грузит процессор на 300–600%. Дальше — петля: сторож видит скачок нагрузки, зовёт фильтр; фильтр будит модель; модель грузит процессор; сторож видит новый скачок — уже от самой модели; зовёт фильтр снова. Сам себя кусающий за хвост дятел. Лечится тем, что процессы самого движка модели просто вычёркиваешь из наблюдения, чтобы сторож на них не реагировал, — но додуматься до этого можно только напоровшись.
После всех трёх фиксов фильтр прошёл мой тест-набор начисто: 12 кейсов из 12, семь ложных тревог отсеяны, пять реальных угроз пропущены дальше. Модель работала корректно. Можно было оставлять. Я снёс её на следующий день.
Почему я всё равно ушёл на API
И вот здесь начинается главное. Модель работала. Тест проходила. Денег за неё не платил. По всем признакам — оставлять. Но когда я посмотрел на полную картину, оказалось, что «бесплатная» локальная модель стоит дороже платного облака сразу по нескольким статьям, и ни одну из них нельзя закрыть инженерией.
Память. Те самые 23,7 гигабайта. На сервере всего 43 гигабайта, и на нём же живёт ещё десяток сервисов — база данных, кэш, графовая база, несколько бэкендов, боты. Когда модель встала в память постоянно (а для скорости она должна быть всегда наготове, иначе каждый запрос — это лишние секунды на её загрузку с нуля), свободной памяти на пиках оставалось около 400 мегабайт. Сервер начинал залезать в своп — это когда оперативки не хватает и система сбрасывает часть данных на медленный диск, отчего подтормаживает всё подряд. Любая тяжёлая операция — большая переиндексация, активная сессия — упиралась в потолок. Запас прочности стал нулевым. Модель не просто заняла память: она отобрала её у всего остального.
Скорость. Локально один запрос — 5–12 секунд, тот же запрос через облачный API — меньше полутора. Уже в несколько раз быстрее на каждом обращении. А на полном прогоне разрыв ещё шире: весь мой тест-набор локальная модель молотила две с половиной минуты — запросы вставали в очередь друг за другом, процессор-то один; API тот же набор отдал за одиннадцать секунд, потому что обрабатывал их разом, не упираясь в одно железо. Для единственного фильтра 5–12 секунд ещё терпимо. Для той самой мечты про десяток фоновых агентов это уже очередь на минуты.
Деньги — и вот тут переворот. Я считал, что экономлю. Посчитал честно. Мой фильтр гонит около четверти миллиона токенов в месяц (токен — это примерно три четверти слова). Через облако это стоит меньше десяти центов в месяц. Даже весь воображаемый парк агентов — это единицы долларов. А «бесплатная» локальная модель стоила 23,7 гигабайта оперативки и 20 гигабайт диска — ресурсов, которые на общем сервере были нужнее десяти другим сервисам. Я экономил копейки, расплачиваясь самым дефицитным, что есть на машине. Это не экономия, это размен рубля на копейку.
Железо как потолок. И это не докрутить. На сервере нет видеокарты, а процессор — без специальных инструкций для быстрых вычислений (того, что в больших моделях даёт основной разгон). Без этого упираешься в жёсткий предел скорости, который не сдвинуть никакими настройками — только сменой железа. Я даже проверил, не станет ли быстрее, если убрать Ollama и взять более низкоуровневый движок напрямую: разница в пределах нескольких процентов. Узкое место не в обвязке, а в самом процессоре.
Когда я снёс локальную модель, сервер вернул себе 18,5 гигабайта оперативной памяти (ровно столько она держала постоянно) и 20 гигабайт диска. А облачный фильтр на том же тест-наборе показал ровно ту же точность — 12 из 12. Для выбора из двух вариантов — свой или чужой — качество неотличимо. Я отдал кучу ресурсов и скорости ради приватности, которая этой конкретной задаче была не нужна: сами-то тревоги и так уходили в Telegram, секретов в них нет. Заплатил ресурсами за сейф, в котором нечего прятать.
Что у сторожа внутри — и что на самом деле поменялось
Раз уж речь зашла о переезде с локальной модели на облако — покажу, как сторож устроен под капотом и что в нём реально пришлось переписать. Это тот технический разбор, ради которого половина читателей сюда и пришла. Спойлер: переписывать пришлось куда меньше, чем кажется.
Сначала про саму архитектуру, иначе непонятно, где там вообще место для ИИ. Сторож фильтрует события в два рубежа, и модель — только второй из них.
Первый рубеж — дешёвый текстовый фильтр, без всякого ИИ. У сторожа есть список заведомо своих процессов и портов — то, что на машине крутится легально. Каждое событие сначала прогоняется через этот список простым сопоставлением текста. Очевидно «своё» отсекается мгновенно и бесплатно, и так уходит большая часть потока. Звать модель на каждый чих — дорого и незачем; она нужна только для того, что простой список не разрулил.
Второй рубеж — ИИ-судья. Всё, что прошло первый фильтр и осталось непонятным, уходит модели. На вход — описание события плюс короткая справка о том, какие процессы и порты на сервере легитимны (она уезжает в системный промпт). На выход — одно слово: реальная угроза или ложная тревога. Ответ я разбираю нарочно грубо — просто выдёргиваю из текста «реальная» или «ложная», не требуя точного формата: модель с включённым рассуждением любит добавить точку или лишнюю строку, и придираться к формату значит ловить пустые сбои на ровном месте.
И две вещи, без которых ИИ в охранной задаче ставить нельзя. Fail-open: если модель почему-то не ответила (таймаут, пустота, не разобрался формат) — сторож шлёт тревогу без фильтрации. Лучше лишний пуш в Telegram, чем пропущенное вторжение, пока ИИ молчит. Обходные категории: для самого опасного — новый SSH-ключ, новый незнакомый сервис — ИИ не зовётся вообще. Такое уходит в тревогу всегда: цена пропуска тут слишком высока, чтобы доверять её вероятностной модели.
А теперь — собственно разница между «было» и «стало».
Самое важное в этой картинке — насколько мало пришлось трогать. Вся логика сторожа — два рубежа, fail-open, обходы — не зависит от того, где живёт модель. Переезд с локали на облако свёлся к смене адреса и способа авторизации: правка конфига, а не переписывание логики. Провайдера и модель я с тех пор переключаю через переменные окружения, вообще не лазая в код и не пересобирая. Сравните с теми сутками, что ушли на выхаживание локальной Gemma.
И это ещё один довод в пользу облака, которого не видно в таблице со стоимостью: уйти на API почти ничего не стоит, и — что важнее — вернуться обратно тоже. Локальная модель привязывает тебя к железу; вызов по стандартному формату оставляет дверь открытой в обе стороны.
Когда локальная модель всё-таки оправдана
Из всего этого легко сделать неправильный вывод «облако всегда лучше». Нет. Локальная модель проиграла в моих условиях — слабое железо, общий сервер, задача без чувствительных данных. Поменяйте условия — и ответ перевернётся обратно. Я держу в голове ровно три ситуации, в которых вернусь к идее.
| Подход | Цена в месяц | Задержка | Когда брать |
|---|---|---|---|
| Облачный API (что выбрал) | меньше $5 | 0,7–1,5 с | по умолчанию, пока данные не запрещено слать наружу |
| Локальная модель на общем CPU-сервере | $0 деньгами, но 23,7 ГБ памяти | 5–12 с, очереди на пиках | почти никогда: память дороже денег |
| Свой сервер с видеокартой | порядка $200 | 0,5–2 с | если данные по закону нельзя в облако |
Первый триггер — данные, которые нельзя отдавать наружу: персональные данные под закон, чужие приватные репозитории, медицина. Тогда вопрос экономии вообще снимается, и это уже не «бесплатно на общем сервере», а отдельная арендованная машина с видеокартой под одну эту задачу — порядка двухсот долларов в месяц за то, чтобы данные не покидали твой контур.
Второй — если облачные провайдеры резко подорожают или закроются. Маловероятно на горизонте года, но это реальный риск, который стоит держать в уме.
Третий — если объём вырастет в сотню раз и копейки за API превратятся в ощутимые суммы. На моём масштабе фоновых задач этого не случится, но на чужом — запросто.
Пока ни один из трёх не сработал — остаюсь на API.
Что я с этого унёс
Переписанный под облако фильтр стал у меня шаблоном для любого нового фонового агента: подключаешь провайдера, отдаёшь задачу, платишь центы. А локальный эксперимент оставил после себя вывод, который я теперь применяю до того, как что-то поднимать.
«Локальное» и «бесплатное» — не синонимы, особенно на общем сервере. У облачного вызова цена честная и видимая: вот счёт. У локальной модели цена спрятана — она не в деньгах, а в оперативке, диске, скорости и в том, что ты отбираешь ресурсы у всех остальных сервисов на машине. У меня эта вторая, спрятанная цена оказалась в разы выше первой.
Поэтому правило на выходе простое: считай не «сколько я не заплачу за API», а «сколько у меня заберёт своя модель — памяти, скорости, запаса прочности», и сравнивай эти две цифры, а не одну. На обычном сервере без видеокарты в девяти случаях из десяти дешёвый облачный вызов выйдет выгоднее «бесплатной» своей модели. Десятый — это когда данные нельзя выпускать наружу: вот тогда берёшь отдельное железо с видеокартой и платишь за приватность сознательно.
// Обсуждение
Можно писать анонимно. Укажите email, чтобы получать уведомления об ответах.