Lesson 32 · Slide 1
Deployment Patterns
Taking LLM applications from prototype to production — architecture patterns, hosting options, scaling strategies, and cost optimization.
Learning Objectives
- Choose the right architecture pattern (API gateway, queue-based, streaming) for your use case
- Compare hosting options: serverless, containers, and managed services
- Deploy and serve open-weight models with vLLM, TGI, and llama.cpp
- Implement scaling strategies: horizontal scaling, load balancing, rate limiting
- Apply caching (semantic and exact-match) to reduce latency and cost
- Set up CI/CD pipelines with canary deployments and rollback for LLM apps
Lesson 32 · Slide 2
Architecture Patterns
LLM applications typically follow one of three request-handling patterns, each suited to different latency and throughput requirements.
API Gateway
A reverse proxy sits in front of your LLM service. Routes requests, enforces auth, applies rate limits, and handles retries. Good for synchronous use cases where the client waits for a response.
Queue-Based Processing
Requests land in a message queue (SQS, RabbitMQ, Redis Streams). Workers pull jobs at their own pace. Ideal for batch workloads — document processing, bulk classification, offline summarisation.
Streaming (SSE / WebSocket)
Tokens stream to the client as the model generates them. First-token latency drops from seconds to milliseconds. Essential for chat interfaces and real-time UX.
Choosing a Pattern
- Latency-sensitive + interactive → Streaming
- Synchronous + moderate volume → API Gateway
- High volume + tolerant of delay → Queue-based
Notes: Many production systems combine patterns — e.g., an API gateway for real-time chat with a queue for background summarisation jobs.
Lesson 32 · Slide 3
Hosting Options
Where you run your LLM application depends on cost, scale, and whether you are calling an external API or self-hosting a model.
| Option | Best For | Trade-offs |
| Serverless (Lambda, Cloud Functions) | Low-traffic API wrappers, webhook handlers | Cold starts (up to 10s), 15-min timeout, no GPU |
| Containers (Docker + ECS/K8s) | Persistent services, self-hosted models | More ops overhead, but full control over runtime |
| Managed ML services (SageMaker, Vertex AI) | Self-hosted models with autoscaling | Higher cost, vendor lock-in, but minimal ops |
| GPU cloud (RunPod, Modal, Replicate) | On-demand GPU inference | Pay-per-second GPU, great for bursty workloads |
Key Decision: API vs. Self-Hosted
If you are calling Claude, GPT, or Gemini APIs, you don't need GPU infrastructure — a lightweight container or serverless function is sufficient. Self-hosting open models (Llama, Mistral) requires GPU instances and a serving framework.
Lesson 32 · Slide 4
Self-Hosting Open Models
Three production-grade serving frameworks dominate the self-hosting landscape.
vLLM
- PagedAttention for efficient memory management
- Continuous batching for high throughput
- OpenAI-compatible API out of the box
- Best for: high-throughput GPU serving
Text Generation Inference (TGI)
- Built by Hugging Face, Rust-based
- Tensor parallelism for multi-GPU
- Production-tested on Hugging Face Inference Endpoints
- Best for: HF ecosystem integration
llama.cpp Server
- CPU inference with optional GPU offloading
- GGUF quantised formats (Q4, Q5, Q8)
- Minimal dependencies, runs on consumer hardware
- Best for: edge, local, cost-constrained deploys
# Launch vLLM with OpenAI-compatible API
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--port 8000 --max-model-len 4096
# Launch TGI via Docker
docker run --gpus all -p 8080:80 \
ghcr.io/huggingface/text-generation-inference:latest \
--model-id meta-llama/Llama-3.1-8B-Instruct
# Launch llama.cpp server
./llama-server -m llama-3.1-8b-instruct-q5_k_m.gguf \
--port 8080 --ctx-size 4096
Lesson 32 · Slide 5
Scaling: Load Balancing & Rate Limiting
LLM inference is GPU-bound and slow compared to typical web APIs. Scaling strategies must account for long request durations and high per-request cost.
Horizontal Scaling
- Replicate your inference service behind a load balancer (NGINX, ALB, Traefik)
- Use least-connections routing (not round-robin) — LLM requests vary wildly in duration
- Autoscale on GPU utilisation or queue depth, not CPU
Rate Limiting
- Token-bucket or sliding-window per API key / user
- Limit both requests/minute and tokens/minute (a single long request can be as expensive as 100 short ones)
- Return
429 Too Many Requests with a Retry-After header
# Example: FastAPI rate limiter with slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.post("/v1/chat/completions")
@limiter.limit("20/minute")
async def chat(request: Request, body: ChatRequest):
return await generate_response(body)
Notes: Upstream API providers (Anthropic, OpenAI) have their own rate limits. Your gateway should handle 429s from upstream with exponential backoff.
Lesson 32 · Slide 6
Caching Strategies
LLM inference is expensive. Caching identical or semantically similar requests can cut costs by 30-80% in many production workloads.
Exact-Match Cache
- Hash the full prompt (system + user messages)
- Store response in Redis / DynamoDB with a TTL
- Works well for deterministic tasks (classification, extraction, structured output)
- Use
temperature=0 for consistent cache hits
Semantic Cache
- Embed the prompt and find nearest neighbours in a vector store
- If similarity > threshold, return the cached response
- Works for FAQ-style queries where paraphrasing is common
- Libraries: GPTCache, LangChain CacheBackedEmbeddings
import hashlib, json, redis
r = redis.Redis()
def cached_completion(messages, model, **kwargs):
cache_key = hashlib.sha256(
json.dumps({"messages": messages, "model": model}, sort_keys=True).encode()
).hexdigest()
cached = r.get(cache_key)
if cached:
return json.loads(cached)
response = client.messages.create(model=model, messages=messages, **kwargs)
r.setex(cache_key, 3600, json.dumps(response.to_dict()))
return response
Notes: Anthropic also offers prompt caching at the API level, which caches the KV-cache for long system prompts. This is complementary to application-level caching.
Lesson 32 · Slide 7
CI/CD, Canary Deploys & Rollback
LLM applications need special CI/CD considerations because behaviour changes can be subtle and hard to test with unit tests alone.
CI Pipeline for LLM Apps
- Lint & type-check — standard code quality gates
- Unit tests — mock LLM responses, test parsing and orchestration logic
- Eval suite — run a curated set of prompts against the real model; assert on quality metrics (accuracy, format compliance, latency)
- Build & push container — tag with git SHA for traceability
Canary Deployment
- Route 5-10% of traffic to the new version
- Compare metrics: latency p50/p99, error rate, eval scores, user feedback signals
- Promote to 100% only if metrics hold for 30+ minutes
Rollback Triggers
- Error rate exceeds baseline by >2x
- p99 latency increases by >50%
- Eval regression detected on safety or accuracy metrics
- Keep the previous container image tagged
:rollback for instant revert
Notes: When changing prompts (not just code), treat the prompt as a versioned artefact. Store prompts in version control and include them in your eval pipeline.
Lesson 32 · Slide 8
Lab & Homework
Lab Exercises
- Containerise an LLM App — write a Dockerfile for a FastAPI app that wraps the Claude API, build and run the container locally
- Add Caching — implement exact-match caching with Redis and measure the hit rate across repeated requests
- Load Test — use Locust to simulate concurrent users, observe latency under load, and identify bottlenecks
Homework Tasks
- Write a multi-stage Dockerfile that produces a minimal production image and add a health check endpoint
- Design a canary deployment pipeline (as a diagram or YAML config) for an LLM chat service
- Research and compare vLLM vs TGI benchmark throughput for a 7B-parameter model; summarise when you would choose each