Serving Machine Learning Models at Scale: A Guide to Inference Optimization
APP-DEPLOYMENTSeptember 5, 2025

Serving Machine Learning Models at Scale: A Guide to Inference Optimization

A practical guide to deploying and optimizing machine learning models for scalable, low-latency inference across production environments. Includes architectural patterns, tooling tips, and benchmarking approaches.

Sealos
Sealos
Share at:

You’ve trained a model that performs brilliantly offline. Now comes the hard part: getting it into production—and keeping it fast, reliable, and cost-effective as usage grows. Serving machine learning (ML) models at scale is the unsung hero of AI systems. It’s where user expectations collide with infrastructure realities, where milliseconds cost dollars, and where good engineering separates a demo from a durable product.

This guide covers the what, why, and how of inference optimization: the techniques, tooling, and architectural patterns that help you serve models efficiently—with practical examples you can put to work today. Whether you’re deploying a classic classifier, a recommendation system, or a large language model (LLM), the principles are similar: define your service-level objectives (SLOs), remove bottlenecks, and build for observability and scale.

If you run on Kubernetes (or plan to), platforms like Sealos (sealos.io) can simplify the operational layer—GPU orchestration, multi-tenant isolation, autoscaling, and cost controls—so you can focus on optimizing models and services.

What Is Model Serving and Inference?

  • Inference: Running a trained model to produce predictions.
  • Model serving: Exposing the inference process behind an API or streaming interface, managing versions, scaling, observability, and lifecycle.

Common serving modes:

  • Online (real-time): Low-latency requests (e.g., fraud checks, autocomplete).
  • Streaming: Long-lived connections, partial outputs (e.g., LLM token streaming).
  • Batch: Throughput-first, scheduled jobs (e.g., nightly scoring).
  • Edge/on-device: Low-latency, privacy-preserving, bandwidth-saving.

Key objectives (SLOs):

  • Latency: P50/P95/P99 response times and Time to First Byte/Token.
  • Throughput: QPS/TPS, tokens/sec, images/sec.
  • Availability: Uptime, error budgets.
  • Cost: Cost per 1k requests/tokens, GPU hours.
  • Quality: Accuracy, NDCG, toxicity thresholds, etc.

Why Inference Optimization Matters

  • User experience: Fast responses drive engagement and revenue; slow services erode trust.
  • Cost efficiency: Inference can dwarf training costs for high-traffic systems.
  • Scalability: Efficient services scale predictably with demand.
  • Reliability: Optimized systems handle spikes gracefully and degrade intelligently.
  • Sustainability: Better utilization equals fewer resources and lower emissions.

Where Inference Time Goes: Understanding Bottlenecks

  • Pre/post-processing: Tokenization, image transforms, decoding.
  • Model compute: Matrix multiplies, attention blocks, activation functions.
  • Memory bandwidth and data movement: CPU↔GPU transfer, PCIe bottlenecks, host-to-device copies.
  • Kernel launch overheads and framework overhead (Python GIL for CPU-bound paths).
  • Network overhead: TLS termination, serialization (JSON/Protobuf), load balancers.
  • Model load time and cache misses: Cold starts, weight loading from storage.
  • Concurrency and scheduling: Queuing delays, poor batching, context switches.

Knowing the bottleneck dictates the optimization strategy.

Architecture Patterns for Scalable Serving

  • Single-model microservice
    • Simple, good for high-traffic single model endpoints.
  • Multi-model server
    • Dynamically load/unload models; good for long-tail traffic.
  • Serverless/function-based inference
    • Scale-to-zero, pay-per-use, but watch cold-start latency.
  • Model gateway/router
    • Central entry point, handles auth, routing, A/B testing, canaries, shadowing.
  • Hybrid streaming + REST
    • Stream partial outputs for responsiveness (LLM TTFB), finalize with REST payload.

Deployment platforms:

  • Kubernetes with model servers (Triton, TorchServe, TF Serving, KServe, BentoML, Ray Serve).
  • Managed services or PaaS equivalents.
  • Platforms like Sealos provide a Kubernetes-native experience with multi-tenant clusters, GPU scheduling, and built-in app management—useful for productionizing experimentation at team scale.

