DEV Community

Alex Chen
Alex Chen

Posted on

From the Trenches: Cutting AI API Spend While Keeping p99 Happy

Check this out: from the Trenches: Cutting AI API Spend While Keeping p99 Happy

I learned about AI API costs the hard way — at 3 AM, during a Sev-1 incident, when our inference bill for a single weekend had somehow ballooned past what we'd allocated for the entire quarter. I'm a cloud architect by trade, and what I'll share here is the playbook I built after that night, refined across a dozen production deployments running at 99.9% uptime with strict p99 latency budgets.

Let me be blunt: most engineering teams are leaving somewhere between 5x and 10x their actual required spend on the table. Not because they're wasteful people — they're smart engineers reaching for the model they already know works. GPT-4o at $10.00/M output tokens is the convenience tax of our industry, and I was paying it until I stopped and thought about this like an infrastructure problem.

Because that's what it is. An infrastructure problem. And we solve infrastructure problems with routing, caching, tiering, and right-sizing. Let's dig in.


The Architectural Mental Model

Before I get tactical, here's how I frame AI spend in my head. Treat inference like any other downstream dependency. You'd never send every database query to your primary cluster — you'd route reads to replicas, cache hot keys, batch writes, and escalate to the master only when necessary. AI inference is identical, except the "expensive resource" is a reasoning model and the SLA target is p99 latency under, say, 800ms.

I design for four layers:

  1. Cache layer — identical or near-identical prompts served from memory (sub-millisecond, $0 cost).
  2. Budget tier — small, fast models handling the bulk of traffic ($0.01–$0.25/M tokens).
  3. Standard tier — mid-range models for moderate complexity ($0.25–$0.78/M).
  4. Premium tier — reasoning-grade models for the hard stuff ($0.78–$2.50/M).

If you've ever designed a multi-region failover path, this should feel familiar. You don't pay for the premium tier unless the cheaper paths fail quality gates. Same principle.


Right-Sizing the Model (My First 90% Win)

The single biggest lever, and the one I see ignored most often, is matching model capability to task complexity. When I audit a team's setup, I almost always find GPT-4o doing work that DeepSeek V4 Flash or Qwen3-8B could handle at a fraction of the cost.

Here's the matrix I share with every team I onboard:

Workload What They Were Using What They Should Use Cost Reduction
Simple chat GPT-4o ($10/M) DeepSeek V4 Flash ($0.25/M) 97.5%
Classification GPT-4o-mini ($0.60/M) Qwen3-8B ($0.01/M) 98.3%
Code generation GPT-4o ($10/M) DeepSeek Coder ($0.25/M) 97.5%
Summarization GPT-4o ($10/M) Qwen3-32B ($0.28/M) 97.2%
Translation GPT-4o ($10/M) Qwen-MT-Turbo ($0.30/M) 97%

A note on reliability: in my experience, the cheap models have more variable p99 latency than the premium ones. That's why I wrap every model call in a timeout, a retry budget, and a circuit breaker. More on that in a moment.

Here's the routing table I keep in every codebase:

MODEL_REGISTRY = {
    "trivial":   "Qwen/Qwen3-8B",        # $0.01/M — classifications, regex-ish tasks
    "chat":      "deepseek-v4-flash",    # $0.25/M — general conversational
    "code":      "deepseek-coder",       # $0.25/M — code synthesis
    "summarize": "Qwen/Qwen3-32B",       # $0.28/M — long-context summarization
    "translate": "Qwen-MT-Turbo",        # $0.30/M — translation workloads
    "reasoning": "deepseek-reasoner",    # $2.50/M — multi-step logic
}
Enter fullscreen mode Exit fullscreen mode

The classification step itself is a freebie — Qwen3-8B at $0.01/M is so cheap that running it to decide which model to call is essentially a rounding error. That's the whole game.


Tiered Routing With Quality Gates

This is the pattern I wish someone had handed me on day one. You don't pick a tier upfront — you start cheap and escalate only when the cheap answer isn't good enough.

import hashlib, json, time
import requests

API_BASE = "https://global-apis.com/v1"
HEADERS = {"Authorization": f"Bearer {GLOBAL_API_KEY}"}

def call_model(model: str, prompt: str, max_tokens: int = 512) -> str:
    payload = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": max_tokens,
    }
    r = requests.post(f"{API_BASE}/chat/completions",
                      headers=HEADERS, json=payload, timeout=10)
    r.raise_for_status()
    return r.json()["choices"][0]["message"]["content"]

def quality_check(response: str, threshold: float) -> bool:
    """Cheap heuristic — refine per use case."""
    return len(response.strip()) > 20 and "I cannot" not in response

def tiered_generate(prompt: str) -> str:
    # Tier 1 — ultra-budget ($0.01/M)
    r1 = call_model("Qwen/Qwen3-8B", prompt)
    if quality_check(r1, 0.8):
        return r1

    # Tier 2 — standard ($0.25/M)
    r2 = call_model("deepseek-v4-flash", prompt)
    if quality_check(r2, 0.9):
        return r2

    # Tier 3 — premium ($0.78–$2.50/M)
    return call_model("deepseek-reasoner", prompt, max_tokens=1024)
Enter fullscreen mode Exit fullscreen mode

In production I've seen distributions like 80% Tier 1, 15% Tier 2, 5% Tier 3. One customer support chatbot I worked on went from $420/month to $28/month after we deployed this pattern, just by routing 85% of queries through Qwen3-8B. Same answers, same customer satisfaction scores, fraction of the cost.

