Ecstaticloud
Initializing0%
Ecstaticloud Logo
Back to Insights
Cloud & AIAugust 25, 2026

Scaling Enterprise GenAI: Building a High-Throughput LLM Inference Pipeline on Kubernetes with vLLM and Ray

Discover how to architect scalable, low-latency LLM serving infrastructure using vLLM, Ray Cluster, and Kubernetes. Learn actionable strategies to optimize GPU memory utilization with PagedAttention while slashing cloud operational costs.

Moving Generative AI models from proof-of-concept prototypes to enterprise-grade production infrastructure is often where clean architectural design meets harsh operational reality. While API wrappers like OpenAI or Anthropic work well for early MVP validation, scaling proprietary or fine-tuned open-weight models (like Llama 3.3, Qwen 2.5, or DeepSeek-R1) at enterprise scale requires strict control over latency, data privacy, throughput, and cloud expenditure.

Deploying standard PyTorch or Hugging Face pipelines behind simple web services introduces severe performance bottlenecks:

  • GPU Memory Fragmentation: Traditional Key-Value (KV) cache allocation wastes up to 60–80% of GPU VRAM due to static sizing and memory fragmentation.
  • Low Throughput: Naive batching strategies leave Tensor Cores idle while waiting for long-context requests to complete.
  • Orchestration Overhead: Scaling GPU workloads dynamically across heterogeneous nodes without state corruption is notoriously difficult.

To solve this, modern cloud architects use a three-tiered infrastructure stack: vLLM for engine-level optimizations, Ray Cluster for distributed compute execution, and Kubernetes for declarative container orchestration and infrastructure lifecycle management.

In this deep dive, we will design and build an enterprise-ready, high-throughput LLM serving pipeline capable of handling thousands of concurrent requests with ultra-low latency while optimizing your cloud spend.


1. The Core Bottlenecks of Naive LLM Serving

Before diving into the architecture, we need to understand why traditional inference pipelines fail under enterprise loads.

The KV Cache Memory Crisis

During the autoregressive generation phase of a Transformer model, every generated token requires computing Key-Value states for all previous tokens. To avoid recalculating these states at every step, engines store them in GPU memory as the KV Cache.

In standard serving implementations:

  1. Pre-allocation: Memory for the max context length (e.g., 8,192 tokens) is allocated upfront for every request, regardless of actual prompt/output length.
  2. External Fragmentation: Dynamic request arrival and termination leave non-contiguous memory blocks that cannot be reclaimed until entire batches complete.

Static vs. Continuous Batching

Standard batching techniques wait for a batch of $N$ sequences to complete before processing the next batch. Because token generation is non-deterministic in length, fast requests sit idle in VRAM while waiting for long generation requests to finish (the "tail latency" problem).

Static Batching:
Req 1: [Prompt][Gen......................][IDLE UNTIL BATCH COMPLETES]
Req 2: [Prompt][Gen......]
Req 3: [Prompt][Gen.................................]

Continuous (Iteration-Level) Batching:
Req 1: [Prompt][Gen 1][Gen 2][Gen 3]...
Req 2: [Prompt][Gen 1][Gen 2] -> [Finished] -> [Inject Req 4 Prompt]
Req 3: [Prompt][Gen 1][Gen 2][Gen 3][Gen 4]...

The Solution Engine: vLLM and PagedAttention

vLLM solves the memory allocation problem using PagedAttention. Drawing directly from operating system principles of virtual memory and paging:

  • GPU memory is divided into fixed-size physical blocks (e.g., 16 tokens per block).
  • KV caches do not need to occupy contiguous memory space.
  • Memory is allocated dynamically on-demand per token generation step.

This cuts memory waste down to <1% (only in the very last block of a sequence), allowing you to increase your operational batch size by 2x to 4x, drastically boosting token throughput.


2. Production Architecture Blueprint

Our target platform uses Kubernetes as the substrate layer, KubeRay to manage distributed Ray Clusters, and vLLM instances orchestrated across tensor-parallel and pipeline-parallel multi-GPU nodes.

                    +-----------------------------------------+
                    |       Ingress Controller / API Gateway   |
                    |     (Kong / Envoy / Traefik with TLS)   |
                    +--------------------+--------------------+
                                         |
                                         v
                    +-----------------------------------------+
                    |       Ray Unified Head Service          |
                    |      (OpenAI Compatible HTTP Router)    |
                    +--------------------+--------------------+
                                         |
             +---------------------------+---------------------------+
             |                                                       |
             v                                                       v