Hardware Choices and Trade-offs

  • CPU
    • Great for low-QPS, small models, or batch jobs. Leverage AVX/AVX-512, OpenMP, and ONNX Runtime with MKL-DNN/oneDNN.
  • GPU
    • Best for deep learning and parallelizable workloads. Use mixed precision (FP16/BF16), dynamic batching, and high-bandwidth interconnects. Consider MIG (Multi-Instance GPU) for isolation on A100/H100.
  • Accelerators (TPU, AWS Inferentia/Trn1, Habana, etc.)
    • Excellent cost/perf if your stack supports them.
  • Memory and I/O
    • Model size and VRAM dominate feasibility. Use quantization and paged KV cache for LLMs. Prefer NVLink over PCIe when possible.

Core Optimization Techniques (Model-Level)

Quantization

Reduce precision to speed up compute and cut memory footprint.

  • Post-training dynamic quantization (fastest to try)
  • Post-training static quantization (requires calibration)
  • Quantization-aware training (best accuracy retention)
  • LLM-specific methods: AWQ, GPTQ, SmoothQuant, LLM.int8

Example: PyTorch dynamic quantization (CPU):

import torch
from torch import nn
 
class TinyClassifier(nn.Module):
    def __init__(self, in_dim=512, hidden=256, out_dim=10):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(in_dim, hidden),
            nn.ReLU(),
            nn.Linear(hidden, out_dim)
        )
 
    def forward(self, x):
        return self.net(x)
 
model = TinyClassifier().eval()
qmodel = torch.quantization.quantize_dynamic(
    model, {nn.Linear}, dtype=torch.qint8
)
# qmodel is typically faster and smaller on CPU

Example: ONNX Runtime static quantization:

from onnxruntime.quantization import quantize_static, CalibrationDataReader, QuantFormat
 
class MyDataReader(CalibrationDataReader):
    def get_next(self):
        # Return dict[str, np.ndarray] with representative inputs
        ...
 
quantize_static(
    model_input="model.onnx",
    model_output="model.int8.onnx",
    calibration_data_reader=MyDataReader(),
    quant_format=QuantFormat.QDQ
)

Pruning and Distillation

  • Structured pruning removes entire channels/heads, enabling speedups.
  • Knowledge distillation trains a smaller student with teacher outputs to maintain quality at lower cost.

Compilation and Kernel Optimizations

  • Convert to ONNX + TensorRT for GPUs; TorchScript or PyTorch 2.x compile (torch.compile) for fusion.
  • Use optimized kernels: FlashAttention, xFormers, cuBLASLt matmul, fused activation + bias.
  • For LLMs: KV cache, paged attention, speculative decoding.

Graph-level Tweaks

  • Operator fusion, constant folding, removing dead branches.
  • Static shapes where possible; pin memory and pre-allocate buffers.

Serving-Side Optimizations

Batching and Dynamic Batching

Batching increases GPU utilization by combining multiple requests. Trade-off: larger batches improve throughput but can increase tail latency.

Simple async micro-batcher in FastAPI:

import asyncio
from fastapi import FastAPI
from pydantic import BaseModel
 
app = FastAPI()
queue = asyncio.Queue()
MAX_BATCH = 16
MAX_DELAY_MS = 8
 
class Inp(BaseModel):
    x: list[float]
 
class Out(BaseModel):
    y: list[float]
 
async def model_infer(batch):
    # Replace with actual framework call (Torch/ONNX/TensorRT)
    return [sum(item["x"]) for item in batch]
 
async def batch_worker():
    while True:
        first = await queue.get()
        batch = [first]
        try:
            # Wait briefly to accumulate more
            await asyncio.sleep(MAX_DELAY_MS / 1000)
            while not queue.empty() and len(batch) < MAX_BATCH:
                batch.append(queue.get_nowait())
        except asyncio.QueueEmpty:
            pass
        inputs = [req["payload"] for req in batch]
        result = await model_infer(inputs)
        for r, item in zip(result, batch):
            item["future"].set_result(Out(y=[r]))
        for _ in batch:
            queue.task_done()
 
