Bare Metal vs VM — выбор стратегии фундаментальный выбор до начала работы
Прежде чем трогать любой инструмент — определяем архитектуру развёртывания. Это решение влияет на всё: от флагов сборки до конфигурации сервера.
Облако / гипервизор
- Быстрый старт, масштабируемость, снапшоты
- 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-инференс в прод для таких размеров неприемлем.
Подготовка окружения OS, зависимости, драйверы
1.1 — Базовая ОС (Ubuntu 22.04 / 24.04 LTS)
# Обновляем систему 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)
# Проверяем 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
В 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)
# Официальный установщик 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
Сборка llama.cpp компиляция под конкретное железо
pip install llama-cpp-python или готовые бинари не оптимизированы под ваше железо. Сборка с правильными CMake флагами даёт +20–40% производительности на том же железе.
2.1 — Клонируем репозиторий
git clone https://github.com/ggerganov/llama.cpp cd llama.cpp git log --oneline -5 # смотрим последние коммиты, берём стабильный # Для продакшна — пинаем на тег, а не latest git checkout b4210 # пример — используйте актуальный тег
2.2 — Сборка под CUDA (NVIDIA)
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
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 -B build \ -DGGML_HIPBLAS=ON \ -DAMDGPU_TARGETS="gfx1100;gfx1030" \ -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release -j$(nproc)
2.4 — CPU-only сборка (с оптимизациями)
# Проверяем поддержку 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)
Модель и квантизация выбор формата и загрузка весов
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 | ×1 | 99% | Средне | Если много VRAM |
| Q6_K | ×0.75 | 98% | Хорошо | Качество важнее размера |
| Q4_K_M | ×0.5 | 96% | Быстро | ✅ Золотой стандарт |
| Q4_K_S | ×0.46 | 94% | Быстро | Если нужно меньше RAM |
| Q3_K_M | ×0.37 | 90% | Очень быстро | Ограниченный VRAM |
| Q2_K | ×0.28 | 80% | Max скорость | Только как крайний случай |
3.3 — Скачивание GGUF с HuggingFace
# Устанавливаем 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 — Собственная квантизация (если нужен нестандартный формат)
# Скачиваем оригинальную модель в 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
Запуск сервера первый старт и базовые параметры
4.1 — Минимальный запуск для теста
./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
./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 N | GPU layers — сколько слоёв в VRAM | 999 если хватает VRAM; иначе подбирайте |
| -c N | Context size (tokens) | 4096 минимум; 8192–32768 для длинных диалогов |
| -t N | CPU threads | = физические ядра / 2 (без HT) |
| -tb N | Threads для batch processing | = физические ядра |
| --n-predict N | Макс. токенов в ответе | -1 (бесконечно) или 2048–4096 |
| --parallel N | Параллельных слотов (запросов) | 1–4 для 7B; не больше кол-ва VRAM |
| --flash-attn | Flash Attention 2 | ✅ Всегда включать если CUDA |
| --mlock | Блокировать модель в RAM | ✅ На bare metal если достаточно RAM |
| --numa distribute | NUMA-aware распределение | На многосокетных серверах обязательно |
| --rope-scaling yarn | Расширение контекста | Если нужен контекст > trained max |
4.3 — Проверка работы API
# 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}'
Продакшн-конфигурация systemd, конфиг файлы, многопоточность
5.1 — Конфигурационный файл
{
"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
[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
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
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: распределение слоёв
# Разделение слоёв между двумя 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
Мониторинг и наблюдаемость метрики, алерты, что смотреть
6.1 — Prometheus + Grafana
scrape_configs: - job_name: 'llama-cpp' static_configs: - targets: ['localhost:8080'] metrics_path: '/metrics' scrape_interval: 10s
# Производительность 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 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% | Рестарт, анализ логов |
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 нельзя диагностировать деградацию.
Оптимизация производительности — приоритеты
# 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.
Стратегия масштабирования
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;
}
}
Процесс обновления модели без даунтайма
# 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
① Никогда не биндить на 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 |