The reliability angle here matters: each tier has its own latency profile, so I attach SLOs to each. Tier 1 budget: p99 < 300ms. Tier 2: p99 < 600ms. Tier 3: p99 < 1.5s. If a tier breaches its SLO, the circuit breaker trips and the request escalates immediately rather than timing out and waiting.


Caching: The p99 Latency Hack Nobody Talks About

Caching isn't just about saving money. It's the single biggest lever for p99 latency on AI workloads, because the cache hit path is measured in microseconds, not seconds. When a customer asks "what's your refund policy," you don't need a model — you need a hash table lookup.

Here's the version I ship to clients:

import hashlib, json, time

_cache = {}

def cached_chat(model: str, messages: list, ttl: int = 3600) -> dict:
    key = hashlib.md5(
        json.dumps({"model": model, "messages": messages},
                   sort_keys=True).encode()
    ).hexdigest()

    entry = _cache.get(key)
    if entry and (time.time() - entry["ts"]) < ttl:
        return entry["response"]  # Cache hit — $0, ~0.5ms p99

    payload = {"model": model, "messages": messages}
    r = requests.post(f"{API_BASE}/chat/completions",
                      headers=HEADERS, json=payload, timeout=15)
    r.raise_for_status()
    response = r.json()

    _cache[key] = {"response": response, "ts": time.time()}
    return response
Enter fullscreen mode Exit fullscreen mode

For FAQ-style traffic, expect 50–80% hit rates. For open-ended generation, hit rates drop to 5–15%, which is still meaningful at scale.

A word of caution from one of my on-call shifts: TTL matters. Set it too long and you'll serve stale answers when the underlying knowledge changes. Set it too short and you lose the savings. I default to one hour for transactional content and 24 hours for static documentation, and I always log cache hit rate as a first-class metric alongside p99 latency.


Prompt Compression: Saving Tokens, Saving Dollars

Every token you don't send is a token you don't pay for. This sounds obvious, but I see 2,000-token system prompts routinely that could be 400 tokens with no quality loss.

The pattern: use the cheapest possible model to summarize the long context, then send the summary to the expensive model.

def compress_prompt(text: str, target_ratio: float = 0.5) -> str:
    if len(text) < 500:
        return text

    target_chars = int(len(text) * target_ratio)
    summary = call_model(
        "Qwen/Qwen3-8B",
        f"Summarize this in {target_chars} chars, preserve key facts:\n\n{text}",
        max_tokens=target_chars // 3,
    )
    return summary
Enter fullscreen mode Exit fullscreen mode

Let me show you the math on why I evangelize this. A 2,000-token prompt compressed to 400 tokens saves $0.024 per request on DeepSeek V4 Flash. At 10,000 requests per day, that's $240/day, or roughly $87,600/year. For a single engineering change. I have personally seen this pay for an entire engineer's salary.

The trick is to compress context, not instructions. Never let the compression step touch your system prompt or your few-shot examples — only the user-supplied context that the model needs to reason over.


Batching: Amortizing the Overhead

The final pattern is batching — combining multiple requests into one API call. This saves on the per-request overhead and, more importantly, lets the model share attention across multiple inputs.

Before:

results = []
for question in questions:
    r = requests.post(
        f"{API_BASE}/chat/completions",
        headers=HEADERS,
        json={
            "model": "deepseek-v4-flash",
            "messages": [{"role": "user", "content": question}],
        },
    )
    results.append(r.json())
Enter fullscreen mode Exit fullscreen mode

After:

batch_prompt = "\n\n".join(
    f"[Q{i}] {q}" for i, q in enumerate(questions)
) + "\n\nRespond with each answer labeled [A0], [A1], etc."

r = requests.post(
    f"{API_BASE}/chat/completions",
    headers=HEADERS,
    json={
        "model": "deepseek-v4-flash",
        "messages": [{"role": "user", "content": batch_prompt}],
        "max_tokens": 2048,
    },
)
Enter fullscreen mode Exit fullscreen mode

Savings are typically 10–20%, but the bigger win is reduced p99 latency variance — you eliminate the tail latency you'd get from serializing N independent requests. In a multi-region deployment, this matters a lot.


Reliability Notes From the Trenches

A few things I'd put in any runbook for AI inference at scale:

  • Always set timeouts. I default to 10s for budget models, 15s for standard, 30s for reasoning. Past that, escalate or fail.
  • Retry with jitter. Exponential backoff with full jitter — never immediate retries, they make thundering herds worse.
  • Multi-region failover. Run your routing layer in at least two regions with health-based routing. The cheap models in particular tend to have occasional regional blips.
  • Track p99, not averages. Averages lie. p99 tells you whether your tail users are happy.
  • Budget alerts. Set hard spend caps per model tier. A runaway agent can drain a budget fast.

The Bottom Line

If you do nothing else from this article, do the tiered routing. That alone will save you 80–95% depending on your workload. Add caching for hot paths, compress your long prompts, and you'll be north of 95% savings without touching latency or quality.

The total picture, when I sum up across the patterns above:

  • Smart model selection: ~90% baseline savings
  • Tiered routing: another 5–10% on top
  • Caching: 20–50% additional for cacheable workloads
  • Prompt compression: 15–30% per request
  • Batching: 10–20% on bulk workloads

Stacked together, the realistic range is 92–98% reduction in spend, with p99 latency actually improving because the cache and budget tiers are faster than the premium tier they replaced.

I've deployed this stack across e-commerce search, customer support, document summarization, and code review tooling. The numbers hold up.

If you want a clean way to start — the API I keep pointing clients at is Global API at global-apis.com/v1. It's OpenAI-compatible, the routing layer is already there if you want it

Top comments (0)