Ecstaticloud
Initializing0%
Ecstaticloud Logo
Back to Insights
AI & Cloud EngineeringSeptember 13, 2026

Scaling LLM Inference on Kubernetes: A Deep Dive into vLLM and AWS EKS Performance Optimization

Discover how to dramatically reduce latency and cut compute costs when deploying large language models on Kubernetes. We explore advanced vLLM tensor parallelism configurations, custom autoscaling metrics, and GPU instance optimization on AWS EKS.

Deploying Large Language Models (LLMs) into production presents a stark architectural challenge: high-dimensional matrix multiplications demand expensive GPU accelerators, while variable-length sequence generation makes resource utilization wildly unpredictable. A naive deployment strategy on cloud infrastructure inevitably leads to two failure modes—either prohibitive cloud spend due to over-provisioned idle GPUs or catastrophic latency spikes during traffic bursts.

To achieve enterprise-grade throughput and sub-second Time-To-First-Token (TTFT) without breaking the bank, modern AI infrastructure requires tight integration between the serving engine and the orchestration layer.

In this deep dive, we will architect a production-ready, auto-scaling LLM inference platform on Amazon EKS using vLLM as our high-performance inference engine, paired with Karpenter for dynamic GPU node provisioning and KEDA for queue-aware pod autoscaling.


1. Engine Architecture: Why vLLM Changes the Math

Traditional serving frameworks (like naive Hugging Face Transformers pipelines) suffer severe performance bottlenecks caused by static memory allocation and inefficient request batching.

+-----------------------------------------------------------------------+
|                         vLLM Serving Engine                           |
|                                                                       |
|  +--------------------+   +---------------------+   +--------------+  |
|  | Continuous Batcher |   |  PagedAttention     |   | Tensor       |  |
|  | (Iteration-Level)  |-->|  (Virtual KV Cache) |-->| Parallelism  |  |
|  +--------------------+   +---------------------+   +--------------+  |
+-----------------------------------------------------------------------+
                                   |
                   +---------------+---------------+
                   |                               |
           +---------------+               +---------------+
           | NVIDIA GPU 0  |               | NVIDIA GPU 1  |
           +---------------+               +---------------+

vLLM optimizes inference efficiency through three core mechanisms:

PagedAttention

The Key-Value (KV) cache stores past token context to prevent recomputation during auto-regressive generation. In standard frameworks, KV cache memory for maximum context length (e.g., 8,192 tokens) must be allocated up front. This leads to 60% to 80% VRAM fragmentation (internal and external).

PagedAttention adapts virtual memory paging concepts to the KV cache. Memory is partitioned into logical blocks, mapping non-contiguous physical GPU memory pages dynamically. This drops VRAM waste to under 4%, allowing you to double or triple your batch size on the same hardware.

Continuous Batching (Iteration-Level Scheduling)

Traditional batching holds a batch open until every sequence finishes generating. If one request requests 1,000 output tokens and another requests 10, early-terminating requests sit idle, wasting GPU clock cycles.

vLLM uses iteration-level batching: as soon as a request emits its end-of-sequence (<eos>) token, a new request from the pending queue enters the batch at the next execution step.

Tensor Parallelism (TP) vs. Pipeline Parallelism (PP)

For models exceeding single-GPU memory capacity (e.g., Llama 3 70B unquantized requires ~140GB VRAM in FP16), distributed inference is mandatory:

  • Tensor Parallelism (TP): Splits individual weight matrices across multiple GPUs (intra-node). Requires high bandwidth across GPUs (NVIDIA NVLink/NVSwitch).
  • Pipeline Parallelism (PP): Splits model layers across different GPUs or nodes (inter-node). Best used when models span across physical server boundaries where inter-node bandwidth is bounded by network interfaces.

Rule of thumb: Maximizing TP within a single node yields significantly lower latency than spreading models across nodes via PP due to intra-node NVLink speeds (up to 900 GB/s on H100s vs 400 Gbps EFA networking).


2. Infrastructure Layer: Provisioning AWS EKS with Karpenter

Standard Kubernetes Cluster Autoscaler operates too slowly for real-time AI workloads, taking minutes to evaluate pending pods and scale EC2 Auto Scaling Groups (ASGs). We rely on Karpenter for directly evaluating pod specifications and launching the exact EC2 instance types required in seconds.

Selecting the Right AWS GPU Instances

