nevermore pinned LLAMA_SWAP_MODEL=granite-4.1-8b, an alias retired with the
granite seat on 2026-08-12. Every summarization call since then failed: 67
consecutive status=failure rows, 0 tokens, twice daily, entirely silently. The
briefing had been rendering with no LLM pass at all. Nothing alerts on
status=failure in the spend logs, so it took an unrelated question about
reranker VRAM to surface it.
It was also pinned to NEVERMORE_RERANK_MODEL=qwen3-reranker -- the incumbent
Brokkr R43 measured harming 80/90 fleet queries -- and was its ONLY caller,
while the production `reranker` alias sat at 0 calls for 4 days. The R43
cutover repointed the alias but never moved the consumer.
nevermore/.env LLAMA_SWAP_MODEL granite-4.1-8b -> summarizer
NEVERMORE_RERANK_MODEL qwen3-reranker -> reranker
(server-only; .env is excluded from the mirror both ways)
Verified against nevermore's exact call shape: summarizer returns clean content
with 0 reasoning chars at temperature 0.2 / max_tokens 4000; reranker scores
0.95 on-topic vs ~1e-5 off-topic; embedding returns dim-1024.
Retired alongside it:
vllm-rerank :8002 Qwen3-Reranker-0.6B + the qwen3-reranker alias
vllm-rerank-a4 :8014 gte-reranker-modernbert + its alias
vllm-granite :8004 Exited 8 days, dead service block
and vllm-rerank-a3 was promoted from a throwaway `docker run` into this stack
(the selection ledger's own open follow-up). Healthy in 55s. It keeps the
bake-off arm name so the ledger, memory and R43 record stay valid.
VLLM_VERSION is pinned latest -> v0.24.0. Every service in the stack shares that
one variable, so a bare `compose up -d` could have silently upgraded all of
them at once; both tags resolved to the same local image (4091d5593f77), so the
pin changed nothing at runtime.
GPU1 is down to 81,448 of 97,887 MiB -- 13.9 GB reclaimed tonight.
Correction: an earlier claim that A4 had no gateway alias was wrong. It did.
LiteLLM serves both config-defined and DB-defined models -- live showed 32
against config.yaml's 26 -- and grepping the file cannot see the difference.
/v1/models and /model/info (which flags db_model) are the ground truth. DB
models delete hot via POST /model/delete with no restart.
Left alone: reranker-a3-bge-v2-m3, a zero-call duplicate of `reranker` on the
same backend. It is Brokkr's cutover-verification handle -- redundant rather
than broken, and another agent's tooling is not mine to delete unilaterally.
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/embeddingsvllm-rerank— Qwen3-Reranker 0.6B →/rerank+/scorevllm-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
# 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
# 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 fitmodel + KV cachewill OOM the second-to-start container withAvailable 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_MODELand bump the memory fractions accordingly. -
Separate GPUs — if contention hurts latency, split them. Today all three share
${GPU_ID}. AddingGPU_ID_REWARD/etc. is a small compose edit.
History
-
2026-05-13 rename + extend — stack renamed from
vllm-qwen3tovllmand gained thevllm-rewardservice (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
transformersbuild that knew Qwen3; this stack is the canonical replacement for both embed and rerank. Consumers (AIPA agents, LibreChat RAG) point at:8001/:8002.