@app.on_event("startup")
async def startup_event():
    asyncio.create_task(batch_worker())
 
@app.post("/predict", response_model=Out)
async def predict(inp: Inp):
    fut = asyncio.get_event_loop().create_future()
    await queue.put({"payload": inp.dict(), "future": fut})
    return await fut

For LLMs, consider continuous batching (a.k.a. iteration-level batching) to add/remove sequences each decoding step.

Concurrency and Worker Model

  • Use async I/O for network-bound work and pre/post-processing.
  • Use multiple model instances per GPU (careful with memory) to reduce head-of-line blocking.
  • For CPU-bound preprocessing, run thread pools or separate microservices.
  • In Triton Inference Server, configure multiple instances and dynamic batching in model configuration.

Caching

  • Output cache for idempotent calls (e.g., same image, same prompt).
  • Feature/embedding cache to avoid recomputation in RAG pipelines.
  • Tokenization cache in LLM pipelines for repeated prompts/prefixes.

Request Prioritization and Rate Limiting

Protect the system during spikes:

  • Token-based quotas per tenant.
  • Fair queuing and priority classes (premium users get faster service).
  • Back-pressure with 429 and Retry-After.

System-Level Optimization: Tooling and Platforms

Choosing a Model Server

A quick comparison:

Server/FrameworkBest forHighlightsNotes
TensorFlow ServingTF modelsHigh-performance gRPC, versioningTF-native
TorchServePyTorchHandlers, multi-modelCPU/GPU friendly
NVIDIA TritonMixed stacksDynamic batching, ensemble graphs, multiple backendsExcellent GPU utilization
BentoMLPackagingBuild/run, adapters for frameworksGreat developer UX
KServe (on K8s)StandardizationInferenceService CRD, autoscaling, canaryOperates on Kubernetes
Ray ServePython servicesScalable Python serving, compositionGood for pipelines

On Kubernetes, KServe can standardize deployments across model frameworks and support canaries and autoscaling out of the box. Triton is a strong choice for GPU-heavy workloads. BentoML and Ray Serve improve developer ergonomics and flexible pipelines.

If you’re using Kubernetes via Sealos, you can:

  • Launch GPU-backed namespaces and deploy Triton or KServe from a UI (App Launchpad).
  • Set multi-tenant quotas, autoscaling policies, and isolate workloads.
  • Use built-in object storage and registries for model artifacts.
  • Track costs per workspace/team to keep inference spend visible.

Autoscaling and Scheduling

  • Horizontal Pod Autoscaler (HPA): Scale by CPU/GPU utilization or custom metrics (QPS, queue depth, tokens/sec).
  • KEDA: Event-driven scaling from queues/streams; good for bursty traffic.
  • Scale-to-zero for sporadic endpoints; mitigate cold starts via warmers.
  • GPU bin-packing: Assign pods to maximize GPU utilization; use MIG for strict isolation.

Example: KServe InferenceService with autoscaling hints and GPU:

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: resnet
spec:
  predictor:
    triton:
      runtimeVersion: "23.08-py3"
      resources:
        requests:
          nvidia.com/gpu: "1"
          cpu: "1"
          memory: "8Gi"
        limits:
          nvidia.com/gpu: "1"
          cpu: "2"
          memory: "16Gi"
      env:
        - name: TRITON_MODEL_CONTROL_MODE
          value: "poll"
      autoscaling:
        minReplicas: 1
        maxReplicas: 8
        metrics:
          - type: "concurrency"
            target: "8"

Storage and Data Locality

  • Store models close to compute (local SSD or fast object storage with caching).
  • Use lazy loading and warm-up endpoints to avoid cold latency spikes.
  • Pin memory for host↔device copies; use page-locked buffers for GPUs.
  • Package model weights inside the container for mission-critical low-latency services.

CI/CD and Versioning

  • Model registry with immutable versions and metadata (accuracy, drift, artifacts).
  • Blue/green and canary deployments for safe rollouts; shadow traffic to validate new models.
  • Automated validation: performance benchmarks, fairness/toxicity tests, regression tests.