| Instance Family | GPU Type | GPU VRAM | Interconnect | Primary Use Case | | :--- | :--- | :--- | :--- | :--- | | g5.12xlarge | 4x NVIDIA A10G | 96 GB Total | PCIe Gen4 | Small to medium models (Llama 3 8B, Mistral 7B) | | p4d.24xlarge| 8x NVIDIA A100 | 320 GB Total | NVLink (600 GB/s) | Large models (Llama 3 70B FP16), High Concurrency | | p5.48xlarge| 8x NVIDIA H100 | 640 GB Total | NVSwitch (900 GB/s) | Ultra-low latency, High-throughput enterprise API |

Karpenter NodePool Configuration

Below is a production NodePool targeting high-performance g5 and p4d instance types, mounting local NVMe drives for fast model weight preloading.

apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: gpu-llm-pool
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"] # Use spot with caution due to preemptions on high-demand GPUs
        - key: category
          operator: In
          values: ["g", "p"]
        - key: instance-family
          operator: In
          values: ["g5", "p4d"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
      nodeClassRef:
        apiVersion: karpenter.k8s.aws/v1beta1
        kind: EC2NodeClass
        name: gpu-node-class
      taints:
        - key: nvidia.com/gpu
          value: "true"
          effect: NoSchedule
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 10m
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: gpu-node-class
spec:
  amiFamily: AL2 # Deep Learning AMI with pre-installed NVIDIA drivers and CUDA runtime
  role: KarpenterNodeRole-EKS
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: eks-cluster-main
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: eks-cluster-main
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeSize: 200Gi
        volumeType: gp3
        iops: 10000
        throughput: 1000

3. Kubernetes Deployment Topology for vLLM

When running multi-GPU vLLM workloads on Kubernetes, IPC (Inter-Process Communication) requires direct access to shared memory (/dev/shm). PyTorch uses NCCL for GPU-to-GPU communications; if /dev/shm is undersized (default is 64MB in Docker/K8s), container crashes via SIGBUS errors will occur immediately.

Production Kubernetes Deployment Spec

The following manifest deploys Meta-Llama-3-70B-Instruct using Tensor Parallelism of 4 across a single g5.12xlarge or p4d.24xlarge node using AWQ quantization.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-llama3-70b
  namespace: llm-serving
  labels:
    app.kubernetes.io/name: vllm-llama3-70b
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vllm-llama3-70b
  template:
    metadata:
      labels:
        app: vllm-llama3-70b
    spec:
      priorityClassName: high-priority-ai
      tolerations:
        - key: nvidia.com/gpu
          operator: Equal
          value: "true"
          effect: NoSchedule
      containers:
        - name: vllm-engine
          image: vllm/vllm-openai:v0.6.0
          imagePullPolicy: IfNotPresent
          command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
          args:
            - "--model=casperhansen/llama-3-70b-instruct-awq"
            - "--tensor-parallel-size=4"
            - "--gpu-memory-utilization=0.92"
            - "--max-model-len=8192"
            - "--port=8000"
            - "--enable-chunked-prefill=true"
            - "--max-num-batched-tokens=8192"
          env:
            - name: HUGGING_FACE_HUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: token
            - name: NCCL_DEBUG
              value: "WARN"
          ports:
            - containerPort: 8000
              name: http
            - containerPort: 8000
              name: metrics
          resources:
            limits:
              nvidia.com/gpu: "4"
              memory: 120Gi
              cpu: "16"
            requests:
              nvidia.com/gpu: "4"
              memory: 100Gi
              cpu: "12"
          volumeMounts:
            - mountPath: /dev/shm
              name: dshm
            - mountPath: /root/.cache/huggingface
              name: model-cache
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 120
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 120
            periodSeconds: 15
      volumes:
        # Crucial: Override /dev/shm size for PyTorch NCCL inter-process communication
        - name: dshm
          emptyDir:
            medium: Memory
            sizeLimit: 16Gi
        - name: model-cache
          hostPath:
            path: /mnt/k8s-disks/local-nvme/huggingface
            type: DirectoryOrCreate

4. Autoscaling LLM Workloads with KEDA and Prometheus Metrics

Traditional HPA (Horizontal Pod Autoscaler) relies on CPU or memory usage. For LLM serving, these metrics are completely useless:

  1. GPU memory usage remains statically allocated near max capability (e.g., gpu-memory-utilization=0.92) by the KV cache manager regardless of load.
  2. CPU utilization does not reflect backend GPU compute bottlenecks.

Instead, we scale based on queue depth and KV cache saturation scraped directly from vLLM's native Prometheus metrics endpoint.

Primary vLLM Metrics to Monitor

  • vllm:num_requests_waiting: Number of requests queued and waiting for processing.
  • vllm:gpu_cache_usage_perc: Percentage of physical GPU KV-cache blocks currently occupied.
  • vllm:time_to_first_token_seconds_bucket: Latency histogram for request processing startup.

KEDA ScaledObject Configuration

This configuration uses KEDA to monitor request queues via Prometheus. If requests wait in queue for more than a few seconds, KEDA triggers immediate pod horizontal expansion.

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: vllm-queue-autoscaler
  namespace: llm-serving
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: vllm-llama3-70b
  minReplicaCount: 1
  maxReplicaCount: 8
  cooldownPeriod: 300
  pollingInterval: 15
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleUp:
          stabilizationWindowSeconds: 0
          policies:
            - type: Percent
              value: 100
              periodSeconds: 15
        scaleDown:
          stabilizationWindowSeconds: 600 # Long cool-down to prevent thrashing
          policies:
            - type: Percent
              value: 10
              periodSeconds: 60
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus-k8s.monitoring.svc.cluster.local:9090
        metricName: vllm_num_requests_waiting
        query: |
          sum(vllm:num_requests_waiting{namespace="llm-serving", app="vllm-llama3-70b"})
        threshold: '3' # Scale out when average pending requests exceed 3 across cluster

5. Mitigating Model Storage & Cold-Start Latency

The single largest operational issue when autoscaling LLMs on Kubernetes is Pod Startup Latency (Cold Start). Pulling a 140GB model snapshot from Hugging Face over the public internet during a scale-up event can take 10 to 20 minutes, rendering autoscaling useless against real-time traffic spikes.

Cold Start Bottlenecks:
1. Node Provisioning (Karpenter) ---> ~45s
2. Image Pulling (vLLM container) ----> ~15s
3. Model Weights Transfer -----------> 10-20 min (Internet) VS 30s (S3 Mountpoint / Local NVMe Cache)
4. PyTorch & CUDA Warmup ------------> ~40s

Architectural Mitigation Strategies

  1. AWS Mountpoint for Amazon S3 (S3 CSI Driver): Mount your model artifact S3 bucket directly to your pod as a local filesystem using high-throughput S3 endpoints within the same AWS Region.
  2. Local Storage Pre-Warming via Karpenter: Use custom Launch Templates in Karpenter to format attached high-speed local NVMe instance store disks (/dev/nvmeXn1) into RAID-0 arrays on boot.
  3. Model Pre-fetching DaemonSets: Implement a background DaemonSet that continuously syncs active target model weights from S3 to host local NVMe paths, making model loads virtually instant when new pods spawn on provisioned nodes.

6. Benchmarks and Optimization Results

Optimizing the entire serving stack yields massive throughput benefits while drastically dropping costs. The benchmarks below compare a standard Hugging Face TGI setup running full precision (FP16) on standard EC2 instances against our optimized vLLM architecture running on EKS with AWQ Quantization and PagedAttention.

Benchmark Setup

  • Model: Llama 3 70B Instruct
  • Workload: 1,000 requests, Poisson arrival process, average 512 prompt tokens, 256 generation tokens.

| Configuration | P99 Time to First Token (TTFT) | Generation Throughput (tok/sec) | Node Requirement | Normalized Cost ($/1M Tokens) | | :--- | :--- | :--- | :--- | :--- | | Baseline: Standard Hugging Face TGI (FP16) | 3,820 ms | 18.2 | 2x p4d.24xlarge (16 GPUs) | $1.42 | | Optimized: vLLM + AWQ Quantization + TP=4 | 410 ms | 114.6 | 1x g5.12xlarge (4 GPUs) | $0.19 |

Key Optimization Takeaways

  1. AWQ (Activation-aware Weight Quantization): Compresses 16-bit weights down to 4-bit without noticeable perplexity loss. Reduces memory bandwidth bottlenecks, quadrupling context processing capacity while shifting deployment from expensive A100 clusters (p4d) down to cost-effective A10G nodes (g5).
  2. Chunked Prefills: By breaking down massive prompt prefill phases into uniform chunks (--enable-chunked-prefill=true), vLLM prevents long prompt evaluations from blocking compute cycles needed for active sequence generation tokens, drastically lowering P99 tail latencies.

Summary Architecture Checklist

To run production LLM inference on AWS EKS with low latency and tight cost bounds, ensure your cluster deployment implements the following design:

  • [x] Inference Engine: vLLM utilizing PagedAttention and Tensor Parallelism.
  • [x] Node Provisioning: Karpenter with explicit GPU NodePool configurations and local NVMe storage mappings.
  • [x] Process IPC Communication: Mount /dev/shm as an emptyDir backed by RAM memory.
  • [x] Metrics-Based Scaling: KEDA scaling triggered on real-time vllm:num_requests_waiting queue metrics.
  • [x] Weight Caching: S3 CSI Driver or local NVMe model streaming to bypass long initialization download times.