+--------------------------+                               +--------------------------+
|  Ray Worker Pod (Node 1) |                               |  Ray Worker Pod (Node 2) |
|  +--------------------+  |                               |  +--------------------+  |
|  |  vLLM Engine #1    |  |  <--- Ray IPC / gRPC --->     |  |  vLLM Engine #2    |  |
|  |  (GPU 0: TP Rank 0)|  |   NCCL Ring All-Reduce Bus    |  |  (GPU 1: TP Rank 1)|  |
|  +--------------------+  |                               |  +--------------------+  |
|    NVIDIA A100 (80GB)    |                               |    NVIDIA A100 (80GB)    |
+--------------------------+                               +--------------------------+
             ^                                                       ^
             +---------------------------+---------------------------+
                                         |
                                         v
                    +-----------------------------------------+
                    |    KubeRay Operator + Auto-scaler       |
                    |  (Monitors Queue Depth & GPU Metrics)   |
                    +-----------------------------------------+

Core Architecture Components

  1. Kubernetes (EKS/GKE/AKS): Handles node provisioning, fault domain isolation, and pod orchestration.
  2. KubeRay Operator: Manages the lifecycle of custom Ray resources (RayCluster, RayJob, RayService).
  3. Ray Cluster (Head & Worker Nodes): Acts as the distributed compute layer. Allows distributed tensor parallelism across multiple GPUs across pods via NCCL interconnects.
  4. vLLM Core: Runs within the Ray worker execution contexts, managing GPU execution, PagedAttention block tables, and continuous batching queues.

3. Step-by-Step Deployment Guide

Let's walk through deploying this stack on a Kubernetes cluster with NVIDIA GPU support (e.g., g5.12xlarge or p4d.24xlarge instances on AWS EKS).

Prerequisites

  • Kubernetes v1.28+ cluster with nvidia.com/gpu resource limits configured.
  • NVIDIA GPU Operator installed.
  • Helm v3 installed.

Step 1: Install the KubeRay Operator

Deploy the KubeRay operator using Helm. This operator reconciles Ray custom resource definitions (CRDs).

helm repo add kuberay https://ray-project.github.io/kuberay-helm/
helm repo update

# Create namespace and install kuberay-operator
kubectl create namespace kuberay
helm install kuberay-operator kuberay/kuberay-operator \
    --version 1.1.0 \
    --namespace kuberay

Verify operator status:

kubectl get pods -n kuberay -l app.kubernetes.io/name=kuberay-operator

Step 2: Define and Deploy the RayCluster Custom Resource

Below is a production RayCluster CRD spec designed for running continuous serving of Llama-3.3-70B-Instruct across 4 NVIDIA A100 (80GB) GPUs using Tensor Parallelism ($TP=4$).

Save this configuration as ray-cluster-vllm.yaml:

apiVersion: ray.io/v1
kind: RayCluster
metadata:
  name: vllm-llama3-70b-cluster
  namespace: default
  labels:
    environment: production
    app: vllm-inference
spec:
  rayVersion: '2.35.0'
  headGroupSpec:
    rayStartParams:
      dashboard-host: '0.0.0.0'
      num-gpus: '0' # Head node is pure control plane
    template:
      spec:
        containers:
          - name: ray-head
            image: rayproject/ray:2.35.0-py310-gpu
            resources:
              limits:
                cpu: "4"
                memory: "16Gi"
              requests:
                cpu: "2"
                memory: "8Gi"
            ports:
              - containerPort: 6379
                name: gcs
              - containerPort: 8265
                name: dashboard
              - containerPort: 8000
                name: metrics
  workerGroupSpecs:
    - groupName: gpu-group
      replicas: 1
      minReplicas: 1
      maxReplicas: 4
      rayStartParams:
        num-gpus: '4'
      template:
        metadata:
          labels:
            ray.io/node-type: worker
        spec:
          nodeSelector:
            node.kubernetes.io/instance-type: "p4d.24xlarge" # Or g5.12xlarge depending on vendor
          tolerations:
            - key: "nvidia.com/gpu"
              operator: "Exists"
              effect: "NoSchedule"
          containers:
            - name: ray-worker
              image: vllm/vllm-openai:v0.6.3
              securityContext:
                capabilities:
                  add: ["SYS_PTRACE"] # Required for NCCL shared memory communication
              resources:
                limits:
                  cpu: "32"
                  memory: "128Gi"
                  nvidia.com/gpu: "4"
                requests:
                  cpu: "16"
                  memory: "64Gi"
                  nvidia.com/gpu: "4"
              volumeMounts:
                - mountPath: /dev/shm
                  name: dshm
                - mountPath: /root/.cache/huggingface
                  name: hf-cache
          volumes:
            # High-throughput shared memory is CRITICAL for NCCL inter-GPU communication
            - name: dshm
              emptyDir:
                medium: Memory
                sizeLimit: 10Gi
            # Persistent Cache volume for HuggingFace model weights
            - name: hf-cache
              persistentVolumeClaim:
                claimName: efs-hf-cache-pvc

