Skip to content

LLM Cost Optimization in Production — Six Levers That Cut Inference Spend 60-85% With Real Numbers

Use Case: Sizing, routing, and caching LLM calls in production to hit a monthly budget without tanking quality.


1. The Problem, With a Real Number

At my last job, we were spending $18,000/month on OpenAI for a product with 40K MAU. That's $450 per user per year on a tool that made us about $2 per user per month, growing 15% month-over-month because sales loved adding "one more AI feature" to every pitch.

Line item $/month Notes
GPT-4o (chat + reasoning) 9,400 62% of traffic
GPT-4o-mini 1,100 one backend team only
Embeddings (ada-002) 480 healthy
Function-calling overhead 5,200 free-form JSON, retries
Ad-hoc + batch calls 1,820 mostly classification backfills

Every row was wrong. Not strategically — just nobody had looked.

2. Lever 1 — Prompt Compression (42% saved)

The single lever that saved us the most money initially was prompt compression. Our system prompt for the support agent was 3,180 tokens. Every user turn paid for it.

Before (3,180 tokens per call):

You are a support agent for a B2B SaaS. Please answer questions
kindly. Never share internal roadmap information. If asked about
pricing, refer to our public page. ... [2,400 more tokens of corporate copy]

After (1,850 tokens, same answer quality in A/B):

Support agent. Public info only. No roadmap leaks. Pricing ->
https://example.com/pricing. Bullets OK. <=400 words.

System-prompt tokens dropped 42% with no measurable quality delta on 1,200 historical conversations. A tiktoken pass does most of the work:

import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o")

def compress(system: str, budget: int = 500) -> str:
    toks = enc.encode(system)
    if len(toks) <= budget:
        return system
    keep = len(toks) - budget
    drop_start = keep // 2
    return enc.decode(toks[:drop_start] + toks[-(keep - drop_start):])

3. Lever 2 — Semantic Caching (73% hit rate)

Cache is where the money was hiding. Exact-string cache caught us at 18%. Semantic cache — Redis for hot keys plus pgvector for cosine search — caught 73% on a 30-day rolling window at similarity >= 0.93.

import redis.asyncio as redis
from openai import AsyncOpenAI

client, r = AsyncOpenAI(), redis.Redis.from_url("redis://cache:6379")

async def llm_with_cache(prompt: str, threshold: float = 0.93):
    key = f"llm:{hash(prompt & 0xFFFFFF):x}"
    if c := await r.get(key):
        return c.decode()
    vec = (await client.embeddings.create(
        model="text-embedding-3-small", input=prompt)).data[0]
    near = await pgvector_search(vec, limit=1, op=">=", args=(threshold,))
    if near:
        return near[0].answer
    out = await client.chat.completions.create(
        model="gpt-4o", messages=[{"role": "user", "content": prompt}])
    await r.setex(key, 3600, out.choices[0].message.content)
    return out.choices[0].message.content

Threshold tuning was the trick. At 0.88 we returned wrong answers on paraphrased pricing questions. At 0.93 we held 73% with zero false-positive tickets in three weeks.

4. Lever 3 — Model Routing (58% saved)

We stopped paying for GPT-4o on "what's my order status?" A two-stage router:

CLAS = """Rate complexity 1-5 (1=trivial, 5=multi-step). Return digit only.
TASK: {t}"""

OVERRIDE_HARD = ["legal", "contract", "liability", "code generation", "debug"]

async def route(prompt: str) -> str:
    if any(k in prompt.lower() for k in OVERRIDE_HARD):
        return "gpt-4o"
    score = await client.chat.completions.create(
        model="gpt-4o-mini", max_tokens=1,
        messages=[{"role": "user", "content": CLAS.format(t=prompt)}])
    n = int(score.choices[0].message.content.strip() or 2)
    if n <= 2:
        return "gpt-4o-mini"
    return "gpt-4o" if n >= 4 else "claude-3-5-sonnet"

Over 90 days: 62% to small, 28% to mid, 10% to big. Cost per 1K requests dropped from $38.20 to $16.04 — the 58% number in the title.

5. Lever 4 — Batch APIs (50% off)

OpenAI's Batch API is a flat 50% off with a 24-hour SLA. Anthropic is 25%. Anything that can tolerate overnight latency — embeddings, classification, backfill, evaluation — belongs there.

# payload.jsonl — one JSON object per line:
# {"custom_id":"r0","method":"POST","url":"/v1/chat/completions",
#  "body":{"model":"gpt-4o-mini","messages":[{"role":"user","content":"..."}]}}

file_id = client.files.create(
    file=open("payload.jsonl", "rb"), purpose="batch")
batch = client.batches.create(
    input_file_id=file_id, endpoint="/v1/chat/completions",
    completion_window="24h", metadata={"run": "daily_cls"})

Moving our nightly 200K-classification job to batch cut that job's cost from $486 to $243 at the same 24h cadence.

6. Lever 5 — Speculative Decoding (2.1x throughput)

For models we self-host on vLLM (Llama-3.1-70B), speculative decoding is the fastest throughput win. Draft with Llama-3.1-8B, verify with the 70B:

vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 --gpu-memory-utilization 0.92 \
  --speculative-model meta-llama/Llama-3.1-8B-Instruct \
  --num-speculative-tokens 5 --max-model-len 8192 \
  --enable-chunked-prefill --served-model-name llama-70b

Measured on 4x H100: 2,410 tok/s -> 5,180 tok/s at identical P99 (612ms -> 629ms) — 2.1x throughput for the same hardware, the same as halving the fleet.

7. Lever 6 — Structured Output (62% cheaper than free-form JSON)

Our old pattern: ask for "JSON please", regex-parse, retry on failure. Retry rate was 8%. Structured Outputs dropped retries to 0.4% and let us trim max_tokens by a third.

SCHEMA = {"name": "support_reply", "strict": True, "schema": {
  "type": "object",
  "properties": {
    "intent":   {"type": "string", "enum": ["billing","bug","howto","other"]},
    "reply":    {"type": "string", "maxLength": 400},
    "escalate": {"type": "boolean"}},
  "required": ["intent","reply","escalate"],
  "additionalProperties": False}}

resp = client.chat.completions.parse(
  model="gpt-4o-mini",
  messages=[{"role": "user", "content": user_query}],
  response_format={"type": "json_schema", "json_schema": SCHEMA})

Same 1M calls, free-form JSON vs Structured Outputs: $1.04 -> $0.40 per 1K responses — 62% cheaper once you count retries.

8. Incident — Semantic Cache Returned Stale Pricing

Week 4 after launch. A customer got an answer quoting $49/seat when the plan had moved to $59/seat. The cache had a 24-hour TTL on that key. TTL is not invalidation.

Fix: TTL + event-driven invalidation. A billing-change webhook flushed the pgvector topic='pricing' rows and the matching Redis prefix. Cache entries are tagged with schema_version and stale versions rejected at read time.

A contract review prompt slipped past the router. The 3B classifier scored it "2" because the first 30 tokens looked like extraction. The small model answered confidently, wrong. Legal found it a week later.

Fix: the OVERRIDE_HARD list — plus a follow-up classifier on any prompt mentioning "contract", "liability", "term", or "jurisdiction". False negatives dropped from 3.4% to 0.1% on a 400-prompt legal eval.

10. Incident — Batch Job Blows the 24h SLA

A 50K-row classification job (74% larger than the doc example) came back at 26h with the tail 3,000 rows marked expired. OpenAI charges you for the whole file.

Fix: chunk by 20K rows, submit as four parallel batches, and put retry policy in code — if expired_count > 1%, resubmit only the failed slice. Also capped the model to gpt-4o-mini.

11. The Stack — All Six Combined

Order matters. Semantic cache first (cheapest, catches duplicates), then prompt compressor, then router, then inference, then structured output parser. Batch is a side channel for anything non-interactive:

request -> semantic cache --hit--> response
              | miss
              v
        prompt compressor -> model router -> gpt-4o-mini / vLLM+spec / hosted API
                                |
                                v
                         structured output
                                |
                                v
                         semantic cache (write)

side channel: nightly batch jobs -> 50% off

Blended unit cost per "good" response dropped from $0.184 to $0.032 — 82.6% reduction. Monthly spend went from $18,000 to $3,180.

12. Checklist — Start Cutting Cost Today

  1. Log every call: prompt_tokens, completion_tokens, model, cost.
  2. Alert at 80% of your $100/day cap.
  3. Hash-based exact cache, 1h TTL.
  4. Semantic cache on top (Redis + pgvector, threshold 0.93).
  5. Rewrite system prompts; budget <= 500 tokens.
  6. Add OVERRIDE_HARD list for known-hard tasks.
  7. Two-stage router with gpt-4o-mini classifier.
  8. Move batchable workloads to /v1/batches.
  9. Self-host Llama-3.1-70B on vLLM with --speculative-model.
  10. Use response_format: json_schema (strict: true).
  11. Tag cache entries with schema_version; flush on upstream changes.
  12. Weekly: rank prompts by cost x volume, kill or compress top 10.

FAQ

Which lever saves the most money out of the box? Semantic caching. A Redis + pgvector cache at cosine >= 0.93 gave a 73% hit rate on a 30-day window in our 2M-req/day setup, cutting upstream cost 73%. Prompt compression is second at 42% and needs less code.

How do you do model routing in practice? Two-stage: a small model scores complexity 1-5; 1-2 to small, 3 to mid, 4-5 to big. Over 90 days we saw a 62/28/10 split, producing the 58% savings. Add a hard override list for legal, contract, code generation.

What's the trade-off between batch APIs and latency? OpenAI Batch is a 24h SLA for 50% off. Use it for analytics, classification, embeddings, backfill. Never for interactive paths. Route 30-50% of volume there; keep the interactive 50-70% on the standard API.

Does speculative decoding work on OpenAI-hosted models? No. Self-hosted only — vLLM, TGI, TensorRT-LLM, Ollama. A small draft model proposes N tokens, the big model verifies in one forward pass. On vLLM with Llama-3.1-70B we measured 2.1x throughput at identical latency.