MAATRIX / Блог / Что делать, если модель отвечает медленнее с каждым днём

Что делать, если модель отвечает медленнее с каждым днём

MAATRIX

Если ИИ-сервис упал целиком — всё понятно: смотрим логи, ищем ошибку, чиним. Гораздо неприятнее другой сценарий: модель не падает, а месяц за месяцем отвечает всё медленнее — было полторы секунды на запрос, стало пять, через месяц будет десять. Никто не менял конфиг, никто не трогал сервер, а скорость ползёт вниз. Это разбор именно постепенной деградации: какие причины стоит проверять в первую очередь и как отличить одну от другой, вместо того чтобы гадать и перезапускать сервис вслепую.

Почему постепенная деградация — это не то же самое, что отказ

Резкий отказ обычно имеет одну точку во времени и один явный триггер: обновили драйвер, кончилось место на диске, упал контейнер. Постепенная деградация устроена иначе — это тренд, растянутый на недели, и у него почти всегда есть накопительная причина: что-то в системе монотонно растёт (объём данных, число пользователей, потребление памяти процессом) и медленно тянет время ответа вверх.

Ключевая практическая мысль: если деградация постепенная, значит, где-то в системе есть накопитель — переменная, которая с каждым днём становится немного больше и никогда сама не уменьшается. Задача диагностики — найти именно этот накопитель, а не перебирать случайные гипотезы. Ниже — четыре наиболее вероятных кандидата и как проверить каждый по отдельности.

Причина 1: растёт объём контекста, который модель обрабатывает на каждый запрос

Это самая частая причина в продакшен-системах с RAG или с сохранением истории диалога. Логика простая: чем больше токенов модель должна прочитать перед тем, как начать генерировать ответ, тем дольше идёт стадия prefill, и итоговая задержка растёт — даже если сама модель и железо не изменились ни на йоту. Подробно о том, почему рост контекста напрямую бьёт по памяти и скорости, разобрано в статье про цену контекстного окна в памяти и скорости.

Где именно копится этот рост:

  • База знаний RAG-системы. Каждую неделю в неё добавляют новые документы, эмбеддинги накапливаются, retriever начинает возвращать больше релевантных чанков или чанки становятся объёмнее. Если k (число чанков в контексте) фиксировано, но сами чанки стали длиннее из-за изменений в пайплайне чанкинга — контекст растёт незаметно.
  • История диалога. Если система хранит и переиспользует всю переписку с пользователем (a не только последние N сообщений), у активных пользователей с историей в 200 сообщений средний контекст на порядок больше, чем у новых. Чем дольше живёт сервис, тем больше доля "старых" пользователей с длинной историей.
  • Системный промпт, который со временем оброс инструкциями. Каждое исправление добавляет пару строк в system prompt — это тоже накопитель, просто более медленный.

Как проверить: замерьте средний размер контекста (в токенах) по логам запросов за последние 4-8 недель, по неделям. Практически это можно сделать, если у вас логируется prompt_tokens (для OpenAI-совместимого API) или число токенов на входе (для vLLM/TGI это есть в метриках /metrics). Простой скрипт:

# средний prompt_tokens по дням из access-лога с JSON-полями
jq -r 'select(.prompt_tokens) | "\(.date) \(.prompt_tokens)"' app.log \
  | awk '{sum[$1]+=$2; cnt[$1]++} END {for (d in sum) print d, sum[d]/cnt[d]}' \
  | sort

Если график среднего размера контекста растёт с той же скоростью, что и график задержки ответа — вот ваш накопитель.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Причина 2: растёт реальная нагрузка без соответствующего масштабирования

Вторая частая причина — сервис становится популярнее (или просто добавили новых внутренних пользователей), а инфраструктура осталась той же. Один GPU или один инстанс модели держит ограниченное число одновременных запросов — при превышении этого предела запросы встают в очередь, и субъективная задержка растёт, хотя сама модель отвечает с прежней скоростью на каждый отдельный запрос. Про предел по пользователям на одну карту — в статье сколько пользователей выдержит одна видеокарта, про механику самой очереди — в статье про очередь запросов к локальной LLM.

Здесь важно различать два разных времени:

  • Время генерации — сколько модель реально работает над конкретным запросом (обычно стабильно, если контекст и параметры не менялись).
  • Время в очереди — сколько запрос ждёт своей очереди на выполнение до того, как GPU вообще начнёт им заниматься.

Пользователь видит сумму этих двух чисел и не различает их. Если растёт именно время в очереди — дело не в модели, а в нагрузке.

Как проверить: если используете vLLM или TGI, оба отдают метрику числа запросов в очереди (num_requests_waiting у vLLM) через /metrics в формате Prometheus. Смотрите тренд этой метрики по неделям:

