MiniRouter is the SN74 miner workspace for the Gittensor LLM routing competition. This repo was adapted from the original TinyRouter project at https://github.com/harrrshall/tinyrouter/ and then expanded into the competition workspace used here. The goal is not to build one giant model, but to learn a better router: for each question, decide which model should answer and what role it should play. Miners can improve the trainer, the evaluation pipeline, the model pool, or the web app that publishes results.
The core router is deliberately tiny and cheap. A frozen 0.6B encoder reads the question into a single vector, and a ~10K-parameter head turns that vector into the routing decision. It is trained by separable CMA-ES, a derivative-free evolution strategy, against a simple right/wrong reward. The coordinator never solves the question itself; it only learns who to ask.
The method follows TRINITY (Xu et al., ICLR 2026, arXiv:2512.04695), rebuilt from scratch with an open model pool and the miner-facing competition tooling in this repo.
- Implemented the full coordinator: the 0.6B encoder feature, the ~10K routing head, the three roles, the multi-turn loop (up to 5 turns, terminated by a Verifier accept), and the sep-CMA-ES trainer.
- Wired a 3-model open-source pool plus an automatic grader (exact-match for math, letter-match for MMLU, pass@1 code execution for LiveCodeBench) that produces the binary reward.
- Trained per-task coordinators by evolution: breed thousands of candidate heads, keep the ones that route best, repeat.
- Evaluated rigorously on 120 held-out questions, with every single-model baseline averaged over 3 runs to remove run-to-run noise, against each model alone and against random routing.
- Built an oracle-ceiling diagnostic to ask whether the pool even leaves room for routing to help, and used it to decide where improvement effort was worth spending.
- Implemented and tested two upgrades from that diagnostic (supervised warm-start of the head, shaped training fitness) and measured them on the task with real headroom.
- Tracked every dollar of API spend and logged each result.
src/trinity/- training, evaluation, routing, and reward codeconfigs/- benchmark and model-pool configurationbenchmarks/- benchmark loaders and task definitionsscripts/- local and remote training/eval entry pointsexperiments/- run outputs and saved training artifactssubmissions/- submit-ready bundles for the evaluation backendvalidator/- competition backend that ingests submissions and runs evalweb/- public competition site and leaderboard frontenddocs/- research notes, results, and implementation notes
Miners should work in their own branch, keep changes scoped, and open PRs for review. The branch
prefix rule is documented in CONTRIBUTOIN.md, and the submit-ready model bundle should stay in
submissions/final_model/ when a run is ready to evaluate or publish.
The repository also includes a GitHub Actions PR automation workflow. It labels PRs by path,
tags miner submission PRs, marks valid submissions as awaiting_ci, and leaves them pending until
a maintainer starts the manual dispatch workflow. The backend worker then evaluates the bundle,
comments back the result, and updates the PR commit status. No separate GitHub bot is required for
that flow.
The validator backend stores submissions and evaluation runs in Postgres. Set
DATABASE_URL in the repo-root secrets.env before starting the API or worker.
Use PIPELINE_MODE=submission_eval for the current checkpoint-evaluation flow, or
PIPELINE_MODE=train_eval to switch the validator into the server-side
train-then-evaluate flow for PR code submissions.
| Slot | Model | Strong at |
|---|---|---|
| A | deepseek-v4-pro |
knowledge (MMLU) |
| B | glm-5p2 |
math |
| C | kimi-k2p6 |
general |
The 0.6B encoder and the evolution loop run on a single NVIDIA H200; the three LLMs are called over HTTP.
- The frozen 0.6B encoder turns the question into one 1024-dim vector.
- The ~10K head reads that vector and picks a model and a role.
- The chosen model answers in that role; its output is appended to the transcript.
- Steps 1 to 3 repeat for up to 5 turns; a Verifier turn can accept and stop early.
- The final answer is graded right/wrong, and that reward drives the evolutionary training.
Rigorous eval: 120 held-out questions per task; single-model baselines are the mean over 3 runs. Scores are fraction correct (0.792 = 79.2%).
Math
| system | score |
|---|---|
| glm-5p2 | 0.794 (best single) |
| TinyRouter | 0.792 |
| random routing | 0.792 |
| deepseek-v4-pro | 0.747 |
| kimi-k2p6 | 0.742 |
MMLU
| system | score |
|---|---|
| TinyRouter | 0.925 |
| deepseek-v4-pro | 0.922 (best single) |
| random routing | 0.875 |
| glm-5p2 | 0.783 |
| kimi-k2p6 | 0.539 |
Both tasks together
| system | math | MMLU | average |
|---|---|---|---|
| TinyRouter | 0.792 | 0.925 | 0.858 |
| deepseek-v4-pro | 0.747 | 0.922 | 0.835 |
| random routing | 0.792 | 0.875 | 0.833 |
| glm-5p2 | 0.794 | 0.783 | 0.789 |
| kimi-k2p6 | 0.742 | 0.539 | 0.640 |
The tiny router scores 0.858 on average, higher than any single model. No single model is good at both tasks: deepseek is the knowledge specialist, glm is the math specialist. The router wins the average by sending each task to the right specialist.
Reading it straight: the win is across tasks, not within a task. On MMLU, where the models differ a lot (0.54 to 0.92), routing clearly helps and the router beats random (0.925 vs 0.875). On math, where all three models sit around 0.79, there is nothing to route around, so the router ties both the best model and random routing. Routing pays off when the models genuinely differ.
A tie on math could mean two very different things: either the pool has no headroom (every model is equally good or bad on the same questions), or the headroom exists but our router fails to capture it. To tell them apart we built a diagnostic that estimates the best score a perfect query-conditional router could reach, debiased for the winner's-curse with split-half cross-fit, and read the verdict off bootstrap confidence intervals rather than point estimates.
| benchmark | best single | perfect router | real headroom (95% CI) | verdict |
|---|---|---|---|---|
| math500 | 0.808 | 0.856 | +0.049 [0.005, 0.085] | ROUTER_BOUND |
| MMLU | 0.939 | ≥0.939 | +0.025 [0.000, 0.058] | inconclusive (near-ceiling) |
This overturned the easy reading of math as "no benefit." There is about 4.9 points of real, achievable headroom on math; our trained router just captures none of it. So the math limit is the router, not the pool. MMLU sits near its ceiling, where deepseek already dominates and the router already matches it.
The diagnostic pointed effort at math, so we tried two upgrades: warm-starting the head with a supervised fit against per-(question, model) correctness labels (instead of starting the evolution from a blank head), and shaping the training reward (format bonus, turn penalty, variance reweighting) while keeping the eval pure right/wrong.
| system | math (held-out 120) |
|---|---|
| best single (glm-5p2) | 0.817 |
| TinyRouter (warm-start + shaped) | 0.808 |
| prior router, same test | 0.792 |
| random routing | 0.733 |
The retrained router scored 0.808 vs the prior 0.792, but we read this as inconclusive, not a win. The eval samples each model once per question, and that sampling noise is large: random routing alone swung from 0.792 to 0.733 between runs with nothing changed. A swing that size swamps a 1.6-point router delta. We did not run the clean control (blank-init, pure-binary, same settings), so there is no causal claim that warm-start or shaping moved the number. The result is still below the best single model (0.817) and below the 0.856 ceiling, so the headroom the diagnostic found remains on the table. The two upgrades are implemented and covered by 54 offline tests; whether they move the held-out score is unproven.
Use submissions/final_model/ as the submit-ready bundle for your final checkpoint and metadata.
Keep the trained checkpoint and the JSON files together in that folder, then open a PR from your
required sn74-<miner-name> branch.
Expected contents:
best_theta.npy- the trained routing headsummary.json- training summary for the runhistory.json- optional per-generation historyeval.json- optional local evaluation output
Typical workflow:
git checkout main
git pull upstream main
git checkout -b sn74-your-github-username
mkdir -p submissions/final_model
cp experiments/math500/<run-name>/best_theta.npy submissions/final_model/
cp experiments/math500/<run-name>/summary.json submissions/final_model/
cp experiments/math500/<run-name>/history.json submissions/final_model/ # optional
python utility/validate_submission.py --dir submissions/final_model
python -m trinity.eval \
--benchmark math500 \
--theta submissions/final_model/best_theta.npy \
--provider chutes \
--models configs/models.chutes.yaml \
--device cpu \
--dtype float32 \
--out submissions/final_model/eval.json
git add submissions/final_model
git commit -m "Add final model bundle"
git push origin sn74-your-github-usernameOpen a pull request from your branch. The validator and maintainer workflow will pick up the bundle from the PR, evaluate it, and record the result.
The CI workflow lives in .github/workflows/ci.yml and runs on every pull request and on push to
main. It does not require any secrets or a database.
- root package:
pip install -e ".[dev]"thenpytest tests -q validator/:pip install -e ".[dev]"thenpytest -qweb/:npm ci,npm run build(type-checks viatsc -b),npm run lint
This catches a broken test suite or web build on the PR itself, instead of only after merge when
deploy-web.yml runs against main.
The PR automation workflow lives in .github/workflows/pr-automation.yml.
It:
- adds labels such as
web,validator,train,eval,benchmark,docs,miner, andsubmission - registers miner submission PRs in the validator backend as
awaiting_ci - leaves the PR pending until a maintainer starts the manual dispatch workflow
- queues the uploaded bundle in the validator backend, which then runs the worker and publishes the result
Repository setup:
- set
GITHUB_WEBHOOK_SECRETin the validatorsecrets.env - set
MINIROUTER_WEBHOOK_SECRETas a GitHub Actions secret with the same value - optionally set
BACKEND_BASE_URLas a repository variable if the backend URL changes - the PR-open path registers submission metadata with
POST /webhooks/github - the manual dispatch path uploads
submissions/final_model/toPOST /webhooks/github/submission - start a submission from the GitHub Actions UI by running
PR automationwith a PR number - set
EVAL_PROVIDER=chutesandEVAL_MODELS_CONFIG=configs/models.chutes.yamlfor default validator evals using the Chutes pool