llama.cpp: метрики та моніторинг з VictoriaMetrics
0 (0)

Автор |  21/08/2026
Click to rate this post!
[Total: 0 Average: 0]

Є у нас сервер, на якому будемо запускати self-hosted LLM. Довго вибирали на чому саме запускати моделі – vLLM, SGLang, або llama.cpp, і врешті-решт зупинились саме на llama.cpp – принаймні на даний момент.

В пості NixOS: знайомство, установка пакетів та конфігурація системи як раз описана установка Node Exporter на NixOS, де у нас будуть запускатись LLM, ще є чорнетка по NVIDIA DCGM Exporter та FluentBit і VictoriaLogs, може допишу.

А сьогодні тема теж по моніторингу – але моніторингу саме llama.cpp.

Сервер ще не в production, реального трафіку нема, тому і графіки не дають якоїсь картини – але для початку просто розберемось з метриками і познайомимось з тим, що ми можемо побачити по роботі llama.cpp.

Що будемо робити:

  • розберемось з деякими нюансами того, як llama.cpp віддає метрики
  • глянемо які основні метрики вона повертає
  • налаштуємо збір метрик до VictoriaMetrics – бо тут теж є нюанс
  • і глянемо приклад Grafana dahshboard

Аби llama.cpp почала генерацію метрик – при запуску додаємо опцію --metrics, ендпоінт – стандартний, /metrics.

Але далі є один важливий момент в тому, як саме ці метрики отримувати.

llama.cpp: метрики в single-model vs router mode

llama.cpp може працювати в двох режимах:

  • single-model: один інстанс llama.cpp обслуговує запити до одної моделі
  • router mode: в такому режимі вона має декілька сконфігурованих моделей, які запускає при потребі

Див. New in llama.cpp: Model Management.

При роботі в single-model всі метрики доступні стандартно при запитах до /metrics.

Але при роботі в router mode просто запит до /metrics поверне помилку 400:

# curl 'http://127.0.0.1:31000/metrics'
{"error":{"code":400,"message":"model name is missing from the request","type":"invalid_request_error"}}

Бо в цьому режимі llama.cpp очікує параметр ?model=<MODEL_NAME>:

# curl 'http://127.0.0.1:37259/metrics?model=gemma-4-26b-a4b-it-q8'
# HELP llamacpp:prompt_tokens_total Number of prompt tokens processed.
# TYPE llamacpp:prompt_tokens_total counter
llamacpp:prompt_tokens_total 26
# HELP llamacpp:prompt_seconds_total Prompt process time
# TYPE llamacpp:prompt_seconds_total counter
llamacpp:prompt_seconds_total 0.093
...

Є і інший варіант: при роботі в router mode кожна модель запускається окремим інстансом (процесом) llama.cpp і слухає окремий порт:

# ps aux | grep 'llama-server'
[...] /nix/store/3vq7fxic7zaznzsip9zwbj4nblxfqpk5-llama-cpp-cuda-0.0.0/bin/llama-server --metrics --no-ui --host 0.0.0.0 --port 31000 --models-dir /srv/llm/models/llama-cpp/local --models-preset /nix/store/4g0chnaiidzbz25izygnxf1lj2rqwx78-matrix-llama-models.ini --models-max 3
[...] /nix/store/3vq7fxic7zaznzsip9zwbj4nblxfqpk5-llama-cpp-cuda-0.0.0/bin/llama-server --host 127.0.0.1 --metrics --port 37259 --no-webui --alias gemma-4-26b-a4b-it-q8 --ctx-size 65536 --device CUDA0 --flash-attn on --fit off --model /srv/llm/models/gguf/gemma-4-26b-a4b-it-q8-gguf/gemma-4-26B-A4B-it-Q8_0.gguf --n-gpu-layers all --parallel 1 --split-mode none

Тут на порту 31000 маємо “master process”, який, власне, займається роутингом запитів до моделей, а другий процес на порту 37259 приймає запити до Gemma-4.

Тому можна просто отримувати метрики через 127.0.0.1:37259/metrics:

# curl -s http://127.0.0.1:37259/metrics
# HELP llamacpp:prompt_tokens_total Number of prompt tokens processed.
# TYPE llamacpp:prompt_tokens_total counter
llamacpp:prompt_tokens_total 83
# HELP llamacpp:prompt_seconds_total Prompt process time
# TYPE llamacpp:prompt_seconds_total counter
llamacpp:prompt_seconds_total 0.36
...

