Glint-Router-1M

a prompt router in 985,826 parameters. you hand it a user request, it hands back a domain, a complexity score, code/math/reasoning flags, and which model tier should take the job. one forward pass, around 0.06 ms on a gpu, 0.2 ms on cpu.

the target was SupraLabs/Supra-Router-51M, which does the same job with 51,786,240 parameters. we came in at 1/52nd the size and beat it on data neither model had seen.

what it actually is

  • a bidirectional encoder with typed heads. 3 layers, dim 128, 8 heads, ffn hidden 208, 4096-token bpe vocab, 256-token context.
  • the heads: domain over a closed 25-class taxonomy, complexity 1 to 5 as an ordinal head, four binary flags (code, math, reasoning, long_output), a routing head, and a 64-d projection used to add your own categories later.
  • 985,826 parameters. the embedding table is 524,288 of them, the three blocks are 418,560, the heads are the remaining 25k.
  • everything comes out of one forward pass. there is no decode loop and nothing to parse.

why an encoder

supra generates its answer as text: Domain: Programming | Complexity: 3 | Math: F | Code: T | Route: big model | Justification: .... about 40 greedy decode steps, one argmax at a time, under a causal mask.

the causal mask is the expensive part. read "write a short poem about a Python" and the decision is already forming; the word "snake" arrives afterward and cannot reach back. an encoder reads the whole prompt before it commits. that is the entire architectural argument, and the trap slice below is where you can see it pay.

the rest follows from dropping generation. no output format to malform, so no parse failures. probabilities instead of tokens, so the routing decision can be arithmetic. 40 sequential steps collapse to 1.

the numbers

house rule, same as always: real scores, no dressing, including the ones that make us look bad.

both obvious comparisons are rigged and we are going to say so before showing them. our own validation set uses our label pipeline, which this model trained on and supra never saw. supra's 992-row dataset is supra's training data, so scoring it there is asking a model to recite. neither answers the question.

so the verdict comes from a set neither model has ever seen: dolly-15k, MBPP, HumanEval and AQuA-RAT, 4,272 prompts, labels taken from dataset provenance alone. every MBPP row is a code task because it is in MBPP. every AQuA row is math because it is in AQuA. no hand labelling, no model in the loop.

neutral holdout, 4,272 unseen prompts

Glint-Router-1M Supra-Router-51M
parameters 985,826 51,786,240
route accuracy 94.41 90.37
math detection 95.26 92.07
code detection 97.51 96.43
complexity within 1 95.90 91.63
complexity exact 46.28 39.06
ms per prompt 0.06 14.33
parse failures 0 4

239x faster, 52x smaller, ahead on every field.

one caveat we owe you: the complexity label rule on that holdout is ours, so the two complexity rows lean our way. route, code and math do not. the route rule is supra's own published one, copied verbatim, and code/math come from which dataset a row lives in.

the other two views, for completeness

on our own validation split (7,617 rows, our labels, we are in-distribution):

Glint Supra
route 93.96 85.76
code 99.15 97.61
math 97.81 94.00
complexity exact 79.23 46.29

on supra's own 992 rows, held out of our training, which are supra's training data:

Glint Supra
route 89.01 96.70
code 90.11 100.00
math 81.32 97.80
complexity exact 50.55 85.71

supra wins that table because it is reciting the set it was fine-tuned on. we publish it because leaving it out would be dressing.

the keyword trap

supra's model card claims "keyword-trap evasion" and never measures it. a keyword trap is a prompt that mentions a technical token while being trivial: "write a short poem about a Python snake in the garden" is a poem, not a coding job.

we generated 3,000 counterfactual pairs, trained on them, and scored the held-out slice separately because at 4% of the corpus it vanishes into any aggregate.

trap slice, 230 held-out prompts Glint Supra
route 100.00 90.87
code 100.00 98.70
complexity within 1 100.00 83.91

100 is a suspicious number and it should be read as one. we generated those traps from templates, so the family was in training even though the specific rows were not. supra faces them completely cold. take it as evidence that traps are learnable, not that the problem is solved.

calibration

the routing decision is expected-cost arithmetic over the model's probabilities, so those probabilities have to mean what they say. a head that is 90% confident should be right 90% of the time.

after 42 epochs it was not. temperature scaling, fitted on held-out data after training, one scalar per head group:

expected calibration error, routing head
  raw          0.0556
  calibrated   0.0203

the fitted temperature on the routing head is 4.71, which is a blunt statement about how overconfident a small model gets when you let it see the same 95k rows forty times. the scalars ship inside the weights and calibrated() applies them for you.

routing stuff

supra decides with a boolean baked into its weights: complexity >= 3, or code, or math, means big model. two tiers. want three, or a different threshold, or "legal always goes to the frontier model"? you are collecting data and retraining 51M parameters.

here the model stops at calibrated probabilities about the prompt. what to do with them is policy.json, which you own:

{
  "tiers": [
    {"name": "local",    "cost": 0.0, "capability": 0.35, "tools": false},
    {"name": "mid",      "cost": 1.0, "capability": 0.65, "tools": false},
    {"name": "frontier", "cost": 8.0, "capability": 0.95, "tools": true}
  ],
  "failure_penalty": 50.0,
  "sharpness": 20.0,
  "abstain_margin": 0.05,
  "overrides": []
}

the decision is argmin over tiers of cost + failure_penalty * P(tier fails), where P(fail) is a logistic in the gap between prompt difficulty and tier capability. n tiers, not 2. an abstain band escalates ties instead of coin-flipping them. overrides short-circuit the arithmetic on any predicted field:

