AI Infrastructure at Scale
The ops playbook for running AI in production — from GPU metal to multi-region failover. Built for VP-level engineers managing AI workloads at Coursera-scale platforms.
The AI Inference Stack
▼The Serving Landscape in 2025
Model serving is the single most impactful infrastructure decision you'll make. The wrong choice costs you 3-10x in compute or adds 500ms+ latency. Here's the real landscape:
| Framework | Best For | Throughput | Latency (P50) | Production-Ready | Ecosystem |
|---|---|---|---|---|---|
| vLLM | High-throughput batch + online | ★★★★★ | ★★★★ | ★★★★ | HuggingFace native |
| TensorRT-LLM | Lowest latency on NVIDIA | ★★★★★ | ★★★★★ | ★★★ | NVIDIA only |
| TGI | HuggingFace ecosystem | ★★★★ | ★★★★ | ★★★★ | HF Inference Endpoints |
| Ollama | Dev/local, CPU+GPU | ★★ | ★★★ | ★★ | Easy setup, limited scale |
| SGLang | Structured generation | ★★★★★ | ★★★★ | ★★★ | Constrained decoding |
vLLM Deep Dive — The Default Choice
vLLM dominates production deployments for good reason: PagedAttention gives you 2-4x throughput over naive serving by eliminating KV cache memory waste. It's the PostgreSQL of inference — not always the fastest, but reliable and well-understood.
When to use vLLM
- Serving open-weight models (Llama 3.1, Mixtral, Qwen2.5, DeepSeek) in production
- You need high throughput with reasonable latency (<2s TTFT for 70B models)
- Multi-model serving from a single GPU pool
- You want continuous batching (critical for throughput under concurrent load)
# Production vLLM deployment — Docker Compose
# This config runs Llama 3.1 70B on 4x A100 80GB
version: "3.8"
services:
vllm:
image: vllm/vllm-openai:v0.6.4
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 4
capabilities: [gpu]
command: >
--model meta-llama/Llama-3.1-70B-Instruct
--tensor-parallel-size 4
--max-model-len 8192
--gpu-memory-utilization 0.92
--enable-prefix-caching
--max-num-batched-tokens 32768
--max-num-seqs 256
--port 8000
ports:
- "8000:8000"
volumes:
- /models:/root/.cache/huggingface
shm_size: "16gb"
--gpu-memory-utilization 0.92 — Leave 8% headroom. Going to 0.95+ causes OOM under burst load. --max-num-seqs 256 — Concurrent sequences. Higher = more throughput but higher P99 latency. Tune for your SLA. --enable-prefix-caching — Free 20-40% throughput boost if requests share system prompts.
TensorRT-LLM — When Latency is King
If you're on NVIDIA hardware (you probably are) and need the absolute lowest latency, TensorRT-LLM compiles models into optimized CUDA kernels. The tradeoff: complex build process, NVIDIA lock-in, and less community support.
When to pick TensorRT-LLM over vLLM
- Latency SLA under 200ms TTFT for interactive use cases
- You have dedicated SRE capacity for the build pipeline
- Running on NVIDIA Triton Inference Server already
- H100/H200 hardware (TRT-LLM extracts more from newer GPUs)
Throughput vs Latency — The Fundamental Tradeoff
API vs Self-Hosted: The First Decision
API-Based (Anthropic/OpenAI) API
- Zero infrastructure — just HTTP calls
- Auto-scaling handled by provider
- Frontier models (Claude Sonnet/Opus, GPT-4o)
- Cost: $3-15/MTok input, $15-75/MTok output
- Latency: 200-800ms TTFT typical
- Rate limits: 4K-10K RPM (enterprise)
- Best when: <10M queries/month, need frontier quality, small infra team
Self-Hosted (vLLM + Open Source) SELF
- Full control over latency, throughput, cost
- No rate limits — scale with hardware
- Models: Llama 3.1 70B, Mixtral, Qwen2.5
- Cost: $1.50-3.00/hr per A100 (amortized)
- Latency: 50-200ms TTFT (tunable)
- Need: GPU ops team, monitoring, on-call
- Best when: >50M queries/month, latency-critical, data sovereignty
Use API (Claude Sonnet) for complex reasoning tasks (essay grading, content generation) and self-hosted (Llama 3.1 8B quantized) for high-volume simple tasks (autocomplete, classification, translation). This cuts costs 60-70% vs all-API while keeping quality where it matters.
Quantization — Trading Precision for Speed
▼Why Quantization Matters
A 70B parameter model at FP16 requires ~140GB VRAM — that's 2x A100 80GB just to load. Quantize to INT4 and it fits on a single A100 with room for a 4K context KV cache. Quantization is the single biggest lever for reducing inference cost.
Quantization Formats Compared
| Format | Bits | VRAM (70B) | Quality Loss | Speed vs FP16 | Best Framework | Notes |
|---|---|---|---|---|---|---|
| FP16 | 16 | 140 GB | Baseline | 1.0x | Any | Reference quality |
| FP8 | 8 | 70 GB | ~0% | 1.5-1.8x | TRT-LLM, vLLM | H100/H200 native support |
| AWQ | 4 | 35 GB | 1-2% | 2.0-2.5x | vLLM, TGI | Activation-aware, best INT4 quality |
| GPTQ | 4 | 35 GB | 2-3% | 2.0-2.3x | vLLM, TGI | Older but well-tested, large zoo |
| GGUF | 2-8 | Varies | Varies | CPU+GPU | llama.cpp, Ollama | Best for CPU/mixed inference |
| INT4 (W4A16) | 4 | 35 GB | 1-3% | 2.0-2.5x | TRT-LLM | Weights INT4, activations FP16 |
Quality Benchmarks — Real Numbers
Internal benchmarks on Llama 3.1 70B across standard evals. Your mileage will vary by task — always benchmark on YOUR data.
| Format | MMLU (5-shot) | HumanEval | MT-Bench | Relative Quality |
|---|---|---|---|---|
| FP16 (baseline) | 82.0 | 80.5 | 8.95 | 100% |
| FP8 | 81.9 | 80.5 | 8.93 | ~99.8% |
| AWQ INT4 | 81.1 | 79.3 | 8.82 | ~98.5% |
| GPTQ INT4 | 80.4 | 78.0 | 8.71 | ~97.3% |
| GGUF Q4_K_M | 80.7 | 78.8 | 8.76 | ~97.8% |
| GGUF Q2_K | 74.2 | 68.1 | 7.95 | ~89% |
For most production tasks, AWQ INT4 is the sweet spot — you get 2x throughput with <2% quality loss. Below INT4 (e.g., GGUF Q2_K), quality degrades significantly. FP8 on H100/H200 is "free" quality — use it if your hardware supports it.
When Quantization Is Worth It
Practical: Quantizing with AutoAWQ
# Quantize Llama 3.1 70B to AWQ INT4
# Requires ~160GB RAM + 80GB GPU for calibration
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model_path = "meta-llama/Llama-3.1-70B-Instruct"
quant_path = "llama-3.1-70b-instruct-awq"
# Calibration config
quant_config = {
"zero_point": True,
"q_group_size": 128,
"w_bit": 4,
"version": "GEMM" # Use GEMM for vLLM compat
}
model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)
# 128-512 samples of real production prompts
calib_data = load_calibration_data()
model.quantize(tokenizer, quant_config=quant_config, calib_data=calib_data)
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)
Running Llama 3.1 70B on AWS — FP16 on 2x A100 80GB: $6.52/hr. AWQ INT4 on 1x A100 80GB: $3.26/hr. That's a 50% cost reduction with <2% quality loss. At 10M queries/month, that's ~$2,400/month saved per model instance.
Prompt Caching & Optimization
▼The Caching Landscape
Caching is the highest-ROI optimization in AI infrastructure. Unlike traditional web caching, LLM caching operates at multiple layers — from exact response dedup to KV cache reuse for shared prefixes.
| Cache Type | Where | Savings | Complexity | Applies To |
|---|---|---|---|---|
| Anthropic Prompt Caching | API-side | 90% on cached input tokens | Low | API |
| KV Cache (PagedAttention) | GPU VRAM | 2-4x throughput | Built-in | SELF |
| Prefix Caching | GPU/CPU | 20-60% latency reduction | Medium | SELF |
| Semantic Cache | Redis/app layer | 100% (exact hit) | Medium | BOTH |
| Batch API | API-side | 50% cost | Low | API |
Anthropic Prompt Caching — The Biggest Win for API Users
If you're using Claude, prompt caching is the single most impactful cost optimization. Cached input tokens cost 90% less. For applications with long system prompts or repeated context (course materials, rubrics, knowledge bases), this is transformative.
# Python - Anthropic SDK with prompt caching
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=[
{
"type": "text",
"text": LONG_RUBRIC_TEXT, # 4000+ tokens of grading rubric
"cache_control": {"type": "ephemeral"}
}
],
messages=[
{"role": "user", "content": student_essay}
]
)
# Check cache performance
usage = response.usage
print(f"Cache read: {usage.cache_read_input_tokens} tokens")
print(f"Cache miss: {usage.cache_creation_input_tokens} tokens")
Cost Math: Prompt Caching at Coursera Scale
Essay Grading — 500K Essays/Month
Prefix Caching — Self-Hosted
vLLM's --enable-prefix-caching reuses KV cache entries when requests share the same prefix (system prompt). This is the self-hosted equivalent of Anthropic's prompt caching.
The Batch API — 50% Off for Non-Urgent Work
Anthropic's Batch API processes requests asynchronously at 50% cost. For offline tasks — grading backlogs, content generation pipelines, bulk classification — this is pure savings.
# Batch API usage - process 10K essays overnight
import anthropic
client = anthropic.Anthropic()
batch = client.messages.batches.create(
requests=[
{
"custom_id": f"essay-{i}",
"params": {
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"messages": [{"role": "user", "content": essay}]
}
}
for i, essay in enumerate(essays[:10000])
]
)
# Cost: 50% of standard pricing. Completes in 15min-24hr.
Semantic Caching — Application Layer
For high-volume applications with repetitive queries (FAQ bots, tutoring on popular topics), semantic caching at the application layer can eliminate LLM calls entirely for ~20-40% of queries.
# Simple semantic cache with Redis
import hashlib, json, redis
r = redis.Redis()
def cached_completion(prompt, model):
cache_key = hashlib.sha256(
json.dumps({"prompt": prompt, "model": model}).encode()
).hexdigest()
cached = r.get(f"llm:{cache_key}")
if cached:
return json.loads(cached) # Hit! Zero cost.
result = call_llm(prompt, model)
r.setex(f"llm:{cache_key}", 3600, json.dumps(result))
return result
Layer 1: Anthropic prompt caching (always on, 30-40% savings). Layer 2: Semantic cache in Redis for FAQ/tutoring (eliminates 20-40% of calls). Layer 3: Batch API for offline grading (50% on batch work). Combined: 50-65% cost reduction vs naive API usage.
GPU Provisioning
▼GPU Hardware Landscape (2025)
| GPU | VRAM | FP16 TFLOPS | FP8 TFLOPS | Mem BW | Interconnect | Cloud $/hr | Best For |
|---|---|---|---|---|---|---|---|
| A100 80GB | 80 GB | 312 | — | 2.0 TB/s | NVLink 600GB/s | $1.50-3.26 | General inference, fine-tuning |
| H100 80GB | 80 GB | 990 | 1,979 | 3.35 TB/s | NVLink 900GB/s | $2.50-4.76 | Large model inference, FP8 |
| H200 141GB | 141 GB | 990 | 1,979 | 4.8 TB/s | NVLink 900GB/s | $3.50-5.50 | 70B+ without quantization |
| B200 | 192 GB | 2,250 | 4,500 | 8.0 TB/s | NVLink 1.8TB/s | $5.00-8.00 | Next-gen, largest models |
| A10G | 24 GB | 125 | — | 600 GB/s | PCIe | $0.75-1.20 | Small models (<13B quantized) |
| L4 | 24 GB | 121 | 242 | 300 GB/s | PCIe | $0.40-0.80 | Budget inference, FP8 |
Cloud Provider Comparison
| Provider | A100 80GB | H100 80GB | Spot Savings | Min Commit | Strengths |
|---|---|---|---|---|---|
| AWS (p4d/p5) | $3.26/hr | $4.76/hr | 50-70% | None / 1yr RI | Ecosystem, SageMaker |
| GCP (a2/a3) | $2.95/hr | $4.08/hr | 60-70% | None / 1yr CUD | TPU option, GKE ML |
| Azure (NC/ND) | $3.10/hr | $4.52/hr | 40-60% | None / 1yr | Enterprise, OpenAI integration |
| Lambda Labs | $1.50/hr | $2.49/hr | — | None | Cheapest on-demand, simple |
| RunPod | $1.64/hr | $2.69/hr | 30-50% | None | Serverless GPU, pay-per-sec |
Spot vs Reserved — The Cost Decision
Multi-GPU Inference — Parallelism Strategies
| Strategy | When to Use | Example | Overhead |
|---|---|---|---|
| Tensor Parallel | Model doesn't fit on 1 GPU | 70B FP16 -> 2x A100 | Low (NVLink) |
| Pipeline Parallel | Cross-node serving | 405B -> 8x H100 across 2 nodes | Medium (network) |
| Data Parallel | Scale throughput, model fits 1 GPU | 8B INT4 -> 4x replicas on 4x A100 | None (independent) |
# Terraform - Provision GPU cluster on AWS
resource "aws_instance" "inference_gpu" {
count = 4
ami = "ami-0abcdef1234567890" # NVIDIA Deep Learning AMI
instance_type = "p4d.24xlarge" # 8x A100 80GB
root_block_device {
volume_size = 500
volume_type = "gp3"
}
tags = {
Name = "inference-node-${count.index}"
Environment = "production"
Team = "ai-platform"
}
# Placement group for low-latency NVLink/EFA
placement_group = aws_placement_group.inference.id
}
resource "aws_placement_group" "inference" {
name = "inference-cluster"
strategy = "cluster"
}
# Auto Scaling Group for spot-based burst capacity
resource "aws_autoscaling_group" "inference_spot" {
desired_capacity = 0
max_size = 8
min_size = 0
mixed_instances_policy {
instances_distribution {
on_demand_percentage_above_base_capacity = 0
spot_allocation_strategy = "capacity-optimized"
}
launch_template {
launch_template_specification {
launch_template_id = aws_launch_template.gpu_spot.id
}
override { instance_type = "p4d.24xlarge" }
override { instance_type = "p4de.24xlarge" }
}
}
}
Baseline: 4x A100 80GB (reserved) running Llama 3.1 70B AWQ for high-volume tasks. Burst: Auto-scaling spot group 0-8x A100 for peak hours. API: Claude Sonnet for complex grading (no GPUs needed). Total steady-state cost: ~$15K-20K/month for self-hosted + $10-15K/month for API.
Scaling Patterns
▼Reference Architecture — Load Balancing AI Inference
Multi-Model Routing — The Smart Scaling Pattern
Not every request needs your most powerful (and expensive) model. A routing layer that classifies request complexity can cut costs 40-60% by sending simple tasks to cheap models.
# Model router - classify and route requests
from enum import Enum
class ModelTier(Enum):
FAST = "fast" # Haiku / Llama 8B - $0.25/MTok
SMART = "smart" # Sonnet / Llama 70B - $3/MTok
BEST = "best" # Opus - $15/MTok
def route_request(request) -> ModelTier:
task = request.task_type
# Classification, translation, autocomplete -> cheap model
if task in ["classify", "translate", "autocomplete", "summarize_short"]:
return ModelTier.FAST
# Essay grading, content generation -> capable model
if task in ["grade_essay", "generate_content", "explain_concept"]:
return ModelTier.SMART
# Curriculum design, complex reasoning -> best model
if task in ["curriculum_design", "research_synthesis"]:
return ModelTier.BEST
return ModelTier.SMART # default
Auto-Scaling Configuration
# Kubernetes HPA for vLLM inference pods
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: vllm-inference-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: vllm-llama-70b
minReplicas: 2 # Always-on baseline
maxReplicas: 8 # Peak capacity
metrics:
- type: Pods
pods:
metric:
name: vllm_pending_requests # Custom metric from vLLM
target:
type: AverageValue
averageValue: 50 # Scale up when queue > 50
- type: Pods
pods:
metric:
name: gpu_utilization
target:
type: AverageValue
averageValue: 80 # Scale at 80% GPU util
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 2
periodSeconds: 120
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 300
Request Queuing & Rate Limiting
GPUs don't scale like CPU services. Adding requests to an overloaded GPU doesn't linearly increase latency — it causes catastrophic P99 blowup. A vLLM server handling 200 concurrent requests at 100ms P50 might have 5s P99 at 300 concurrent. Always use backpressure (queue limits + 503 rejection) rather than unbounded request acceptance.
Observability
▼What to Measure
LLM observability is different from traditional service monitoring. Latency percentiles matter more than averages, cost is a first-class metric, and "quality" is something you need to track but can't directly measure from metrics alone.
| Metric | What | Target (Coursera-scale) | Alert Threshold |
|---|---|---|---|
| TTFT | Time to first token | P50 < 500ms, P99 < 2s | P99 > 3s for 5 min |
| TPS | Tokens per second (output) | > 40 tok/s per request | < 20 tok/s for 5 min |
| E2E Latency | Total request time | P50 < 3s, P99 < 10s | P99 > 15s for 5 min |
| Error Rate | 5xx + timeouts | < 0.1% | > 1% for 5 min |
| Cost/Query | Dollar cost per request | < $0.005 avg | > $0.02 avg (spike) |
| GPU Util | Compute utilization | 60-85% | < 30% or > 95% |
| Queue Depth | Pending requests | < 100 | > 500 for 2 min |
| Cache Hit | Prompt + semantic cache | > 40% | < 20% (misconfigured) |
Tracing LLM Calls
# Structured logging for LLM calls
import json, logging
logger = logging.getLogger("llm_trace")
class LLMTracer:
def trace_call(self, request, response, timing):
trace = {
"trace_id": request.trace_id,
"model": response.model,
"task_type": request.task_type,
# Token counts
"input_tokens": response.usage.input_tokens,
"output_tokens": response.usage.output_tokens,
"cached_tokens": response.usage.cache_read_input_tokens,
# Timing
"queue_time_ms": timing.queue_ms,
"ttft_ms": timing.ttft_ms,
"generation_ms": timing.generation_ms,
"total_ms": timing.total_ms,
# Cost
"cost_usd": self.calc_cost(response),
"cache_hit": response.usage.cache_read_input_tokens > 0,
}
logger.info(json.dumps(trace))
def calc_cost(self, response):
# Claude Sonnet pricing
P = {"input": 3.0/1e6, "output": 15.0/1e6, "cache": 0.3/1e6}
u = response.usage
return (
(u.input_tokens - u.cache_read_input_tokens) * P["input"] +
u.cache_read_input_tokens * P["cache"] +
u.output_tokens * P["output"]
)
Observability Stack
Error Budgets for LLM Services
| SLI | SLO | Error Budget (30d) | Burn Rate Alert |
|---|---|---|---|
| Availability | 99.9% | 43.2 min downtime | 14.4x in 1hr |
| Latency (TTFT P99) | < 3s | 0.1% requests over 3s | 3x in 6hr |
| Quality (human eval) | > 90% "good" | 10% poor responses | Weekly review |
| Cost per query | < $0.01 avg | 10% budget overrun | 2x daily average |
Cost Optimization
▼The Real Math: Cost Per Query
The most important number in AI infrastructure is cost per query. Everything else (GPU utilization, throughput, model selection) is a lever to move this number. Let's build the cost model ground-up.
API-Based Cost Model (Claude Sonnet)
Scenario: AI Tutor — 10M Queries/Month
Self-Hosted Cost Model (vLLM + Llama 70B AWQ)
Same AI Tutor — 10M Queries/Month, Self-Hosted
API vs Self-Hosted Breakeven Analysis
The breakeven chart above only shows cost. Claude Sonnet typically outperforms Llama 70B on complex tasks (essay grading, nuanced feedback) by 10-20% on quality benchmarks. The right answer is usually hybrid: self-hosted for volume/simple tasks, API for quality-critical tasks. At Coursera scale, a 70/30 split (70% self-hosted, 30% API) often minimizes cost while maintaining quality where it matters.
Cost Optimization Levers — Ranked by Impact
| # | Lever | Savings | Effort | Risk |
|---|---|---|---|---|
| 1 | Model routing (Haiku for simple) | 40-60% | Medium | Low |
| 2 | Prompt caching (API) / Prefix (self) | 30-40% | Low | None |
| 3 | Batch API for offline workloads | 50% on batch | Low | None |
| 4 | Quantization (AWQ INT4 / FP8) | 50% compute | Medium | Low |
| 5 | Semantic caching | 20-40% of calls | Medium | Stale responses |
| 6 | Prompt optimization (shorter) | 10-30% | Medium | Quality impact |
| 7 | Spot instances | 50-70% compute | High | Interruptions |
| 8 | Self-hosting (at scale) | 60-90% | Very High | Ops burden |
Cost Per Student — The Business Metric
Coursera-Scale: 100M Registered, 10M MAU
Production Architecture
▼Reference Architecture: AI-Powered EdTech Platform
Streaming Responses — Critical for UX
For interactive AI features (tutoring, Q&A), streaming is mandatory. Users perceive 200ms TTFT with streaming as "instant" but 3s wait-then-dump as "slow" — even if total time is identical.
# Server-Sent Events (SSE) streaming endpoint
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import anthropic, json
app = FastAPI()
async def stream_response(prompt: str):
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
) as stream:
for text in stream.text_stream:
yield f"data: {json.dumps({'text': text})}\n\n"
yield "data: [DONE]\n\n"
@app.post("/api/ai/stream")
async def ai_stream(request: Request):
body = await request.json()
return StreamingResponse(
stream_response(body["prompt"]),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no", # Disable nginx buffering
}
)
Failover Strategy
# Failover implementation with retries
class AIService:
FAILOVER_CHAIN = [
{"provider": "anthropic", "model": "claude-sonnet-4-20250514"},
{"provider": "self_hosted", "model": "llama-70b-awq"},
{"provider": "anthropic", "model": "claude-haiku-4-5-20251001"},
]
async def complete(self, request):
for i, target in enumerate(self.FAILOVER_CHAIN):
try:
response = await self._call_model(target, request)
if i > 0: # Log failover event
self.metrics.increment("failover_count",
tags={"from": self.FAILOVER_CHAIN[0]["model"],
"to": target["model"]})
return response
except (RateLimitError, TimeoutError, ServiceUnavailable):
continue
# All providers failed
return await self.graceful_degradation(request)
Multi-Region Deployment
Docker Compose — Full Local Dev Stack
# docker-compose.yml - Development AI platform stack
version: "3.8"
services:
ai-router:
build: ./services/ai-router
ports: ["8080:8080"]
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- VLLM_ENDPOINT=http://vllm:8000
- REDIS_URL=redis://redis:6379
- POSTGRES_URL=postgresql://ai:ai@postgres:5432/ai_platform
depends_on: [redis, postgres]
vllm:
image: vllm/vllm-openai:v0.6.4
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command: >
--model meta-llama/Llama-3.1-8B-Instruct
--max-model-len 4096
--gpu-memory-utilization 0.90
--enable-prefix-caching
ports: ["8000:8000"]
redis:
image: redis:7-alpine
ports: ["6379:6379"]
volumes: [redis-data:/data]
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ai_platform
POSTGRES_USER: ai
POSTGRES_PASSWORD: ai
ports: ["5432:5432"]
volumes: [pg-data:/var/lib/postgresql/data]
prometheus:
image: prom/prometheus:latest
ports: ["9090:9090"]
volumes:
- ./config/prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports: ["3000:3000"]
volumes:
- ./config/grafana/dashboards:/var/lib/grafana/dashboards
volumes:
redis-data:
pg-data:
Production Checklist
You now have a comprehensive understanding of AI infrastructure at production scale — from GPU metal to multi-region failover. The key takeaway: AI infrastructure is a continuous optimization problem across cost, quality, and latency. Start with API-based serving, add self-hosted when volume justifies it, and always measure cost per query as your north star metric.
Need this for a date?
Turn this course into a ramp-up pack sized to your minutes per day, or build an interview or certification pack for the day you need it.