Втім, цей варіант не дуже надійний, бо для кожної моделі треба знати порти, які можуть змінюватись – тому краще використовувати саме “мастер-інстанс”.

Тут, звісно, є свої складності і підхід не дуже зручний, і є GitHub Feature Request [Feat Request] Aggregated Prometheus metrics endpoint in router mode на те, щоб мати агрегований ендпоінт – може, колись і змінять цю поведінку.

Бо чисто імхо – це, звісно, доволі дивне рішення: чому не можна було просто зробити ім’я моделі в лейблі і повертати метрику типу llamacpp:prompt_tokens_total{model_name="gemma-4-26B"}?

Але розробникам llama.cpp видніше, якісь причини зробити саме так, як є зараз явно були, тому маємо те, що маємо.

Метрики llama.cpp

Звісно, краще було б спочатку поганяти llama.cpp локально і подивитись на її налаштування взагалі, бо я з локальних рантаймів грався тільки з Ollama, і це було давно (див. AI: знайомство з Ollama для локального запуску LLM), але зараз на це нема часу – тому розберемось з метриками “на льоту”.

Документація по метрикам – GET /metrics: Prometheus compatible metrics exporter, можливо будуть зміни, але на сьогодні з цікавого маємо такі:

  • llamacpp:prompt_tokens_total: загальна кількість токенів вхідних промптів, оброблених від моменту запуску сервера
  • llamacpp:prompt_seconds_total: сумарний час у секундах, витрачений на обробку вхідних промптів від моменту запуску сервера
  • llamacpp:prompt_tokens_seconds: середня швидкість обробки промптів в токенах на секунду (більше значення – швидше обробка)
  • llamacpp:tokens_predicted_total: скільки токенів згенеровано для відповідей
  • llamacpp:tokens_predicted_seconds_total: скільки часу витрачено на генерацію відповідей
  • llamacpp:predicted_tokens_seconds: аналогічно до prompt_tokens_seconds – середня швидкість генерації відповіді в токенах на секунду (більше токенів в секунду згенеровано – швидше генерація)
  • llamacpp:requests_processing: скільки запитів від клієнтів оброблюється зараз
    • корисно було б порівнювати із загальною кількістю слотів (задається в --parallel – максимальна кількість паралельної обробки запитів), але окремої метрики на це (поки що?) нема
    • можна отримати значення з GET /props і поля total_slots, або GET /slots – порахувати кількість елементів, або просто читати значення --parallel з конфігу
  • llamacpp:requests_deferred: кількість запитів, які зараз очікують на початок обробки (наприклад, через нестачу ресурсів чи зайнятість слотів)
  • llamacpp:n_tokens_max: найбільша кількість токенів в контекстному вікні
    • тут теж було б цікаво порівнювати з розміром самого контексту (значення з --ctx-size) – але в метриках теж нема
  • llamacpp:n_decode_total: кількість запусків обчислень моделі (виклики функції llama_decode())  для обробки вхідних або згенерованих токенів
    • або, умовно кажучи – скільки раз llama.cpp “прогнав” дані через модель
    • маємо на увазі, що один виклик llama_decode() може обробляти одразу кілька токенів і запити з кількох слотів – а тому ця метрика не дорівнює кількості токенів або запитів
    • див. The Core Llama Class — Request Lifecycle
  • llamacpp:n_busy_slots_per_decode: середня кількість зайнятих слотів на один виклик llama_decode()
    • відображає ефективність continuous batching – об’єднання токенів із кількох паралельних запитів в один пакет для обробки моделлю (див. Continuous Batching: Optimizing LLM Inference Throughput)
    • якщо n_busy_slots_per_decode ~1 – запити оброблюються послідовно, якщо більше – то запити об’єднуються в batches, які потім передаються до моделі через виклик llama_decode()
  • llamacpp:spec_decode_num_draft_tokens_total: кількість токенів, запропонованих draft-моделлю при використанні Speculative Decoding
    • при використанні speculative decoding запит спочатку передається простішій, “допоміжній” моделі, яка швидко генерує кілька наступних токенів, а основна модель потім перевіряє їх одним batch і приймає ті, що підходять
    • задається опцією --spec-type, але у нас (поки?) не використовується
  • llamacpp:spec_decode_num_accepted_tokens_total: кількість токенів, запропонованих draft-моделлю, і які були прийняті основною LLM
    • чим вище значення тут – тим ефективніша робота draft model і speculative decoding
  • llamacpp:spec_decode_num_drafts_total: кількість кроків перевірки припущень draft-моделі
  • llamacpp:spec_decode_num_accepted_tokens_per_pos_total: кількість прийнятих токенів для кожної позиції у припущені, які пропонує draft model

