Skip to content

LLM Infrastructure

Building production LLM systems requires understanding deployment options, scaling patterns, and operational concerns. This chapter covers the infrastructure layer.

Table of Contents


Deployment Options

API vs Self-Hosted

Factor API Providers Self-Hosted
Setup time Minutes Days to weeks
Operational burden None Significant
Cost at low volume Lower Higher (fixed costs)
Cost at high volume Higher Lower (scale economics)
Latency control Limited Full control
Data privacy Data leaves your infra Data stays local
Model selection Provider's models Any open model
Customization Fine-tuning via API Full control

When to Use API Providers

# Decision framework
def should_use_api(requirements: dict) -> bool:
    # Strong signals for API
    if requirements["time_to_market"] == "urgent":
        return True
    if requirements["query_volume"] < 100_000_per_month:
        return True
    if requirements["team_ml_expertise"] == "low":
        return True

    # Strong signals for self-hosted
    if requirements["data_residency"] == "strict":
        return False
    if requirements["latency_p99_ms"] < 100:
        return False
    if requirements["query_volume"] > 10_000_000_per_month:
        return False

    # Default to API for simplicity
    return True

Self-Hosting Options

Option Complexity Performance Use Case
vLLM Medium Excellent Production serving
TGI (HuggingFace) Medium Very good HuggingFace ecosystem
TensorRT-LLM High Best (NVIDIA) Maximum performance
Ollama Low Good Development, small scale
llama.cpp Low Good CPU inference, edge

Serving Architecture

Single Model Serving

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Client    │────▶│   Gateway   │────▶│  LLM Server │
└─────────────┘     └─────────────┘     └─────────────┘
                           │
                           ▼
                    ┌─────────────┐
                    │    Cache    │
                    └─────────────┘

Multi-Model Serving

                    ┌─────────────────────────────── │
                    │         Load Balancer          │
                    └───────────────┬────────────────┘
                                    │
            ┌───────────────────────┼───────────────────────┐
            │                       │                       │
            ▼                       ▼                       ▼
    ┌───────────────┐       ┌───────────────┐       ┌───────────────┐
    │  GPT-4 Pool   │       │  Claude Pool  │       │ Llama 70B Pool│
    │  (API calls)  │       │  (API calls)  │       │ (self-hosted) │
    └───────────────┘       └───────────────┘       └───────────────┘

Model Router Pattern

class ModelRouter:
    def __init__(self):
        self.models = {
            "simple": GPT4oMini(),
            "complex": Claude35Sonnet(),
            "code": Claude35Sonnet(),
            "long_context": Gemini15Pro(),
            "vision": GPT4o()
        }
        self.classifier = QueryClassifier()

    async def route(self, request: Request) -> Response:
        # Classify request type
        request_type = self.classifier.classify(request)

        # Route to appropriate model
        model = self.models[request_type]

        # Execute with fallback
        try:
            return await model.generate(request)
        except RateLimitError:
            return await self.fallback(request, request_type)

    async def fallback(self, request: Request, original_type: str) -> Response:
        # Define fallback order
        fallbacks = {
            "simple": ["complex", "long_context"],
            "complex": ["simple"],
            "code": ["complex"]
        }

        for fallback_type in fallbacks.get(original_type, []):
            try:
                return await self.models[fallback_type].generate(request)
            except Exception:
                continue

        raise ServiceUnavailableError("All models unavailable")

Scaling Patterns

Horizontal Scaling

# Kubernetes HPA config for LLM service
hpa_config = """
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: llm-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: llm-service
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Pods
    pods:
      metric:
        name: requests_per_second
      target:
        type: AverageValue
        averageValue: 100
"""

GPU Scaling for Self-Hosted

Scale GPUs Suggested Setup
Dev/Test 1 Single A10G or L4
Small prod 2-4 2x A100 with tensor parallel
Medium prod 4-8 4x H100 with tensor parallel
Large prod 8+ Multi-node with pipeline parallel

Queue-Based Architecture

For high-throughput async workloads:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  Producers  │────▶│    Queue    │────▶│  Consumers  │
└─────────────┘     │  (Redis/    │     │  (LLM       │
                    │   SQS)      │     │   Workers)  │
                    └─────────────┘     └─────────────┘
                                               │
                                               ▼
                                        ┌─────────────┐
                                        │  Results    │
                                        │  Store      │
                                        └─────────────┘