Apply the CRD:

kubectl apply -f ray-cluster-vllm.yaml

Step 3: Deploying the Orchestrated vLLM Service

To run vLLM on top of Ray seamlessly, we can leverage the vllm.entrypoints.openai.api_server module launched across the Ray cluster.

Create a Kubernetes Deployment/Service file vllm-service.yaml that submits a Ray Job or serves as an entrypoint routing engine:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-openai-api
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vllm-openai-api
  template:
    metadata:
      labels:
        app: vllm-openai-api
    spec:
      containers:
        - name: vllm-api-server
          image: vllm/vllm-openai:v0.6.3
          env:
            - name: HUGGING_FACE_HUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-token-secret
                  key: token
          command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
          args:
            - "--model=meta-llama/Llama-3.3-70B-Instruct"
            - "--tensor-parallel-size=4"
            - "--gpu-memory-utilization=0.92"
            - "--max-model-len=8192"
            - "--max-num-batched-tokens=32768"
            - "--enable-chunked-prefill=true"
            - "--port=8000"
          ports:
            - containerPort: 8000
              name: http
          resources:
            limits:
              cpu: "16"
              memory: "32Gi"
              nvidia.com/gpu: "4"
            requests:
              cpu: "8"
              memory: "16Gi"
              nvidia.com/gpu: "4"
          volumeMounts:
            - mountPath: /dev/shm
              name: dshm
      volumes:
        - name: dshm
          emptyDir:
            medium: Memory
            sizeLimit: 10Gi
---
apiVersion: v1
kind: Service
metadata:
  name: vllm-service
  namespace: default
spec:
  type: ClusterIP
  ports:
    - port: 8000
      targetPort: 8000
      name: http
  selector:
    app: vllm-openai-api

4. Advanced Performance Tuning Strategies

Default configurations are rarely optimal for high-concurrency production setups. To achieve maximum throughput and minimal tail latencies, apply these engine and system adjustments:

1. Enable Chunked Prefills

Long prompt requests ("prefill" phase) consume significant Compute/TFLOPs and choke short requests ("decode" phase), driving up Time to First Token (TTFT). Chunked Prefill breaks large prompts into smaller chunks, interleaving prefill execution with background decoding steps.

Pass the following parameter to your startup script:

--enable-chunked-prefill=true

2. Tuning GPU Memory Allocation

By default, vLLM reserves 0.90 (90%) of available GPU memory for KV Cache after model weights are loaded. For high concurrency, maximize this without causing CUDA Out-of-Memory (OOM) errors during dynamic allocations:

# Programmatic Configuration inside custom Python serving wrapper
from vllm import AsyncEngineArgs, AsyncLLMEngine

engine_args = AsyncEngineArgs(
    model="meta-llama/Llama-3.3-70B-Instruct",
    tensor_parallel_size=4,
    gpu_memory_utilization=0.94, # Adjusted safely for dedicated GPU nodes
    max_num_seqs=256,             # Maximum active sequences in a single batch step
    kv_cache_dtype="fp8",         # Cuts KV cache memory footprints by 50%
    enable_prefix_caching=True    # Reuses KV caches for system prompts across users
)
engine = AsyncLLMEngine.from_engine_args(engine_args)

3. Quantization Strategies (FP8 vs. AWQ)

