Route every request to the smallest model that can handle it. A local three-model serving stack with a difficulty judge at the front door — built on a 70-prompt eval that falsified the project's original thesis and redirected the design halfway through.
Headline result: kept 98% of the largest model's quality at 42% less compute time, measured against a labeled eval set, not estimated.
Original thesis: serve one model at multiple quantization levels (4-bit / 8-bit / fp16) and route each request to the cheapest precision that can handle it.
What the data said: we built the eval first — 70 prompts across five categories with automatic grading. Result: at 1.5B parameters, precision made no measurable difference in quality on any category, while 4-bit was ~2x faster than fp16.
| category | 4-bit | 8-bit | fp16 |
|---|---|---|---|
| facts | 100% | 100% | 100% |
| arithmetic | 90% | 90% | 90% |
| reasoning traps | 50% | 47% | 47% |
| summarization | 100% | 100% | 100% |
| code | 100% | 100% | 100% |
| avg sec/req | 0.7s | 0.9s | 1.5s |
Conclusion: quantization is free at this scale. So we quantize everything to 4-bit and route across the axis where quality actually lives — model size:
| category | 0.5B | 1.5B | 3B |
|---|---|---|---|
| facts | 100% | 100% | 100% |
| arithmetic | 90% | 90% | 100% |
| reasoning traps | 3% | 50% | 63% |
| summarization | 100% | 100% | 90% |
| code | 90% | 100% | 100% |
| avg sec/req | 0.3s | 0.7s | 1.5s |
A real quality ladder (the 0.5B model falls for 29 of 30 trick questions) and a 5x cost spread between rungs. Now routing has a job.
Two judges compete to predict, per request, which rung is enough:
Policies are scored offline against logged eval outcomes — we already know which model passed which prompt and how long it took, so any routing policy's exact quality and cost can be computed without re-running models.
| policy | pass rate | avg sec/req | mix 0.5B/1.5B/3B |
|---|---|---|---|
| always-0.5B | 55.7% | 0.35s | 70/0/0 |
| always-3B | 82.9% | 1.46s | 0/0/70 |
| judge-v0 | 81.4% | 0.84s | 21/35/14 |
| judge-v1 | 72.9% | 0.77s | 38/30/2 |
The regex heuristics beat the LLM judge. judge-v1 routed only 2 of 70 prompts to the large model and rated trick questions "easy" — the same trick questions it fails itself. A model too small to solve hard problems is also too small to recognize them. judge-v0's entire quality loss vs always-3B traces to a single under-routed hard-arithmetic prompt.
FastAPI app that loads all three models at startup (all Q4_K_M, ~3.5 GB RAM total) and routes live requests through judge-v0:
POST /ask — routes, answers, and returns metadata: chosen model,
routing reason, latency, estimated time saved vs always-3B.
Supports force_tier override for demos.GET /stats — running session mix and cumulative compute saved.pip3 install -r requirements.txt
python3 -m uvicorn server.app:app --port 8000
# then open http://localhost:8000/docs
├── phase1_compare.py # first experiment: 3 precisions, 5 prompts
├── evals/
│ ├── eval_set.json # 70 labeled prompts, 5 categories
│ ├── run_evals.py # harness: runs ladder, grades, scorecard
│ └── show_failures.py # failure-analysis helper
├── router/
│ ├── judge.py # judge-v0 (heuristics) + judge-v1 (LLM)
│ └── evaluate_router.py # offline policy scoring vs logged outcomes
├── server/
│ └── app.py # FastAPI adaptive serving layer
└── results/ # raw eval + routing results (committed)
Qwen2.5 Instruct 0.5B/1.5B/3B (GGUF, Q4_K_M) · llama.cpp via llama-cpp-python · FastAPI · runs fully local on a MacBook Air, no GPU.
1 commits
Python
71.2%
HTML
28.8%
Route every request to the smallest model that can handle it. A local three-model serving stack with a difficulty judge at the front door — built on a 70-prompt eval that falsified the project's original thesis and redirected the design halfway through.
Headline result: kept 98% of the largest model's quality at 42% less compute time, measured against a labeled eval set, not estimated.
Original thesis: serve one model at multiple quantization levels (4-bit / 8-bit / fp16) and route each request to the cheapest precision that can handle it.
What the data said: we built the eval first — 70 prompts across five categories with automatic grading. Result: at 1.5B parameters, precision made no measurable difference in quality on any category, while 4-bit was ~2x faster than fp16.
| category | 4-bit | 8-bit | fp16 |
|---|---|---|---|
| facts | 100% | 100% | 100% |
| arithmetic | 90% | 90% | 90% |
| reasoning traps | 50% | 47% | 47% |
| summarization | 100% | 100% | 100% |
| code | 100% | 100% | 100% |
| avg sec/req | 0.7s | 0.9s | 1.5s |
Conclusion: quantization is free at this scale. So we quantize everything to 4-bit and route across the axis where quality actually lives — model size:
| category | 0.5B | 1.5B | 3B |
|---|---|---|---|
| facts | 100% | 100% | 100% |
| arithmetic | 90% | 90% | 100% |
| reasoning traps | 3% | 50% | 63% |
| summarization | 100% | 100% | 90% |
| code | 90% | 100% | 100% |
| avg sec/req | 0.3s | 0.7s | 1.5s |
A real quality ladder (the 0.5B model falls for 29 of 30 trick questions) and a 5x cost spread between rungs. Now routing has a job.
Two judges compete to predict, per request, which rung is enough:
Policies are scored offline against logged eval outcomes — we already know which model passed which prompt and how long it took, so any routing policy's exact quality and cost can be computed without re-running models.
| policy | pass rate | avg sec/req | mix 0.5B/1.5B/3B |
|---|---|---|---|
| always-0.5B | 55.7% | 0.35s | 70/0/0 |
| always-3B | 82.9% | 1.46s | 0/0/70 |
| judge-v0 | 81.4% | 0.84s | 21/35/14 |
| judge-v1 | 72.9% | 0.77s | 38/30/2 |
The regex heuristics beat the LLM judge. judge-v1 routed only 2 of 70 prompts to the large model and rated trick questions "easy" — the same trick questions it fails itself. A model too small to solve hard problems is also too small to recognize them. judge-v0's entire quality loss vs always-3B traces to a single under-routed hard-arithmetic prompt.
FastAPI app that loads all three models at startup (all Q4_K_M, ~3.5 GB RAM total) and routes live requests through judge-v0:
POST /ask — routes, answers, and returns metadata: chosen model,
routing reason, latency, estimated time saved vs always-3B.
Supports force_tier override for demos.GET /stats — running session mix and cumulative compute saved.pip3 install -r requirements.txt
python3 -m uvicorn server.app:app --port 8000
# then open http://localhost:8000/docs
├── phase1_compare.py # first experiment: 3 precisions, 5 prompts
├── evals/
│ ├── eval_set.json # 70 labeled prompts, 5 categories
│ ├── run_evals.py # harness: runs ladder, grades, scorecard
│ └── show_failures.py # failure-analysis helper
├── router/
│ ├── judge.py # judge-v0 (heuristics) + judge-v1 (LLM)
│ └── evaluate_router.py # offline policy scoring vs logged outcomes
├── server/
│ └── app.py # FastAPI adaptive serving layer
└── results/ # raw eval + routing results (committed)
Qwen2.5 Instruct 0.5B/1.5B/3B (GGUF, Q4_K_M) · llama.cpp via llama-cpp-python · FastAPI · runs fully local on a MacBook Air, no GPU.
1 commits
Python
71.2%
HTML
28.8%