class AsyncLLMProcessor:
    def __init__(self):
        self.queue = RedisQueue("llm_requests")
        self.results = RedisResults("llm_results")

    async def submit(self, request: Request) -> str:
        request_id = generate_id()
        await self.queue.enqueue({
            "id": request_id,
            "request": request.to_dict()
        })
        return request_id

    async def get_result(self, request_id: str, timeout: int = 300) -> Response:
        return await self.results.wait_for(request_id, timeout)

    # Worker process
    async def worker_loop(self):
        while True:
            job = await self.queue.dequeue()
            try:
                result = await self.llm.generate(job["request"])
                await self.results.store(job["id"], result)
            except Exception as e:
                await self.results.store_error(job["id"], str(e))

Cost Management

Cost Tracking

class CostTracker:
    # Pricing as of December 2025 (verify current rates)
    PRICING = {
        "gpt-4o": {"input": 2.50, "output": 10.00},  # per 1M tokens
        "gpt-4o-mini": {"input": 0.15, "output": 0.60},
        "claude-3.5-sonnet": {"input": 3.00, "output": 15.00},
        "claude-3.5-haiku": {"input": 0.25, "output": 1.25},
    }

    def calculate_cost(
        self,
        model: str,
        input_tokens: int,
        output_tokens: int
    ) -> float:
        pricing = self.PRICING[model]
        input_cost = (input_tokens / 1_000_000) * pricing["input"]
        output_cost = (output_tokens / 1_000_000) * pricing["output"]
        return input_cost + output_cost

    def track(self, request_id: str, model: str, tokens: dict):
        cost = self.calculate_cost(
            model,
            tokens["input"],
            tokens["output"]
        )

        self.metrics.record(
            "llm_cost",
            cost,
            tags={"model": model, "request_id": request_id}
        )

        return cost

Cost Optimization Strategies

Strategy Savings Implementation
Model routing 50-80% Route simple queries to cheap models
Caching 30-70% Cache frequent queries
Prompt optimization 10-30% Shorter prompts, structured output
Batch API 50% Use batch endpoints for async work
Self-hosting Variable At scale, can be cheaper

Budget Alerts

class BudgetManager:
    def __init__(self, daily_budget: float, alert_threshold: float = 0.8):
        self.daily_budget = daily_budget
        self.alert_threshold = alert_threshold

    async def check_and_alert(self):
        today_cost = await self.get_today_cost()
        utilization = today_cost / self.daily_budget

        if utilization >= 1.0:
            await self.alert("CRITICAL: Daily budget exceeded", today_cost)
            # Consider enabling cost controls
            await self.enable_rate_limiting()
        elif utilization >= self.alert_threshold:
            await self.alert("WARNING: Approaching daily budget", today_cost)

    async def enable_rate_limiting(self):
        # Reduce throughput to stay within budget
        self.rate_limiter.set_rate(
            requests_per_minute=self.calculate_safe_rate()
        )

Monitoring and Alerting

Key Metrics

LLM_METRICS = {
    # Latency
    "ttft_seconds": "Time to first token",
    "total_latency_seconds": "Total request time",

    # Throughput
    "requests_per_second": "Request rate",
    "tokens_per_second": "Token generation rate",

    # Resources
    "gpu_utilization": "GPU compute usage",
    "gpu_memory_utilization": "GPU memory usage",
    "kv_cache_utilization": "KV cache usage",

    # Quality (sampled)
    "quality_score": "LLM-as-judge score",
    "faithfulness_score": "RAG faithfulness",

    # Errors
    "error_rate": "Failed requests percentage",
    "rate_limit_hits": "Rate limit rejections",

    # Cost
    "cost_per_request": "Average cost per request",
    "daily_cost": "Total daily spend"
}

Alert Configuration

alerts:
  - name: high_error_rate
    condition: error_rate > 0.05
    for: 5m
    severity: critical

  - name: high_latency
    condition: p99_latency > 10s
    for: 5m
    severity: warning

  - name: cost_spike
    condition: hourly_cost > 2 * avg_hourly_cost
    for: 1h
    severity: warning

  - name: quality_degradation
    condition: avg_quality_score < 3.5
    for: 30m
    severity: warning

  - name: gpu_memory_pressure
    condition: gpu_memory_utilization > 0.95
    for: 5m
    severity: warning

