complexity router container - Designed for LiteLLM's new Autorouter feature (POC)
87
This service wraps predict_complexity_chunked_v2.py with FastAPI and server-side dynamic batching.
asyncio.Queue.BATCH_WAIT_MS for nearby requests.MAX_BATCH_SIZE conversations are preprocessed together.Only one model inference worker executes against a model instance, preventing a burst of HTTP requests from causing 100 simultaneous calls into the same model.
Mount the existing trained directory at /models/router:
/models/router/
├── encoder/
│ └── config.json
├── router_model.pt
├── router_config.json
└── tokenizer/
The encoder weights do not need to be duplicated in encoder/model.safetensors; the complete trained state is loaded from router_model.pt by the existing predictor.
docker build -f Dockerfile.cpu -t modernbert-router:cpu .
docker run --rm \
-p 8000:8000 \
-v "$PWD/complexity-router-v2/best:/models/router:ro" \
-e CPU_THREADS=8 \
-e MAX_BATCH_SIZE=16 \
-e MAX_ENCODER_BATCH_SIZE=32 \
modernbert-router:cpu
CPU defaults to FP32. BF16 can be forced with -e DTYPE=bf16 if the CPU supports it and it benchmarks better.
docker build -f Dockerfile.cuda -t modernbert-router:cuda .
docker run --rm \
--gpus all \
-p 8000:8000 \
-v "$PWD/complexity-router-v2/best:/models/router:ro" \
modernbert-router:cuda
CUDA defaults to BF16.
The CUDA image is based on pytorch/pytorch:2.11.0-cuda12.8-cudnn9-runtime.
curl -s http://127.0.0.1:8000/v1/complexity \
-H 'Content-Type: application/json' \
-d '{
"messages": [
{
"role": "user",
"content": "Write a Python function that parses a CSV file."
}
]
}' | jq
Example response shape:
{
"complexity": 2.4173,
"request_id": "...",
"device": "cuda",
"dtype": "torch.bfloat16",
"batch_size": 12,
"queue_ms": 2.31,
"preprocessing_ms": 0.74,
"encoder_ms": 31.82,
"router_head_ms": 1.09,
"service_ms": 34.12,
"total_ms": 36.43,
"history_chunks": 0,
"final_tokens": 17
}
POST /v1/complexity - score a conversation.GET /health - process health.GET /ready - confirms model is loaded and reports batching configuration.GET /metrics - lightweight queue/batch counters in JSON.Use /ready for Kubernetes readiness checks. Use /health for liveness checks.
| Variable | CPU default | CUDA default | Meaning |
|---|---|---|---|
MODEL_DIR | /models/router | /models/router | Model directory |
DEVICE | cpu | cuda | cpu, cuda, or auto |
DTYPE | fp32 | bf16 | fp32, bf16, or auto |
CPU_THREADS | 8 | 4 | PyTorch CPU threads |
MAX_BATCH_SIZE | 16 | 32 | Max conversations per micro-batch |
MAX_ENCODER_BATCH_SIZE | 32 | 64 | Max encoded sequences per encoder invocation |
BATCH_WAIT_MS | 3 | 2 | Maximum micro-batching collection delay |
MAX_QUEUE_SIZE | 1000 | 2000 | Bounded burst queue |
REQUEST_TIMEOUT_SECONDS | 15 | 15 | Per-request timeout |
Install the test dependency on the client:
pip install httpx
Then:
python burst_test.py \
--url http://127.0.0.1:8000/v1/complexity \
--requests 100 \
--concurrency 100
The script reports throughput, HTTP statuses, p50/p95/p99/max latency, and observed batch size.
A bounded queue protects the process from bursts. It does not create unlimited compute capacity: if requests arrive faster than the model can drain them, latency grows. When the queue is completely full the API returns HTTP 503 with Retry-After: 1 instead of consuming unbounded memory.
Run one Uvicorn worker per pod. Do not set --workers above 1 unless you explicitly want one complete model copy per worker.
For more throughput, scale pods horizontally. Each pod gets its own resident model and batching queue. Configure the readiness probe against /ready so a pod receives traffic only after its model is loaded.
For a GPU deployment, request one GPU per pod unless intentionally using another topology.
Content type
Image
Digest
sha256:0bc156b99…
Size
4 GB
Last updated
18 days ago
docker pull icsy7867/complexity-router:cuda