AI / LLMOps · Python Flagship

LLM Orchestrator

A working replication of NVIDIA's “Small Language Models are the Future of Agentic AI” — their six-step LLM→SLM conversion pipeline, run twice on my own agent telemetry, with a pre-registered promotion gate deciding every result.

0.82converted 3.2 GB model, vs 0.62 for a 9.5 GB one
+3.3points bought by 21× more training data
25documented failure modes, mostly about instruments
Research basis

Replicating NVIDIA's SLM-agents paper on my own telemetry

NVIDIA Research's position paper Small Language Models are the Future of Agentic AI (Belcak et al., arXiv:2506.02153) argues that most agent calls don't need a frontier model, and gives a concrete six-step procedure for converting an LLM-backed agent to smaller specialist models: log real usage, curate and strip it, cluster the tasks, pick candidate SLMs, fine-tune, then retrain on a schedule.

Rather than take the claim on faith, I ran the procedure against my own agent traffic. Two of the paper's arguments shaped the build directly:

  • Agentic interactions are the dataset. The paper treats ordinary usage as the data-collection pathway, so the router logs every decision to a JSONL ledger — that ledger is step one, not an afterthought.
  • Don't judge SLMs on generalist benchmarks. The paper is explicit that MMLU-style scores mispredict agentic utility, so the evals here score routing decisions on real logged tasks instead.

The first check was the paper's own estimate that 40–70% of LLM calls in published agent systems are SLM-replaceable. Replaying my telemetry in hindsight — asking, for each decision, what the cheapest model that still succeeded would have been — put the ceiling at 66% of local-tier spend. Probing a real 35B against those same tasks brought the defensible figure to 57%, landing inside the band the paper predicted, on a corpus it never saw.

That measures whether conversion is worth doing. It doesn't do it. So I ran the actual pipeline — twice.

Full method, every caveat, and the twenty-five findings: the reproduction write-up in evince55/aria-llmops.

The reproduction

Two rounds, and what each one cost me to believe

Round one converted the router's own task-difficulty classifier. A QLoRA-tuned 3.2 GB Gemma-4-E2B matched a 5.8 GB keyword+9B hybrid on a promotion gate fixed before the first run — promote iff accuracy ≥ incumbent and no tier recall regresses by more than 0.05 — and ships as the default. It is a tie, not a win; the case for shipping it is operational, at 55% of the memory with no network dependency.

Round one's own conclusion was that I had picked the wrong thing to convert. A perfect classifier changes only 16.5% of routing decisions, so the pipeline was sound and the target was low-leverage. Round two acted on that: it chose its target by measurement first — probing two candidates and discarding the one whose base model already scored 1.00 — and converted tool-call emission, an agentic subtask rather than a classification. Ground truth is authored with each example, so verification is exact structural comparison: no LLM judge, no teacher, and the round costs nothing to run.

0.885Converted Gemma-4-E2B3.2 GB · from a 0.541 base
0.885Converted Qwen3.5-9B5.6 GB · from a 0.754 base
0.623Ornith-1.0-9B, tool-tuned9.5 GB · at its best config

Strict accuracy on the same 61-task held-out set, greedy decoding. Two different model families, two different sizes, converging on exactly the same score from bases 21 points apart — which is what says the result belongs to the pipeline and not to one lucky checkpoint. Conversion beat selection at every interface and operating point tested. Both converted arms were retrained here under a smaller-batch schedule so a 9B would fit in 16 GB of unified memory, which is why they read a little above the 0.82 headline — that figure comes from the original schedule, and the two are not mixed within a comparison.

The one thing the paper doesn’t tell you

The paper trains on 10,000–100,000 examples. I could only reach 460, so I lifted the generator’s ceiling and ran the whole range to find out whether it mattered. The rule for reading the answer — plateau if the best larger arm gains under 0.05 — was fixed before any data existed.

460-example baseline 0.85 0.80 0.885 0.836 460 1k 2.5k 5k 10k training examples · fixed compute
21× the data buys 3.3 points, then goes backwards. 5k and 10k both land below the 460-example baseline.
  1. The verdict was computed, not eyeballed. Best arm above baseline gains 0.033, under the pre-registered 0.05 threshold — the same tolerance round one’s promotion gate used, chosen before this curve existed.
  2. It isn’t compute starvation. The obvious objection is that larger sets get fewer passes at a fixed budget, so I ran one epoch-matched arm: 2,500 examples at 5.4× the compute scored 0.820, six and a half points worse.
  3. Validation loss was useless throughout. Every arm converged to ~0.001, and the epoch-matched arm hit 0.000 while being the worst model tested. Anyone selecting on val loss picks exactly that one.
  4. The caveat ships with the claim. This scales rows of the same sentence shapes, not linguistic diversity — so it bounds what template-generated data buys, not what data buys.