Disaster Recovery

Multi-Provider Failover

class MultiProviderClient:
    def __init__(self):
        self.providers = [
            OpenAIClient(),
            AnthropicClient(),
            GoogleClient()
        ]
        self.primary = 0

    async def generate(self, request: Request) -> Response:
        # Try primary provider first
        try:
            return await self.providers[self.primary].generate(request)
        except (RateLimitError, ServiceError) as e:
            return await self.failover(request, e)

    async def failover(self, request: Request, original_error: Exception) -> Response:
        for i, provider in enumerate(self.providers):
            if i == self.primary:
                continue
            try:
                response = await provider.generate(request)
                # Log failover for monitoring
                self.log_failover(self.primary, i, original_error)
                return response
            except Exception:
                continue

        raise AllProvidersUnavailable("All LLM providers failed")

Graceful Degradation

class GracefulDegradation:
    def __init__(self):
        self.cache = ResponseCache()
        self.fallback_responses = FallbackResponses()

    async def handle_outage(self, request: Request) -> Response:
        # Level 1: Try cache
        cached = await self.cache.get_similar(request.query)
        if cached and cached.similarity > 0.9:
            return Response(
                content=cached.response,
                metadata={"source": "cache", "degraded": True}
            )

        # Level 2: Try fallback responses
        fallback = self.fallback_responses.get(request.intent)
        if fallback:
            return Response(
                content=fallback,
                metadata={"source": "fallback", "degraded": True}
            )

        # Level 3: Graceful error
        return Response(
            content="I am currently experiencing issues. Please try again later or contact support.",
            metadata={"source": "error", "degraded": True}
        )

May 2026 AI Accelerator Landscape

The hardware picture has shifted faster between January and May 2026 than at any previous moment in the AI build-out. The capacity announcements add up to over a trillion dollars in committed cloud spend and the supply chain is no longer single-vendor. This section is the snapshot a senior architect should be carrying into capacity-planning conversations in May 2026.

NVIDIA Blackwell Ultra (B300 / GB300 NVL72)

The flagship is the B300 ("Blackwell Ultra"), shipping in volume since January 2026 (NVIDIA newsroom announcement).

Spec B300 / GB300 NVL72
HBM3e per GPU 288 GB
Peak FP4 (sparse) ~15 PFLOPS
Form factor NVL72 rack: 72 Blackwell Ultra GPUs + 36 Grace CPUs
Aggregate NVLink bandwidth in NVL72 ~130 TB/s
Total HBM per NVL72 ~20 TB
Projected racks shipping in 2026 ~60,000 (Jensen Huang, GTC 2026 keynote)

