Files
homelab/ansible/roles/deploy-vllm/templates/vllm.service.j2
Hermes Agent service account 53a55e7317 feat(deploy-vllm): swap Qwen2.5-32B for DeepSeek-R1-Distill-Qwen-32B-AWQ (t_r1d32b_swap)
Single-model deployment per Ryan's direction:
- Primary model: casperhansen/deepseek-r1-distill-qwen-32b-awq, max_model_len=32768
- nomic-embed-text-v1.5 and Qwen3-8B-AWQ both disabled (single-model requirement)
- kv_cache_dtype: int4_per_token_head required to fit 32768 ctx on 24GB RTX 3090
  (fp16 KV: 26GB needed, doesn't fit at any utilization; fp8 KV: OOM'd during
  FlashInfer warmup with ~50-150MB margin; int4 KV: clean single-attempt start)
- Added kv_cache_dtype / kv_cache_memory_bytes as new optional per-model
  template fields in vllm.service.j2 (guarded, no effect on other models)

Verified live: /health 200, /v1/models confirms max_model_len=32768,
live /v1/completions smoke test + manual chat completion both passed
(genuine <think> reasoning trace, correct arithmetic). NRestarts=0,
steady-state VRAM 23.2GB/24.576GB. Ansible idempotent re-run confirmed
changed=0.

Known follow-up (not done here): Hindsight's HINDSIGHT_API_LLM_MODEL
cluster config still references the retired Qwen2.5-32B-Instruct-AWQ —
needs separate GitOps update to point at the new model.
2026-08-31 20:21:27 -05:00

77 lines
3.1 KiB
Django/Jinja

[Unit]
Description=vLLM OpenAI-compatible inference server — {{ item.id }} ({{ item.hf_repo }})
After=network-online.target nvidia-persistenced.service
Wants=network-online.target nvidia-persistenced.service
[Service]
Type=simple
User={{ vllm_venv_owner }}
Group={{ vllm_venv_owner }}
EnvironmentFile={{ vllm_api_key_env_file }}
Environment="HOME=/home/{{ vllm_venv_owner }}"
Environment="HF_HUB_CACHE={{ vllm_hf_hub_cache }}"
Environment="HF_HOME={{ vllm_cache_dir }}"
# vLLM's torch.compile path shells out to `ninja` by bare name (not via
# venv-relative path) — without the venv's bin/ on PATH, systemd's minimal
# default PATH causes FileNotFoundError: 'ninja' deep in compile, even
# though `pip install vllm` installs the ninja package (and its console
# script) INTO the venv. Caught during Phase 5 validation (t_ca1af9fb,
# 2026-08-31): interactive SSH sessions have a different PATH than systemd
# services, so this only reproduces under systemd, not manual testing.
Environment="PATH={{ vllm_venv_path }}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# FlashInfer's bundled sampling.cu JIT-compiles against a cub template API
# (BlockAdjacentDifference::FlagHeads) that this flashinfer/CUDA toolkit
# combination does not provide on RTX 3090 (SM86) — 100 compile errors,
# confirmed upstream-known (vLLM GH #23023, #44305: FlashInfer sampler JIT
# breaks on various SM targets across flashinfer/vLLM version combos).
# Falls back to vLLM's native PyTorch sampler, which is fully supported and
# only marginally slower for single-request/low-concurrency serving. Caught
# during Phase 5 validation (t_ca1af9fb, 2026-08-31).
Environment="VLLM_USE_FLASHINFER_SAMPLER=0"
ExecStart={{ vllm_venv_path }}/bin/python -m vllm.entrypoints.openai.api_server \
--model {{ item.hf_repo }} \
--served-model-name {{ item.id }} \
--host {{ vllm_serve_host }} \
--port {{ item.port }} \
{% if item.role == 'embedding' %}
--runner pooling \
--convert embed \
{% endif %}
{% if item.trust_remote_code is defined and item.trust_remote_code %}
--trust-remote-code \
{% endif %}
{% if item.enforce_eager is defined and item.enforce_eager %}
--enforce-eager \
{% endif %}
{% if item.quantization is defined and item.quantization != 'none' %}
--quantization {{ item.quantization }} \
{% endif %}
{% if item.kv_cache_dtype is defined %}
--kv-cache-dtype {{ item.kv_cache_dtype }} \
{% endif %}
{% if item.kv_cache_memory_bytes is defined %}
--kv-cache-memory-bytes {{ item.kv_cache_memory_bytes }} \
{% endif %}
--gpu-memory-utilization {{ item.gpu_memory_utilization }} \
--max-model-len {{ item.max_model_len }} \
--dtype {{ vllm_dtype }} \
--api-key ${VLLM_API_KEY} \
{% if item.role != 'embedding' %}
--enable-prefix-caching
{% else %}
--no-enable-prefix-caching
{% endif %}
Restart={{ vllm_restart_policy }}
RestartSec=10
# vLLM torch.compile can take 4+ minutes before /health responds even after
# weights are loaded (homelab-llm-inference skill pitfall) — give it room.
TimeoutStartSec=600
StandardOutput=journal
StandardError=journal
SyslogIdentifier=vllm-{{ item.id }}
[Install]
WantedBy=multi-user.target