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.
9. Incident — Router Sent a Legal Query to a Small Model
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
- Log every call:
prompt_tokens,completion_tokens,model,cost. - Alert at 80% of your
$100/daycap. - Hash-based exact cache, 1h TTL.
- Semantic cache on top (Redis + pgvector, threshold 0.93).
- Rewrite system prompts; budget <= 500 tokens.
- Add
OVERRIDE_HARDlist for known-hard tasks. - Two-stage router with
gpt-4o-miniclassifier. - Move batchable workloads to
/v1/batches. - Self-host Llama-3.1-70B on vLLM with
--speculative-model. - Use
response_format: json_schema(strict: true). - Tag cache entries with
schema_version; flush on upstream changes. - Weekly: rank prompts by
cost x volume, kill or compress top 10.