The strategic pitch is "AI factories": the NVL72 is sold as the smallest unit of a coherent, NVLink-domain inference / training cell rather than as individual cards. For frontier model training (Anthropic, OpenAI, Google's external work) and the largest reasoning-model inference workloads, this is still the default in May 2026.

The trade-off has stayed the same: highest absolute performance, highest absolute price, deepest software lock-in. CUDA, NCCL, and TensorRT-LLM all assume NVIDIA. If you architect around them, you have committed.

AMD MI400 and Helios Rack

AMD's MI400 (announced Q4 2025, sampling Q1 2026, GA mid-2026) is the credible second source.

Spec MI400
Memory HBM4, 432 GB per GPU
Memory bandwidth ~20 TB/s
Peak FP4 ~13 PFLOPS
Rack solution Helios: EPYC Venice CPUs, MI400 GPUs, Pensando Vulcano 800Gb NICs
Software ROCm 7.x with PyTorch / vLLM / SGLang first-class support

The 432 GB per GPU is the headline: it sits more than 50% above the B300's 288 GB. For MoE serving (where the limiting factor is keeping expert weights resident) and for KV-cache-heavy long-context workloads, the per-GPU memory advantage is real. AMD has also closed most of the software gap; ROCm 7.x is no longer the disqualifier it was in 2023. Open-source serving frameworks routinely test on both.

The catch: production deployment maturity. NVIDIA has shipped at scale to every hyperscaler for two generations; AMD is still ramping the volume side of the supply chain. Hyperscalers (Meta, Microsoft, Oracle Cloud, and notably the AWS Trainium fleet for non-Trainium workloads) are running mixed fleets.

AWS Trainium3 and the Anthropic $100B+ Deal

In November 2025, Anthropic and AWS announced an expansion to up to 5 gigawatts of compute capacity through 2026, anchored on Trainium chips and described as a "$100B+" deal (AWS news release).

Key numbers:

Spec Trainium3
Process node 3nm
Configuration Trn3 UltraServer with 144 chips per system
Peak perf vs T2 ~4.4x in target workloads
Memory HBM3e
Networking NeuronLink across the UltraServer; EFA across the cluster

The strategic implication: AWS now has a credible vertically-integrated AI fabric (Trainium silicon + Annapurna networking + EC2 + Bedrock). For inference-heavy workloads on Anthropic models, the price/performance is competitive with NVIDIA on H200-class hardware and improving toward B300 parity by end of 2026.

The constraint: Trainium runs the AWS Neuron SDK, not CUDA. Porting a stack means rebuilding kernels, retesting numerics, and re-tuning batching. Worth it at scale, painful at small scale.

Cerebras IPO (May 2026)

Cerebras priced its IPO on May 14, 2026 at $185/share and raised roughly $5.55B, opening above $190 and closing the first day near a ~$100B valuation (CNBC coverage; The Register).

What changed in the market because of it:

  • AWS partnered with Cerebras for high-throughput inference (AWS / Cerebras blog post). The pitch is Trainium3 for serving Anthropic and other in-house workloads, Cerebras for ultra-low-latency Llama / OSS workloads.
  • The CS-3 wafer-scale engine remains the only credible option for single-chip, single-replica inference of a 70B+ model at <50ms TTFT.
  • The Cerebras Cloud API has been used as a quick second source for teams whose primary stack is GPU-based and want a latency edge without porting.

The IPO is structurally important because it changes the financing thesis: there is now a public-market path for a non-NVIDIA inference vendor, which makes it cheaper for the next entrants to raise.

Tenstorrent Galaxy Blackhole

Tenstorrent's Galaxy reached general availability on April 28, 2026 (The Register; EE Times).

Spec Galaxy Blackhole
Per-server 32 Blackhole chips
Per-chip RISC-V cores, Tensix tiles, no external memory hierarchy
Peak BlockFP8 ~23 PFLOPS per server
Memory LPDDR4X (chip-attached) + on-chip SRAM
List price ~$110,000 per 32-chip server
Architecture Fully open RISC-V control plane, open firmware, open compiler

The open-source RISC-V story matters for two audiences:

  • Hyperscalers and sovereign clouds that want a non-CUDA stack with full visibility into firmware and toolchain.
  • Research labs building custom kernels who hit walls with CUDA's closed bits.

At $110K per server, Galaxy is roughly an order of magnitude cheaper than a comparable NVIDIA inference rack for some workloads. It is not a frontier-training competitor. It is an inference and small-fine-tuning competitor where the per-dollar argument is overwhelming.

Stargate and the Scale of Cloud Commitments

The capacity story is no longer just about chips; it is about the buildings around them.

  • Stargate (OpenAI / Oracle / SoftBank joint venture) has committed roughly $1.4 trillion in total cloud spend across the program (OpenAI announcement page).
  • The Abilene, Texas flagship site is online at 1.2 GW as of Q1 2026, with multi-gigawatt expansions under construction across seven announced sites totaling roughly 7 GW of planned capacity.
  • Over $400B has already been invested or contracted toward this footprint per public filings and announcements (Oracle Q3 FY26 earnings, SoftBank investor materials).

The architectural implication for senior engineers: the marginal cost of inference for frontier-model providers is dropping faster than the public API pricing would suggest. Spot capacity, off-peak inference batching, and multi-region failover are all easier in 2026 because the underlying buildings exist.

A Three-Tier Fleet Strategy

flowchart TD
    A[Production AI workload] --> B{What is the dominant constraint?}
    B -->|Frontier training, max FLOPS, NVLink coherency| C[Tier 1: Training and Heavy Compute]
    B -->|Cost per token, throughput, MoE serving| D[Tier 2: High-Throughput Inference]
    B -->|Edge, latency, sovereignty, open stack| E[Tier 3: Edge and Specialty]

    C --> C1[B300 NVL72 racks]
    C --> C2[MI400 Helios racks for MoE training]

    D --> D1[Trainium3 UltraServers for Anthropic workloads]
    D --> D2[MI400 for memory-bound inference]
    D --> D3[Cerebras CS-3 for single-replica low-latency]

    E --> E1[Tenstorrent Galaxy for cheap inference]
    E --> E2[Apple Silicon / consumer GPUs for on-device]
    E --> E3[Groq LPU for specific low-latency niches]
Tier What It Serves Default Hardware Why
Tier 1: Training and Heavy Compute Frontier model training, reasoning-heavy inference, multi-trillion-parameter MoE B300 NVL72, MI400 Helios Need NVLink-class coherency and the largest HBM pools available
Tier 2: High-Throughput Inference API products, RAG backends, agent platforms Trainium3, MI400, Cerebras CS-3, B300 Optimize for cost per token and predictable P99, often MoE-aware
Tier 3: Edge and Specialty Latency-critical, sovereign, open-source-firmware mandated, low total spend Tenstorrent Galaxy, Apple Silicon, consumer GPUs, Groq LPU $/perf, open stack, regulatory locality

The framing that matters in 2026: no senior architect designs a serious AI product around a single vendor anymore. The capacity is too contested, the price moves too fast, and the failure modes are too correlated within a single vendor's stack. Multi-vendor is the new default.

Take-Aways for Capacity Planning

  • Plan around memory per accelerator as much as FLOPS. MoE serving is bottlenecked on expert residency.
  • Treat CUDA lock-in as a real cost. ROCm 7.x is good enough for most production serving. Neuron is good enough for Anthropic and any team willing to do the porting work. Open RISC-V is good enough for cost-sensitive inference.
  • The hyperscaler choice now drives the chip choice as much as the other way around. AWS = Trainium + Cerebras + some NVIDIA. Microsoft = NVIDIA + Maia. Google = TPU + some NVIDIA. Oracle = NVIDIA at scale.
  • $/token has been falling roughly 3-5x per year through 2025 and 2026 (a16z State of AI Compute). Long-term contracts at 2024 prices are now usually a worse deal than spot.

Interview Questions

Q: How would you design infrastructure for 1M LLM queries per day?

Strong answer:

"At 1M queries per day, that is about 12 queries per second on average, with peaks potentially 3-5x higher. Here is my approach:

Architecture: - Load balancer distributing across multiple API endpoints - Model router for cost optimization (route simple queries to cheaper models) - Redis cache for frequent queries - Queue-based processing for async workloads

Cost optimization is critical at this scale: - Route 60-70% of simple queries to GPT-4o-mini or Claude Haiku - Implement semantic caching (30%+ cache hit rate target) - Use batch API for non-urgent requests (50% discount) - At this volume, self-hosting becomes cost-competitive

Reliability: - Multi-provider setup with automatic failover - Rate limiting per user to prevent abuse - Queue-based architecture for handling spikes - Graceful degradation when providers are unavailable

Monitoring: - Real-time cost tracking with budget alerts - Latency percentiles (p50, p95, p99) - Quality metrics sampled continuously - Error rate and rate-limit hit tracking

At 1M queries with average 2K tokens, using GPT-4o would cost about $25K/day. With routing and caching, I can reduce this to $5-8K/day."

Q: When would you self-host vs use API providers?

Strong answer:

"My decision framework considers several factors:

Use API providers when: - Volume is under 1M queries/month (cost crossover point) - Time-to-market is critical - Team lacks GPU infrastructure expertise - You want the latest models immediately - Workload is variable and hard to predict

Self-host when: - Data cannot leave your infrastructure (compliance, security) - Volume exceeds 10M queries/month (significant savings) - You need latency under 100ms P99 - You need custom model weights or fine-tuning - You want full control over model behavior

Hybrid approach often works best: - Self-host for high-volume predictable workloads - API for spikes and specialized models - API as fallback for self-hosted failures

The hidden costs of self-hosting: GPU procurement/rental, engineering time for ops, model updates, monitoring infrastructure. Factor in at least 1-2 dedicated engineers for infrastructure."


References

  • vLLM: https://docs.vllm.ai/
  • TensorRT-LLM: https://github.com/NVIDIA/TensorRT-LLM
  • Text Generation Inference: https://huggingface.co/docs/text-generation-inference
  • OpenAI Pricing: https://openai.com/pricing
  • Anthropic Pricing: https://www.anthropic.com/pricing

Next: CI/CD for LLM Applications