curl -s http://localhost:8000/metrics | grep num_requests_waiting

Параллельно стоит смотреть число уникальных активных пользователей в день/неделю (из логов или биллинга) и загрузку GPU по времени (nvidia-smi dmon -s u в непрерывном режиме, агрегированное по дням). Если очередь и число пользователей растут синхронно с задержкой — это причина №2, а не №1 и не №3.

Причина 3: медленная утечка памяти в процессе инференса

Третий вариант — классическая утечка памяти, только растянутая во времени: процесс инференса (или обвязывающий его код — веб-сервер, планировщик очереди, кэш эмбеддингов) с каждым обработанным запросом теряет немного памяти безвозвратно. Пока памяти много — утечка незаметна. Когда свободной RAM/VRAM становится мало, начинается своп, фрагментация аллокатора, более частый и более дорогой garbage collection — и всё это напрямую замедляет обработку запросов, до следующего перезапуска процесса, после которого скорость снова временно восстанавливается.

Общий принцип — рост потребления памяти почти всегда является ранним сигналом проблемы задолго до полного отказа, это подробно разобрано в статье про три сигнала проблемы до падения. В нашем случае сигнал тот же, только вместо падения на конце — деградация скорости.

Как проверить: логируйте RSS-память конкретного процесса инференса каждый час на протяжении нескольких недель и стройте график.

# крон-задача раз в час
PID=$(pgrep -f "vllm.entrypoints" | head -n1)
echo "$(date -Iseconds) $(ps -o rss= -p $PID)" >> /var/log/inference-rss.log

Признак утечки — монотонный рост RSS без выхода на плато после прогрева (первые часы после старта процесс закономерно растёт, пока не заполнит кэши — это нормально; проблема, если рост продолжается неделями без остановки). Сопоставьте этот график с графиком задержки ответа: если оба растут с одинаковым темпом и оба обнуляются после планового перезапуска процесса — утечка памяти подтверждена.

Отдельно стоит проверить, не растёт ли своп (vmstat 1 или free -h в динамике) — если система начала активно свопить из-за нехватки RAM, это само по себе объясняет замедление независимо от того, есть ли утечка в самом процессе инференса.

Причина 4: деградация диска, если он участвует в обработке запроса

Четвёртая, менее частая, но реальная причина — диск. Она актуальна, если веса модели или KV-кэш частично выгружаются на диск (offloading при нехватке VRAM/RAM), если диск используется для подкачки (своп на SSD/NVMe), или если growing RAG-база хранится на диске и постепенно заполняет том, приближаясь к порогу, после которого многие файловые системы и SSD-контроллеры заметно теряют производительность на запись.

Практически это проявляется так: диск, заполненный на 60%, работает быстро; тот же диск, заполненный на 92%, может davать заметно более высокую задержку записи — особенно на SSD/NVMe без TRIM или с исчерпанным запасом overprovisioning. Если growing векторная база или кэш эмбеддингов постепенно съедают свободное место, деградация диска растягивается на месяцы и совпадает по темпу с деградацией ответов модели.

Как проверить:

# место на диске по неделям (можно логировать в крон)
df -h /path/to/model-cache

# честный замер latency диска под нагрузкой, а не просто throughput
fio --name=randread --rw=randread --bs=4k --size=1G --numjobs=4 --runtime=30 --time_based

# текущая загрузка и задержка диска в реальном времени
iostat -x 1

Смотрите на %util и await в iostat — если они растут по неделям вместе с ростом занятого места на диске, диск — вероятный виновник. Про сам эффект см. в статьях про медленный диск на VPS и про общие правила fio-замера.

Как сопоставить тренды и найти реальную причину именно вашего случая

Правильная методика — не перебирать причины по очереди наугад, а с самого начала собирать все четыре метрики параллельно, на одной временной шкале, и потом визуально или в таблице сопоставить их тренд с трендом задержки ответа.

Практический план на 2-4 недели:

  1. Заведите единый лог метрик, который пишется раз в час: средний prompt_tokens за последний час, число запросов в очереди (или число одновременных пользователей), RSS процесса инференса, %util и await диска, свободное место на диске.
  2. Параллельно логируйте контрольный синтетический запрос — один и тот же фиксированный промпт фиксированной длины, отправляемый в систему раз в час, желательно в момент минимальной нагрузки (например, ночью). Время ответа на этот контрольный запрос — это ваша "чистая" метрика скорости, минимально зависящая от чужой очереди и контекста.
  3. Через 2-4 недели постройте пять графиков на одной оси времени: реальная задержка ответа пользователей, средний размер контекста, длина очереди/число пользователей, RSS процесса, состояние диска.
  4. Найдите визуальное совпадение темпа роста. Если реальная задержка растёт, а контрольный синтетический запрос (ночью, при пустой очереди, с фиксированным контекстом) остаётся стабильным — значит, дело не в самой модели и не в утечке, а в контексте или очереди днём. Если же и контрольный запрос замедляется — вероятнее утечка памяти или деградация диска, потому что оба этих фактора не зависят от того, чей это запрос и какой у него контекст.
  5. Не делайте выводов по одной неделе данных — рост может быть неравномерным (например, RAG-базу пополнили один раз большим импортом), поэтому важен именно устойчивый тренд, а не разовый скачок.