The problem

A capable assistant is easy to buy and hard to afford

Running every request against Claude is fast and smart, but the bill scales with volume and every keystroke leaves the building. Running everything locally on one consumer GPU is cheap and private — but a single quantized model isn't reliable on the hard ten percent, and naive "just use the big model" routing throws the cost advantage away.

I wanted one harness-agnostic interface that defaults to local inference, escalates to Claude only when a task genuinely needs it, and can prove — per task — what each routing decision cost. The constraints were real:

  • One AMD RX 7600 XT (16 GB) — ROCm/Vulkan, no CUDA — so model size, quantization, and KV-cache tuning are hard limits, not nice-to-haves.
  • Must work across harnesses (Claude Code + opencode) without bespoke logging bolted into each one.
  • Every routing decision has to be measurable after the fact, not guessed.
Architecture

Tiered routing, local-first inference

A task is classified, then the router picks the cheapest tier that can handle it. Local models carry the routine majority; Claude is reserved for the hard tail. Every call is logged, scored, and fed back into the routing decision.

Task / prompt Claude Code · opencode Multi-state classifier trivial · standard · hard · tool-use + silence detection ModelRouter cost-aware · budget-bounded LOCAL · RX 7600 XT CLOUD · ANTHROPIC Qwen3.6-9B Q4 · fast path Ornith-1.0-35B Q5 · deep path Claude Sonnet mid tier Claude Opus hard tail Cost monitor $ / resolved task Telemetry ledger events.jsonl → evals · dashboard tune routing
Task in → classified → routed → measured → fed back.
  1. 1Classify
    Each task is typed (trivial → tool-use) and screened for silence / no-ops before a model ever runs.
  2. 2Route
    ModelRouter picks the cheapest tier that fits the task class and the remaining cost budget.
  3. 3Local tier
    Quantized Qwen on the RX 7600 XT via llama.cpp handles the routine majority — fast and private.
  4. 4Cloud tier
    Claude Sonnet / Opus is reserved for the hard tail, so cloud spend tracks difficulty, not volume.
  5. 5Measure
    Every call lands in events.jsonl; evals score routing accuracy and cost / task feeds back to tune the router.
Key decisions

What I chose, and why

Decision

Local-first routing

over cloud-only

The marginal task is cheap and private on local hardware; Claude is worth paying for only on the hard tail, not by default.

Decision

4–5-bit quantized local models

over full-precision models

Q4/Q5 fits a 14B model into 16 GB of VRAM with usable throughput; evals showed the quality cost was small for routine work.

Decision

llama.cpp on Vulkan/ROCm

over PyTorch + CUDA

The GPU is AMD — CUDA isn't on the table. llama.cpp gives portable, low-overhead local inference on the hardware I actually have.

Decision

Explicit multi-state classification

over a single confidence threshold

Typed task states make every routing decision auditable and tunable, instead of a black-box heuristic I can't reason about.

Decision

Cost per resolved task

over cost per token

A cheap model that fails and gets retried isn't cheap. Success-weighted cost is the number that actually reflects value delivered.

Measured outcomes

What the routing telemetry shows

The numbers above are the conversion pipeline. These are the router it feeds — measured by replaying the ledger, not projected.

66%Hindsight-optimal ceilingreplaying every routing decision
57%Expected saving, 35Bgraded replay of real tasks
24%Expected saving, 9Bsame probe, smaller model
0.810Routing quality, hybridvs 0.762 keyword-only
0.286Random-assignment floorwhat the eval scores against
21.5sMean 35B responsefaster than the 9B it replaced

Replayed from the telemetry ledger (events.jsonl) via the eval + cost-replay harness. The savings figures are expected values from graded replays, not money already banked — one large session dominates the pool and grading was single-grader, so the 9B estimate in particular moved from 45% to 24% once I sampled it more than once. The gap between the 9B and 35B columns is the whole argument for measuring rather than assuming.