Збір метрик llama.cpp до VictoriaMetrics

Тут основна складність як раз в тому, що нам треба передавати конкретну модель для отримання метрик.

Опція 1: VMAgent та static_configs

Найпростіший варіант – просто описати кілька targets в scrape job, і кожній вказати власний параметр з іменем моделі.

Перевіряємо всі моделі, які зараз запущені:

# curl -sS http://127.0.0.1:31000/v1/models \
  | jq -r '.data[] | select(.status.value == "loaded") | .id'
gemma-4-26b-a4b-it-q8

Додаємо нову job до VMAgent:

- job_name: matrix-llama-cpp
  metrics_path: /metrics
  params:
    model: ["gemma-4-26b-a4b-it-q8"]
    autoload: ["false"]
  static_configs:
    - targets: ["matrix.neoc.vpn.ops.example.co:31000"]
      labels:
        model: "gemma-4-26b-a4b-it-q8"

Тут значення в params формує query-параметри, які додаються до URI, тобто весь запит буде виглядати як /metrics?autoload=false&model=gemma-4-26b-a4b-it-q8, див. How to modify scrape URLs in targets.

З autoload вказуємо llama.cpp не завантажувати модель, якщо вона зараз не запущена.

А в labels додаємо статичну лейблу з іменем моделі, аби потім простіше було робити графіки і алерти (власне саме те, чого я очікував від самої llama.cpp).

Деплоїмо, перевіряємо targets:

І перевіряємо метрики – запит sum({job="matrix-llama-cpp"}) by (__name__):

Опція 2: VMAgent та http_sd_configs

В PR є варіант автоматизації через додавання локального до llama.cpp сервісу, який опитує ендпоінт /models і повертає JSON з моделями (див. приклад в коментах до PR).

Тоді для VMAgent можна було б створити job із http_sd_configs типу такого:

scrape_configs:
  - job_name: llama-cpp
    metrics_path: /metrics

    http_sd_configs:
      - url: http://matrix.neoc.vpn.ops.example.co:31000/targets.json

    relabel_configs:
      - source_labels: [llama_model_id]
        target_label: __param_model

      - target_label: __param_autoload
        replacement: "false"

      - source_labels: [llama_model_id]
        target_label: model

Але, по-перше, це має сенс коли моделей багато або вони часто змінюються, по-друге – сподіваюсь, що сам PR таки приймуть, і колись будемо мати простіший спосіб отримання метрик, а тому зараз робити якусь автоматизацію сенсу нема. Тим більш, що конкретно в моєму випадку буде 2-3 моделі максимум.

Grafana dashboard

Є вже готові дашборди, наприклад llama.cpp Monitoring, і для початку можна просто взяти якусь таку і трохи змінити під себе.

Власне, я так і зробив, але додав можливість вибору моделі:

Відповідно, треба було оновити queries і додати sum by (model) (лейбла, яка задається в нашій scrape_job) та фільтр по змінній $model_name.

Наприклад, запит для графіку “Tokens per Decode”:

sum by (model) (
  rate(llamacpp:tokens_predicted_total{model=~"$model_name"}[$__rate_interval])
)
/
on(model)
sum by (model) (
  rate(llamacpp:n_decode_total{model=~"$model_name"}[$__rate_interval])
)

І вся дашборда зараз виглядає так – тут, знов-таки, реального трафіку ще нема, але загальна ідея поки така:

llama.cpp: метрики та моніторинг з VictoriaMetrics

Готово.

І по ходу гуглінга знайшов цікавий пост llama.cpp guide – Running LLMs locally, on any hardware, from scratch – залишу тут лінк на майбутнє.

Loading