Таблица для быстрой ориентации, какая метрика какую причину подтверждает:

Метрика растётКонтрольный ночной запросВероятная причина
prompt_tokensстабиленрост объёма контекста (п.1)
очередь/пользователистабилен ночью, растёт днёмрост нагрузки (п.2)
RSS процессарастёт даже ночьюутечка памяти (п.3)
await диска, занятое месторастёт даже ночьюдеградация диска (п.4)

Перезапуск процесса — это временное облегчение, а не решение

Если диагностика указала на утечку памяти, самый быстрый способ вернуть скорость — плановый регулярный перезапуск процесса инференса: раз в сутки или раз в несколько дней, по расписанию, в момент минимальной нагрузки.

# пример юнита systemd с автоперезапуском по таймеру
# /etc/systemd/system/inference-restart.timer
[Unit]
Description=Плановый перезапуск инференса для сброса накопленной утечки

[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=true

[Install]
WantedBy=timers.target

Это действительно работает — скорость после перезапуска возвращается к исходной. Но важно называть вещи своими именами: это лечение симптома, а не устранение причины. Если утечка реальна, она гарантированно вернётся после каждого перезапуска — просто снова начнёт копиться с нуля, и через какое-то время вы снова упрётесь в тот же порог замедления. Регулярный перезапуск может быть разумной временной мерой, пока идёт настоящее расследование (профилирование памяти процесса, поиск конкретного объекта или кэша, который не освобождается — например, через memory_profiler для Python-обвязки или через мониторинг фрагментации аллокатора), но полагаться на него как на постоянное "решение" — значит откладывать работу над корневой причиной бесконечно, при этом с каждым релизом рискуя, что накопление будет расти быстрее, чем успевает "прощать" перезапуск.

Если у вас нет ресурсов на полноценный профайлинг прямо сейчас — плановый перезапуск лучше, чем ничего, и точно лучше, чем ждать полного отказа под нагрузкой. Но зафиксируйте это как временную меру в задачах, а не закрывайте тикет.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Как быстро отличить рост контекста от роста очереди, если нет времени на многонедельный сбор метрик?

Отправьте один и тот же короткий фиксированный запрос ночью, в момент минимальной нагрузки, и сравните его время ответа с дневным. Если ночью быстро, а днём медленно — это очередь/нагрузка. Если медленно даже ночью — контекст, утечка или диск, и здесь уже нужно смотреть на prompt_tokens, RSS и диск по отдельности.

Утечка памяти в самой модели (весах) реальна или это всегда обвязка?

Сами веса модели статичны и не текут. Утечка почти всегда в KV-кэше (если он не освобождается корректно между запросами), в кэшах эмбеддингов, в очереди задач на Python/Node-стороне или в библиотеках, управляющих батчингом. Проверяйте в первую очередь обвязывающий код, а не саму inference-библиотеку — хотя баги случаются и там.

Можно ли доверять одному общему графику CPU/RAM из панели хостинга, или нужен отдельный мониторинг?

Общий график полезен для первого впечатления, но для этой диагностики нужны более точные метрики: RSS конкретного процесса (а не всей системы), число токенов на запрос, глубина очереди конкретного инференс-сервера. Панель хостинга обычно этого не даёт — нужен Prometheus + node_exporter (для системных метрик) плюс метрики самого инференс-сервера (/metrics у vLLM/TGI).

Если RAG-база продолжит расти, деградация неизбежна навсегда?

Нет — если рост контекста подтверждён как причина, решение не в перезапусках, а в ограничении контекста: например, top-k retrieval с более строгим порогом релевантности, реранкинг перед подачей в модель, суммаризация старой истории диалога вместо хранения полного текста, или отдельный меньший/быстрый индекс для "горячих" данных.

Стоит ли сразу переезжать на более мощное железо, если видна деградация?

Только после диагностики. Если причина — утечка памяти или заполненный диск, более мощное железо просто отодвинет момент, когда деградация станет заметной, но не устранит саму причину — вы получите ту же кривую замедления, просто более пологую.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →