llama.cpp + Qwen
deployment handbook

рекомендации по настройке и эксплуатации инференса на bare metal и ВМ

00

Bare Metal vs VM — выбор стратегии фундаментальный выбор до начала работы

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

🖥️ Bare Metal

Физический сервер

  • Прямой доступ к GPU/CPU без hypervisor overhead
  • Максимальная производительность, минимальная задержка
  • Полный контроль над NUMA, CPU pinning, huge pages
  • Требует больше DevOps-работы по настройке
  • Идеально для продакшна с постоянной нагрузкой
  • CUDA, ROCm, Metal — без прослоек
☁️ Virtual Machine

Облако / гипервизор

  • Быстрый старт, масштабируемость, снапшоты
  • GPU passthrough (KVM/QEMU) для частичного доступа
  • Оверхед 5–15% на CPU-инференсе vs bare metal
  • На GPU: почти без потерь при правильном passthrough
  • Хорошо для dev/staging, тестирования квантизации
  • AWS g4dn, GCP A2, Hetzner GPU — готовые варианты
Параметр Bare Metal VM (CPU only) VM (GPU passthrough)
Tokens/sec (Qwen2.5-7B Q4_K_M) 55–80 t/s 35–55 t/s 50–75 t/s
Latency (TTFT) Минимальная +10–20% +2–5%
Стоимость настройки Высокая Низкая Средняя
Рекомендуется для Production Dev / тесты Staging / prod (cloud)
ℹ
Правило выбора

Если это постоянный production-сервис с предсказуемой нагрузкой → Bare Metal. Если нужна эластичность или это pre-production → VM с GPU passthrough или мощный CPU. Никогда не запускайте 72B модели без GPU — CPU-инференс в прод для таких размеров неприемлем.

01

Подготовка окружения OS, зависимости, драйверы

1.1 — Базовая ОС (Ubuntu 22.04 / 24.04 LTS)

Первичная настройка сервераBASH
# Обновляем систему
sudo apt update && sudo apt upgrade -y

# Устанавливаем core зависимости
sudo apt install -y \
  build-essential cmake git curl wget \
  libopenblas-dev liblapack-dev \
  pkg-config libssl-dev \
  python3-pip python3-venv \
  numactl htop nvtop iotop \
  screen tmux

# Включаем huge pages для лучшей производительности
echo 'vm.nr_hugepages=1024' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

1.2 — CUDA (если есть NVIDIA GPU)

CUDA 12.x SetupBASH
# Проверяем GPU и доступные драйверы
ubuntu-drivers devices

# Устанавливаем рекомендованный драйвер
sudo ubuntu-drivers autoinstall
sudo reboot

# После перезагрузки — устанавливаем CUDA Toolkit
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt update && sudo apt install -y cuda-toolkit-12-4

# Добавляем в PATH
echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc

# Проверка
nvidia-smi
nvcc --version
⚠
Важно для VM с GPU passthrough

В KVM/QEMU убедитесь что: vfio-pci настроен до загрузки ОС, nvidia.conf содержит options nvidia NVreg_OpenRmEnableUnsupportedGpus=1 для consumer GPU. Проверьте что GPU не захвачен host-системой: lspci -k | grep -A 3 NVIDIA — должно быть Kernel driver in use: vfio-pci.

1.3 — ROCm (AMD GPU)

AMD ROCm SetupBASH
# Официальный установщик ROCm
wget https://repo.radeon.com/amdgpu-install/6.1.3/ubuntu/jammy/amdgpu-install_6.1.60103-1_all.deb
sudo dpkg -i amdgpu-install_6.1.60103-1_all.deb
sudo amdgpu-install --usecase=rocm
sudo usermod -aG render,video $USER
sudo reboot

# Проверка
rocm-smi
02

Сборка llama.cpp компиляция под конкретное железо

★
Всегда собирайте из исходников

pip install llama-cpp-python или готовые бинари не оптимизированы под ваше железо. Сборка с правильными CMake флагами даёт +20–40% производительности на том же железе.

2.1 — Клонируем репозиторий

Clone & prepareBASH
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
git log --oneline -5  # смотрим последние коммиты, берём стабильный
# Для продакшна — пинаем на тег, а не latest
git checkout b4210  # пример — используйте актуальный тег

2.2 — Сборка под CUDA (NVIDIA)

CMake build — CUDA backendBASH
cmake -B build \
  -DGGML_CUDA=ON \
  -DCMAKE_CUDA_ARCHITECTURES="80;86;89;90" \
  -DGGML_CUDA_F16=ON \
  -DGGML_BLAS=ON \
  -DGGML_BLAS_VENDOR=OpenBLAS \
  -DCMAKE_BUILD_TYPE=Release

cmake --build build --config Release -j$(nproc)

# Проверяем что CUDA подхватилась
./build/bin/llama-server --version
ℹ
CUDA Architectures — правило выбора

sm_80 = A100, sm_86 = RTX 3090/A6000, sm_89 = RTX 4090/L4, sm_90 = H100. Указывайте только архитектуры ваших GPU — лишние замедляют сборку и добавляют размер бинаря. Проверить: nvidia-smi --query-gpu=compute_cap --format=csv.

2.3 — Сборка под ROCm (AMD)

CMake build — ROCm backendBASH
cmake -B build \
  -DGGML_HIPBLAS=ON \
  -DAMDGPU_TARGETS="gfx1100;gfx1030" \
  -DCMAKE_BUILD_TYPE=Release

cmake --build build --config Release -j$(nproc)

2.4 — CPU-only сборка (с оптимизациями)

CMake build — CPU + AVX512BASH
# Проверяем поддержку AVX512
grep -o 'avx[^ ]*' /proc/cpuinfo | sort -u

cmake -B build \
  -DGGML_BLAS=ON \
  -DGGML_BLAS_VENDOR=OpenBLAS \
  -DGGML_AVX512=ON \
  -DGGML_AVX512_VBMI=ON \
  -DGGML_AVX512_VNNI=ON \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_CXX_FLAGS="-march=native"

cmake --build build -j$(nproc)
03

Модель и квантизация выбор формата и загрузка весов

3.1 — Выбор модели Qwen

Модель VRAM (Q4_K_M) RAM (CPU) Tokens/sec GPU Рекомендовано для
Qwen2.5-0.5B ~0.5 GB ~1 GB 200–400 Edge, встройка
Qwen2.5-3B ~2 GB ~3 GB 120–200 Лёгкий чатбот
Qwen2.5-7B ~5 GB ~8 GB 70–120 ✅ Оптимальный баланс
Qwen2.5-14B ~9 GB ~14 GB 40–70 Серьёзные задачи
Qwen2.5-32B ~20 GB ~32 GB 20–40 2× A100 40GB или CPU
Qwen2.5-72B ~45 GB ~72 GB 5–15 4× A100 или 8× 3090

3.2 — Форматы квантизации (GGUF)

ФорматРазмерКачествоСкоростьРекомендация
F16×2 от FP32ЭталонМедленноТолько для сравнения
Q8_0×199%СреднеЕсли много VRAM
Q6_K×0.7598%ХорошоКачество важнее размера
Q4_K_M×0.596%Быстро✅ Золотой стандарт
Q4_K_S×0.4694%БыстроЕсли нужно меньше RAM
Q3_K_M×0.3790%Очень быстроОграниченный VRAM
Q2_K×0.2880%Max скоростьТолько как крайний случай

3.3 — Скачивание GGUF с HuggingFace

Загрузка моделиBASH
# Устанавливаем huggingface-hub
pip install huggingface-hub

# Создаём директорию для моделей
mkdir -p /opt/models/qwen

# Скачиваем Qwen2.5-7B Q4_K_M (рекомендуемый вариант)
huggingface-cli download \
  Qwen/Qwen2.5-7B-Instruct-GGUF \
  qwen2.5-7b-instruct-q4_k_m.gguf \
  --local-dir /opt/models/qwen \
  --local-dir-use-symlinks False

# Альтернатива — wget напрямую
wget -c \
  https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF/resolve/main/qwen2.5-7b-instruct-q4_k_m.gguf \
  -O /opt/models/qwen/qwen2.5-7b-instruct-q4_k_m.gguf \
  --progress=bar:force

# Проверяем целостность — сверяем sha256 с HF страницей
sha256sum /opt/models/qwen/qwen2.5-7b-instruct-q4_k_m.gguf

3.4 — Собственная квантизация (если нужен нестандартный формат)

Конвертация и квантизация из HFBASH
# Скачиваем оригинальную модель в safetensors
huggingface-cli download Qwen/Qwen2.5-7B-Instruct --local-dir /tmp/qwen-hf

# Конвертируем в GGUF (F16)
python3 convert_hf_to_gguf.py /tmp/qwen-hf \
  --outtype f16 \
  --outfile /opt/models/qwen/qwen2.5-7b-f16.gguf

# Квантизируем в нужный формат
./build/bin/llama-quantize \
  /opt/models/qwen/qwen2.5-7b-f16.gguf \
  /opt/models/qwen/qwen2.5-7b-q4_k_m.gguf \
  Q4_K_M
04

Запуск сервера первый старт и базовые параметры

4.1 — Минимальный запуск для теста

Быстрый тест — CPUBASH
./build/bin/llama-server \
  -m /opt/models/qwen/qwen2.5-7b-instruct-q4_k_m.gguf \
  --host 0.0.0.0 \
  --port 8080 \
  -c 4096 \
  -t 8 \
  --log-format json
Быстрый тест — с GPU (CUDA)BASH
./build/bin/llama-server \
  -m /opt/models/qwen/qwen2.5-7b-instruct-q4_k_m.gguf \
  --host 0.0.0.0 \
  --port 8080 \
  -c 8192 \
  -ngl 999 \       # выгружаем все слои в GPU
  -t 4 \
  --log-format json

4.2 — Ключевые параметры

ФлагОписаниеРекомендация
-ngl NGPU layers — сколько слоёв в VRAM999 если хватает VRAM; иначе подбирайте
-c NContext size (tokens)4096 минимум; 8192–32768 для длинных диалогов
-t NCPU threads= физические ядра / 2 (без HT)
-tb NThreads для batch processing= физические ядра
--n-predict NМакс. токенов в ответе-1 (бесконечно) или 2048–4096
--parallel NПараллельных слотов (запросов)1–4 для 7B; не больше кол-ва VRAM
--flash-attnFlash Attention 2✅ Всегда включать если CUDA
--mlockБлокировать модель в RAM✅ На bare metal если достаточно RAM
--numa distributeNUMA-aware распределениеНа многосокетных серверах обязательно
--rope-scaling yarnРасширение контекстаЕсли нужен контекст > trained max

4.3 — Проверка работы API

Тест OpenAI-compatible endpointBASH
# Health check
curl http://localhost:8080/health

# Chat completion (OpenAI-compatible)
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen2.5-7b",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "Hello! What is 2+2?"}
    ],
    "temperature": 0.7,
    "max_tokens": 256
  }'

# Streaming
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen","messages":[{"role":"user","content":"Count to 5"}],"stream":true}'
05

Продакшн-конфигурация systemd, конфиг файлы, многопоточность

5.1 — Конфигурационный файл

/etc/llama/qwen-7b.jsonCONFIG
{
  "model":          "/opt/models/qwen/qwen2.5-7b-instruct-q4_k_m.gguf",
  "host":           "127.0.0.1",
  "port":           8080,
  "ctx-size":       8192,
  "n-gpu-layers":   999,
  "threads":        4,
  "threads-batch":  8,
  "parallel":       2,
  "flash-attn":     true,
  "mlock":          true,
  "cont-batching":  true,
  "log-format":     "json",
  "metrics":        true,
  "slots-endpoint": true,
  "chat-template":  "chatml",
  "rope-scaling":   "yarn",
  "rope-scale":     1.0,
  "defrag-thold":   0.1
}

5.2 — systemd unit

/etc/systemd/system/llama-qwen.serviceSYSTEMD
[Unit]
Description=llama.cpp Qwen2.5-7B Inference Server
After=network.target
Wants=network.target

[Service]
Type=simple
User=llama
Group=llama
WorkingDirectory=/opt/llama.cpp
ExecStart=/opt/llama.cpp/build/bin/llama-server \
  --config /etc/llama/qwen-7b.json
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

# Лимиты ресурсов
LimitMEMLOCK=infinity
LimitNOFILE=65536
OOMScoreAdjust=-900

# Безопасность
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/models /var/log/llama

# Переменные окружения
Environment=CUDA_VISIBLE_DEVICES=0
Environment=GGML_CUDA_NO_PINNED=1

[Install]
WantedBy=multi-user.target
Активация сервисаBASH
sudo useradd -r -s /bin/false llama
sudo chown -R llama:llama /opt/models /opt/llama.cpp
sudo systemctl daemon-reload
sudo systemctl enable llama-qwen
sudo systemctl start llama-qwen
sudo systemctl status llama-qwen
journalctl -u llama-qwen -f

5.3 — Nginx reverse proxy с rate limiting

/etc/nginx/sites-available/llama-apiNGINX
limit_req_zone $binary_remote_addr zone=llm_api:10m rate=10r/m;

server {
    listen 443 ssl http2;
    server_name llm.yourdomain.com;

    ssl_certificate     /etc/ssl/certs/llm.crt;
    ssl_certificate_key /etc/ssl/private/llm.key;

    location /v1/ {
        limit_req zone=llm_api burst=5 nodelay;
        proxy_pass         http://127.0.0.1:8080;
        proxy_set_header   Host $host;
        proxy_read_timeout 300s;
        proxy_buffering    off;    # критично для streaming!

        # Auth через header
        if ($http_authorization != "Bearer your-secret-key") {
            return 401;
        }
    }

    location /metrics {
        allow 10.0.0.0/8;
        deny  all;
        proxy_pass http://127.0.0.1:8080/metrics;
    }
}

5.4 — Мульти-GPU: распределение слоёв

2x GPU setup — Qwen2.5-32BBASH
# Разделение слоёв между двумя GPU
# Qwen2.5-32B имеет 64 слоя
./build/bin/llama-server \
  -m /opt/models/qwen/qwen2.5-32b-q4_k_m.gguf \
  -ngl 999 \
  --tensor-split 1,1 \    # равномерное распределение
  --main-gpu 0 \
  -c 16384 \
  --flash-attn \
  --parallel 2
06

Мониторинг и наблюдаемость метрики, алерты, что смотреть

Tokens / sec
TPS
Основной показатель производительности. Норма: >50 t/s для 7B Q4
TTFT
ms
Time to first token. Должен быть <500ms для хорошего UX
VRAM usage
%
Держать <90%. При 95%+ начинаются OOM и деградация
Slot occupancy
N/M
Занятые / всего слотов. Помогает понять насыщение
KV Cache
%
Заполнение KV-кэша. При 100% — дефрагментация или OOM
Queue depth
N
Очередь запросов. >5 — сервер перегружен, нужно масштабировать

6.1 — Prometheus + Grafana

prometheus.yml — scrape configCONFIG
scrape_configs:
  - job_name: 'llama-cpp'
    static_configs:
      - targets: ['localhost:8080']
    metrics_path: '/metrics'
    scrape_interval: 10s
Ключевые метрики llama.cppMETRICS
# Производительность
llamacpp:tokens_second           # текущий TPS
llamacpp:prompt_tokens_total     # общий объём промптов
llamacpp:completion_tokens_total # общий объём генерации

# Ресурсы
llamacpp:kv_cache_usage_ratio    # заполнение KV cache
llamacpp:kv_cache_tokens         # токены в кэше
llamacpp:requests_processing     # активных запросов
llamacpp:requests_deferred       # ожидающих слота

# Мониторинг через /slots endpoint
curl http://localhost:8080/slots | jq '.[].state'

6.2 — Мониторинг GPU

GPU мониторинг в реальном времениBASH
# Непрерывный мониторинг GPU
watch -n 1 nvidia-smi

# Подробные метрики в цикле
nvidia-smi --query-gpu=utilization.gpu,utilization.memory,memory.used,memory.total,temperature.gpu,power.draw \
  --format=csv -l 2

# nvtop — удобный dashboard
nvtop

# Логировать в файл каждые 5 сек
nvidia-smi --query-gpu=timestamp,utilization.gpu,memory.used,temperature.gpu \
  --format=csv -l 5 >> /var/log/gpu-metrics.csv

6.3 — Алерты: пороги и реакции

МетрикаПредупреждениеКритичноДействие
GPU VRAM>80%>92%Снизить --parallel или контекст
GPU Temperature>80°C>90°CПроверить охлаждение, throttle
KV Cache ratio>70%>90%Форсить дефрагментацию, рестарт
Tokens/sec<30 t/s<10 t/sДиагностика, перезапуск сервера
Queue depth>3>10Добавить реплику или мощность
Server errors 5xx>1%>5%Рестарт, анализ логов
07

Best Practices что должен делать исполнитель