On platforms like Sealos, GitOps pipelines and integrated registries help push new images/models safely across environments, with audit and rollback.

Observability and Performance Engineering

Track the golden signals:

  • Latency: P50/P95/P99, TTFB/TTFT (LLMs).
  • Throughput: Requests/sec, tokens/sec.
  • Errors: 5xx rates, timeouts, OOMs.
  • Saturation: GPU/CPU/memory utilization, queue lengths.

Tools and practices:

  • Metrics: Prometheus + Grafana; export framework metrics (Torch/TensorRT/Triton).
  • Tracing: OpenTelemetry to pinpoint slow spans (tokenization, network, inference).
  • Profilers: PyTorch Profiler, NVIDIA Nsight Systems/Compute, TensorBoard, perf/VTune.
  • Load testing: Locust, k6, wrk; LLM-specific harnesses (llm-perf, vLLM benchmarks).
  • Chaos and resilience tests: Pod restarts, node drains, network latency injection.

Benchmark under realistic traffic patterns. Optimize for the metric that matters (e.g., P95 latency vs cost per 1k tokens) and validate after each change.

Practical Patterns and Examples

1) Real-time Image Classification with Triton

  • Export model to ONNX or TensorRT engine.
  • Use Triton’s dynamic batching and multiple instances per GPU to maximize throughput.

Example: Triton model configuration enabling dynamic batching:

name: "resnet50"
platform: "tensorrt_plan"
max_batch_size: 32
dynamic_batching {
  preferred_batch_size: [4, 8, 16, 32]
  max_queue_delay_microseconds: 5000
}
instance_group [
  { kind: KIND_GPU, count: 2 }
]

Tune max_queue_delay_microseconds to balance latency and throughput.

2) LLM API with Streaming

  • Stream tokens to reduce perceived latency.
  • Use paged KV cache, FlashAttention, and continuous batching when possible.
  • Separate tokenizer microservice if CPU-bound.

Example: Server-Sent Events (SSE) streaming skeleton with FastAPI:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
 
app = FastAPI()
 
async def generate(prompt: str):
    # Pseudocode for token streaming
    yield "data: " + "Hello" + "\n\n"
    yield "data: " + " world" + "\n\n"
    yield "data: [DONE]\n\n"
 
@app.get("/stream")
async def stream(prompt: str):
    return StreamingResponse(generate(prompt), media_type="text/event-stream")

On Kubernetes/Sealos, ensure your ingress/gateway preserves HTTP/1.1 and streaming semantics; configure timeouts appropriately.

3) RAG (Retrieval-Augmented Generation) Serving

  • Split pipeline: retrieval service, ranking/filters, LLM generation.
  • Cache embeddings and retrieval results aggressively.
  • Pre-tokenize static context; keep corpora in vector DB close to compute.

LLM throughput depends on both the LLM and I/O to the retrieval store—profile both.

4) Batch Scoring Pipelines

  • Use dataflow engines (Spark, Ray) with vectorized inference (UDFs calling ONNX Runtime/TensorRT).
  • Prefer CPU with int8 for cost efficiency unless model requires GPU.
  • Checkpoint outputs and monitor for silent failures.

Cost Optimization Strategies

  • Right-size hardware: Don’t overprovision VRAM; use MIG for isolation.
  • Quantization and compilation: Reduce compute, increase throughput.
  • Increase effective batch size: Dynamic batching, micro-batching for streams.
  • Autoscaling: Scale down during low traffic; use spot/preemptible nodes for tolerant workloads.
  • Cache strategically: Tokenization, embeddings, deduplicated prompts.
  • Multi-tenancy: Share GPUs across teams with quotas; platforms like Sealos help enforce resource boundaries and track costs per namespace/project.
  • Avoid over-serialization: Prefer gRPC/Protobuf over JSON for high-QPS internal calls.

Calculate and track cost per 1k requests/tokens; tie it to product metrics.

