# vllm Multi-service vLLM stack on ana-ml2. Started as Qwen3 embedding + rerank (replacing the unmaintained Infinity stack); generalized to host any vLLM model on the box, currently three services: - **`vllm-embed`** — Qwen3-Embedding 0.6B → OpenAI `/v1/embeddings` - **`vllm-rerank`** — Qwen3-Reranker 0.6B → `/rerank` + `/score` - **`vllm-reward`** — Skywork-Reward-V2-Llama-3.1-8B-AWQ → `/classify` **Server:** ana-ml2 **Ports:** `8001` (embed), `8002` (rerank), `8003` (reward) — all configurable via `.env` **GPU:** all three services share GPU 1 by default (configurable) ## Why one process per service vLLM runs **one model per process**, so each model gets its own container. All three pin to the same GPU and split VRAM via `--gpu-memory-utilization`. Embed + rerank use `--runner pooling`; reward uses `--task classify` (newer vLLM flag for sequence-classification heads). ## Reranker caveat Qwen/Qwen3-Reranker-0.6B is a causal-LM checkpoint. The `--hf-overrides` flag in `compose.yaml` re-maps it to `Qwen3ForSequenceClassification` so vLLM's `/rerank` and `/score` endpoints work and the model emits only `no`/`yes` class logits instead of the full 151k-token distribution. If that override breaks after a vLLM upgrade, the pre-converted checkpoint `tomaarsen/Qwen3-Reranker-0.6B-seq-cls` is a drop-in replacement that needs no overrides — set `RERANK_MODEL=tomaarsen/Qwen3-Reranker-0.6B-seq-cls` in `.env` and remove the `--hf-overrides` line from the compose. ## Reward / Skywork specifics `vllm-reward` serves `Skywork-Reward-V2-Llama-3.1-8B-AWQ` — an AWQ quantization produced locally (not pulled from HF). The quant output lives at `/tank/aimodels/llm/Skywork-Reward-V2-Llama-3.1-8B-AWQ` on ana-ml2 and is bind-mounted read-only into the container at `/local-models`. vLLM loads it as a local-path HF-format model (config.json + safetensors). `--max-model-len 16384` is a server-side cap; JudgeClient on the consumer side also enforces this at dispatch time. Defense-in-depth. `--dtype auto` lets vLLM pick the right path for AWQ-quantized weights. ## Deploy ```bash # Sync canonical → ana-ml2 scripts/deploy-stack.sh ana-ml2 vllm # Pre-download Qwen3 models from HF (Skywork is local-path, no pull needed) scripts/elway ana-ml2 --playbook playbooks/pull-hf-repo.yaml \ --var hf_repo=Qwen/Qwen3-Embedding-0.6B scripts/elway ana-ml2 --playbook playbooks/pull-hf-repo.yaml \ --var hf_repo=Qwen/Qwen3-Reranker-0.6B # Launch ssh ana-ml2 'cd /opt/docker/compose/vllm && docker compose up -d && docker compose logs --tail=30' ``` First boot compiles CUDA graphs and can take 2–3 minutes per service (longer for the 8B reward — `start_period: 240s`). ## Verify ```bash # Health (each on its own port) curl -s http://localhost:8001/health curl -s http://localhost:8002/health curl -s http://localhost:8003/health # Embedding (OpenAI-compatible) curl -s http://localhost:8001/v1/embeddings \ -H "Content-Type: application/json" \ -d '{"model":"Qwen/Qwen3-Embedding-0.6B","input":["hello world"]}' | jq . # Reranker curl -s http://localhost:8002/rerank \ -H "Content-Type: application/json" \ -d '{"model":"Qwen/Qwen3-Reranker-0.6B","query":"what is a cat","documents":["cats are mammals","dogs bark"]}' | jq . # Reward classify (Skywork) curl -s http://localhost:8003/classify \ -H "Content-Type: application/json" \ -d '{"model":"Skywork/Skywork-Reward-V2-Llama-3.1-8B-AWQ","input":"User: hello\nAssistant: hi there"}' | jq . # Listed models curl -s http://localhost:8001/v1/models | jq . curl -s http://localhost:8002/v1/models | jq . curl -s http://localhost:8003/v1/models | jq . ``` ## Scaling knobs - **`EMBED_GPU_MEM_UTIL` / `RERANK_GPU_MEM_UTIL` / `REWARD_GPU_MEM_UTIL`** — fractions of **total** GPU VRAM each service reserves (not of free VRAM). Each profiler runs independently with no awareness of the others, so any slice that's too small to fit `model + KV cache` will OOM the second-to-start container with `Available KV cache memory: -X.XX GiB`. Default 0.20/0.20/0.30 totals 0.70, leaving ~14 GB headroom on a 48 GB Ada (matches the production tune-down from the original 0.40/0.40 defaults — embed/rerank fit comfortably in 0.20 each). Bump REWARD up first if you see OOM — the 8B AWQ model's KV slice at 16k ctx is the tightest. Drop embed/rerank further only if you've extended REWARD past 0.40 and still need room. - **`EMBED_MAX_MODEL_LEN` / `RERANK_MAX_MODEL_LEN` / `REWARD_MAX_MODEL_LEN`** — lower to reduce KV-cache allocation if VRAM is tight. Qwen3 supports up to 32k natively; Skywork's reward cap of 16k is intentional (matches JudgeClient's dispatch-side cap). - **Larger Qwen3 models** — embedding/reranker come in 0.6B / 4B / 8B. Swap `EMBED_MODEL` / `RERANK_MODEL` and bump the memory fractions accordingly. - **Separate GPUs** — if contention hurts latency, split them. Today all three share `${GPU_ID}`. Adding `GPU_ID_REWARD`/etc. is a small compose edit. ## History - **2026-05-13 rename + extend** — stack renamed from `vllm-qwen3` to `vllm` and gained the `vllm-reward` service (Skywork-Reward-V2 8B AWQ). No GPU memory rebalance needed in practice — production had already tuned EMBED/RERANK down from 0.40/0.40 to 0.20/0.20; adding REWARD at 0.30 fits cleanly with ~14 GB headroom on the 48 GB Ada. - **Migrated off Infinity** — Infinity stopped shipping a `transformers` build that knew Qwen3; this stack is the canonical replacement for both embed and rerank. Consumers (AIPA agents, LibreChat RAG) point at `:8001`/`:8002`.