High-performance, autoscaling inference architecture using Kubernetes, KEDA, and WebSockets for real-time token streaming.
A production-grade LLMOps architecture that handles high-concurrency inference requests without dropping connections. Uses an Event-Driven Architecture (EDA) with:
- Redis Queue for decoupling API from inference workers
- WebSockets for real-time token streaming (ChatGPT-style)
- KEDA for intelligent autoscaling based on queue depth
- Native llama.cpp for high-performance CPU inference
- Prometheus/Grafana for observability
┌─────────────┐ WebSocket ┌─────────────┐
│ Client │◄────────────────────►│ Gateway │──► /metrics
└─────────────┘ └──────┬──────┘
│
rpush │ subscribe
▼
┌──────────────┐
│ Redis │
│ Queue/PubSub│
└──────┬───────┘
│
blpop │ publish
▼
┌──────────────┐
│ Worker │ ◄── KEDA scales based
│ (llama.cpp) │ on queue depth
└──────────────┘
- Client connects via WebSocket to
/ws/generate - Gateway creates request ID, pushes job to Redis queue, subscribes to response channel
- KEDA monitors queue depth, scales workers when jobs > threshold
- Worker pops job, runs llama.cpp CLI, streams tokens via Redis Pub/Sub
- Gateway receives tokens from Redis, forwards to client in real-time
| Component | Technology |
|---|---|
| Orchestration | Kubernetes (Minikube) |
| Traffic | NGINX Ingress |
| Message Broker | Redis (Queue + Pub/Sub) |
| Autoscaling | KEDA |
| API Gateway | FastAPI + WebSockets |
| Inference | llama.cpp (native CLI, built from source) |
| Observability | Prometheus + Grafana |
| Model | Qwen 3 1.7B (Q8_0 quantized) |
- Docker & Docker Compose
- Python 3.11+
- ~2GB disk space for model
- For K8s: Minikube, Helm, kubectl
# 1. Download the model (~1.8GB)
make download-model
# 2. Build and start (first build takes ~5 min for llama.cpp)
make docker-up
# 3. Watch logs
make docker-logs
# 4. Open http://localhost:8000# 1. Setup Minikube with KEDA
make k8s-setup
# 2. Build images in Minikube
make k8s-build
# 3. Deploy everything
make k8s-deploy
# 4. Deploy observability
make k8s-observability
# 5. Add to /etc/hosts and tunnel
echo "127.0.0.1 api.local" | sudo tee -a /etc/hosts
minikube tunnel
# 6. Open http://api.localReal-time streaming generation.
// Request
{"prompt": "What is AI?", "max_tokens": 512, "temperature": 0.7}
// Response stream
{"type": "start", "request_id": "uuid"}
{"type": "token", "content": "A", "request_id": "uuid"}
{"type": "token", "content": "rtificial", "request_id": "uuid"}
{"type": "complete", "request_id": "uuid", "token_count": 142}| Endpoint | Description |
|---|---|
GET / |
Chat UI |
GET /health |
Health check |
GET /metrics |
Prometheus metrics |
GET /queue/status |
Queue depth |
POST /api/generate |
Non-streaming (queues job) |
gateway_requests_total- Request count by statusgateway_active_websocket_connections- Active connectionsgateway_redis_queue_length- Queue depthgateway_request_latency_seconds- Request latency histogramgateway_tokens_generated_total- Total tokens generated
Access Grafana after deploying observability:
make grafana-port # Opens localhost:3000
# Login: admin / fluxDashboard includes:
- Queue length gauge
- Active WebSocket connections
- Worker pod count
- Request rate over time
- Latency percentiles (p50, p95, p99)
- Token generation rate
# Light test (10 requests)
make load-test
# Heavy test (50 requests)
make load-test-heavy
# Watch KEDA scale workers
kubectl get pods -n flux -w# Flood the queue with 50 jobs
make flood-queue
# Watch KEDA scale workers from 1 → N
kubectl get pods -n flux -w
# Check queue status
make queue-statusFlux/
├── services/
│ ├── gateway/ # FastAPI WebSocket Gateway
│ │ ├── gateway.py # WebSocket handling, Prometheus metrics
│ │ ├── Dockerfile
│ │ └── requirements.txt
│ └── worker/ # Inference Worker
│ ├── worker.py # Redis consumer, llama.cpp CLI wrapper
│ ├── Dockerfile # Builds llama.cpp from source
│ └── requirements.txt
├── k8s/
│ ├── namespace.yaml
│ ├── configmap.yaml
│ ├── gateway-deployment.yaml
│ ├── worker-deployment.yaml
│ ├── ingress.yaml
│ ├── keda-scaledobject.yaml
│ ├── observability/ # Prometheus & Grafana
│ └── charts/llm-stack/ # Helm Chart
├── scripts/
│ ├── download_model.sh
│ └── load_test.py
├── models/ # Model files (gitignored)
├── docker-compose.yml
├── Makefile
└── pyproject.toml
| Variable | Default | Description |
|---|---|---|
REDIS_HOST |
localhost | Redis host |
REDIS_PORT |
6379 | Redis port |
QUEUE_NAME |
llm_queue | Job queue name |
CHANNEL_PREFIX |
task_channel | Pub/Sub channel prefix |
MODEL_PATH |
/models/Qwen3-1.7B-Q8_0.gguf | Model file path |
LLAMA_CLI_PATH |
/opt/llama.cpp/build/bin/llama-cli | llama-cli binary |
N_CTX |
2048 | Context window |
N_THREADS |
4 | CPU threads |
N_GPU_LAYERS |
0 | GPU layers (0=CPU only) |
- Phase 1: Local Docker Prototype
- Phase 2: Kubernetes Foundations
- Phase 3: Containerization & Deployment
- Phase 4: KEDA Autoscaling
- Phase 5: Observability (Prometheus/Grafana)
- Event-Driven Architecture: Decoupling request handling from compute
- Streaming with Pub/Sub: Real-time token delivery via Redis channels
- KEDA Operators: Extending K8s with queue-based autoscaling
- Native llama.cpp: Building from source for best performance
- LLMOps: Serving quantized models efficiently on CPU