Security, Reliability, and Governance

  • Data privacy: Encrypt in transit and at rest; isolate tenant data; consider in-cluster vector stores for RAG.
  • Secrets management: Use KMS/secret stores; never bake secrets into images.
  • Supply chain: Verify model artifacts and containers; sign images; pin base layers.
  • Resource isolation: Namespace policies, network policies, PodSecurity; MIG for GPU isolation.
  • Rate limiting and quotas: Protect shared clusters from noisy neighbors.
  • Compliance and audit: Keep lineage: which model version served which request.

Putting It All Together: A Repeatable Playbook

  1. Define SLOs and constraints
  • Latency targets (P50/P95), availability, budget (cost per 1k requests), quality thresholds.
  1. Establish a baseline
  • Simple service, no fancy batching. Measure end-to-end: TTFB, end latency, throughput, utilization.
  1. Optimize the model
  • Mixed precision, quantization, compilation (TensorRT/ONNX/Torch compile), cache KV for LLMs.
  1. Optimize serving
  • Dynamic batching, concurrency tuning, streaming, tokenizer offload, multiple instances per GPU.
  1. Optimize the system
  • Autoscaling tuned to actual signals (queue depth/tokens/sec), co-locate storage, pre-warm caches, use efficient serialization.
  1. Instrument and test
  • Add metrics/tracing, run load tests, perform canary/shadow tests with real traffic.
  1. Automate deployment and rollback
  • Versioned artifacts, model registry, blue/green or canary, automated smoke tests.

On Kubernetes with Sealos:

  • Create a GPU-enabled workspace, deploy Triton/KServe from the App Launchpad, attach object storage for models.
  • Set HPA/KEDA policies per service; enforce quotas per team.
  • Use built-in dashboarding/observability integrations or bring your own Prometheus/Grafana stack.
  • Track per-namespace costs to keep optimizations grounded in reality.

Common Pitfalls to Avoid

  • Overfitting to microbenchmarks: Always validate against realistic traffic and payloads.
  • Ignoring pre/post-processing: Tokenization and image transforms often dominate CPU time.
  • Oversized containers: Slow cold starts and wasted bandwidth.
  • One-size-fits-all batching: Different endpoints need different policies.
  • No back-pressure: Queues fill, latency explodes; implement timeouts and 429s.
  • Under-instrumentation: Without metrics and traces, you’re guessing.

Quick Optimization Checklist

  • SLOs defined and dashboards in place.
  • Model converted for inference: ONNX/TensorRT/Torch compile, mixed precision on GPU.
  • Quantization evaluated; accuracy impact measured.
  • Dynamic batching enabled; queue delays tuned for P95 target.
  • Concurrency configured: multiple instances per GPU if memory allows.
  • Tokenization and preprocessing profiled and parallelized.
  • Streaming enabled for LLMs; TTFB monitored.
  • Autoscaling tied to meaningful signals (queue depth/tokens/sec).
  • Warm-up routines and model caches set up.
  • Canary/shadow rollouts for new versions; automated regression tests.
  • Cost per 1k requests tracked; capacity plans reviewed.

Conclusion: Optimize What Matters, Measure Everything

Serving ML models at scale is an engineering discipline, not a one-off task. Start with clear SLOs that reflect user needs and business constraints. Build a baseline system, instrument it, and iterate. Optimize the model (quantization, compilation), the serving layer (dynamic batching, concurrency, streaming), and the system (autoscaling, storage locality, CI/CD). Validate each change with robust load tests and canary rollouts.

The payoff is real: lower latency, higher throughput, predictable costs, and a stable platform that lets your teams ship features faster. On Kubernetes, leveraging a platform like Sealos can reduce operational friction—GPU orchestration, multi-tenancy, and app deployment—so you can focus your energy where it counts: inference performance and product impact.

Serving is where your model meets the world. Make it fast, make it reliable, and keep it measurable.

Explore with AI

Get AI insights on this article

📤 Share this article

Tip: AI will help you summarize key points and analyze technical details.

Ready to experience Sealos?

Develop, deploy, and scale in one seamless cloud platform

Get Started
Develop, deploy, and scale in one seamless cloud platform
Copyright © 2025 Sealos. All rights reserved.
YouTube