Как мониторить нагрузку локальной LLM
Модель отвечала за секунду, а через месяц реальной нагрузки ответ стал занимать восемь — и никто не может сказать, с какого дня это началось, потому что никто это не мерил. Нагрузка локальной LLM складывается из трёх независимых слоёв — железа, движка инференса и самих запросов, — и без метрик по каждому из них проблема обнаруживается по жалобе пользователя, а не по графику. Разберём, что снимать на каждом слое, какими инструментами и как читать получившиеся цифры.
Содержание
- Из чего складывается «нагрузка» локальной LLM
- Почему одного nvidia-smi и top недостаточно
- Инфраструктурный мониторинг: GPU-метрики поверх Prometheus
- Что отдают сами движки: ollama ps, /api/ps и метрики vLLM
- Langfuse: то, что видно только на уровне запроса
- Пороговые значения: как читать дашборд, а не просто смотреть на него
- Какой сервер и локацию брать под мониторинг LLM в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Из чего складывается «нагрузка» локальной LLM
«Нагрузка» — это не один процент CPU, а минимум четыре независимых показателя, и путать их между собой — самая частая ошибка мониторинга.
- GPU — загрузка вычислительных ядер и отдельно занятая видеопамять: это разные величины, и полная VRAM при невысокой загрузке ядер — признак упёртости в память, а не в вычисления.
- CPU и RAM — критичны и для GPU-инференса (токенизация, HTTP-слой, KV-кэш), и тем более для CPU-инференса, где это единственный ресурс.
- Диск — веса читаются с NVMe при каждой загрузке модели в память, а логи и трассировка накапливаются постоянно.
- Сам инференс-движок — сколько моделей загружено, есть ли очередь запросов, растёт ли задержка до первого токена.
Без метрик по всем четырём деградация выглядит как «сервер иногда тупит», а не как измеримая проблема. Хуже, если процесс уже упал: первым об этом узнаёт не мониторинг, а пользователь, у которого клиент вернёт curl: (7) Failed to connect to 127.0.0.1 port 11434: Connection refused. К этому моменту тренд по памяти или очереди запросов был виден заранее — просто на него никто не смотрел.
Почему одного nvidia-smi и top недостаточно
Первый инстинкт — открыть nvidia-smi или htop. Для разовой диагностики достаточно, для мониторинга — нет.
nvidia-smi без параметров — снимок на момент запуска команды. Есть режим наблюдения:
nvidia-smi dmon -s u -d 2
Он обновляется каждые две секунды, но это по-прежнему поток чисел в терминале: закрыли SSH-сессию — история пропала, и разобраться, что происходило ночью, нечем.
top/htop внутри контейнера обманывают ещё конкретнее. Если движок инференса запущен в Docker с лимитом --cpus=4 на 16-ядерном хосте, top внутри контейнера всё равно покажет все 16 ядер и посчитает проценты от них — реальная загрузка может упираться в лимит, а снаружи выглядеть как «всего 25%». Правильный источник — docker stats снаружи контейнера:
docker stats --no-stream ollama
Оба инструмента одинаково слепы к главному — у них нет истории. Без неё каждый инцидент расследуется заново, а закономерности вроде «деградация начинается ровно на пятом одновременном запросе» просто не видны. Нужен слой, который цифры пишет, а не только показывает.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть LangfuseИнфраструктурный мониторинг: GPU-метрики поверх Prometheus
База — Prometheus как хранилище метрик и Grafana как панель поверх него; если этой связки ещё нет, установка с нуля разобрана в статье Grafana и Prometheus на VPS: свой node_exporter для CPU, памяти и диска на порту 9100, готовый дашборд Node Exporter Full по ID 1860.
Для LLM-сервера этого мало — не хватает ровно того показателя, который отличает GPU-инференс от любого другого: видеопамяти. Лёгкий экспортёр поверх nvidia-smi добавляет её отдельным источником (нужен установленный nvidia-container-toolkit):
docker run -d --restart unless-stopped --gpus all -p 9835:9835 \
utkuozdemir/nvidia_gpu_exporter:1.2.1
Проверка: curl -s http://127.0.0.1:9835/metrics | grep nvidia_smi_utilization. Ключевая пара метрик оттуда — nvidia_smi_utilization_gpu_ratio (загрузка ядер) и nvidia_smi_utilization_memory_ratio (занятость VRAM): именно эта пара различает «упёрлись в вычисления» и «упёрлись в память».
Если сервер отдаёт инференс через vLLM, третий источник — его собственный /metrics на порту 8000, без дополнительного экспортёра. Все три цели собираются в один prometheus.yml:
scrape_configs:
- job_name: 'node'
static_configs:
- targets: ['localhost:9100']
- job_name: 'gpu'
static_configs:
- targets: ['localhost:9835']
- job_name: 'vllm'
metrics_path: /metrics
static_configs:
- targets: ['localhost:8000']
И правило алерта — чтобы узнать о проблеме до жалобы пользователя, а не после:
groups:
- name: llm-node
rules:
- alert: VRAMNearLimit
expr: nvidia_smi_utilization_memory_ratio > 0.9
for: 5m
labels:
severity: warning
Для GPU-экспортёра готовый дашборд Grafana ищите в каталоге на grafana.com по имени контейнера — панели под метрики nvidia_smi_* там уже собраны, рисовать с нуля не нужно.
Что отдают сами движки: ollama ps, /api/ps и метрики vLLM
Инфраструктурный слой не знает, что именно происходит внутри инференса — для этого смотрят в сам движок.
Ollama. ollama ps показывает, что реально загружено в память прямо сейчас:
NAME ID SIZE PROCESSOR UNTIL
gemma2:9b b1146d5aa3d2 7.4 GB 72%/28% CPU/GPU 9 minutes from now
Столбец PROCESSOR — первое, на что стоит смотреть: ожидали «100% GPU», а видите смешанный оффлоад, как в примере выше, — часть слоёв ушла на CPU из-за нехватки VRAM, и генерация заметно просядет. Для мониторинга полезнее не CLI, а JSON того же списка: curl -s http://127.0.0.1:11434/api/ps можно опрашивать по крону и слать алерт, если ожидаемая модель внезапно пропала из ответа — то есть выгрузилась раньше keep_alive или упала. Переменная OLLAMA_DEBUG=1 перед стартом добавляет в лог время каждого запроса отдельной строкой — без неё понять, что именно затормозило, можно только по косвенным признакам.
vLLM нативно отдаёт Prometheus-метрики на /metrics, без дополнительных экспортёров — сама установка и полный список метрик разобраны в статье про установку vLLM на VPS. Для мониторинга нагрузки важнее всего одна пара: vllm:num_requests_waiting, стабильно больше нуля, — очередь растёт быстрее, чем движок её разбирает, сигнал масштабироваться раньше, чем пользователи почувствуют таймауты; vllm:gpu_cache_usage_perc — заполненность KV-кэша, аналог VRAM-метрики, но именно для контекста запросов, а не весов модели.
Langfuse: то, что видно только на уровне запроса
Всё выше — это ресурсы: сколько занято железа и что творится внутри движка. Ответить на вопрос «а что вообще спрашивали и почему именно этот пользователь получил ответ за 12 секунд, а не за 2» инфраструктурные метрики не могут — агрегаты усредняют, а конкретный медленный запрос теряется в общем графике. Здесь начинается Langfuse: он трассирует каждый вызов отдельно — промпт, ответ, задержку до первого токена и полную, число токенов на входе и выходе — и позволяет резать это по пользователю, сессии и тегу. Перцентиль p95 по задержке в Langfuse почти всегда расскажет о нагрузке больше, чем средняя загрузка GPU: p50 может выглядеть прилично, пока часть пользователей стабильно ловит просадки при параллельных запросах.
В каталоге apps.maatrix.io Langfuse ставится автоматически при заказе сервера — под капотом у него связка из шести контейнеров (веб, воркер, Postgres, ClickHouse, Redis, S3-совместимое хранилище), собирать её руками не нужно, доступы к готовому инстансу появляются в личном кабинете. Подробный разбор архитектуры и установки — в статье «Как установить и настроить Langfuse на VPS», точный расчёт памяти под свой трафик — в материале «Сколько RAM нужно для Langfuse».
Подключение — на стороне вашего приложения, тремя переменными окружения:
export LANGFUSE_PUBLIC_KEY=pk-lf-...
export LANGFUSE_SECRET_KEY=sk-lf-...
export LANGFUSE_BASE_URL=https://langfuse.ваш-домен
Если запросы уже идут через шлюз вроде LiteLLM (его виртуальные ключи разбирали в отдельной статье) — трассировка включается вообще без кода в приложении, одной строкой success_callback: ["langfuse"] в litellm_settings. Честный момент: по умолчанию в трассу улетает весь текст промпта и ответа — если работаете с персональными данными, продумайте маскирование до включения трассировки в проде, а не после.
Пороговые значения: как читать дашборд, а не просто смотреть на него
Голые цифры без интерпретации бесполезны — важно, на что реагировать и когда решение не в железе.
VRAM растёт, загрузка ядер — нет. Если nvidia_smi_utilization_memory_ratio ползёт к единице, а nvidia_smi_utilization_gpu_ratio держится невысоко, — вы упираетесь в память, а не в вычисления. Более мощная карта по чистой производительности здесь не поможет: нужна модель с более агрессивным квантованием или карта с большим объёмом VRAM. Тот же симптом виден и на уровне движка — ollama ps покажет частичный CPU-оффлоад, как в примере из раздела выше.
Очередь не пустеет. vllm:num_requests_waiting, стабильно больше нуля дольше нескольких минут, — не повод крутить параметры движка, а прямой сигнал на горизонтальное масштабирование: добавить реплику или карту. Поднимать --max-num-seqs при заполненной очереди только доведёт до нехватки KV-кэша, а не ускорит ответ.
CPU занят, а токены еле идут. Если движок работает без GPU, все ядра хоста заняты почти под завязку, а скорость генерации не растёт вместе с загрузкой, — узкое место почти всегда пропускная способность памяти, а не число ядер: на каждый токен читаются все веса модели целиком, и это не задача, которую ускоряет ещё один поток. Проверяется той же связкой node_exporter плюс ollama ps, а не переустановкой сервера с большим числом vCPU.
Диск незаметно кончается. Ретеншен метрик в Prometheus (--storage.tsdb.retention.time) и растущий поток трасс в Langfuse без включённой чистки — оба тихо едят NVMe. Алерт на свободное место на диске — такая же обязательная часть связки, как алерт на память.
Какой сервер и локацию брать под мониторинг LLM в MAATRIX
Мониторинг — отдельная нагрузка поверх самой модели, и её стоит закладывать в конфигурацию заранее, а не добавлять по факту, когда Prometheus и Langfuse начнут конкурировать с моделью за одну и ту же память.
Сам стек метрик лёгкий: Prometheus, Grafana, node_exporter и GPU-экспортёр вместе укладываются в 2 vCPU и 2–4 ГБ RAM — ровно то, что требует связка Prometheus и Grafana для любого другого сервера. Langfuse тяжелее на порядок: документация проекта для всех шести контейнеров на одной машине называет ориентир от 4 ядер и 16 ГБ RAM — там уже колоночная база ClickHouse, Postgres, Redis и объектное хранилище разом, точный расчёт под свой поток трасс — в статье про RAM для Langfuse, ссылка выше. Поверх этого — сама модель: у нас на CPU-стенде модель уровня 7B в Q4 держит в памяти около 5 ГБ, точные цифры по моделям — в таблице «Сколько RAM нужно для Ollama», а под GPU-инференс — в материале «Выбор GPU-сервера для инференса LLM».
Честный минимум, если всё на одной машине: 6 vCPU, 24 ГБ RAM, 100 ГБ NVMe. Тесно, но реально: небольшая модель, лёгкий поток трасс, стек метрик рядом. Минус называем прямо — на одном сервере Langfuse, Prometheus и сама модель конкурируют за одну шину памяти, и всплеск нагрузки на любом из трёх задевает остальные два.
Разумный вариант — развести по машинам. Не только ради денег: если сервер с моделью падает под нагрузкой, вместе с ним обычно падает и мониторинг, который должен был объяснить, что случилось, — а это ровно те данные, что нужны после инцидента больше всего. Стек метрик и Langfuse на отдельном сервере переживают падение инференса и продолжают писать историю. Конфигурация под само наблюдение — 4 vCPU, 16 ГБ RAM, 80–100 ГБ NVMe; сервер с моделью считайте отдельно по ссылкам выше.
Локация — Великобритания (Лондон). В трейсах Langfuse по умолчанию лежит полный текст промптов и ответов пользователей — потенциально персональные данные, и держать эту копию рядом с юрисдикцией GDPR разумно для команды на европейскую аудиторию. Если сам инференс уже стоит в другой локации, ничего не мешает вынести именно наблюдение в Лондон отдельным сервером: Prometheus прекрасно опрашивает экспортёры на другом хосте по сети, а Langfuse просто принимает трассы по HTTP независимо от того, где физически считается модель.
Оплата — картой российского банка, по СБП, криптовалютой или токеном MAAT, без необходимости в иностранной карте. Сервер приходит чистым Ubuntu или Debian; Langfuse из каталога устанавливается автоматически и доступен из личного кабинета сразу после заказа, а Prometheus, Grafana и экспортёры — по командам из этой статьи.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть LangfuseОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
Обязательно ли ставить Langfuse, если уже есть Prometheus и Grafana?
Нет, это разные слои. Prometheus и Grafana показывают состояние ресурсов — GPU, память, очередь; Langfuse показывает, что происходило с конкретным запросом конкретного пользователя. Для контроля нагрузки хватает первого, для разбора качества ответов и стоимости нужен второй.
Нужен ли GPU-экспортёр, если модель работает только на CPU?
Нет. Ставьте node_exporter и следите за CPU и памятью в Grafana — GPU-метрики без видеокарты просто нечего собирать.
Можно ли собирать метрики с нескольких LLM-серверов в одну Grafana?
Да. Добавьте node_exporter и GPU-экспортёр каждого сервера отдельной целью в prometheus.yml той машины, где стоит Prometheus, а Langfuse и так агрегирует трассы с любого числа источников — достаточно указать всем один и тот же LANGFUSE_BASE_URL.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.