<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Geek at Work Blog</title><link>https://geekat.work/</link><description>Recent blog posts on Geek at Work Blog</description><generator>Hugo (https://gohugo.io)</generator><language>en-US</language><managingEditor/><webMaster/><lastBuildDate>Mon, 26 Oct 2026 00:00:00 Z</lastBuildDate><atom:link href="https://geekat.work/tags/ai/index.xml" rel="self" type="application/rss+xml"/><item><title>AI Inference Infrastructure from a DevOps Perspective: Self-Managed Kubernetes vs Managed Cloud</title><link>https://geekat.work/posts/ai-inference-infrastructure-devops-kubernetes-vs-managed-cloud/</link><pubDate>Mon, 26 Oct 2026 00:00:00 Z</pubDate><author/><description>
Serving machine learning models is no longer a research problem — it is an infrastructure problem. Model inference workloads have their own shape: bursty traffic, expensive GPU-bound nodes, multi-gigabyte images and model weights, and autoscaling that reacts to tokens per second rather than CPU. In this guide we walk through what a production inference stack actually looks like — first as self-managed infrastructure on Kubernetes, then as managed cloud services from AWS, Google Cloud, Azure, and the GPU-native clouds — and compare the two paths on operations, scaling, latency, and cost from the perspective of the person who has to keep both running at 3 a.m.</description><content:encoded>&lt;p&gt;Serving machine learning models is no longer a research problem — it is an infrastructure problem. Model inference workloads have their own shape: bursty traffic, expensive GPU-bound nodes, multi-gigabyte images and model weights, and autoscaling that reacts to tokens per second rather than CPU. In this guide we walk through what a production inference stack actually looks like — first as self-managed infrastructure on Kubernetes, then as managed cloud services from AWS, Google Cloud, Azure, and the GPU-native clouds — and compare the two paths on operations, scaling, latency, and cost from the perspective of the person who has to keep both running at 3 a.m.&lt;/p&gt;
&lt;h2 id="why-inference-infrastructure-is-different" &gt;
&lt;div&gt;
&lt;a href="#why-inference-infrastructure-is-different"&gt;
#
&lt;/a&gt;
Why Inference Infrastructure Is Different
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;A model inference service looks like a normal web API but behaves very differently under the hood:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hardware constraints.&lt;/strong&gt; GPUs are expensive, scarce, and can&amp;rsquo;t be live-migrated. A pod that pins a GPU shapes how you schedule, drain, upgrade, and preempt. Driver/CUDA/kernel version alignment is a real compatibility matrix you must manage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Big, cold artifacts.&lt;/strong&gt; Images with CUDA runtimes and downloaded model weights easily reach 5–50 GB. Cold starts are measured in minutes, not seconds, which breaks naive scale-to-zero designs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom scaling signals.&lt;/strong&gt; HPA on CPU is nearly useless: vLLM saturates GPU KV-cache utilization way before CPU looks busy. You need scaling on model-specific metrics (queue depth, requests per replica, time to first token).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Different SLOs.&lt;/strong&gt; For LLM serving you don&amp;rsquo;t just care about p99 latency of a response; you care about time to first token (TTFT) and time per output token (TPOT), because streaming changes how users perceive latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost concentration.&lt;/strong&gt; A single A100/H100 node can cost several thousand dollars a month. Inference is where most of your ML cloud bill lives, and idle-GPU waste is the default state unless you engineer against it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These constraints drive the real architectural decision: where does the GPU fleet and the serving logic live — in your cluster, or in a provider&amp;rsquo;s?&lt;/p&gt;
&lt;h2 id="the-anatomy-of-an-inference-request" &gt;
&lt;div&gt;
&lt;a href="#the-anatomy-of-an-inference-request"&gt;
#
&lt;/a&gt;
The Anatomy of an Inference Request
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Before choosing infrastructure, it helps to understand what a single request actually does, because every infra decision downstream is reacting to this behavior.&lt;/p&gt;
&lt;p&gt;For a typical LLM request on vLLM:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Request admission.&lt;/strong&gt; The request arrives over HTTP/gRPC and enters a waiting queue.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weight residency check.&lt;/strong&gt; The model weights must already be in GPU memory — loading 16 GB from disk takes tens of seconds to minutes, so this must happen at startup, per replica.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batching.&lt;/strong&gt; The runtime waits a few milliseconds to coalesce concurrent requests into a batch. Modern serving engines do &lt;em&gt;continuous batching&lt;/em&gt;: new requests join a running batch at each decode step rather than waiting for the whole batch to finish, keeping GPU utilization flat under load.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prefill and decode.&lt;/strong&gt; The prompt is processed (prefill, compute-bound), then tokens are generated one at a time (decode, memory-bandwidth-bound). The KV cache — the stored attention state of the context — grows with prompt + output length and is the real memory consumer, not the weights.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Streaming response.&lt;/strong&gt; Tokens stream out over SSE/WebSocket as they&amp;rsquo;re generated.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Why this matters for DevOps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;KV cache&lt;/strong&gt; means memory, not compute, is often the bottleneck → you tune &lt;code&gt;max-model-len&lt;/code&gt;, quantization, and PagedAttention-style cache management. OOM at the KV cache is the most common production failure of a self-managed LLM deployment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous batching&lt;/strong&gt; means throughput per GPU is highly dependent on concurrency → autoscaling to more replicas can &lt;em&gt;decrease&lt;/em&gt; cost efficiency if it breaks batching. You often want fewer, busier replicas rather than many idle ones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weights at startup&lt;/strong&gt; means every scale-up event pays a multi-minute loading cost → your autoscaler needs long cooldowns and pre-warmed node pools to handle bursts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep this lifecycle in mind; it explains the Kubernetes specifics below.&lt;/p&gt;
&lt;h2 id="option-1-self-managed-inference-on-kubernetes" &gt;
&lt;div&gt;
&lt;a href="#option-1-self-managed-inference-on-kubernetes"&gt;
#
&lt;/a&gt;
Option 1: Self-Managed Inference on Kubernetes
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;The typical self-managed stack looks like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Common choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Serving runtime&lt;/td&gt;
&lt;td&gt;vLLM (LLMs), Triton with TensorRT/TensorRT-LLM, SGLang, KServe, BentoML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU enablement&lt;/td&gt;
&lt;td&gt;NVIDIA GPU Operator (drivers, device plugin, DCGM monitoring, MIG)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster/fleet&lt;/td&gt;
&lt;td&gt;EKS / GKE / AKS with GPU node groups, or bare metal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request routing&lt;/td&gt;
&lt;td&gt;Ingress or service mesh, load balancing aware of in-flight requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Autoscaling&lt;/td&gt;
&lt;td&gt;HPA + KEDA on custom metrics; Ray Autoscaler for LLM serving&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage/weights&lt;/td&gt;
&lt;td&gt;Model artifacts in object storage, PVC or init-container download, local NVMe cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;DCGM exporter, Prometheus, Grafana, LLM-specific dashboards&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="serving-runtimes-know-what-youre-deploying" &gt;
&lt;div&gt;
&lt;a href="#serving-runtimes-know-what-youre-deploying"&gt;
##
&lt;/a&gt;
Serving Runtimes: Know What You&amp;rsquo;re Deploying
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Choosing a runtime is the first real decision, and it&amp;rsquo;s a DevOps-relevant one because each has different operational characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;vLLM&lt;/strong&gt; — the current default for serving open-weight LLMs via an OpenAI-compatible API. Best-in-class continuous batching with PagedAttention. Operationally, you manage GPU memory limits carefully (the &lt;code&gt;gpu_memory_utilization&lt;/code&gt; flag) and version-bump aggressively: the project moves fast and performance improves release to release.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Triton Inference Server&lt;/strong&gt; — NVIDIA&amp;rsquo;s general-purpose server, targeting traditional ML (XGBoost, ONNX, vision models) and TensorRT-LLM for NVIDIA-tuned LLM serving. Best when you have a mixed zoo of model types behind one server, or you want NVIDIA&amp;rsquo;s engines. More config-heavy (model repository structure, config.pbtxt), but predictable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SGLang&lt;/strong&gt; — newer engine with advanced scheduling (RadixAttention, prefix caching), often outperforms vLLM on multi-turn conversation traffic where prompts share prefixes. Watch this space if chat workloads dominate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KServe&lt;/strong&gt; — not a runtime but a serving &lt;em&gt;framework&lt;/em&gt; on Kubernetes: gives you &lt;code&gt;InferenceService&lt;/code&gt; CRDs, canary traffic splits among model revisions, scale-from-zero via queue-proxy, and pluggable backends (it can run vLLM or Triton underneath).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The practical pattern: vLLM (or SGLang) for OpenAI-compatible LLM endpoints, Triton for everything else, KServe if you want model-level reconciliation and canarying as Kubernetes-native objects rather than rolling your own.&lt;/p&gt;
&lt;h3 id="gpu-operator-and-node-management" &gt;
&lt;div&gt;
&lt;a href="#gpu-operator-and-node-management"&gt;
##
&lt;/a&gt;
GPU Operator and Node Management
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Running GPUs in Kubernetes without the operator means hand-installing drivers on node images, which breaks under upgrades. The &lt;a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/index.html" target="_blank" rel="noopener noreferrer"&gt;NVIDIA GPU Operator&lt;/a&gt; packages the whole stack — drivers, container toolkit, device plugin, DCGM exporter for metrics, and MIG manager — as controllable Kubernetes objects. Node pool definition typically looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;apiVersion&lt;/span&gt;: eks.amazonaws.com/v1alpha2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;kind&lt;/span&gt;: NodeGroup &lt;span style="color:#78787e"&gt;# EKS managed node group; GKE/AKS equivalents apply&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;name&lt;/span&gt;: gpu-inference
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;instanceType&lt;/span&gt;: g6e.12xlarge &lt;span style="color:#78787e"&gt;# L40S-class cards&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;labels&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;node-type&lt;/span&gt;: gpu-inference
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;taints&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff6ac1"&gt;key&lt;/span&gt;: nvidia.com/gpu
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;value&lt;/span&gt;: &lt;span style="color:#5af78e"&gt;&amp;#34;dedicated&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;effect&lt;/span&gt;: NoSchedule
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;scaling&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;minSize&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;maxSize&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Things to get right here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Taint GPU nodes.&lt;/strong&gt; Non-GPU pods will happily schedule onto your most expensive nodes unless you taint. Every real cluster has horror stories of a misconfigured logging DaemonSet costing thousands per month.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set up Cluster Autoscaler / Karpenter for GPU instance types.&lt;/strong&gt; GPU capacity is scarce in many regions. Configure least-waste priority (e.g., prefer 8-GPU nodes only for 8-GPU jobs), consolidation carefully — consolidations that reschedule GPU pods cause visible service blips from weight reloading.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MIG (Multi-Instance GPU) for small models.&lt;/strong&gt; On A100/H100 you can partition a GPU into slices (e.g., 7×10GB slices). If your models fit in 10 GB, MIG turns one node into seven serving replicas and slashes cost. The GPU Operator&amp;rsquo;s MIG manager configures this; the device plugin then exposes &lt;code&gt;nvidia.com/mig-1g.10gb&lt;/code&gt; as a schedulable resource.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="weights-the-storage-side-of-inference" &gt;
&lt;div&gt;
&lt;a href="#weights-the-storage-side-of-inference"&gt;
##
&lt;/a&gt;
Weights: The Storage Side of Inference
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Model loading is a first-class ops concern. The naive path —Pod starts, downloads 15 GB from Hugging Face on the fly — gives you multi-minute readiness, wasted bandwidth (every replica downloads), and flaky readiness on network hiccups.&lt;/p&gt;
&lt;p&gt;Better patterns, in increasing order of investment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Local NVMe cache on the node.&lt;/strong&gt; An init container (or &lt;code&gt;hostPath&lt;/code&gt;-backed downloader) checks for the model in a node-local directory and downloads only if absent. After the first scale-up on a node, subsequent pods start in seconds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PVC with &lt;code&gt;ReadOnlyMany&lt;/code&gt;.&lt;/strong&gt; A pre-populated PVC (or a Filestore/EFS/GCFileSystem mount) shared across replicas avoids redundant downloads. Watch network filesystem throughput — for big models, it can become a bottleneck slower than downloading from object storage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pre-baked serving images or snapshots.&lt;/strong&gt; Bake weights into the image (big images, ~15–40 GB pull) or AMI/disk snapshot (fast). This is the &amp;ldquo;warm node pool&amp;rdquo; approach: autoscaler launches nodes from a snapshot with weights and even model server config already in place.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The vLLM deployment example from earlier, with the cache volume pattern:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;apiVersion&lt;/span&gt;: apps/v1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;kind&lt;/span&gt;: Deployment
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;name&lt;/span&gt;: llm-inference
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;replicas&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;selector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;matchLabels&lt;/span&gt;: { &lt;span style="color:#ff6ac1"&gt;app&lt;/span&gt;: llm-inference }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;template&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;labels&lt;/span&gt;: { &lt;span style="color:#ff6ac1"&gt;app&lt;/span&gt;: llm-inference }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;nodeSelector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;node-type&lt;/span&gt;: gpu-inference
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;tolerations&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff6ac1"&gt;key&lt;/span&gt;: nvidia.com/gpu
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;operator&lt;/span&gt;: Exists
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;effect&lt;/span&gt;: NoSchedule
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;initContainers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff6ac1"&gt;name&lt;/span&gt;: fetch-model
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;image&lt;/span&gt;: amazon/aws-cli:latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;command&lt;/span&gt;: [&lt;span style="color:#5af78e"&gt;&amp;#34;sh&amp;#34;&lt;/span&gt;, &lt;span style="color:#5af78e"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;, &lt;span style="color:#5af78e"&gt;&amp;#34;aws s3 sync s3://our-models/llama-8b /models/llama-8b --no-progress&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;volumeMounts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff6ac1"&gt;name&lt;/span&gt;: model-cache
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;mountPath&lt;/span&gt;: /models
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;containers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff6ac1"&gt;name&lt;/span&gt;: vllm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;image&lt;/span&gt;: vllm/vllm-openai:v0.6.4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;args&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - --model
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /models/llama-8b
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - --max-model-len
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#5af78e"&gt;&amp;#34;8192&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - --gpu-memory-utilization
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#5af78e"&gt;&amp;#34;0.92&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;resources&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;limits&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;nvidia.com/gpu&lt;/span&gt;: &lt;span style="color:#5af78e"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;memory&lt;/span&gt;: 64Gi
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;requests&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;cpu&lt;/span&gt;: &lt;span style="color:#5af78e"&gt;&amp;#34;8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;readinessProbe&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;httpGet&lt;/span&gt;: { &lt;span style="color:#ff6ac1"&gt;path&lt;/span&gt;: &lt;span style="color:#ff6ac1"&gt;/health, port&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;8000&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;initialDelaySeconds&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;periodSeconds&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;failureThreshold&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;60&lt;/span&gt; &lt;span style="color:#78787e"&gt;# allow minutes for first load&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;volumeMounts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff6ac1"&gt;name&lt;/span&gt;: model-cache
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;mountPath&lt;/span&gt;: /models
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff6ac1"&gt;name&lt;/span&gt;: model-cache
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;emptyDir&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;sizeLimit&lt;/span&gt;: 40Gi
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice the long &lt;code&gt;failureThreshold&lt;/code&gt; on the readiness probe: cold starts are legitimately minutes. If you set a normal 30-second threshold, every scale-up triggers a crash loop of your new replica and the autoscaler fights itself.&lt;/p&gt;
&lt;h3 id="autoscaling-on-the-right-signal" &gt;
&lt;div&gt;
&lt;a href="#autoscaling-on-the-right-signal"&gt;
##
&lt;/a&gt;
Autoscaling on the Right Signal
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Standard HPA on CPU is the wrong tool, for two reasons: GPU inference barely uses host CPU, and scale-up latency (node launch + image pull + weight load) is minutes, so you need to scale &lt;em&gt;early and hold&lt;/em&gt;, not react and thrash.&lt;/p&gt;
&lt;p&gt;The standard approach is KEDA with Prometheus metrics from the serving runtime. vLLM exposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vllm:num_requests_waiting&lt;/code&gt; — queue depth&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vllm:num_requests_running&lt;/code&gt; — in-flight per replica&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vllm:time_to_first_token_seconds&lt;/code&gt; / &lt;code&gt;vllm:e2e_request_latency_seconds&lt;/code&gt; — latency quantiles&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vllm:gpu_cache_usage_perc&lt;/code&gt; — KV cache saturation&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;apiVersion&lt;/span&gt;: keda.sh/v1alpha1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;kind&lt;/span&gt;: ScaledObject
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;name&lt;/span&gt;: llm-scaledobject
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;scaleTargetRef&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;name&lt;/span&gt;: llm-inference
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;minReplicaCount&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;2&lt;/span&gt; &lt;span style="color:#78787e"&gt;# warm floor: never below your p-cold-start tolerance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;maxReplicaCount&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;cooldownPeriod&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;600&lt;/span&gt; &lt;span style="color:#78787e"&gt;# long enough to avoid thrash — readiness is slow&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;triggers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff6ac1"&gt;type&lt;/span&gt;: prometheus
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;address&lt;/span&gt;: http://prometheus.monitoring:9090
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;query&lt;/span&gt;: sum(vllm:num_requests_waiting)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;threshold&lt;/span&gt;: &lt;span style="color:#5af78e"&gt;&amp;#34;10&amp;#34;&lt;/span&gt; &lt;span style="color:#78787e"&gt;# target: max ~10 queued requests per replica&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Key engineering points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Warm floor over scale-to-zero.&lt;/strong&gt; With minute-scale cold starts, scale-to-zero only works for workloads where users tolerate minutes of latency (background jobs, internal batch tools). Otherwise keep a warm floor and treat scale-down as a cost lever, not a default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Long cooldowns.&lt;/strong&gt; Because readiness is slow, aggressive scale-down after a spike makes you pay the full cold-start cost repeatedly. Ten minutes of cooldown is more normal than datacenter default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node-level pre-scaling.&lt;/strong&gt; Pod autoscaling doesn&amp;rsquo;t help if the autoscaler needs to launch a GPU node to satisfy it. Pre-provision GPU capacity or over-provision GPU nodes with fake low-priority &amp;ldquo;placeholder&amp;rdquo; pods (the classic cluster-autoscaler trick) if burst latency is contractual.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batching-aware reasoning.&lt;/strong&gt; A single replica can often handle 20–50 concurrent LLM requests with good TPOT. If you scale on requests-per-replica with a target like 30 or 50, you keep the batching advantage instead of shattering load across underfilled replicas.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="load-balancing-and-traffic-shape" &gt;
&lt;div&gt;
&lt;a href="#load-balancing-and-traffic-shape"&gt;
##
&lt;/a&gt;
Load Balancing and Traffic Shape
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Standard round-robin across pods is not ideal for LLM serving: replicas have different queue lengths already. Consider a routing layer aware of in-flight requests (e.g., a gateway or a serving framework&amp;rsquo;s own router that balances by &lt;code&gt;num_requests_running&lt;/code&gt;), and sticky routing by conversation/session for multi-turn traffic so prefix caches stay warm. If you&amp;rsquo;re doing streaming (SSE/WebSocket), confirm your ingress layer has long/idle timeouts tuned — common sources of mysterious 502s mid-stream.&lt;/p&gt;
&lt;h3 id="spot-gpus-and-interruption-engineering" &gt;
&lt;div&gt;
&lt;a href="#spot-gpus-and-interruption-engineering"&gt;
##
&lt;/a&gt;
Spot GPUs and Interruption Engineering
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Spot/preemptible GPU capacity is frequently 50–80% cheaper and, because inference is stateless (unlike training checkpoints), somewhat friendlier to interruptions. Engineering it well means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Disruption-tolerant deployments: reasonable PodDisruptionBudgets, including spot-node taints and tolerating instance termination notices (via 2-minute termination handlers draining pods).&lt;/li&gt;
&lt;li&gt;Fast weight loading (the storage patterns above) so a rescheduled replica is serving reasonably quickly after a handoff.&lt;/li&gt;
&lt;li&gt;Multiple capacity shapes: don&amp;rsquo;t demand only &lt;code&gt;p5.48xlarge&lt;/code&gt;; deploy with fallback smaller instance types and run several smaller replicas.&lt;/li&gt;
&lt;li&gt;Some teams run spot for the bulk of capacity and on-demand as a warm internal minimum, using cluster autoscaler &amp;ldquo;prefer-spot&amp;rdquo; strategies.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="observability-for-inference" &gt;
&lt;div&gt;
&lt;a href="#observability-for-inference"&gt;
##
&lt;/a&gt;
Observability for Inference
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;The metrics stack is standard Prometheus/Grafana, but the &lt;em&gt;content&lt;/em&gt; is GPU- and LLM-specific:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DCGM exporter&lt;/strong&gt; — GPU utilization, memory usage, temperature, power, ECC errors. High temperature and throttling directly affect latency and hardware life.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serving engine metrics&lt;/strong&gt; — queue depths (waiting requests), running requests, TTFT and end-to-end latency quantiles, KV cache usage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gateway metrics&lt;/strong&gt; — request rate, streaming connection counts and durations (long-lived streaming requires rounding and thinking about connection limits and timeouts differently than normal HTTP).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost instrumentation&lt;/strong&gt; — tokens served per hour per GPU, dollars per million tokens. This is your north-star SLO for inference economics: track it per model version, because quantization and version changes move it substantially.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alerting on GPU utilization is misleading: a well-batched GPU should run at ~100% memory bandwidth/utilization under load. Alert instead on queue depth and latency SLOs, treating GPU utilization as a capacity-planning graph.&lt;/p&gt;
&lt;h2 id="option-2-managed-cloud-inference-services" &gt;
&lt;div&gt;
&lt;a href="#option-2-managed-cloud-inference-services"&gt;
#
&lt;/a&gt;
Option 2: Managed Cloud Inference Services
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Every major provider has a managed inference product — you bring a model artifact, config, and get an endpoint:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;td&gt;SageMaker Endpoints (incl. async &amp;amp; serverless), Bedrock, EC2 with Deep Learning AMIs&lt;/td&gt;
&lt;td&gt;SageMaker handles serving infra; Bedrock offers hosted foundation models; asynchronous endpoints handle large payloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Cloud&lt;/td&gt;
&lt;td&gt;Vertex AI (Endpoints, Model Garden), GKE Autopilot with GPUs&lt;/td&gt;
&lt;td&gt;Tight integration with BigQuery/Colab and Google&amp;rsquo;s managed Nvidia/TPU fleet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure&lt;/td&gt;
&lt;td&gt;Azure ML online endpoints, Azure OpenAI&lt;/td&gt;
&lt;td&gt;Azure OpenAI is often the shortest path for enterprise LLM use; Azure ML for custom models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU-native clouds&lt;/td&gt;
&lt;td&gt;CoreWeave, RunPod, Lambda, Modal, Anyscale&lt;/td&gt;
&lt;td&gt;Kubernetes-native (CoreWeave) or simpler per-GPU rental; strong price-performance and quick cold-start UX&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The mental model: you give up governing the whole cluster below the model. The platform provides the serving runtime (or a container you supply), GPU instance selection, autoscaling targets, network isolation (VPC endpoints), encryption at rest/in transit, audit trails, and per-invocation pricing modes.&lt;/p&gt;
&lt;p&gt;A typical managed setup is declarative, roughly conceptually:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e2e4e5;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#78787e"&gt;# Conceptually — each provider uses its own DSL/SDK&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff6ac1"&gt;endpoint&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;name&lt;/span&gt;: llama-8b-prod
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;model&lt;/span&gt;: s3://our-models/llama-8b-finetuned.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;container&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;763104351884.&lt;/span&gt;dkr.ecr.us-east-1.amazonaws.com/djl-inference:vllm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;instance_type&lt;/span&gt;: ml.g6e.4xlarge
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;autoscaling&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;target_metric&lt;/span&gt;: ModelInvocationsPerInstance
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;target_value&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;min&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;max&lt;/span&gt;: &lt;span style="color:#ff9f43"&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff6ac1"&gt;data_capture&lt;/span&gt;: enabled &lt;span style="color:#78787e"&gt;# for drift monitoring / replay&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;What you gain&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time to first production call measured in hours:&lt;/strong&gt; no GPU operator, no driver pain, no cluster provisioning, no MIG experiments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in autoscaling on provided inference metrics&lt;/strong&gt;, handled by the provider&amp;rsquo;s control plane, with sensible scale-in defaults and warm pool semantics engineered upstream.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compliance and network posture mostly handled:&lt;/strong&gt; private VPC endpoints, KMS-encrypted model artifacts, IAM auth, audit logs — what would take weeks of K8s policy engineering.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pricing discipline tools:&lt;/strong&gt; serverless variants with per-invocation billing and scale-to-zero handled for you (SageMaker serverless endpoints, Vertex AI predictive); async endpoints for long-running jobs with S3 payloads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Traffic management and monitoring included:&lt;/strong&gt; canary weights on endpoint versions, dashboards, and drift/capture hooks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What you pay for&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-instance markup&lt;/strong&gt; — typically 1.3–2× raw EC2-equivalent GPU-hour cost, in exchange for the managed control plane.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited runtime knobs.&lt;/strong&gt; Batching behavior, continuous batching parameters, KV cache policy, and GPU partitioning are whatever the platform exposes; if the platform&amp;rsquo;s defaults aren&amp;rsquo;t optimal for your traffic shape, you absorb it, or you use custom containers and hope your tuning isn&amp;rsquo;t broken by an endpoint update.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Platform-shaped architectures.&lt;/strong&gt; Your request path routes through the provider&amp;rsquo;s frontend, so p50 latency can add tens of milliseconds; some platforms add queue-before-GPU even for warm endpoints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lock-in to control plane conventions.&lt;/strong&gt; IAM roles, autoscaling metric names, SDK/DSL, endpoint state model — nontrivial to port to another provider later (though most lock-in is at this config layer, not the model itself).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost concentration at sustained load.&lt;/strong&gt; Running one managed endpoint 24/7 is close to always paying the premium. Managed serverless endpoints scale smoothly but pass through latency; if your traffic is sustained rather than bursty, the raw-GPU time-based pricing of EC2 (plus your own ops) often wins on cost per million tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="comparing-the-two-paths" &gt;
&lt;div&gt;
&lt;a href="#comparing-the-two-paths"&gt;
#
&lt;/a&gt;
Comparing the Two Paths
&lt;/div&gt;
&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Self-managed K8s&lt;/th&gt;
&lt;th&gt;Managed cloud&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to first production call&lt;/td&gt;
&lt;td&gt;Days to weeks&lt;/td&gt;
&lt;td&gt;Hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost at sustained 24/7 load&lt;/td&gt;
&lt;td&gt;Lower (spot + reserved + fractioning + MIG)&lt;/td&gt;
&lt;td&gt;Higher per-GPU-hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost at spiky/bursty traffic&lt;/td&gt;
&lt;td&gt;Harder (idle GPUs, warm floors)&lt;/td&gt;
&lt;td&gt;Easier (serverless variants)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold-start behavior&lt;/td&gt;
&lt;td&gt;You engineer it (snapshots, caches, warm pools)&lt;/td&gt;
&lt;td&gt;Platform-managed warm pools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops burden&lt;/td&gt;
&lt;td&gt;High — GPU operator, drivers, serving tuning, spot interrupts&lt;/td&gt;
&lt;td&gt;Low — endpoint ops only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model-level optimization&lt;/td&gt;
&lt;td&gt;Full (quantization, batching, CUDA graphs, speculative decoding)&lt;/td&gt;
&lt;td&gt;Limited to what the platform exposes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling signal&lt;/td&gt;
&lt;td&gt;Custom metrics, high tuning effort&lt;/td&gt;
&lt;td&gt;Platform-provided targets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vendor lock-in&lt;/td&gt;
&lt;td&gt;Low — cluster and serving code are portable&lt;/td&gt;
&lt;td&gt;Significant — endpoint, IAM, SDK layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency overhead&lt;/td&gt;
&lt;td&gt;Direct pod networking&lt;/td&gt;
&lt;td&gt;Provider frontend hop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;DIY (Prometheus, DCGM, Grafana)&lt;/td&gt;
&lt;td&gt;Included dashboards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token-economics visibility&lt;/td&gt;
&lt;td&gt;Complete — you measure everything&lt;/td&gt;
&lt;td&gt;Provider dashboards only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="a-pragmatic-decision-framework" &gt;
&lt;div&gt;
&lt;a href="#a-pragmatic-decision-framework"&gt;
#
&lt;/a&gt;
A Pragmatic Decision Framework
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Most teams don&amp;rsquo;t need a binary answer — they need a routing rule. Patterns that work in practice:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start managed, migrate what deserves it.&lt;/strong&gt; Deploy on SageMaker/Vertex/Azure ML endpoints first — validate the model has product fit before investing in its infrastructure. When a model&amp;rsquo;s sustained traffic, special GPU configuration, or per-token economics justify it, lift the serving code to your cluster.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep self-managed for tuning-sensitive workloads.&lt;/strong&gt; Continuous batching behavior, quantization (AWQ, GPTQ, FP8/FP4), speculative decoding, prefix caching, and KV-cache offloads matter enormously for cost per million tokens. If cost per token is your north-star metric, you want a runtime you control: your own vLLM (or SGLang) on GPUs you own is usually the frontier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use serverless managed endpoints for hard-to-forecast, low-QPS workloads.&lt;/strong&gt; Internal tools, demos, admin features, geographically rare traffic — scale-to-zero in the cloud avoids keeping a GPU warm for three requests a day, and the latency is acceptable when users expect internal tools to be slower.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Match the traffic shape to the pricing model.&lt;/strong&gt; Sustained 24/7 load → self-managed with spot/reserved and MIG fractioning. Bursty, spiky, unpredictable → managed serverless. Long-running batch inference jobs → async endpoints or spot-backed batch jobs on your own clusters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t fight the GPU supply shortage alone.&lt;/strong&gt; If you can&amp;rsquo;t get capacity in-region from your primary cloud, the GPU-native clouds (CoreWeave is literally K8s under the hood; RunPod/Lambda prioritize availability) are often a supplement — treat them in your multi-cloud strategy as capacity, with the same serving images and IaC.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Beware common pitfalls either way:&lt;/strong&gt; under-provisioning GPU memory for KV cache, forgetting to request/raise GPU service quotas in the target region, model weight download times dominating autoscaling behavior, GPU utilization metrics firing &amp;ldquo;capacity&amp;rdquo; alarms when the GPU is simply efficiently batched, and long-lived streaming connections leaking through ingress misconfigurations.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion" &gt;
&lt;div&gt;
&lt;a href="#conclusion"&gt;
#
&lt;/a&gt;
Conclusion
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Inference infrastructure is infrastructure engineering, more than ML engineering. Self-managed Kubernetes gives you control, best unit economics at scale, and deep optimization headroom (quantization, batching, fractioning) — at the price of being responsible for some of the hardest workloads to keep running: GPUs, huge deterministic artifacts, custom autoscaling, interruption engineering. Managed cloud services trade cost per GPU-hour and flexibility for operational simplicity, compliance posture, and speed to production.&lt;/p&gt;
&lt;p&gt;Most mature ML platform teams end up with both: managed endpoints for early-stage and spiky models, and a tuned self-managed GPU fleet on Kubernetes for the workloads where cost per token and tail latency are competitive differentiators. The DevOps answer isn&amp;rsquo;t &amp;ldquo;Kubernetes or managed&amp;rdquo; — it&amp;rsquo;s knowing what your traffic looks like at the token level and using both paths where the tradeoffs serve you.&lt;/p&gt;
&lt;h2 id="further-reading" &gt;
&lt;div&gt;
&lt;a href="#further-reading"&gt;
#
&lt;/a&gt;
Further Reading
&lt;/div&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.vllm.ai/" target="_blank" rel="noopener noreferrer"&gt;vLLM documentation&lt;/a&gt; — serving runtime, continuous batching, and Prometheus metrics&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/index.html" target="_blank" rel="noopener noreferrer"&gt;NVIDIA GPU Operator&lt;/a&gt; — drivers, device plugin, DCGM, and MIG management on Kubernetes&lt;/li&gt;
&lt;li&gt;&lt;a href="https://keda.sh/docs/latest/" target="_blank" rel="noopener noreferrer"&gt;KEDA documentation&lt;/a&gt; — event-driven autoscaling on custom metrics&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kserve.github.io/website/latest/" target="_blank" rel="noopener noreferrer"&gt;KServe&lt;/a&gt; — model serving framework with traffic splitting on Kubernetes&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sgl-project.github.io/" target="_blank" rel="noopener noreferrer"&gt;SGLang&lt;/a&gt; — RadixAttention-based serving engine for prefix-heavy workloads&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/deploy-model.html" target="_blank" rel="noopener noreferrer"&gt;SageMaker Inference&lt;/a&gt; — endpoint types, autoscaling, and serverless inference&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/docs/predictions/deploy-model-api" target="_blank" rel="noopener noreferrer"&gt;Vertex AI Endpoints&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/azure/machine-learning/how-to-deploy-online-endpoints" target="_blank" rel="noopener noreferrer"&gt;Azure ML online endpoints&lt;/a&gt; — managed inference reference&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>DevOps</category><category>Kubernetes</category><category>Cloud Computing</category><category>AI</category><guid isPermaLink="true">https://geekat.work/posts/ai-inference-infrastructure-devops-kubernetes-vs-managed-cloud/</guid></item></channel></rss>