Initial commit: router front-door vLLM stack

Three always-running vLLM services (text TP=2 GPU0+1, ocr + embed on GPU2,
sleep mode) behind a FastAPI router that auto-wakes models on request.
Tiered idle (sleep 15 min / offload 3 h), depth-aware 503s with
Retry-After, persisted wake-intent recovery, admin API on 127.0.0.1:8010.
Routine control via vllmctl is pure HTTP — no docker on the request path.

Verified: 91 router unit tests + 15-test E2E on real hardware
(measurements in CALIBRATION.md; design record in
.claude/memory/router-front-door-plan.md).

Old nginx stack files removed before git init; design survives in
.claude/memory/sleep-mode-implementation-plan.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 10:17:42 +00:00
commit 80eef4ce6a
35 changed files with 6506 additions and 0 deletions

119
compose.yml Normal file
View File

@@ -0,0 +1,119 @@
# vLLM serving stack — router front door (plan v3.3, 2026-08-17).
#
# nginx is GONE: the FastAPI router (./router) is the only public ingress
# (host :8000), with a separate admin listener on 127.0.0.1:8010.
# Each model is a dedicated always-running vLLM service with Sleep Mode
# enabled; the router wakes them on request.
#
# Hardware constraints (NOTES-2026-08-13.md, NOTES-2026-08-17-gpu2-recheck.md):
# * GPU P2P DMA corrupts data host-wide (4/6 paths). NCCL_P2P_DISABLE=1
# + --disable-custom-all-reduce are MANDATORY on every service.
# * text runs TP=2 on GPU0+GPU1 (exclusive); OCR and embed run TP=1 on
# GPU2 (single-GPU services never issue P2P transfers).
# * Moving a service to another GPU = edit its CUDA_VISIBLE_DEVICES (then
# re-tighten slices).
#
# No healthchecks on purpose: sleep mode makes /health flap, and nothing here
# may use a service_healthy dependency (plan §7).
services:
vllm-text:
image: vllm/vllm-openai:${VLLM_VERSION:-v0.27.1}
container_name: vllm-text
restart: unless-stopped
ports:
- "127.0.0.1:8001:8000" # direct debug access (local only)
volumes:
- ${MODEL_ROOT:-/data/home/renbaibing/huggingface}:/models:ro
ipc: host
gpus: all
environment:
VLLM_SERVER_DEV_MODE: "1" # sleep-mode endpoints (/sleep /wake_up /is_sleeping ...)
HF_TOKEN: ${HF_TOKEN:-}
NCCL_P2P_DISABLE: "1" # broken P2P DMA on this host (see header)
CUDA_VISIBLE_DEVICES: "0,1"
command: >
/models/Qwen3.6-35B-A3B-FP8
--served-model-name Qwen3.6-35B-A3B-FP8
--tensor-parallel-size 2
--max-model-len 262144
--gpu-memory-utilization 0.85
--reasoning-parser qwen3
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
--disable-custom-all-reduce
--enable-sleep-mode
vllm-ocr:
image: vllm/vllm-openai:${VLLM_VERSION:-v0.27.1}
container_name: vllm-ocr
restart: unless-stopped
ports:
- "127.0.0.1:8002:8000" # direct debug access (local only)
volumes:
- ${MODEL_ROOT:-/data/home/renbaibing/huggingface}:/models:ro
ipc: host
gpus: all
environment:
VLLM_SERVER_DEV_MODE: "1"
HF_TOKEN: ${HF_TOKEN:-}
NCCL_P2P_DISABLE: "1"
CUDA_VISIBLE_DEVICES: "2"
command: >
/models/OvisOCR2
--served-model-name OvisOCR2
--tensor-parallel-size 1
--max-model-len 32768
--gpu-memory-utilization 0.10
--max-num-seqs 256
--disable-custom-all-reduce
--enable-sleep-mode
vllm-embed:
image: vllm/vllm-openai:${VLLM_VERSION:-v0.27.1}
container_name: vllm-embed
restart: unless-stopped
ports:
- "127.0.0.1:8003:8000" # direct debug access (local only)
volumes:
- ${MODEL_ROOT:-/data/home/renbaibing/huggingface}:/models:ro
ipc: host
gpus: all
environment:
VLLM_SERVER_DEV_MODE: "1"
HF_TOKEN: ${HF_TOKEN:-}
NCCL_P2P_DISABLE: "1"
CUDA_VISIBLE_DEVICES: "2"
command: >
/models/Qwen3-Embedding-8B
--served-model-name Qwen3-Embedding-8B
--tensor-parallel-size 1
--max-model-len 8192
--gpu-memory-utilization 0.25
--disable-custom-all-reduce
--enable-sleep-mode
router:
build: ./router
container_name: vllm-router
restart: unless-stopped
ports:
- "8000:8000" # public API — only public ingress
- "127.0.0.1:8010:8010" # admin API (vllmctl / debugging), local only
volumes:
# Wake-intent state: lets a restarted router tell "awake and ready" from
# "awake because the previous one died between wake_up and reload_weights"
# (plan 6.4 / E2E case 12). Written atomically, a few hundred bytes.
# Named volume (not a bind mount): Docker seeds it with the image's
# /state ownership, so the non-root router user can always write it.
- router-state:/state
depends_on:
vllm-text:
condition: service_started
vllm-ocr:
condition: service_started
vllm-embed:
condition: service_started
volumes:
router-state: