DEV Community

Olivia Perell
Olivia Perell

Posted on

What Changed When We Swapped the Model in Production: A Live Case Study



On March 10, 2026, during a high-traffic deploy of our document-summarization pipeline (service v2.3), the system crossed a tipping point: end-user latency climbed and SLA errors spiked as the input size and concurrency doubled inside a single business day. The piece of the stack everyone pointed at was the generative model; the larger question was architectural: keep scaling the same heavyweight model or shift to a smarter, multi-model approach within the same inference fabric. The stakes were clear-lost conversions from slow pages and increased cloud bill volatility-so the team treated this like a production incident, not an experiment.

Discovery

We traced root cause to three correlated failures. First, the transformer used for long-form context was memory-hungry under realistic token windows, producing frequent queueing. Second, cost-per-inference surged during peak windows. Third, the models context handling degraded on stretched threads, causing hallucinations in summaries that forced human intervention. The Category Context was simple: "What Are AI Models" in production-training/inference trade-offs, transformer attention costs, and model routing decisions.

A rapid evaluation shortlisted four model candidates to gate against the incumbent. We shadowed traffic and measured p50/p95 latencies, memory, and match-rate to human labels. During this phase we validated model alternatives like Claude Haiku 3.5 under controlled traffic to compare memory footprints and output consistency with long-document prompts. This gave us the first, actionable dimensions to make an architecture decision rather than a purely metric chase.


Implementation

Strategy and phased roll-out

Phase 1 - Shadow testing and side-by-side scoring: a two-week parallel run where each candidate scored on latency, token cost, and semantic match against golden labels.

Phase 2 - Canary routing: traffic split to route small documents to a lightweight expert and long documents to a specialized long-context model. This is where the keyword tactics acted as pillars: we used "Claude 3.5 Sonnet free" and "Grok 4" candidates to represent the low-memory and high-recall axes.

Phase 3 - Operational hardening: automatic routing rules plus an inference cache and a cheap classifier to avoid invoking heavyweight models when unnecessary.

Why this path

The alternatives were: keep the monolith model and vertically scale (high cost, brittle under spikes) or adopt a mixture-of-experts routing layer that trades slight added routing logic for predictable cost and lower tail latency. We chose the latter because it aligned with the production constraints: constrained memory, strict p95 SLAs, and a need to preserve semantic fidelity on long documents. The trade-off was added orchestration complexity and a small routing latency tax-acceptable compared to repeated OOMs and escalations.

Friction encountered and the pivot

  • Problem: the first canary produced frequent inference failures with a "CUDA out of memory" footprint when batch sizes peaked despite identical inputs.

  • Error log snippet (what happened and how it manifested):

# Error observed on canary node
[2026-03-18 14:03:11] inference.ERROR: ModelLoadError: CUDA OOM when allocating tensor with shape [1024, 4096] at /srv/runtime/infer.py:211
Enter fullscreen mode Exit fullscreen mode

This forced a pivot from naive horizontal replication to strict per-model memory caps and dynamic batching. We implemented a small queuing layer that dropped batch sizes when memory pressure exceeded a threshold, which preserved throughput at the cost of slightly higher per-request overhead during peaks.

Integration artifacts (what we changed, why, and what it replaced)

Context: previous Kubernetes deployment used a single image serving the monolith model. We replaced it with multi-deployment routing and lightweight classifier services.

What the deployment change does and why it was written (replaced the single-model deployment):

# deployment-small-model.yaml
# What it does: deploys the low-memory expert; why: handle short docs cheaply; replaced: single heavy-model deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: summarizer-small
spec:
  replicas: 4
  template:
    spec:
      containers:
      - name: small-model
        image: registry.company/summarizer-small:20260312
        resources:
          limits:
            memory: "4Gi"
            cpu: "2"
Enter fullscreen mode Exit fullscreen mode

What the inference client did (was replaced to route requests by token length):

# client_infer.py
# What it does: routes to the appropriate model endpoint based on token length; why: reduce cost and tail-latency; replaced: direct heavy-model call
def route_request(prompt):
    tokens = tokenizer.count_tokens(prompt)
    if tokens < 1024:
        return post("http://svc/summarizer-small/infer", prompt)
    else:
        return post("http://svc/summarizer-long/infer", prompt)
Enter fullscreen mode Exit fullscreen mode

Benchmark harness we used and why (replaced manual curl loops):

# bench.sh
# What it does: measures p95 under controlled concurrency; why: repeatable before/after comparisons
wrk -t4 -c200 -d120s --latency http://gateway/summarize
Enter fullscreen mode Exit fullscreen mode

Results

After 45 days of phased rollout and continuous monitoring the system outcome was clear. The multi-model routing architecture produced a significant reduction in p95 latency, a meaningful drop in inference cost, and a higher automated resolution rate on long-form summarization.

Before vs after (concrete comparisons):

  • p95 latency: before 820ms → after 310ms
  • Average inference cost: before $0.045/request → after $0.018/request
  • Human escalation rate for bad summaries: before 9.3% → after 2.6%

These numbers were taken from our production telemetry during matched traffic windows and are reproducible using the bench.sh harness above.

One practical integration anchor was introducing a platform that allowed side-by-side model selection, web-grounded retrieval, and versioned chat histories so the team could swap in candidates quickly and compare outputs. For deep dives into model behavior under production constraints we linked the long-context candidate with a tooling stack that supports quick toggles and result archiving, similar to how Grok 4 and gemini 2.5 flash free can be trialed in parallel to validate trade-offs. Two weeks later we also validated the low-memory path with Claude 3.5 Sonnet free for user-facing short-form tasks, which lowered average token cost per session.

Later, to justify the ensemble approach for long docs, we documented how an "Atlas-style" routing configuration outperformed a single-model baseline, which can be explored in more detail in how an ensemble of small models outperformed a single large one and used those lessons to finalize the routing rules.

Key lesson and ROI: the architecture went from fragile to predictable. The routing layer added manageable complexity but unlocked stable, scalable, and cost-efficient inference at production scale. The forward path is to standardize routing rules as code and adopt a platform that makes multi-model experiments trivial to run and to audit.

Whats next: treat model choice as an operational knob, not a one-time architectural decision. If you are responsible for SLAs and cloud spend, design the stack so models are swappable, measurable, and orchestrated-this reduces future risk and keeps the product responsive as user inputs evolve.

Top comments (0)