When scaling massive models, memory bandwidth becomes your primary execution barrier.

| Quantization Type | VRAM Saving | Latency Impact | Recommendation | | :--- | :--- | :--- | :--- | | FP16 / BF16 | Baseline (100%) | Baseline | High accuracy, high cost footprint. | | FP8 (E4M3) | ~50% | ~1.5x - 2x Speedup | Best enterprise balance for Ada/Hopper architectures. | | AWQ (4-bit) | ~70% | Compute bounded | Best for edge compute or lower-spec GPUs (e.g., L4/T4). |


5. Autoscaling Architecture and Metric-Driven Scaling

Scaling LLM infrastructure using standard CPU or Memory metrics is ineffective. An LLM cluster running at 99% GPU Utilization might simply be running a single token generation task efficiently, or it could be choking under an incoming queue of thousands of requests.

Scaling on Native vLLM Metrics

Expose vLLM Prometheus metrics (enabled natively on port 8000) and scale based on:

  1. vllm:num_requests_waiting: Requests sitting in the execution queue.
  2. vllm:gpu_cache_usage_perc: Percentage of allocated KV Cache blocks currently in use.

PrometheusRule and KEDA Configuration

Using KEDA (Kubernetes Event-driven Autoscaling), write an autoscaling trigger on the vllm:num_requests_waiting metric:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: vllm-autoscaler
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: vllm-openai-api
  minReplicaCount: 1
  maxReplicaCount: 8
  cooldownPeriod: 300
  pollingInterval: 15
  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="default"})
        threshold: '10' # Scale out when average queue exceeds 10 waiting requests

6. Enterprise Observability & Benchmarking

To maintain tight Service Level Agreements (SLAs), you need visibility into token generation dynamics.

Key Metrics Dashboard Standard

+-----------------------------------------------------------------------+
|                       LLM OBSERVABILITY DASHBOARD                      |
+------------------------------------+----------------------------------+
| TTFT (Time To First Token)         | TPOT (Time Per Output Token)     |
| target: < 200ms                    | target: < 30ms/token             |
| Current P95: 142ms  [OK]           | Current P95: 22ms  [OK]          |
+------------------------------------+----------------------------------+
| GPU KV Cache Memory Usage          | Engine Queue Depth               |
| Current: 78.4%                     | Current: 2 Pending Requests      |
| [==================......]         | [==......................]       |
+------------------------------------+----------------------------------+
| System Throughput                  | Cost Per Million Tokens          |
| Current: 4,210 tokens/sec          | Current Avg: $0.18               |
+-----------------------------------------------------------------------+

Exposing Prometheus Targets

Ensure your deployment annotations allow Prometheus scrapers to parse vLLM performance metrics:

spec:
  template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/path: "/metrics"
        prometheus.io/port: "8000"

Useful alerting queries:

# Alert if KV Cache stays exhausted over 95% for more than 2 minutes
sum(vllm:gpu_cache_usage_perc) > 0.95

# Alert if Time-To-First-Token P99 exceeds SLA (e.g., 2000ms)
histogram_quantile(0.99, sum(rate(vllm:time_to_first_token_seconds_bucket[5m])) by (le)) > 2.0

Conclusion & Architecture Checklist

Building a enterprise LLM inference platform requires moving beyond basic deployment patterns. By combining PagedAttention via vLLM, distributed orchestration via Ray, and declarative scaling on Kubernetes, you can achieve sub-hundred-millisecond time-to-first-token latencies while dynamically managing expensive GPU resources.

Enterprise Deployment Checklist

  • [ ] Memory Management: Enabled PagedAttention with optimized gpu_memory_utilization ($\ge 0.90$).
  • [ ] Compute Interconnect: Allocated /dev/shm shared memory volumes for NCCL inter-GPU communication across pods.
  • [ ] Concurrency: Enabled chunked-prefill and Prefix Caching for shared system prompt workloads.
  • [ ] Autoscaling: Configured KEDA using vLLM application metrics (vllm:num_requests_waiting and KV Cache usage) rather than CPU/GPU metrics.
  • [ ] Cost Controls: Implemented model quantization (FP8/AWQ) to fit models into smaller GPU instances without degrading response quality.

With this architecture, your infrastructure is fully prepared to handle high-throughput enterprise GenAI workloads efficiently, reliably, and at scale.