{"overrides": [{"when": {"domain": "legal"}, "tier": "frontier"},
               {"when": {"code": true, "complexity_min": 4}, "tier": "frontier"}]}

two constants in there were tuned the hard way and we left the reasoning in the file. failure_penalty has to be several times the priciest call, or "pay least" beats "try hardest" when every tier is likely to fail, and the router quietly sends its hardest prompts to its weakest model. sharpness has to be steep: at 12, a free local tier still carries a 2.6% residual failure rate, which priced at the penalty is 1.33 and loses to the mid tier's flat cost of 1.0. the free tier won nothing until that was fixed.

adding a category it never trained on

This is the part that I played with for HOURS. Very happy & exited about it.

the 64-d projection head is trained alongside the classifier, so prompts that route alike land near each other. a new category is the mean of a handful of examples:

python infer.py --add-category refunds refund_examples.txt

six example prompts, one forward pass, no optimizer, no gradients. after that decision.category comes back as refunds on matching prompts and you can route on it with an override. this is the thing a 51M generator cannot do at all without a fine-tune.

where it fails

a 1M parameter model has edges and we would rather you learn them here than in production.

math phrased without numbers or symbols. the math training data is GSM8K word problems and LaTeX. symbol-free proofs read as easy:

"Prove that every finite integral domain is a field."   ->  math 0.01, complexity 2, local
"Natalia sold clips to 48 friends in April, and half
 as many in May. How many clips altogether?"            ->  math 0.99, complexity 3, frontier
"Compute the integral of x^2 sin(x) dx from 0 to pi."   ->  math 0.95, frontier

the first one is wrong and it is wrong for a boring reason: nothing in 95k rows looks like it.

word problems with an unusual question verb. "a train leaves at 3pm going 60mph, another at 4pm going 80mph, when do they meet?" scores math 0.03 and routes local. move it to "how far apart are they at 6pm" and it behaves. the corpus taught it "how many" and "calculate" and did not teach it "when do they meet".

raw code with no instruction. HumanEval is our worst slice at 56.7% code detection, and supra's second worst at 67.7%. a bare function stub with a docstring has no instruction verb anywhere in it. neither of us handles it and we are not going to pretend the gap is small.

very short factual prompts. "what is the capital of France" comes back domain programming, complexity 1, routed local. the routing is right and the domain label is noise. domain accuracy on prompts under six tokens is the weakest part of the model.

both data gaps are one more training round away. the model shipped here got one 15-minute round and that was the whole budget.

how it was trained

one round, 15 minutes, one RTX 3060 Ti, 14,479 steps, learning rate fully annealed inside the budget. the schedule measures its own step rate before it starts and sizes the cosine decay to fit, so the run finishes annealed instead of stopping mid-schedule. best-on-validation weights, not last-step weights.

the biggest gap versus supra is on the data side. supra fine-tuned on 992 rows. we built 95,221 and never hand-labelled one of them:

source rows what it labels for free
alpaca 35,000 mixed domains, complexity from answer length
code_instructions 18,000 code = true
squad 15,000 factual, complexity 1
no_robots 9,500 human-written task category
gsm8k 7,473 math = true
hendrycks_math 6,256 math = true, complexity from level
generated traps 3,000 keyword counterfactuals
supra's own set 992 its labels, verbatim

labels come from provenance and from the reference answer, never from a human and never from a model. a row in MBPP is a code task. a 1,200-character reference answer means long_output. the model predicts those labels from the prompt alone and never sees the answer.

the routing label is supra's published override rule copied exactly, so a head-to-head measures prediction quality rather than two people disagreeing about what "big model" means. our route split came out 51/49; supra's own set is 75/25.

the tokenizer is a 4096-vocab bpe trained on routing prompts rather than on web prose, because {, ::, \frac and SELECT are the evidence and a FineWeb vocabulary shreds all four into bytes.

files

model.safetensors     the weights (3.9 MB)
config.json           architecture and label space
tokenizer.json        4096-vocab bpe, trained on routing prompts
modeling.py           the model, standalone, torch only
policy.py             tiers, expected-cost decision, overrides, prototypes
policy.json           the default 3-tier policy, edit this one
infer.py              cli: route, batch, add a category
scores.json           every number on this page, machine readable
requirements.txt      torch, tokenizers, safetensors
README.md             you are here. I hope. 

quickstart

pip install torch tokenizers safetensors

python infer.py --demo
python infer.py "write a python function that reverses a linked list"
python infer.py --file prompts.txt
python infer.py --policy my_policy.json "summarize this thread"
python infer.py --add-category refunds refund_examples.txt

in code:

from modeling import encode_batch, load_router
from policy import Policy, decide

model, tokenizer = load_router()
tokens = encode_batch(tokenizer, ["fix this SQL query"], model.config.max_len)
decision = decide(model, tokens, "fix this SQL query", Policy.load("policy.json"))
print(decision.tier, decision.domain, decision.complexity)

decision.as_line() prints supra's exact output format if you are swapping one for the other.

runs on cpu. the model is 3.9 MB. it is smaller than the favicon budget of most websites.

/lane glint research, 2026, 985,826 parameters, one 15-minute round on a 3060 Ti, 239x faster than the 51M model it beats, 0 parse failures, 3 known failure modes documented above

Downloads last month
11
Safetensors
Model size
986k params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Datasets used to train Glint-Research/Router

Space using Glint-Research/Router 1