Чеклист исполнителя — до запуска в прод

  • Железо задокументировано — CPU model, RAM объём и частота, GPU model + VRAM, тип накопителя. Без этого нельзя обоснованно выбрать параметры.
  • GGUF скачан и верифицирован — sha256 совпадает с HF карточкой. Повреждённая модель запустится но будет генерировать мусор.
  • Сборка под железо — флаги CMake соответствуют реальному GPU/CPU. Нет смысла в -DGGML_CUDA=ON на CPU-сервере.
  • Flash Attention включён — при CUDA обязательно --flash-attn. Экономит VRAM и ускоряет до 40% на длинных контекстах.
  • Continuous batching активен — --cont-batching позволяет обрабатывать несколько запросов параллельно без полной остановки.
  • mlock настроен — на bare metal с достаточной RAM используйте --mlock чтобы модель не свопировалась.
  • systemd unit написан — сервис должен автозапускаться и рестартовать при краше.
  • Nginx/Caddy перед сервером — никогда не открывайте порт 8080 напрямую в интернет.
  • Метрики подключены — /metrics проксируется только для внутреннего Prometheus.
  • Алерты настроены — минимум VRAM, temperature, TPS. Prometheus Alertmanager → Telegram/Slack.
  • Лог ротация — journald или logrotate, иначе за неделю диск заполнится.
  • Baseline замерен — первые 24 часа: TPS, TTFT, VRAM при разной нагрузке. Без baseline нельзя диагностировать деградацию.

Оптимизация производительности — приоритеты

Настройка под максимальный throughputTUNING
# 1. Убеждаемся что все слои в GPU
-ngl 999

# 2. Flash Attention — обязательно на CUDA
--flash-attn

# 3. Continuous batching
--cont-batching

# 4. Подбираем threads — не больше физических ядер
-t $(nproc --all)
-tb $(nproc --all)

# 5. CPU pinning через numactl (bare metal)
numactl --cpunodebind=0 --membind=0 ./build/bin/llama-server ...

# 6. Приоритет процессу
renice -n -10 -p $(pgrep llama-server)

# 7. Дефрагментация KV cache при заполнении
--defrag-thold 0.1   # дефрагментировать при 10% фрагментации
✕
Частые ошибки, которые убивают производительность

1) Запуск от root — не делайте это никогда. 2) Открытый порт 8080 без auth — API доступен всем. 3) -t выше числа физических ядер — overhead контекстных переключений хуже одного потока. 4) --mmap на сетевых файловых системах (NFS/Ceph) — катастрофические задержки. 5) Запуск нескольких экземпляров на одном GPU без tensor-split — GPU memory corruption.

Стратегия масштабирования

Horizontal scaling с nginx upstreamNGINX
upstream llama_backends {
    least_conn;
    server 127.0.0.1:8080 weight=1;
    server 127.0.0.1:8081 weight=1;
    server 127.0.0.1:8082 weight=1;
    keepalive 32;
}

server {
    location /v1/ {
        proxy_pass http://llama_backends;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_read_timeout 300s;
        proxy_buffering off;
    }
}

Процесс обновления модели без даунтайма

Zero-downtime model swapBASH
# 1. Скачиваем новую версию в staging директорию
huggingface-cli download ... --local-dir /opt/models/staging

# 2. Запускаем второй экземпляр на другом порту
./llama-server -m /opt/models/staging/new-model.gguf --port 8081

# 3. Тестируем новый экземпляр
curl http://localhost:8081/v1/chat/completions ...

# 4. Переключаем nginx на новый инстанс
sudo nginx -s reload

# 5. Ждём завершения активных запросов на старом
watch -n 1 "curl -s localhost:8080/slots | jq '.[].state'"

# 6. Останавливаем старый экземпляр
sudo systemctl stop llama-qwen-old

Безопасность в production

✓
Минимальный security чеклист

① Никогда не биндить на 0.0.0.0 без Nginx — только 127.0.0.1. ② API key через Nginx header или llama.cpp --api-key. ③ rate limiting на уровне Nginx. ④ Отдельный system user без shell. ⑤ ProtectSystem=strict в systemd. ⑥ /metrics доступен только из internal network. ⑦ Firewall: разрешён только 443 (HTTPS) снаружи. ⑧ Логи в syslog/journald с ротацией.

От чего отталкиваться при диагностике

СимптомДиагнозРешение
TPS упал в 2 разаМодель выгружена из VRAM в RAMПроверить nvidia-smi, уменьшить --parallel или --ctx-size
CUDA OOMНе хватает VRAM для KV cacheСнизить -c (context), --parallel, или взять меньшую квантизацию
Высокий TTFTБольшой промпт, prefill медленныйFlash attention, увеличить -tb (batch threads)
Сервер зависаетDeadlock или OOM на CPUПроверить ulimits, --mlock требует достаточно RAM
Мусорная генерацияНеверный chat templateУказать --chat-template chatml для Qwen
Медленный стартmmap и медленный дискИспользовать NVMe, или --mlock для загрузки в RAM