- Compare local and hosted models on cost, privacy, capability, latency and availability.
- Design a routing policy that applies hard rules before cost, capacity and difficulty.
- Compute the expected cost and latency of a pre-router and of a cascade.
- Size a local model's memory and decode speed with the AI Index formulas.
- Implement and evaluate a simple router using escalation rate, failure rate, cost and latency.
Before this: What is an AI operating system?, APIs, Tokens and Pricing, Tokens per Second: Estimating Local AI Speed, KV Cache and Context Cost
The best answer to "local or cloud?" is usually "both". Small and private tasks run well on your own hardware for free; the hardest problems still benefit from the largest hosted models. Routing is the practice of sending each request to the right one automatically.
Why not pick just one?
| Local model | Hosted model | |
|---|---|---|
| Cost per request | Effectively zero after you own the hardware (plus electricity) | Paid per token |
| Privacy | Data never leaves your machine | Data goes to the provider under their terms |
| Capability | Limited by your memory; strong open models up to the size your machine can hold | Access to the largest frontier models |
| Speed | Depends on your memory bandwidth (see tokens per second) | Usually fast, but subject to network and rate limits |
| Availability | Works offline | Needs internet and the provider to be up |
Neither column wins everywhere, so a single fixed choice either overpays or underdelivers.
The routing questions
A router asks a few questions about each request, roughly in this order:
- Is there a hard rule? If the content is sensitive or the task is pinned to local, it stays local. Rules come first and are never overridden by cost or convenience.
- How hard is the task? Classifying an email, extracting fields, summarizing a page or drafting a short reply are well within the reach of a good small or mid-size model. Multi-step reasoning across a large codebase may need a frontier model.
- How much context does it need? Long contexts cost memory locally (the KV cache, see KV cache and context cost) and money when hosted.
- How fast must it be? An interactive chat needs a quick first token; an overnight batch job does not.
- What is the budget? Spending caps per task, per agent or per month.
A simple, effective policy many people use: local first, escalate when needed. Try the local model; if the task is classified as hard, or the local answer fails a check, escalate to a hosted model — unless a rule forbids it.
A worked example
Suppose an agent processes 2,000 support emails a day. Each email plus instructions is about 1,500 input tokens and the reply about 300 output tokens.
- All hosted, at an illustrative $3 per million input and $15 per million output tokens: 2,000 × (1,500 × $3 + 300 × $15) ÷ 1,000,000 = $18 a day, about $540 a month.
- Routed: 90% are routine and handled by a local model; 10% are escalated. Hosted cost drops to about $1.80 a day.
The local side has to keep up: 2,000 replies × 300 tokens = 600,000 generated tokens a day. At 40 tokens per second that is about 4 hours of generation, comfortably done in a working day on one machine. (Prices here are round illustrative numbers; check current provider pricing.)
Sizing the local side
Routing only works if your local model is good enough for the routine work and fast enough for the volume. Three numbers decide that:
- Memory decides which models fit.
- Memory bandwidth decides how fast they generate.
- Compute decides how fast long prompts are read.
The AI Hardware & Model Index answers "What can this machine run?" for every machine it lists: the largest model that runs well, the models that fit but are slow, and the ones that need more memory or a cluster. Use it to choose a main local model plus a smaller, faster one for quick routing decisions and simple tasks.
Routing inside Prometheus OS
Prometheus OS routes across the providers you enable, hosted and local, and treats an explicit provider choice as absolute: if a task or agent is set to a particular provider, it does not silently fall back to another. Each request is logged with the model that handled it, so cost and behaviour can be reviewed later. Pooling several of your own machines into one local compute resource is planned and in development.
Going deeper
Routing as an optimisation problem
Stated precisely, a router picks for each request the model that minimises expected cost, but only among the models the rules allow:
Here is the set of models permitted for this request after hard rules are applied (privacy, pinning, region), is the cost of running request on model , is the estimated probability that handles it acceptably, and (lambda) is what a bad answer costs you, in the same units as . Rules shrink the set before any optimisation, which is why cost can never override them. When a bad answer is cheap (a draft you will edit), is small and cheap models win; when it is expensive (a customer-facing legal reply), is large and the router pays for quality. In practice comes from a small classifier, simple rules (length, keywords, task type) or past results; research systems such as FrugalGPT, Hybrid LLM and RouteLLM study how to learn it.
Two architectures: pre-routing and cascades
A pre-router decides before any model runs. A cascade tries the cheaper model first, checks the result, and escalates only on failure. Write for the fraction escalated, for the hosted cost per request, and for local and hosted response times. Taking the local marginal cost as roughly zero:
- Cascade: expected cost ; expected latency , because escalated requests pay for both attempts.
- Pre-router: expected cost ; expected latency , but only if its predictions are right.
Worked example, continuing the support-email case. Per email, hosted cost is dollars. With and 2,000 emails a day, hosted spend is dollars a day, as above. Suppose s and s (illustrative). The cascade averages s per email; a perfect pre-router averages s. The cascade is slower but has evidence (a failed check) for every escalation; a pre-router is faster but can silently send a hard task to a model that cannot do it. Many systems combine them: pre-route the obvious cases and cascade the uncertain ones.
Sizing the local side with the Index formulas
The site's AI Index methodology uses these approximations:
- Memory needed model file + KV cache + 1.5 GB, where the file is parameters bits . It fits if that is at most 85% of usable memory; on macOS the GPU can use about 75% of unified memory by default.
- KV cache bytes per token .
- Decode speed , where bytes per token active parameters bits .
- Prefill active parameters FLOPs per input token.
Worked example. An 8-billion-parameter model at 4 bits with 32 layers, 8 KV heads and head dimension 128, a 16-bit KV cache and an 8,192-token context, on a hypothetical Mac with 16 GB of unified memory and 400 GB/s of bandwidth.
- File: GB; with overhead GB.
- KV cache: bytes per token; tokens GB.
- Need: GB. Limit: GB. It fits.
- Decode: ms, plus ms, gives ms per token, or about 51 tokens per second.
Now the workload. The 1,800 routine replies need output tokens, which at 51 tokens per second is about 10,600 s, or 2.9 hours. Prefill for million input tokens costs FLOPs; assuming the machine sustains 20 TFLOPS of dense compute in practice (an assumption, check your own hardware), that is about 2,160 s, or 0.6 hours. Roughly 3.5 hours of work a day: one machine copes. A 70-billion-parameter model at 4 bits, by contrast, is a 35 GB file that does not fit in 16 GB at all, and even on a machine with enough memory at the same bandwidth it would decode at about 6 tokens per second, taking about 24 hours for the same 540,000 tokens. Bigger is not better if it cannot keep up.
A rules-first router in code
def route(task, local_ok=True):
# 1. Hard rules first: never overridden by cost or speed.
if task["sensitive"] or task.get("pinned") == "local":
return "local" if local_ok else "fail: local required but unavailable"
if task.get("pinned") == "hosted":
return "hosted"
# 2. Capacity: does the prompt fit the local context budget?
if task["context_tokens"] > 32_000:
return "hosted"
# 3. Difficulty estimate (0..1) from a classifier or heuristics.
if task["difficulty"] >= 0.7:
return "hosted"
return "local" if local_ok else "hosted"
tasks = [
{"sensitive": True, "context_tokens": 3_000, "difficulty": 0.9},
{"sensitive": False, "context_tokens": 1_800, "difficulty": 0.2},
{"sensitive": False, "context_tokens": 90_000, "difficulty": 0.3},
{"sensitive": False, "context_tokens": 2_500, "difficulty": 0.8},
{"sensitive": False, "context_tokens": 1_500, "difficulty": 0.1, "pinned": "local"},
]
print([route(t) for t in tasks])
print(route(tasks[0], local_ok=False))
The order of checks encodes the priorities: rules, then capacity, then difficulty. It prints ['local', 'local', 'hosted', 'hosted', 'local']: the sensitive task stays local despite being hard, and the long-context and hard tasks escalate. When the local model is down, the sensitive task returns a failure rather than leaking to a hosted model.
Measuring whether routing works
Track four numbers per week: the escalation rate (share sent to hosted), the local failure rate (local answers that later failed review), cost per completed task, and 95th-percentile latency. Then sweep the difficulty threshold (0.7 in the code) up and down on a labelled sample and plot cost against quality. Pick the point where extra spend stops buying noticeable quality. Also have a person read a small random sample of local answers each week. The local failure rate is only as good as the checks that measure it, and human review catches kinds of failure that automated checks were never written to detect.
Common pitfalls
- Cost logic overriding rules. If a budget check or a fallback can send sensitive data to a hosted model, the rules are not hard rules. Filter allowed models first.
- Uncapped cascades. A task that fails locally, escalates, fails again and retries can cost more than going hosted directly. Cap attempts and log each one.
- Weak checks. A cascade is only as good as its check. "The output is valid JSON" is not "the answer is right"; use task-specific tests where possible.
- Ignoring prefill. Long prompts make local prefill slow, especially on machines with modest compute. Size for input tokens as well as output tokens.
- Stale prices and models. Provider prices and model line-ups change often; re-check before relying on cost projections.
- Sizing for the average. Work arrives in bursts. Size the local side for peak hour, or allow overflow to a queue.
- Not logging the routing decision. Without the model, reason and cost recorded per request, you cannot tune the router or explain its behaviour.
In practice
- Write down your rules first: what must never leave your machine.
- List your recurring AI tasks and mark each as routine or hard.
- Use the AI Index to check which models your machine runs well, and test one on your routine tasks.
- Send routine work local, escalate the rest, and review the log after a week: what escalated, why, and what it cost.
Key takeaways
- Local and hosted models have opposite strengths, so most serious setups use both.
- A router decides per request using rules first, then difficulty, context length, speed needs and budget.
- "Local first, escalate when needed" cuts hosted costs sharply while keeping access to frontier models for hard tasks.
- Memory, bandwidth and compute decide whether your local side can handle the routine volume; the AI Index shows this per machine.
Terms in this lesson
- Model routing
- Automatically choosing which model handles each request based on rules, difficulty, cost, speed and privacy.
- Escalation
- Passing a task from a smaller or local model to a more capable model when it is too hard or fails a check.
- Hybrid AI
- A setup that combines models running on your own hardware with hosted models from providers.
- Model cascade
- A routing pattern that tries a cheaper model first, checks the result, and escalates to a stronger model only when the check fails.
- Pre-routing
- Choosing a model for a request before running any model, based on rules and a prediction of the request's difficulty.
Check your understanding
- 1.
In a well-designed router, what should be checked first?
- 2.
An agent generates 600,000 tokens a day locally at 40 tokens per second. Roughly how long is that?
- 3.
Which machine resource mainly decides how fast a local model generates text?
- 4.
Using the AI Index decode formula, about how many tokens per second would a model needing 4 GB read per token generate on a machine with 200 GB/s of memory bandwidth?
- 5.
A cascade handles 1,000 requests; 15% fail the local check and escalate to a hosted model costing 0.02 dollars per request. What is the hosted spend?
Exercises
Work each problem before opening the solution.
- Exercise 1Core
A service handles 5,000 requests a day, each with 2,000 input and 500 output tokens. Hosted prices are 3 dollars per million input tokens and 15 dollars per million output tokens (illustrative). Compute the daily hosted cost (a) if everything goes hosted, and (b) if 80% is handled locally.
Show solution
Per request: dollars.
(a) dollars a day.
(b) hosted requests dollars a day, a saving of 54 dollars a day (about 1,620 dollars over 30 days), before counting local hardware and electricity.
- Exercise 2Core
Use the Index formulas for a 14-billion-parameter model at 4 bits with 40 layers, 8 KV heads, head dimension 128, a 16-bit KV cache and a 16,384-token context, on a GPU with 24 GB of usable memory and 1,000 GB/s of bandwidth. Does it fit, and what decode speed should you expect?
Show solution
File: GB, GB.
KV per token: bytes; GB.
Need: GB. Limit: GB. Fits with room to spare.
Decode: ms, ms ms per token, about 70 tokens per second.
- Exercise 3Core
Coding. Extend the router with a daily budget for hosted calls. If a hosted call would push spending over the budget, queue the task instead of overspending. Hard rules must still come first. Test with a 0.05-dollar budget and four hard tasks costing 0.02 dollars each.
Show solution
class BudgetRouter: def __init__(self, daily_budget_usd): self.budget, self.spent = daily_budget_usd, 0.0 def route(self, task, est_hosted_cost, local_ok=True): if task["sensitive"] or task.get("pinned") == "local": return "local" if local_ok else "fail: local required" wants_hosted = task["difficulty"] >= 0.7 or task["context_tokens"] > 32_000 if not wants_hosted: return "local" if local_ok else self._hosted(est_hosted_cost) return self._hosted(est_hosted_cost) def _hosted(self, cost): if self.spent + cost > self.budget: return "queue: daily budget reached" # never overspend silently self.spent += cost return "hosted" r = BudgetRouter(daily_budget_usd=0.05) hard = {"sensitive": False, "difficulty": 0.9, "context_tokens": 4_000} print([r.route(hard, est_hosted_cost=0.02) for _ in range(4)], round(r.spent, 2))It prints
['hosted', 'hosted', 'queue: daily budget reached', 'queue: daily budget reached'] 0.04. The third call would bring spending to 0.06, over the 0.05 budget, so it is queued. Sensitive or local-pinned tasks never reach the budget logic, so a budget can never push private data to a hosted model. - Exercise 4Challenge
20% of tasks are hard. Local takes 5 s, hosted 3 s and costs 0.01 dollars. Compare per 1,000 tasks: (a) a pre-router that catches 90% of hard tasks but also sends 10% of easy tasks to hosted; (b) a cascade with a perfect check. Give hosted cost, average latency and the number of hard tasks answered badly by the local model. Which would you choose for customer-facing replies, and why?
Show solution
(a) Hosted share: , so 260 hosted calls, costing 2.60 dollars. Latency: s. Hard tasks sent local by mistake: 20, answered badly with no signal.
(b) All 1,000 tried locally; 200 fail the check and escalate, costing 2.00 dollars. Latency: s. Hard tasks answered badly: 0 (assuming the check is perfect).
For customer-facing replies the cascade is usually better: it is cheaper here, and it never silently sends a weak answer; the price is about 1.1 s more average latency. If latency mattered most, a hybrid would pre-route obvious hard cases straight to hosted (saving the wasted local attempt) and cascade the rest. The conclusion depends heavily on how good the check really is; with an imperfect check, some bad local answers slip through either way.
Further reading
- paperLingjiao Chen, Matei Zaharia, James Zou, FrugalGPT: How to Use Large Language Models While Reducing Cost and Improving Performance (2023)
- paperDujian Ding et al., Hybrid LLM: Cost-Efficient and Quality-Aware Query Routing (ICLR 2024)
- paperIsaac Ong et al., RouteLLM: Learning to Route LLMs with Preference Data (2024)
Go further
Sources
Fact-checked and last reviewed 2026-11-05