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>
6.0 KiB
Front Door Proxy Design — Handling Requests to Unloaded Models
Date: 2026-08-13 Status: Design exploration — vLLM Sleep Mode available as better alternative
TL;DR: vLLM 0.11.0+ includes Sleep Mode — a built-in feature that enables fast model switching (18-200x faster than full reload) by hibernating weights while preserving process state. This is likely the solution we need instead of building a custom front door.
Current Behavior: Request to Unloaded Model
When a client makes a request to http://<host>:8000/v1/... and no model is loaded:
- Container is down (auto-unload via
idle-watchor manualdown) - Immediate connection failure — client gets
Connection refusedor similar - No retry guidance — client doesn't know if the service is down, moving, or loading
- Manual recovery required — someone must run
./vllmctl up <model>
What the client sees
$ curl http://host:8000/v1/models
curl: (7) Failed to connect to host port 8000: Connection refused
No indication of:
- Whether the service is permanently down
- Which model should be loaded
- How long loading would take (~2-10 min for large models from NFS)
The Shared Front Door Concept
A reverse proxy that sits in front of vLLM and handles the "unloaded model" case gracefully.
Architecture
Client → Front Door (port 8000) → vLLM Container (port 8001 or similar)
↓
[Model unloaded?]
↓
Trigger load / Queue / Inform
Design Options
Option 1: Wake-on-Request (Auto-Load)
On receiving a request when no model is loaded:
- Spawn the container with the requested model (from URL path or header)
- Queue the request (or return 503 with
Retry-After) - Client retries after model is ready (or proxy forwards queued request)
Pros: Transparent to clients, no manual intervention
Cons: Long wait times (2-10 min), cold start, need to decide which model to load
Complexity: Need request queue, loading state tracking, model selection logic
Option 2: Friendly Error + Status Endpoint
On receiving a request when no model is loaded:
- Return 503 Service Unavailable with clear JSON body
- Include status info: "No model loaded. Load with
vllmctl up <model>" - Provide a status endpoint clients can poll:
GET /front-door/status- Shows: models available, currently loaded, loading state (if any)
Pros: Simple to implement, clear client guidance
Cons: Still requires manual load or client-side logic
Complexity: Low — just a small nginx/envoy/python proxy
Option 3: Request Queue + Load Notification
On receiving a request when no model is loaded:
- Return 202 Accepted with a
Locationheader pointing to a job status URL - Trigger model load in background
- Client polls status or gets notified (webhook) when ready
- When ready, client retries original request
Pros: Clear async flow, client knows what's happening
Cons: More complex, needs job tracking, notification or polling
Complexity: Medium — need job store, status API
Implementation Sketch (Option 2: Friendly Error)
Simple Python Front Door
# front-door.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import subprocess
import requests
import json
VLLM_PORT = 8001
FRONT_PORT = 8000
MODELS_ROOT = "/data/home/renbaibing/huggingface"
class FrontDoorHandler(BaseHTTPRequestHandler):
def do_GET(self):
# Check if vLLM is healthy
try:
resp = requests.get(f"http://127.0.0.1:{VLLM_PORT}/health", timeout=1)
# Proxy the request
proxy_resp = requests.get(f"http://127.0.0.1:{VLLM_PORT}{self.path}")
self.wfile.write(proxy_resp.content)
except requests.exceptions.RequestException:
# vLLM is not ready
self.send_response(503)
self.send_header("Content-Type", "application/json")
self.end_headers()
response = {
"error": "No model currently loaded",
"message": "Load a model with: ./vllmctl up <model>",
"available_models": self.list_models(),
"status_url": f"http://{self.server.server_port}/status"
}
self.wfile.write(json.dumps(response).encode())
def list_models(self):
# Scan MODEL_ROOT for model directories
# Return list of available model names
pass
HTTPServer(("", FRONT_PORT), FrontDoorHandler).serve_forever()
Integration with existing setup
- Move vLLM to port 8001 (
VLLM_PORT=8001in .env) - Run front door on port 8000 (or via compose as a separate service)
- Front door checks vLLM health before proxying
Open Questions
-
Model selection: If auto-loading, how does the proxy know which model to load?
- From URL path? (
/v1/models/NAME/completions?) - From header? (
X-Model-Name: Qwen3.6-35B-A3B) - Configured default?
- From URL path? (
-
Cold start cost: Large models take 2-10 minutes to load from NFS
- Is this acceptable for clients?
- Should we cache recently-used models?
-
Multi-model concurrent serving: Does vLLM support serving multiple models simultaneously?
- If yes, the proxy could load models on-demand and keep them warm
- If no, we need model switching logic (potentially disruptive to existing requests)
-
Authentication: Should the front door handle API keys?
- Current setup has no auth
- Adding auth at the front door would be natural
Next Steps
- Confirm requirements: Decide between Option 1 (auto-load), 2 (friendly error), or 3 (async queue)
- Prototype: Build a minimal front door for the chosen option
- Integration: Wire into existing
compose.ymlandvllmctlworkflow - Testing: Simulate various failure modes and client behaviors
References
- Current setup:
README.md,NOTES-2026-08-13.md - vLLM health endpoint:
http://127.0.0.1:8000/health - vLLM metrics:
http://127.0.0.1:8000/metrics(used by idle-watch) - OpenAI API compatibility:
http://127.0.0.1:8000/v1/...