Token caching, model routing, prompt compression, and the boring discipline of measuring. The levers that cut our LLM bill 60% without touching feature scope.
Our LLM bill grew 8x in six months. Some of that was feature growth; most was operational waste. We spent a quarter doing nothing but cost optimization and cut the bill 60% with no user-visible regression. This is what worked, what we tried and dropped, and the discipline that keeps the bill in line now.
Before any optimization, instrument. We added cost-per-request to our request logs:
Once this was in place we could ask the questions that matter: "Which feature is 80% of cost?" "Which prompts have 100x cost variance?" "What's the cost per user / per session?"
Before measurement, every conversation about cost was vibes. After, it was specific. Two features turned out to be 70% of the bill; the rest was rounding. That's where we focused.
A lot of LLM use is repeated work. The same prompts come in over and over (same system message, same retrieval context for popular queries, same user re-asking). Caching at multiple levels:
Provider-level prompt caching. Anthropic Claude (and now OpenAI) support marking sections of the prompt as cacheable. The provider keeps the KV cache for those sections; subsequent requests with the same prefix pay a fraction of the input-token cost (often 10x cheaper). The cache TTL is 5 minutes by default; if you have high enough QPS to keep it warm, the savings compound.
We marked our system message + retrieval context as cacheable. Hit rate ~70%. Input token cost dropped ~60% on cached requests.
Application-level response caching. Some prompts are deterministic enough to cache the response. If a user asks "summarize this document" and we've already summarized it, return the cached summary. We cache responses keyed by hash of prompt + retrieval-context + model. TTL 1 hour for fresh content, 7 days for stable content.
Hit rate ~25% on the use cases where it applies. The savings here are 100% (no LLM call at all).
Be careful: response caching is wrong for personalized output or stateful chat. We allow it only on idempotent operations (summarize, extract, classify).
Not every request needs the most powerful model. We added a router that picks the model based on the request:
We started with rules ("if endpoint = X, use small model"), then added a learned classifier ("predict if Sonnet suffices; fall back to Opus if confidence < threshold"). The classifier itself is a cheap call.
Result: ~40% of requests now hit a smaller model. Quality on those requests is comparable; cost is roughly 5x lower.
The risk: routing to a model that can't handle the task. We added evaluation gates — every routing decision is sampled and the output evaluated against the result the big model would have produced. If quality drops below threshold for a route, we re-route to the bigger model and update the rules.
The prompts we'd grown over six months were enormous. System messages were 4K tokens. Retrieval context was 8K tokens. Many of those tokens weren't earning their keep.
What we cut:
Net prompt size dropped ~50%. Cost dropped proportionally on uncached requests. Quality (measured by eval suite) was unchanged.
For async/background work (re-summarizing a corpus, generating embeddings for a knowledge base, batch classification), batch APIs are usually 50% cheaper than synchronous ones.
We moved everything not user-facing to batch. This was a one-time refactor with permanent savings.
Note: batch APIs have higher latency (minutes to hours), so they're not a fit for user-facing requests. But "user-facing" is a smaller fraction of LLM work than you'd think.
Every time we audited, we found bugs:
These aren't optimization; they're firefighting. But they were the single biggest delta in the first month. The instrumentation from step one is what surfaced them.
Prompt compression libraries (LLMLingua etc). Quality regression in our use cases didn't justify the savings. The hand-crafted prompt edits gave better results.
Distilling our own small model. Considered fine-tuning Llama or similar on our task. The eval/maintenance cost of running our own model didn't pay back at our scale (~10M LLM calls/month). At 10x that scale, it might.
Streaming for cost. Some teams claim streaming "feels" faster so users wait less and you save tokens. We didn't see this effect; users let long responses finish. Streaming is a latency feature, not a cost one.
Reducing output token max length. Cutting max tokens by 30% to "cap costs." Caused truncated responses. Users retried. Net cost increase.
What we do continuously now:
The biggest savings came from non-LLM bugs. Half of our reduction was "fix dumb things in code that was hitting LLM unnecessarily." The remaining half was thoughtful optimization. We expected the inverse.
Prompt caching is undersold. Provider prompt caching is a recent feature and many teams haven't integrated it. The savings are huge for high-QPS use cases. Restructure prompts so the long stable parts come first; pay attention to cache invalidation.
Routing has a quality ceiling. You can't route everything to a small model. The classifier itself isn't perfect; the cheaper models are worse at edge cases. Accept some baseline of "everything goes to the big model"; optimize the rest.
LLM cost optimization is mostly the same discipline as any other infra cost optimization: measure, find the big things, fix them, build operational rhythm to keep finding them. The specifics (prompt caching, model routing) are LLM-flavored, but the meta-pattern is timeless.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
pg_upgrade is fast but takes downtime; logical replication lets you cut over while the old DB still serves traffic. The runbook, the gotchas, and the post-cutover checklist.
When the service is slow and the network is suspect, these are the tools we reach for, in this order, with the exact flags that find the answer.
Explore more articles in this category
A demo RAG app is easy; one users trust is not. This is the map for reliable retrieval-augmented generation: grounding, evaluation, retrieval quality, guardrails, and safe rollout.
A prompt tweak or model bump can quietly wreck answers everywhere. Ship LLM changes the way you ship risky code: gate, shadow, canary, roll back.
When RAG answers go sideways, the model usually isn't the problem. Here's the top-to-bottom checklist we run to find where retrieval actually breaks.
Evergreen posts worth revisiting.