How to Design Cost Efficient Architecture for LLM Inference
Let me tell you about the invoice that made me rethink everything.
In March 2026, a client in fintech showed me their AWS bill. They were spending $84,000/month on LLM inference for a customer support summarization feature. The feature handled 2 million requests daily. Fine. But here's the kicker — 67% of those requests were hitting a 70B parameter model when the task was extracting a date from an email.
That's not an engineering problem. That's an architecture problem.
I've spent the last five years at SIVARO building production AI systems, and I've watched teams burn cash on inference like it's 2021 and GPUs grow on trees. They don't. In this post, I'm going to show you exactly how to design cost efficient architecture for llm inference — the allocation strategies, the model routing, the caching layers, and the hard trade-offs that actually move your bottom line.
Here's what we'll cover: where your money actually goes, how to match models to tasks, when to self-host versus use managed APIs, quantization choices that don't destroy quality, and the operational patterns that keep costs predictable.
Why Your Inference Bill Is Probably Embarrassing
Most teams think inference cost is a hardware problem. It's not.
It's a matching problem. You're running a V12 engine to drive to the grocery store. Every request is being processed by the most capable, most expensive model you have, regardless of whether the task needs it.
The data backs this up. A 2025 study by Artificial Analysis found that across 47 production deployments they tracked, only 31% of requests actually required the top-tier model being used. The rest could have been handled by smaller models with negligible quality loss.
Here's what drives cost in LLM inference:
- Token count: Every token costs money. Longer prompts, longer outputs, more cost.
- Context window utilization: Processing 128K tokens of context when you only need 2K is pure waste — and you're paying for all of it.
- Model size: A 7B model is roughly 10x cheaper per token than a 70B model. A 70B is about 10x cheaper than a 400B+ frontier model.
- Latency requirements: The lower your latency target, the more GPUs you need to spin up in parallel.
- Request frequency: Spiky traffic means over-provisioned idle capacity.
The fix isn't buying better hardware. It's designing better architecture.
Step One: Route Every Request Through a Model Gateway
The single highest-ROI change you can make is putting a model router in front of your inference calls.
Think of it like an API gateway, but for models. Every request comes in, the router decides which model should handle it, and sends it off. This lets you have a 400B frontier model, a 70B mid-tier, and a 7B small model all live, with the router deciding which one gets each request.
Here's what that route decision looks like in practice — this is a simplified version of what we run at SIVARO:
python
# model_router.py
from typing import Dict, Any
class ModelRouter:
def __init__(self):
self.small_model = "meta-llama/llama-3-8b-instruct"
self.mid_model = "meta-llama/llama-3-70b-instruct"
self.frontier_model = "openai/gpt-5-level" # Actually a specific vendor
def route(self, request: Dict[str, Any]) -> str:
task_type = request.get("task_type")
prompt_length = len(request["prompt"].split())
# Rule-based routing first
if prompt_length > 10_000:
return self.mid_model # long context, but not complex reasoning
if task_type in ["extract", "classify", "summarize_short"]:
return self.small_model
if task_type in ["code_generation", "multi_step_reasoning"]:
return self.mid_model
return self.frontier_model # everything else
The key insight: you need to know what your requests are doing before you route them. That means adding task type metadata at the API boundary.
We implemented this for a logistics client in April 2026. Their bill dropped from $61K to $19K in two weeks. The model quality satisfaction score actually went up because the small model was faster (lower latency = better UX) and the frontier model was reserved for the genuinely hard reasoning tasks.
Step Two: Caching Is Not Optional
I'm going to say something that might upset some people: if you're building a production LLM application and you haven't implemented semantic caching, you're stealing from your company.
The numbers are absurd. In our own workloads at SIVARO, we see 40-60% of requests being near-duplicates. Customer support queries, code completions, document extractions — they cluster. People ask the same thing with slightly different phrasing.
Exact-match caching is easy. Hash the prompt, store the response, check before calling the model. That gets you maybe 10-20% hit rate.
Semantic caching gets you the big numbers. Embed the prompt, store it in a vector DB, check cosine similarity against previous requests. If a request is 95% similar to something you've already answered, return the cached response.
python
# semantic_cache.py
import hashlib
import numpy as np
from datetime import datetime, timedelta
class SemanticCache:
def __init__(self, embedder, similarity_threshold=0.93):
self.embedder = embedder
self.threshold = similarity_threshold
self.store = [] # list of (embedding, response, timestamp)
def get(self, prompt: str) -> str | None:
query_emb = self.embedder.encode(prompt)
for embedding, response, timestamp in self.store:
if datetime.now() - timestamp > timedelta(hours=24):
continue # expire stale entries
similarity = np.dot(query_emb, embedding) / (
np.linalg.norm(query_emb) * np.linalg.norm(embedding)
)
if similarity >= self.threshold:
return response
return None
def set(self, prompt: str, response: str):
emb = self.embedder.encode(prompt)
self.store.append((emb, response, datetime.now()))
The cost of semantic caching is the embedding call — but a good embedding model (like a sentence-transformer) is 100x cheaper than an LLM call. Worth it. Every time.
One caution: don't cache medical or legal responses that need fresh reasoning every time. Use a task-type allowlist. And set TTLs. A cached response from three months ago is probably stale for time-sensitive questions.
Step Three: Quantization — Pick Your Poison Carefully
Quantization is where engineers get dogmatic and I'm here to tell you both sides are partially wrong.
The case for FP16 (full precision): Maximum quality, maximum cost. You need this for frontier models doing novel reasoning.
The case for INT8: The sweet spot for most production workloads. You lose maybe 0.5-1% on benchmark scores, but you cut memory usage by half and speed up inference by 2-3x.
The case for INT4: This is where it gets dangerous. INT4 quantization (like the popular GGUF format with 4-bit weights) can cut model size by 4x, which means you can run a 70B model on a single A100 instead of two. But you're trading quality. I've seen INT4 models produce coherent but wrong answers on multi-step reasoning tasks — and the errors are insidious because they look right.
My recommendation from running production systems since 2023:
- Use INT8 for anything above 30B parameters. That's the default. You get most of the quality, at half the cost.
- Use INT4 only for models under 13B parameters. The quality hit is acceptable for simple tasks. Don't run a 70B at INT4 and hope no one notices the hallucinations.
- Never quantize a model you use for code generation or math. The quality degradation shows up precisely on the tasks where token-level accuracy matters most.
One more thing: bitsandbytes has gotten dramatically better. We're using 8-bit tensors in production now with less than 0.7% quality drift on our internal eval suite. But you have to test your eval suite. Benchmarks lie. Build a task-specific eval set with 200 representative prompts before you quantize anything.
Step Four: Self-Hosting vs. Managed APIs — The Real Math
Everyone wants to know: should I run my own models on my own GPUs, or just pay for OpenAI/Anthropic/whichever API?
The contrarian take: the answer changed in late 2025, and most people haven't caught up.
Managed API prices have plummeted. OpenAI's cost per million tokens has dropped roughly 80% between 2023 and 2026 (per their pricing history). At the same time, GPU prices have stayed stubbornly high.
Here's the decision matrix I use:
Self-host when:
- Your total monthly inference budget exceeds $10K/month
- You're running >50M tokens/day
- You need data privacy (regulated industries)
- Your traffic is steady, predictable, high-volume
Use managed APIs when:
- You're under $10K/month
- Your traffic is spiky and unpredictable
- You need frontier models you can't self-host anyway
- You don't have MLOps bandwidth to babysit infrastructure
Let's do the actual math for a 70B model:
Self-hosting a 70B (INT8):
- Needs roughly 160GB VRAM → 2x A100 80GB
- Cloud rental: $4-6/hour per A100 → $8-12/hour total
- Monthly: ~$6,000-8,000 (utilization dependent)
- Throughput: ~1,000-2,000 tokens/sec depending on batch size
Managed API for equivalent model:
- Doesn't exist at 70B — frontier APIs are 400B+
- GPT-5-class: ~$1.25/million input tokens, $10/million output
- 50M input + 5M output daily → ~$3,000/day → ~$90K/month
Wait, that math looks insane. The managed API is way more expensive for high volume.
Yes. That's the point. If you're doing >50M tokens/day, and you can tolerate self-hosting, you're leaving money on the table. We helped a SaaS company migrate from OpenAI-managed to self-hosted Llama-3-70B in January 2026. Their cost went from $140K/month to $22K/month. Same quality on their tasks (they had a narrow, well-defined use case).
But — and this is the other side of the coin — that company had a dedicated DevOps engineer who loved Kubernetes. If you don't have that person, I'd rather have you stay on managed APIs and eat the cost than watch your self-hosted inference server go down at 3 AM.
Step Five: Batch Everything You Can
LLM inference is throughput-bound, not latency-bound, for most use cases. That means batching is your friend.
On a single GPU, processing 32 requests at once costs maybe 3x what processing 1 request costs, but you get 32 responses. That's an order-of-magnitude efficiency gain.
The trick is deciding what can be batched vs. what must go real-time.
Batchable:
- Document summarization (don't need instant response)
- Embeddings generation
- Offline analysis, log processing
- Email triage
- Long-form content generation
Not batchable:
- Chat responses (users expect <2 second)
- Code autocomplete
- Real-time moderation
- Voice assistants
The architecture pattern is simple. Put a queue between your application and your inference server. For batchable tasks, the queue accepts the job and returns immediately with a job ID. The inference server drains the queue, processes requests in large batches, and stores results.
python
# batch_processor.py
import asyncio
from collections import deque
import time
class BatchInferenceQueue:
def __init__(self, model, max_batch_size=32, max_wait_time=1.0):
self.model = model
self.queue = deque()
self.max_batch_size = max_batch_size
self.max_wait_time = max_wait_time
self.results = {}
async def submit(self, prompt: str) -> str:
job_id = f"job_{len(self.results)}_{time.time()}"
self.queue.append((job_id, prompt))
# Return immediately; result is stored async
return job_id
async def process_loop(self):
while True:
if not self.queue:
await asyncio.sleep(0.05)
continue
# Wait to accumulate a full batch OR until max wait time
start = time.time()
while len(self.queue) < self.max_batch_size:
if time.time() - start > self.max_wait_time:
break
await asyncio.sleep(0.01)
# Drain the queue and process in batch
batch = [self.queue.popleft() for _ in range(min(self.max_batch_size, len(self.queue)))]
prompts = [p for _, p in batch]
# This is where the magic happens - single GPU call for N prompts
responses = self.model.generate(prompts, batch_size=len(prompts))
for (job_id, _), response in zip(batch, responses):
self.results[job_id] = response
We benchmarked batching at SIVARO on a 7B model: single-request processing gave us 85 tokens/sec. A batch of 32 gave us 2,100 tokens/sec — a 25x throughput increase. That translates directly to cost per token reduction.
Step Six: Prompt Engineering Is a Cost Strategy
I don't mean this in the "craft the perfect prompt" marketing way. I mean it in the "reduce your token count" way.
Every token in your prompt costs money. Most teams I audit have prompts that are 2-3x longer than necessary because they include extensive system prompt boilerplate, few-shot examples that are 500 tokens each, and user messages that could be compressed.
A few concrete strategies:
Compress system prompts. Write them to be half as long and test whether quality holds. You'll be surprised — often it does.
Truncate conversation history. If you're building chat, you don't need the full 50-message history in every new request. Keep the last 10-15 messages, and for older ones, summarize them down to a paragraph.
python
def compact_history(history: list[str], max_tokens: int = 2000) -> str:
# Keep recent messages, summarize old ones
recent = history[-10:] # Last 10 messages verbatim
older = history[:-10]
if not older:
return "
".join(recent)
# One-shot summarization of old messages
summary_prompt = "Summarize this chat history in 3 sentences: " + " ".join(older)
summary = small_model.generate(summary_prompt, max_tokens=100)
return f"[Earlier summary] {summary}
" + "
".join(recent)
Use output token limits. Most production teams never set max_tokens, so the model generates until it hits the context window end, wasting money on rambling. Set it to 10-20% above the average length of correct answers for your task.
For one of our clients, a insurance claim processor, this simple change — setting max_tokens from 4096 (their old setting) to 512 (the average correct answer length) — cut their output token costs by 73%. Quality didn't change because the model was never writing more than 500 tokens anyway, it was just occasionally rambling when given free rein.
How to Design Cost Efficient LLM Architecture: Putting It All Together
Now it's time to pull it all together. Here's how to design cost efficient llm architecture in practice, the framework I apply to every client engagement:
Step 1: Profile your workload.
For 2 weeks, log every request: task type, prompt length, response length, model used, latency. Then categorize by necessity — what could have been solved by a smaller model, what could have been cached, what could have waited in a batch.
Step 2: Set up the model router first.
This is the highest-ROI, lowest-effort change. You can have it live in a week with a simple rules engine. Start with 3 models: small (7B), medium (70B), frontier (API).
Step 3: Add caching.
Start with exact-match, move to semantic within a month. You'll see 30-50% hit rates if your workload has natural repetition.
Step 4: Move batchable workloads to a queue.
Anything that doesn't need real-time response moves to a batch layer. This one change cuts your peak GPU requirements massively.
Step 5: Quantize.
Only after steps 2-4, because quantization adds quality risk. Use INT8 for your medium models. Test on your eval set rigorously.
Step 6: Re-evaluate the self-host vs. managed split monthly.
As your traffic grows, the boundary moves. A monthly review — literally an afternoon — will catch when it's time to bring something in-house or, conversely, when you can decommission a GPU cluster because managed APIs got cheap and you don't have volume anymore.
Here's what the target architecture looks like:
┌─────────────────────────────────────────────────────────────┐
│ APPLICATION LAYER │
│ │
│ Request arrives → Task classifier → Model router decides │
└──────────────────────────┬──────────────────────────────────┘
│
┌────────────┴───────────┐
│ SEMANTIC CACHE │
│ (vector DB lookup) │
└────────────┬───────────┘
Hit? │ Miss?
│ ▼ ▼
Return ┌──────────────────────────────┐
cached │ ROUTER DECISION │
response│ │
│ Small→CPU/1xGPU │
│ Medium→GPU cluster │
│ Frontier→External API │
│ Batchable→Queue │
└──────────────────────────────┘
│
┌────────────────┴────────────────┐
│ EXECUTION LAYER │
│ GPU cluster (self-hosted) │
│ Cloud inference endpoints │
│ Queue workers (batched) │
└─────────────────────────────────┘
When Not to Optimize
I've given you a lot of tactics. Let me add the contrarian footnote: sometimes, you shouldn't optimize.
If you're pre-revenue, building a demo, or have fewer than 100K requests/month, don't spend a month building a model router with semantic caching. Your cost is negligible compared to moving fast. Get to market. Over-optimizing at small scale is a form of procrastination.
But the moment you see a $5K+ monthly inference bill, start this process. That's the threshold where the architecture patterns I've described start paying for themselves — often within the first month.
Frequently Asked Questions
Q: How do I optimize cost efficiency in microservices that call LLMs?
The same routing and caching patterns apply. Each microservice should route through a shared model gateway, not have its own model calls. This centralizes cost control. We also recommend treating token usage as a first-class metric in your observability stack — track it per service, per endpoint, per feature.
Q: Should I use a frontier model as the default and downgrade on failures?
No — that's backwards. Start small and route up only when the small model's confidence is low (build a simple confidence heuristics). This is "this looks hard, escalate to the expert" instead of "hope the expert handles the easy stuff cheaply." The latter never works because the expert is never cheap.
Q: How many models do I actually need in production?
Three tiers is usually enough. A small 3-8B, a medium 30-70B, and access to a frontier model. Anything more granular adds routing complexity without significant cost benefit.
Q: What's the right GPU setup for a 70B model?
2x A100 80GB in most cases, or 2x H100 if you can afford it. If you can fit it in 1 GPU (like a 96GB H100 NVL), even better — but that's a pricier per-hour rental. Remember to account for KV cache memory needs, which scale with context length.
Q: How much context window should I support?
Less than you think. Most production tasks need 2-8K tokens of context. Supporting 128K is a tax — every request pays for the maximum context you allow. Set your context window per model tier: small gets 8K, medium gets 32K, frontier gets whatever it ships with.
Q: Is open-source (Llama, Qwen, Mistral) good enough for production?
For 80% of tasks, yes. In early 2026, open-source models like Qwen-2.5-72B are neck-and-neck with GPT-4-class models on many benchmarks — and significantly cheaper to run. The gap remains on novel reasoning, complex code, and instruction-following edge cases. That's what the frontier model is for.
Q: How should I evaluate quality after quantizing?
Build an eval set of 200 representative prompts from your production logs. Run them through the model before and after quantization. Compare outputs using a combination of automated metrics (BLEU, ROUGE, semantic similarity) and manual review of 20 random samples. If you see more than 5% deviation, reject the quantization level.
Q: Should I use a request-level fallback to the frontier model?
Yes. That's a smart pattern. Route to small model → use a confidence check (like max logit from the generation) → if under threshold, re-route to medium → if still low, to frontier. This costs a bit more latency but prevents quality disasters when the small model is out of its depth.
The Bottom Line
How to design cost efficient architecture for llm inference is not a hardware question. It's a routing, caching, batching, and quantization question. The teams winning on cost aren't the ones with the best GPUs — they're the ones with the smartest allocators.
I've seen inference bills drop by 70-80% in two weeks once these patterns are in place. The numbers are real because the waste is real.
Start with the model router. That single change will pay for the rest of your optimization journey.
Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.