Upload 11 files
Browse files- README.md +226 -3
- config.json +28 -0
- infer.py +115 -0
- model.safetensors +3 -0
- modeling.py +198 -0
- policy.json +34 -0
- policy.py +239 -0
- refund_examples.txt +6 -0
- requirements.txt +3 -0
- scores.json +179 -0
- tokenizer.json +0 -0
README.md
CHANGED
|
@@ -1,3 +1,226 @@
|
|
| 1 |
-
--
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Glint-Router-1M
|
| 2 |
+
|
| 3 |
+
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 release: weights, tokenizer, two inference files, the routing policy as an editable json document, every score we measured. no training code, no pipeline, no framework. `pip install torch tokenizers safetensors` and you are routing.
|
| 4 |
+
|
| 5 |
+
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.
|
| 6 |
+
|
| 7 |
+
## what it actually is
|
| 8 |
+
|
| 9 |
+
- a bidirectional encoder with typed heads. 3 layers, dim 128, 8 heads, ffn hidden 208, 4096-token bpe vocab, 256-token context.
|
| 10 |
+
- 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.
|
| 11 |
+
- 985,826 parameters. the embedding table is 524,288 of them, the three blocks are 418,560, the heads are the remaining 25k.
|
| 12 |
+
- everything comes out of one forward pass. there is no decode loop and nothing to parse.
|
| 13 |
+
|
| 14 |
+
## why an encoder
|
| 15 |
+
|
| 16 |
+
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.
|
| 17 |
+
|
| 18 |
+
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.
|
| 19 |
+
|
| 20 |
+
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.
|
| 21 |
+
|
| 22 |
+
## the numbers
|
| 23 |
+
|
| 24 |
+
house rule, same as always: real scores, no dressing, including the ones that make us look bad.
|
| 25 |
+
|
| 26 |
+
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.
|
| 27 |
+
|
| 28 |
+
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.
|
| 29 |
+
|
| 30 |
+
### neutral holdout, 4,272 unseen prompts
|
| 31 |
+
|
| 32 |
+
| | Glint-Router-1M | Supra-Router-51M |
|
| 33 |
+
|---|---|---|
|
| 34 |
+
| parameters | **985,826** | 51,786,240 |
|
| 35 |
+
| route accuracy | **94.41** | 90.37 |
|
| 36 |
+
| math detection | **95.26** | 92.07 |
|
| 37 |
+
| code detection | **97.51** | 96.43 |
|
| 38 |
+
| complexity within 1 | **95.90** | 91.63 |
|
| 39 |
+
| complexity exact | **46.28** | 39.06 |
|
| 40 |
+
| ms per prompt | **0.06** | 14.33 |
|
| 41 |
+
| parse failures | **0** | 4 |
|
| 42 |
+
|
| 43 |
+
239x faster, 52x smaller, ahead on every field.
|
| 44 |
+
|
| 45 |
+
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.
|
| 46 |
+
|
| 47 |
+
### the other two views, for completeness
|
| 48 |
+
|
| 49 |
+
on our own validation split (7,617 rows, our labels, we are in-distribution):
|
| 50 |
+
|
| 51 |
+
| | Glint | Supra |
|
| 52 |
+
|---|---|---|
|
| 53 |
+
| route | **93.96** | 85.76 |
|
| 54 |
+
| code | **99.15** | 97.61 |
|
| 55 |
+
| math | **97.81** | 94.00 |
|
| 56 |
+
| complexity exact | **79.23** | 46.29 |
|
| 57 |
+
|
| 58 |
+
on supra's own 992 rows, held out of our training, which are supra's training data:
|
| 59 |
+
|
| 60 |
+
| | Glint | Supra |
|
| 61 |
+
|---|---|---|
|
| 62 |
+
| route | 89.01 | **96.70** |
|
| 63 |
+
| code | 90.11 | **100.00** |
|
| 64 |
+
| math | 81.32 | **97.80** |
|
| 65 |
+
| complexity exact | 50.55 | **85.71** |
|
| 66 |
+
|
| 67 |
+
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.
|
| 68 |
+
|
| 69 |
+
## the keyword trap
|
| 70 |
+
|
| 71 |
+
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.
|
| 72 |
+
|
| 73 |
+
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.
|
| 74 |
+
|
| 75 |
+
| trap slice, 230 held-out prompts | Glint | Supra |
|
| 76 |
+
|---|---|---|
|
| 77 |
+
| route | **100.00** | 90.87 |
|
| 78 |
+
| code | **100.00** | 98.70 |
|
| 79 |
+
| complexity within 1 | **100.00** | 83.91 |
|
| 80 |
+
|
| 81 |
+
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.
|
| 82 |
+
|
| 83 |
+
## calibration
|
| 84 |
+
|
| 85 |
+
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.
|
| 86 |
+
|
| 87 |
+
after 42 epochs it was not. temperature scaling, fitted on held-out data after training, one scalar per head group:
|
| 88 |
+
|
| 89 |
+
```
|
| 90 |
+
expected calibration error, routing head
|
| 91 |
+
raw 0.0556
|
| 92 |
+
calibrated 0.0203
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
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.
|
| 96 |
+
|
| 97 |
+
## routing is a policy, not a threshold
|
| 98 |
+
|
| 99 |
+
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.
|
| 100 |
+
|
| 101 |
+
here the model stops at calibrated probabilities about the prompt. what to do with them is `policy.json`, which you own:
|
| 102 |
+
|
| 103 |
+
```json
|
| 104 |
+
{
|
| 105 |
+
"tiers": [
|
| 106 |
+
{"name": "local", "cost": 0.0, "capability": 0.35, "tools": false},
|
| 107 |
+
{"name": "mid", "cost": 1.0, "capability": 0.65, "tools": false},
|
| 108 |
+
{"name": "frontier", "cost": 8.0, "capability": 0.95, "tools": true}
|
| 109 |
+
],
|
| 110 |
+
"failure_penalty": 50.0,
|
| 111 |
+
"sharpness": 20.0,
|
| 112 |
+
"abstain_margin": 0.05,
|
| 113 |
+
"overrides": []
|
| 114 |
+
}
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
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:
|
| 118 |
+
|
| 119 |
+
```json
|
| 120 |
+
{"overrides": [{"when": {"domain": "legal"}, "tier": "frontier"},
|
| 121 |
+
{"when": {"code": true, "complexity_min": 4}, "tier": "frontier"}]}
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
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.
|
| 125 |
+
|
| 126 |
+
## adding a category it never trained on
|
| 127 |
+
|
| 128 |
+
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:
|
| 129 |
+
|
| 130 |
+
```
|
| 131 |
+
python infer.py --add-category refunds refund_examples.txt
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
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.
|
| 135 |
+
|
| 136 |
+
## where it fails
|
| 137 |
+
|
| 138 |
+
a 1M parameter model has edges and we would rather you learn them here than in production.
|
| 139 |
+
|
| 140 |
+
**math phrased without numbers or symbols.** the math training data is GSM8K word problems and LaTeX. symbol-free proofs read as easy:
|
| 141 |
+
|
| 142 |
+
```
|
| 143 |
+
"Prove that every finite integral domain is a field." -> math 0.01, complexity 2, local
|
| 144 |
+
"Natalia sold clips to 48 friends in April, and half
|
| 145 |
+
as many in May. How many clips altogether?" -> math 0.99, complexity 3, frontier
|
| 146 |
+
"Compute the integral of x^2 sin(x) dx from 0 to pi." -> math 0.95, frontier
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
the first one is wrong and it is wrong for a boring reason: nothing in 95k rows looks like it.
|
| 150 |
+
|
| 151 |
+
**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".
|
| 152 |
+
|
| 153 |
+
**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.
|
| 154 |
+
|
| 155 |
+
**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.
|
| 156 |
+
|
| 157 |
+
both data gaps are one more training round away. the model shipped here got one 15-minute round and that was the whole budget.
|
| 158 |
+
|
| 159 |
+
## how it was trained
|
| 160 |
+
|
| 161 |
+
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.
|
| 162 |
+
|
| 163 |
+
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:
|
| 164 |
+
|
| 165 |
+
| source | rows | what it labels for free |
|
| 166 |
+
|---|---|---|
|
| 167 |
+
| alpaca | 35,000 | mixed domains, complexity from answer length |
|
| 168 |
+
| code_instructions | 18,000 | code = true |
|
| 169 |
+
| squad | 15,000 | factual, complexity 1 |
|
| 170 |
+
| no_robots | 9,500 | human-written task category |
|
| 171 |
+
| gsm8k | 7,473 | math = true |
|
| 172 |
+
| hendrycks_math | 6,256 | math = true, complexity from level |
|
| 173 |
+
| generated traps | 3,000 | keyword counterfactuals |
|
| 174 |
+
| supra's own set | 992 | its labels, verbatim |
|
| 175 |
+
|
| 176 |
+
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.
|
| 177 |
+
|
| 178 |
+
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.
|
| 179 |
+
|
| 180 |
+
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.
|
| 181 |
+
|
| 182 |
+
## files
|
| 183 |
+
|
| 184 |
+
```
|
| 185 |
+
model.safetensors the weights (3.9 MB)
|
| 186 |
+
config.json architecture and label space
|
| 187 |
+
tokenizer.json 4096-vocab bpe, trained on routing prompts
|
| 188 |
+
modeling.py the model, standalone, torch only
|
| 189 |
+
policy.py tiers, expected-cost decision, overrides, prototypes
|
| 190 |
+
policy.json the default 3-tier policy, edit this one
|
| 191 |
+
infer.py cli: route, batch, add a category
|
| 192 |
+
scores.json every number on this page, machine readable
|
| 193 |
+
requirements.txt torch, tokenizers, safetensors
|
| 194 |
+
README.md you are here
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
## quickstart
|
| 198 |
+
|
| 199 |
+
```
|
| 200 |
+
pip install torch tokenizers safetensors
|
| 201 |
+
|
| 202 |
+
python infer.py --demo
|
| 203 |
+
python infer.py "write a python function that reverses a linked list"
|
| 204 |
+
python infer.py --file prompts.txt
|
| 205 |
+
python infer.py --policy my_policy.json "summarize this thread"
|
| 206 |
+
python infer.py --add-category refunds refund_examples.txt
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
in code:
|
| 210 |
+
|
| 211 |
+
```python
|
| 212 |
+
from modeling import encode_batch, load_router
|
| 213 |
+
from policy import Policy, decide
|
| 214 |
+
|
| 215 |
+
model, tokenizer = load_router()
|
| 216 |
+
tokens = encode_batch(tokenizer, ["fix this SQL query"], model.config.max_len)
|
| 217 |
+
decision = decide(model, tokens, "fix this SQL query", Policy.load("policy.json"))
|
| 218 |
+
print(decision.tier, decision.domain, decision.complexity)
|
| 219 |
+
```
|
| 220 |
+
|
| 221 |
+
`decision.as_line()` prints supra's exact output format if you are swapping one for the other.
|
| 222 |
+
|
| 223 |
+
runs on cpu. the model is 3.9 MB. it is smaller than the favicon budget of most websites.
|
| 224 |
+
|
| 225 |
+
/lane
|
| 226 |
+
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
|
config.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Glint-Router-1M",
|
| 3 |
+
"author": "Glint-Research",
|
| 4 |
+
"task": "prompt routing / request triage",
|
| 5 |
+
"parameters": 985826,
|
| 6 |
+
"model": {
|
| 7 |
+
"vocab_size": 4096,
|
| 8 |
+
"dim": 128,
|
| 9 |
+
"n_heads": 8,
|
| 10 |
+
"layers": 3,
|
| 11 |
+
"ffn_hidden": 208,
|
| 12 |
+
"max_len": 256,
|
| 13 |
+
"rope_base": 10000.0,
|
| 14 |
+
"proj_dim": 64,
|
| 15 |
+
"n_domains": 25
|
| 16 |
+
},
|
| 17 |
+
"labels": {
|
| 18 |
+
"domains": 25,
|
| 19 |
+
"complexity": "ordinal 1-5 (CORAL)",
|
| 20 |
+
"binary": [
|
| 21 |
+
"code",
|
| 22 |
+
"math",
|
| 23 |
+
"reasoning",
|
| 24 |
+
"long_output"
|
| 25 |
+
],
|
| 26 |
+
"route": "binary big/small, plus N-tier expected-cost policy"
|
| 27 |
+
}
|
| 28 |
+
}
|
infer.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""glint-router-1m: route prompts, edit the policy, teach it new categories.
|
| 2 |
+
|
| 3 |
+
python infer.py "write a python function that reverses a linked list"
|
| 4 |
+
python infer.py --file prompts.txt
|
| 5 |
+
python infer.py --policy my_policy.json "summarize this thread"
|
| 6 |
+
python infer.py --add-category refunds examples.txt
|
| 7 |
+
python infer.py --demo
|
| 8 |
+
|
| 9 |
+
everything runs on cpu. one forward pass per prompt, around 0.2 ms.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import sys
|
| 15 |
+
import time
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
|
| 20 |
+
from modeling import encode_batch, load_router
|
| 21 |
+
from policy import Policy, Prototypes, decide
|
| 22 |
+
|
| 23 |
+
HERE = Path(__file__).parent
|
| 24 |
+
PROTOTYPES = HERE / "prototypes.json"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def route(prompts: list[str], policy_path: Path | None = None,
|
| 28 |
+
prototypes_path: Path | None = None, device: str = "cpu"):
|
| 29 |
+
model, tokenizer = load_router(HERE, device)
|
| 30 |
+
policy = Policy.load(policy_path or HERE / "policy.json")
|
| 31 |
+
prototypes = Prototypes.load(prototypes_path)
|
| 32 |
+
tokens = encode_batch(tokenizer, prompts, model.config.max_len).to(device)
|
| 33 |
+
return [decide(model, tokens[i:i + 1], prompts[i], policy, prototypes)
|
| 34 |
+
for i in range(len(prompts))]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def add_category(name: str, examples: list[str], device: str = "cpu") -> None:
|
| 38 |
+
"""teach it a label it was never trained on. one forward pass, no optimizer."""
|
| 39 |
+
model, tokenizer = load_router(HERE, device)
|
| 40 |
+
tokens = encode_batch(tokenizer, examples, model.config.max_len).to(device)
|
| 41 |
+
with torch.no_grad():
|
| 42 |
+
projections = model(tokens)["proj"]
|
| 43 |
+
prototypes = Prototypes.load(PROTOTYPES)
|
| 44 |
+
prototypes.add(name, projections)
|
| 45 |
+
prototypes.save(PROTOTYPES)
|
| 46 |
+
print(f"added category {name!r} from {len(examples)} examples -> {PROTOTYPES}")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def show(decisions, prompts) -> None:
|
| 50 |
+
for decision, prompt in zip(decisions, prompts, strict=True):
|
| 51 |
+
flags = [k for k, v in decision.signals.items() if v]
|
| 52 |
+
head = prompt if len(prompt) <= 58 else prompt[:55] + "..."
|
| 53 |
+
print(f"{decision.tier:<9} {decision.domain:<20} c={decision.complexity} "
|
| 54 |
+
f"d={decision.difficulty:.2f} {head}")
|
| 55 |
+
print(f" {decision.reason}"
|
| 56 |
+
+ (f" | signals={flags}" if flags else "")
|
| 57 |
+
+ (f" | category={decision.category}" if decision.category else ""))
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
DEMO = [
|
| 61 |
+
"What's the capital of France?",
|
| 62 |
+
"Write a short poem about a Python snake in the garden.",
|
| 63 |
+
"Write a Python function that reverses a linked list in place.",
|
| 64 |
+
"A train leaves at 3pm going 60mph, another at 4pm going 80mph, when do they meet?",
|
| 65 |
+
"Summarize this email thread in two sentences.",
|
| 66 |
+
"What are the latest news headlines today?",
|
| 67 |
+
"Explain the difference between a mutex and a semaphore, with an example.",
|
| 68 |
+
]
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def main() -> int:
|
| 72 |
+
args = sys.argv[1:]
|
| 73 |
+
if not args or args[0] in ("-h", "--help"):
|
| 74 |
+
print(__doc__)
|
| 75 |
+
return 0
|
| 76 |
+
|
| 77 |
+
if args[0] == "--demo":
|
| 78 |
+
started = time.perf_counter()
|
| 79 |
+
decisions = route(DEMO)
|
| 80 |
+
elapsed = time.perf_counter() - started
|
| 81 |
+
show(decisions, DEMO)
|
| 82 |
+
print(f"\n{len(DEMO)} prompts in {elapsed * 1000:.0f} ms "
|
| 83 |
+
f"({elapsed * 1000 / len(DEMO):.2f} ms each, cpu, includes model load)")
|
| 84 |
+
return 0
|
| 85 |
+
|
| 86 |
+
if args[0] == "--add-category":
|
| 87 |
+
if len(args) < 3:
|
| 88 |
+
print("usage: python infer.py --add-category <name> <examples.txt>")
|
| 89 |
+
return 1
|
| 90 |
+
examples = [line.strip() for line in Path(args[2]).read_text().splitlines()
|
| 91 |
+
if line.strip()]
|
| 92 |
+
add_category(args[1], examples)
|
| 93 |
+
return 0
|
| 94 |
+
|
| 95 |
+
policy_path = None
|
| 96 |
+
if args[0] == "--policy":
|
| 97 |
+
policy_path, args = Path(args[1]), args[2:]
|
| 98 |
+
|
| 99 |
+
if args and args[0] == "--file":
|
| 100 |
+
prompts = [line.strip() for line in Path(args[1]).read_text().splitlines()
|
| 101 |
+
if line.strip()]
|
| 102 |
+
else:
|
| 103 |
+
prompts = [" ".join(args)]
|
| 104 |
+
|
| 105 |
+
decisions = route(prompts, policy_path,
|
| 106 |
+
PROTOTYPES if PROTOTYPES.is_file() else None)
|
| 107 |
+
show(decisions, prompts)
|
| 108 |
+
if len(prompts) == 1:
|
| 109 |
+
print()
|
| 110 |
+
print(decisions[0].as_line())
|
| 111 |
+
return 0
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
if __name__ == "__main__":
|
| 115 |
+
raise SystemExit(main())
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1e49b3aef312902c7af0044336b17f97f7d5dd57075ae1d7d769adafea6018a4
|
| 3 |
+
size 3946036
|
modeling.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""glint-router-1m: standalone inference. torch + tokenizers + safetensors, nothing else.
|
| 2 |
+
|
| 3 |
+
one forward pass over the prompt gives you every field at once: domain,
|
| 4 |
+
complexity, code, math, reasoning, long_output, route, and a 64-d projection
|
| 5 |
+
used for user-defined categories. no decoding loop, no output parsing.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
import math
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
from torch import Tensor, nn
|
| 17 |
+
from torch.nn import functional
|
| 18 |
+
|
| 19 |
+
HERE = Path(__file__).parent
|
| 20 |
+
PAD_ID = 0
|
| 21 |
+
BOS_ID = 1
|
| 22 |
+
BINARY_FIELDS = ("code", "math", "reasoning", "long_output")
|
| 23 |
+
COMPLEXITY_LEVELS = 5
|
| 24 |
+
|
| 25 |
+
DOMAINS = (
|
| 26 |
+
"programming", "web_dev", "databases", "devops_sysadmin", "security",
|
| 27 |
+
"data_science_ml", "math", "science", "engineering", "reasoning_puzzle",
|
| 28 |
+
"creative_writing", "professional_writing", "editing_grammar", "translation",
|
| 29 |
+
"factual_qa", "education", "business", "finance", "legal", "health",
|
| 30 |
+
"travel", "food", "entertainment", "lifestyle", "other",
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@dataclass(frozen=True)
|
| 35 |
+
class RouterConfig:
|
| 36 |
+
vocab_size: int = 4096
|
| 37 |
+
dim: int = 128
|
| 38 |
+
n_heads: int = 8
|
| 39 |
+
layers: int = 3
|
| 40 |
+
ffn_hidden: int = 208
|
| 41 |
+
max_len: int = 256
|
| 42 |
+
rope_base: float = 10_000.0
|
| 43 |
+
proj_dim: int = 64
|
| 44 |
+
n_domains: int = len(DOMAINS)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def build_rope_cache(config: RouterConfig) -> tuple[Tensor, Tensor]:
|
| 48 |
+
head_dim = config.dim // config.n_heads
|
| 49 |
+
positions = torch.arange(config.max_len, dtype=torch.float32)
|
| 50 |
+
inv_freq = 1.0 / (config.rope_base ** (torch.arange(0, head_dim, 2).float() / head_dim))
|
| 51 |
+
angles = torch.outer(positions, inv_freq)
|
| 52 |
+
return torch.cos(angles), torch.sin(angles)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def apply_rope(x: Tensor, cos: Tensor, sin: Tensor) -> Tensor:
|
| 56 |
+
x_even, x_odd = x[..., 0::2], x[..., 1::2]
|
| 57 |
+
rotated_even = x_even * cos - x_odd * sin
|
| 58 |
+
rotated_odd = x_even * sin + x_odd * cos
|
| 59 |
+
return torch.stack((rotated_even, rotated_odd), dim=-1).flatten(-2)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class SwiGlu(nn.Module):
|
| 63 |
+
def __init__(self, dim: int, hidden: int) -> None:
|
| 64 |
+
super().__init__()
|
| 65 |
+
self.gate_up = nn.Linear(dim, 2 * hidden, bias=False)
|
| 66 |
+
self.down = nn.Linear(hidden, dim, bias=False)
|
| 67 |
+
|
| 68 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 69 |
+
gate, up = self.gate_up(x).chunk(2, dim=-1)
|
| 70 |
+
return self.down(functional.silu(gate) * up)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class RouterAttention(nn.Module):
|
| 74 |
+
def __init__(self, config: RouterConfig) -> None:
|
| 75 |
+
super().__init__()
|
| 76 |
+
self.n_heads = config.n_heads
|
| 77 |
+
self.head_dim = config.dim // config.n_heads
|
| 78 |
+
self.qkv = nn.Linear(config.dim, 3 * config.dim, bias=False)
|
| 79 |
+
self.out = nn.Linear(config.dim, config.dim, bias=False)
|
| 80 |
+
|
| 81 |
+
def forward(self, x: Tensor, cos: Tensor, sin: Tensor, attn_mask: Tensor) -> Tensor:
|
| 82 |
+
batch, seq_len, dim = x.shape
|
| 83 |
+
q, k, v = self.qkv(x).split(dim, dim=-1)
|
| 84 |
+
shape = (batch, seq_len, self.n_heads, self.head_dim)
|
| 85 |
+
q = apply_rope(q.view(shape).transpose(1, 2), cos, sin)
|
| 86 |
+
k = apply_rope(k.view(shape).transpose(1, 2), cos, sin)
|
| 87 |
+
v = v.view(shape).transpose(1, 2)
|
| 88 |
+
attended = functional.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask)
|
| 89 |
+
return self.out(attended.transpose(1, 2).reshape(batch, seq_len, dim))
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class RouterBlock(nn.Module):
|
| 93 |
+
def __init__(self, config: RouterConfig) -> None:
|
| 94 |
+
super().__init__()
|
| 95 |
+
self.attn_norm = nn.RMSNorm(config.dim)
|
| 96 |
+
self.attn = RouterAttention(config)
|
| 97 |
+
self.ffn_norm = nn.RMSNorm(config.dim)
|
| 98 |
+
self.ffn = SwiGlu(config.dim, config.ffn_hidden)
|
| 99 |
+
|
| 100 |
+
def forward(self, x: Tensor, cos: Tensor, sin: Tensor, attn_mask: Tensor) -> Tensor:
|
| 101 |
+
x = x + self.attn(self.attn_norm(x), cos, sin, attn_mask)
|
| 102 |
+
return x + self.ffn(self.ffn_norm(x))
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
class GlintRouter(nn.Module):
|
| 106 |
+
def __init__(self, config: RouterConfig | None = None) -> None:
|
| 107 |
+
super().__init__()
|
| 108 |
+
config = config or RouterConfig()
|
| 109 |
+
self.config = config
|
| 110 |
+
self.embed = nn.Embedding(config.vocab_size, config.dim, padding_idx=PAD_ID)
|
| 111 |
+
self.blocks = nn.ModuleList(RouterBlock(config) for _ in range(config.layers))
|
| 112 |
+
self.final_norm = nn.RMSNorm(config.dim)
|
| 113 |
+
cos, sin = build_rope_cache(config)
|
| 114 |
+
self.register_buffer("rope_cos", cos, persistent=False)
|
| 115 |
+
self.register_buffer("rope_sin", sin, persistent=False)
|
| 116 |
+
|
| 117 |
+
pooled = 2 * config.dim
|
| 118 |
+
self.domain_head = nn.Linear(pooled, config.n_domains)
|
| 119 |
+
self.complexity_dir = nn.Linear(pooled, 1, bias=False)
|
| 120 |
+
self.complexity_bias = nn.Parameter(torch.zeros(COMPLEXITY_LEVELS - 1))
|
| 121 |
+
self.binary_head = nn.Linear(pooled, len(BINARY_FIELDS))
|
| 122 |
+
self.route_head = nn.Linear(pooled, 1)
|
| 123 |
+
self.proj_head = nn.Linear(pooled, config.proj_dim)
|
| 124 |
+
self.register_buffer("temperature", torch.ones(3), persistent=True)
|
| 125 |
+
|
| 126 |
+
def encode(self, tokens: Tensor) -> Tensor:
|
| 127 |
+
valid = tokens != PAD_ID
|
| 128 |
+
seq_len = tokens.shape[1]
|
| 129 |
+
cos = self.rope_cos[:seq_len].to(self.embed.weight.dtype)
|
| 130 |
+
sin = self.rope_sin[:seq_len].to(self.embed.weight.dtype)
|
| 131 |
+
attn_mask = torch.zeros(tokens.shape, dtype=self.embed.weight.dtype,
|
| 132 |
+
device=tokens.device)
|
| 133 |
+
attn_mask = attn_mask.masked_fill(~valid, float("-inf"))[:, None, None, :]
|
| 134 |
+
x = self.embed(tokens)
|
| 135 |
+
for block in self.blocks:
|
| 136 |
+
x = block(x, cos, sin, attn_mask)
|
| 137 |
+
x = self.final_norm(x)
|
| 138 |
+
mask = valid.unsqueeze(-1)
|
| 139 |
+
mean = (x * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1)
|
| 140 |
+
maximum = torch.nan_to_num(x.masked_fill(~mask, float("-inf")).max(dim=1).values,
|
| 141 |
+
neginf=0.0)
|
| 142 |
+
return torch.cat((mean, maximum), dim=-1)
|
| 143 |
+
|
| 144 |
+
def forward(self, tokens: Tensor) -> dict[str, Tensor]:
|
| 145 |
+
pooled = self.encode(tokens)
|
| 146 |
+
return {
|
| 147 |
+
"domain": self.domain_head(pooled),
|
| 148 |
+
"complexity": self.complexity_dir(pooled) + self.complexity_bias,
|
| 149 |
+
"binary": self.binary_head(pooled),
|
| 150 |
+
"route": self.route_head(pooled).squeeze(-1),
|
| 151 |
+
"proj": functional.normalize(self.proj_head(pooled), dim=-1),
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
def calibrated(self, tokens: Tensor) -> dict[str, Tensor]:
|
| 155 |
+
"""probabilities with the fitted temperatures applied. the routing
|
| 156 |
+
arithmetic in policy.py runs on these numbers, so they have to mean
|
| 157 |
+
something. temperatures were fitted on held-out data after training."""
|
| 158 |
+
out = self.forward(tokens)
|
| 159 |
+
route_t, complexity_t, binary_t = self.temperature.unbind()
|
| 160 |
+
return {
|
| 161 |
+
"domain": out["domain"].softmax(dim=-1),
|
| 162 |
+
"complexity": (out["complexity"] / complexity_t).sigmoid(),
|
| 163 |
+
"binary": (out["binary"] / binary_t).sigmoid(),
|
| 164 |
+
"route": (out["route"] / route_t).sigmoid(),
|
| 165 |
+
"proj": out["proj"],
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def complexity_from_cumulative(probabilities: Tensor) -> Tensor:
|
| 170 |
+
"""coral decode. level = 1 + how many thresholds the prompt clears."""
|
| 171 |
+
return 1 + (probabilities > 0.5).sum(dim=-1)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def encode_batch(tokenizer, texts: list[str], max_len: int) -> Tensor:
|
| 175 |
+
"""bos-prefixed, right-padded. long prompts lose their tail, because the
|
| 176 |
+
instruction verb lives at the front and the pasted context lives at the back."""
|
| 177 |
+
out = torch.full((len(texts), max_len), PAD_ID, dtype=torch.long)
|
| 178 |
+
for row, text in enumerate(texts):
|
| 179 |
+
ids = [BOS_ID] + tokenizer.encode(text).ids[: max_len - 1]
|
| 180 |
+
out[row, : len(ids)] = torch.tensor(ids, dtype=torch.long)
|
| 181 |
+
return out
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def load_router(directory: Path = HERE, device: str = "cpu"):
|
| 185 |
+
"""returns (model, tokenizer). reads config.json + model.safetensors + tokenizer.json."""
|
| 186 |
+
from safetensors.torch import load_file
|
| 187 |
+
from tokenizers import Tokenizer
|
| 188 |
+
|
| 189 |
+
directory = Path(directory)
|
| 190 |
+
config = RouterConfig(**json.loads((directory / "config.json").read_text())["model"])
|
| 191 |
+
model = GlintRouter(config).to(device)
|
| 192 |
+
model.load_state_dict(load_file(directory / "model.safetensors", device=device))
|
| 193 |
+
model.eval()
|
| 194 |
+
return model, Tokenizer.from_file(str(directory / "tokenizer.json"))
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def count_parameters(model: nn.Module) -> int:
|
| 198 |
+
return sum(p.numel() for p in model.parameters())
|
policy.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tiers": [
|
| 3 |
+
{
|
| 4 |
+
"name": "local",
|
| 5 |
+
"cost": 0.0,
|
| 6 |
+
"capability": 0.35,
|
| 7 |
+
"tools": false
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"name": "mid",
|
| 11 |
+
"cost": 1.0,
|
| 12 |
+
"capability": 0.65,
|
| 13 |
+
"tools": false
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"name": "frontier",
|
| 17 |
+
"cost": 8.0,
|
| 18 |
+
"capability": 0.95,
|
| 19 |
+
"tools": true
|
| 20 |
+
}
|
| 21 |
+
],
|
| 22 |
+
"failure_penalty": 50.0,
|
| 23 |
+
"sharpness": 20.0,
|
| 24 |
+
"abstain_margin": 0.05,
|
| 25 |
+
"difficulty_weights": [
|
| 26 |
+
0.6,
|
| 27 |
+
0.3,
|
| 28 |
+
0.1
|
| 29 |
+
],
|
| 30 |
+
"overrides": [],
|
| 31 |
+
"freshness_needs_tools": true,
|
| 32 |
+
"multilingual_threshold": 0.05,
|
| 33 |
+
"prototype_threshold": 0.55
|
| 34 |
+
}
|
policy.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Routing policy: everything you can change without touching the weights.
|
| 2 |
+
|
| 3 |
+
Supra-Router-51M bakes its decision into the model. two tiers, one threshold,
|
| 4 |
+
one domain vocabulary, all frozen at fine-tune time. Changing "route legal work
|
| 5 |
+
to the frontier model regardless of complexity" means collecting data and
|
| 6 |
+
retraining a 51M model.
|
| 7 |
+
|
| 8 |
+
Here the model's job stops at *calibrated probabilities about the prompt*. What
|
| 9 |
+
to do with them is this file, driven by a JSON document the user owns:
|
| 10 |
+
|
| 11 |
+
* N tiers, not 2, each with a cost and a capability
|
| 12 |
+
* expected-cost arithmetic instead of a hard-coded boolean
|
| 13 |
+
* an abstain band that escalates ties rather than coin-flipping them
|
| 14 |
+
* hard overrides on any predicted field
|
| 15 |
+
* user-defined categories matched by prototype, no retraining
|
| 16 |
+
|
| 17 |
+
Also here: the freshness / tool-use / multilingual signals. Those are keyword
|
| 18 |
+
and character-class rules. a learned head for them would only be memorising
|
| 19 |
+
the regex below, which costs parameters and teaches nothing. They live where a
|
| 20 |
+
rule belongs, and the user can edit them.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
from __future__ import annotations
|
| 24 |
+
|
| 25 |
+
import json
|
| 26 |
+
import re
|
| 27 |
+
from dataclasses import asdict, dataclass, field
|
| 28 |
+
from pathlib import Path
|
| 29 |
+
|
| 30 |
+
import torch
|
| 31 |
+
from torch import Tensor
|
| 32 |
+
|
| 33 |
+
from modeling import BINARY_FIELDS, DOMAINS, GlintRouter, complexity_from_cumulative
|
| 34 |
+
|
| 35 |
+
FRESHNESS = re.compile(
|
| 36 |
+
r"\b(today|tonight|current|currently|latest|right now|this (week|month|year)|"
|
| 37 |
+
r"recent|news|stock price|weather|who won|as of \d{4}|202\d|203\d)\b", re.I)
|
| 38 |
+
TOOL_USE = re.compile(
|
| 39 |
+
r"\b(search the web|look ?up|browse|run this|execute|call the api|fetch|"
|
| 40 |
+
r"my file|this (csv|pdf|spreadsheet|repo)|book a|send an email|schedule)\b", re.I)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def non_ascii_ratio(text: str) -> float:
|
| 44 |
+
letters = [c for c in text if c.isalpha()]
|
| 45 |
+
if not letters:
|
| 46 |
+
return 0.0
|
| 47 |
+
return sum(1 for c in letters if ord(c) > 127) / len(letters)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
@dataclass
|
| 51 |
+
class Tier:
|
| 52 |
+
name: str
|
| 53 |
+
cost: float # relative price of one call, any unit you like
|
| 54 |
+
capability: float # 0..1; the difficulty this tier handles before it starts failing
|
| 55 |
+
tools: bool = False # can this tier search / execute?
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
@dataclass
|
| 59 |
+
class Policy:
|
| 60 |
+
tiers: list[Tier] = field(default_factory=lambda: [
|
| 61 |
+
Tier("local", cost=0.0, capability=0.35),
|
| 62 |
+
Tier("mid", cost=1.0, capability=0.65),
|
| 63 |
+
Tier("frontier", cost=8.0, capability=0.95, tools=True),
|
| 64 |
+
])
|
| 65 |
+
# A wrong answer must cost several times the most expensive call, or the
|
| 66 |
+
# arithmetic degenerates: when every tier is likely to fail, "pay least"
|
| 67 |
+
# beats "try hardest", and the router quietly sends its hardest prompts to
|
| 68 |
+
# the weakest model. Penalty >> max(cost) is what makes escalation rational.
|
| 69 |
+
failure_penalty: float = 50.0 # what a wrong answer costs, in the same units as `cost`
|
| 70 |
+
# Sharpness sets the residual failure rate a tier carries on prompts well
|
| 71 |
+
# inside its capability, and it has to be steep. At sharpness 12 the local
|
| 72 |
+
# tier still shows a 2.6% failure rate on a difficulty-0.05 prompt, which
|
| 73 |
+
# priced at `failure_penalty` (1.33) loses to the mid tier's flat cost of
|
| 74 |
+
# 1.0, so a free tier that handles the prompt perfectly never gets used.
|
| 75 |
+
# 20 puts that residual at 0.25% and the cheap tier wins what it should.
|
| 76 |
+
sharpness: float = 20.0
|
| 77 |
+
abstain_margin: float = 0.05 # ties inside this band escalate instead of coin-flipping
|
| 78 |
+
difficulty_weights: tuple[float, float, float] = (0.6, 0.3, 0.1) # route, complexity, code/math
|
| 79 |
+
# {"when": {...}, "tier": "frontier"}. first match wins, checked before the arithmetic.
|
| 80 |
+
overrides: list[dict] = field(default_factory=list)
|
| 81 |
+
freshness_needs_tools: bool = True
|
| 82 |
+
multilingual_threshold: float = 0.05
|
| 83 |
+
prototype_threshold: float = 0.55
|
| 84 |
+
|
| 85 |
+
@classmethod
|
| 86 |
+
def load(cls, path: Path | None) -> Policy:
|
| 87 |
+
if path is None:
|
| 88 |
+
return cls()
|
| 89 |
+
blob = json.loads(Path(path).read_text())
|
| 90 |
+
tiers = [Tier(**t) for t in blob.pop("tiers", [])]
|
| 91 |
+
policy = cls(**blob)
|
| 92 |
+
if tiers:
|
| 93 |
+
policy.tiers = tiers
|
| 94 |
+
return policy
|
| 95 |
+
|
| 96 |
+
def save(self, path: Path) -> None:
|
| 97 |
+
Path(path).write_text(json.dumps(asdict(self), indent=2))
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
@dataclass
|
| 101 |
+
class Decision:
|
| 102 |
+
tier: str
|
| 103 |
+
domain: str
|
| 104 |
+
complexity: int
|
| 105 |
+
difficulty: float
|
| 106 |
+
probabilities: dict[str, float]
|
| 107 |
+
signals: dict[str, bool]
|
| 108 |
+
reason: str
|
| 109 |
+
category: str | None = None
|
| 110 |
+
escalated: bool = False
|
| 111 |
+
|
| 112 |
+
def as_line(self) -> str:
|
| 113 |
+
"""Supra's output format, so the two are directly comparable."""
|
| 114 |
+
p = self.probabilities
|
| 115 |
+
return (f"Domain: {self.domain} | Complexity: {self.complexity} | "
|
| 116 |
+
f"Math: {'T' if p['math'] > 0.5 else 'F'} | "
|
| 117 |
+
f"Code: {'T' if p['code'] > 0.5 else 'F'} | "
|
| 118 |
+
f"Route: {self.tier} | Justification: {self.reason}")
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
class Prototypes:
|
| 122 |
+
"""User-defined categories, added from a handful of examples at runtime.
|
| 123 |
+
|
| 124 |
+
The projection head is trained jointly with the classifier, so prompts that
|
| 125 |
+
share a routing-relevant character land near each other. A new category is
|
| 126 |
+
the mean of its examples' projections. adding one is a forward pass over
|
| 127 |
+
5-10 prompts, not a training run.
|
| 128 |
+
"""
|
| 129 |
+
|
| 130 |
+
def __init__(self, vectors: dict[str, list[float]] | None = None) -> None:
|
| 131 |
+
self.vectors = {k: torch.tensor(v) for k, v in (vectors or {}).items()}
|
| 132 |
+
|
| 133 |
+
def add(self, name: str, projections: Tensor) -> None:
|
| 134 |
+
centroid = projections.mean(dim=0)
|
| 135 |
+
self.vectors[name] = centroid / centroid.norm().clamp(min=1e-6)
|
| 136 |
+
|
| 137 |
+
def match(self, projection: Tensor, threshold: float) -> tuple[str | None, float]:
|
| 138 |
+
if not self.vectors:
|
| 139 |
+
return None, 0.0
|
| 140 |
+
names = list(self.vectors)
|
| 141 |
+
bank = torch.stack([self.vectors[n] for n in names]).to(projection.device)
|
| 142 |
+
scores = bank @ projection
|
| 143 |
+
best = int(scores.argmax())
|
| 144 |
+
score = float(scores[best])
|
| 145 |
+
return (names[best], score) if score >= threshold else (None, score)
|
| 146 |
+
|
| 147 |
+
def save(self, path: Path) -> None:
|
| 148 |
+
Path(path).write_text(json.dumps({k: v.tolist() for k, v in self.vectors.items()}))
|
| 149 |
+
|
| 150 |
+
@classmethod
|
| 151 |
+
def load(cls, path: Path | None) -> Prototypes:
|
| 152 |
+
if path is None or not Path(path).is_file():
|
| 153 |
+
return cls()
|
| 154 |
+
return cls(json.loads(Path(path).read_text()))
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def difficulty_of(route_p: float, complexity: float, code_p: float, math_p: float,
|
| 158 |
+
weights: tuple[float, float, float]) -> float:
|
| 159 |
+
w_route, w_complexity, w_technical = weights
|
| 160 |
+
scaled_complexity = (complexity - 1.0) / 4.0
|
| 161 |
+
technical = max(code_p, math_p)
|
| 162 |
+
total = w_route + w_complexity + w_technical
|
| 163 |
+
return (w_route * route_p + w_complexity * scaled_complexity
|
| 164 |
+
+ w_technical * technical) / max(total, 1e-6)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def _override_hit(rule: dict, domain: str, complexity: int, probs: dict[str, float],
|
| 168 |
+
signals: dict[str, bool], category: str | None) -> bool:
|
| 169 |
+
when = rule.get("when", {})
|
| 170 |
+
if "domain" in when and when["domain"] != domain:
|
| 171 |
+
return False
|
| 172 |
+
if "category" in when and when["category"] != category:
|
| 173 |
+
return False
|
| 174 |
+
if "complexity_min" in when and complexity < when["complexity_min"]:
|
| 175 |
+
return False
|
| 176 |
+
if "complexity_max" in when and complexity > when["complexity_max"]:
|
| 177 |
+
return False
|
| 178 |
+
for f in BINARY_FIELDS:
|
| 179 |
+
if f in when and bool(when[f]) != (probs[f] > 0.5):
|
| 180 |
+
return False
|
| 181 |
+
for s in ("freshness", "tool_use", "multilingual"):
|
| 182 |
+
if s in when and bool(when[s]) != signals[s]:
|
| 183 |
+
return False
|
| 184 |
+
return True
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def decide(model: GlintRouter, tokens: Tensor, text: str, policy: Policy,
|
| 188 |
+
prototypes: Prototypes | None = None) -> Decision:
|
| 189 |
+
"""One prompt -> one routing decision. `tokens` is a (1, seq) batch."""
|
| 190 |
+
with torch.no_grad():
|
| 191 |
+
out = model.calibrated(tokens)
|
| 192 |
+
probs = {f: float(out["binary"][0, i]) for i, f in enumerate(BINARY_FIELDS)}
|
| 193 |
+
probs["route_big"] = float(out["route"][0])
|
| 194 |
+
domain = DOMAINS[int(out["domain"][0].argmax())]
|
| 195 |
+
complexity_p = out["complexity"][0]
|
| 196 |
+
complexity = int(complexity_from_cumulative(complexity_p))
|
| 197 |
+
expected_complexity = 1.0 + float(complexity_p.sum())
|
| 198 |
+
|
| 199 |
+
signals = {
|
| 200 |
+
"freshness": bool(FRESHNESS.search(text)),
|
| 201 |
+
"tool_use": bool(TOOL_USE.search(text)),
|
| 202 |
+
"multilingual": non_ascii_ratio(text) > policy.multilingual_threshold,
|
| 203 |
+
}
|
| 204 |
+
category, _ = (prototypes.match(out["proj"][0], policy.prototype_threshold)
|
| 205 |
+
if prototypes else (None, 0.0))
|
| 206 |
+
|
| 207 |
+
for rule in policy.overrides:
|
| 208 |
+
if _override_hit(rule, domain, complexity, probs, signals, category):
|
| 209 |
+
return Decision(rule["tier"], domain, complexity, 0.0, probs, signals,
|
| 210 |
+
f"override: {json.dumps(rule.get('when', {}))}", category)
|
| 211 |
+
|
| 212 |
+
difficulty = difficulty_of(probs["route_big"], expected_complexity,
|
| 213 |
+
probs["code"], probs["math"], policy.difficulty_weights)
|
| 214 |
+
|
| 215 |
+
tiers = policy.tiers
|
| 216 |
+
if (signals["freshness"] or signals["tool_use"]) and policy.freshness_needs_tools:
|
| 217 |
+
with_tools = [t for t in tiers if t.tools]
|
| 218 |
+
if with_tools:
|
| 219 |
+
tiers = with_tools
|
| 220 |
+
|
| 221 |
+
costs = []
|
| 222 |
+
for tier in tiers:
|
| 223 |
+
fail = torch.sigmoid(torch.tensor(
|
| 224 |
+
(difficulty - tier.capability) * policy.sharpness)).item()
|
| 225 |
+
costs.append((tier.cost + policy.failure_penalty * fail, tier))
|
| 226 |
+
costs.sort(key=lambda pair: pair[0])
|
| 227 |
+
|
| 228 |
+
best_cost, best = costs[0]
|
| 229 |
+
escalated = False
|
| 230 |
+
if len(costs) > 1 and (costs[1][0] - best_cost) <= policy.abstain_margin * best_cost:
|
| 231 |
+
runner_up = costs[1][1]
|
| 232 |
+
if runner_up.capability > best.capability:
|
| 233 |
+
best, escalated = runner_up, True
|
| 234 |
+
|
| 235 |
+
reason = (f"difficulty={difficulty:.2f} complexity={complexity} "
|
| 236 |
+
f"code={probs['code']:.2f} math={probs['math']:.2f}"
|
| 237 |
+
+ (" (escalated: tie inside abstain band)" if escalated else ""))
|
| 238 |
+
return Decision(best.name, domain, complexity, difficulty, probs, signals,
|
| 239 |
+
reason, category, escalated)
|
refund_examples.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
I want a refund for my order, it arrived broken.
|
| 2 |
+
How do I return this item and get my money back?
|
| 3 |
+
My package never showed up, can I be refunded?
|
| 4 |
+
Cancel my subscription and refund last month.
|
| 5 |
+
The product is defective, I demand a refund.
|
| 6 |
+
Requesting reimbursement for a duplicate charge.
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
tokenizers
|
| 3 |
+
safetensors
|
scores.json
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "Glint-Router-1M",
|
| 3 |
+
"author": "Glint-Research",
|
| 4 |
+
"parameters": 985826,
|
| 5 |
+
"baseline": "SupraLabs/Supra-Router-51M (51,786,240 params)",
|
| 6 |
+
"training": {
|
| 7 |
+
"params": 985826,
|
| 8 |
+
"train_rows": 87604,
|
| 9 |
+
"val_rows": 7617,
|
| 10 |
+
"steps": 14479,
|
| 11 |
+
"minutes": 15.0,
|
| 12 |
+
"domain": 0.9464356045687279,
|
| 13 |
+
"route": 0.939608769856899,
|
| 14 |
+
"complexity": 0.7923066824209006,
|
| 15 |
+
"code": 0.991466456610214,
|
| 16 |
+
"math": 0.978075357752396,
|
| 17 |
+
"reasoning": 0.8949717736641722,
|
| 18 |
+
"long_output": 0.9516870158855192,
|
| 19 |
+
"ece_raw": 0.05564934242829622,
|
| 20 |
+
"ece_calibrated": 0.02029288645098859,
|
| 21 |
+
"hardware": "RTX 3060 Ti",
|
| 22 |
+
"rounds": 1,
|
| 23 |
+
"wall_clock_minutes": 15.0,
|
| 24 |
+
"corpus_rows": 95221
|
| 25 |
+
},
|
| 26 |
+
"neutral_holdout": {
|
| 27 |
+
"val_rows": 3418,
|
| 28 |
+
"slice_rows": {
|
| 29 |
+
"aqua_rat": 254,
|
| 30 |
+
"dolly": 2500,
|
| 31 |
+
"humaneval": 164,
|
| 32 |
+
"mbpp": 500
|
| 33 |
+
},
|
| 34 |
+
"glint": {
|
| 35 |
+
"route_accuracy": 0.9441193680514921,
|
| 36 |
+
"code_accuracy": 0.9751316559391457,
|
| 37 |
+
"math_accuracy": 0.9526038619075483,
|
| 38 |
+
"complexity_exact": 0.46284376828554713,
|
| 39 |
+
"complexity_within_1": 0.9590403744880047,
|
| 40 |
+
"ms_per_prompt": 0.05992146021082586,
|
| 41 |
+
"parse_failures": 0
|
| 42 |
+
},
|
| 43 |
+
"glint_aqua_rat": {
|
| 44 |
+
"route_accuracy": 0.9094488188976378,
|
| 45 |
+
"code_accuracy": 0.9960629921259843,
|
| 46 |
+
"math_accuracy": 0.889763779527559,
|
| 47 |
+
"complexity_exact": 0.1141732283464567,
|
| 48 |
+
"complexity_within_1": 0.8070866141732284,
|
| 49 |
+
"ms_per_prompt": 0.05992146021082586,
|
| 50 |
+
"parse_failures": 0
|
| 51 |
+
},
|
| 52 |
+
"glint_dolly": {
|
| 53 |
+
"route_accuracy": 0.9424,
|
| 54 |
+
"code_accuracy": 0.9956,
|
| 55 |
+
"math_accuracy": 0.9612,
|
| 56 |
+
"complexity_exact": 0.5688,
|
| 57 |
+
"complexity_within_1": 0.9944,
|
| 58 |
+
"ms_per_prompt": 0.05992146021082586,
|
| 59 |
+
"parse_failures": 0
|
| 60 |
+
},
|
| 61 |
+
"glint_humaneval": {
|
| 62 |
+
"route_accuracy": 0.8719512195121951,
|
| 63 |
+
"code_accuracy": 0.5670731707317073,
|
| 64 |
+
"math_accuracy": 0.9024390243902439,
|
| 65 |
+
"complexity_exact": 0.4451219512195122,
|
| 66 |
+
"complexity_within_1": 0.9817073170731707,
|
| 67 |
+
"ms_per_prompt": 0.05992146021082586,
|
| 68 |
+
"parse_failures": 0
|
| 69 |
+
},
|
| 70 |
+
"glint_mbpp": {
|
| 71 |
+
"route_accuracy": 0.994,
|
| 72 |
+
"code_accuracy": 0.996,
|
| 73 |
+
"math_accuracy": 0.958,
|
| 74 |
+
"complexity_exact": 0.116,
|
| 75 |
+
"complexity_within_1": 0.852,
|
| 76 |
+
"ms_per_prompt": 0.05992146021082586,
|
| 77 |
+
"parse_failures": 0
|
| 78 |
+
},
|
| 79 |
+
"supra": {
|
| 80 |
+
"route_accuracy": 0.903744880046811,
|
| 81 |
+
"code_accuracy": 0.9643066120538326,
|
| 82 |
+
"math_accuracy": 0.9207138677589234,
|
| 83 |
+
"complexity_exact": 0.3905792861322411,
|
| 84 |
+
"complexity_within_1": 0.9163253364540667,
|
| 85 |
+
"ms_per_prompt": 14.334112498536923,
|
| 86 |
+
"parse_failures": 4
|
| 87 |
+
},
|
| 88 |
+
"supra_aqua_rat": {
|
| 89 |
+
"route_accuracy": 0.9133858267716536,
|
| 90 |
+
"code_accuracy": 1.0,
|
| 91 |
+
"math_accuracy": 0.9094488188976378,
|
| 92 |
+
"complexity_exact": 0.0,
|
| 93 |
+
"complexity_within_1": 0.09448818897637795,
|
| 94 |
+
"ms_per_prompt": 14.334112498536923,
|
| 95 |
+
"parse_failures": 4
|
| 96 |
+
},
|
| 97 |
+
"supra_dolly": {
|
| 98 |
+
"route_accuracy": 0.8896,
|
| 99 |
+
"code_accuracy": 0.996,
|
| 100 |
+
"math_accuracy": 0.9428,
|
| 101 |
+
"complexity_exact": 0.482,
|
| 102 |
+
"complexity_within_1": 0.9776,
|
| 103 |
+
"ms_per_prompt": 14.334112498536923,
|
| 104 |
+
"parse_failures": 4
|
| 105 |
+
},
|
| 106 |
+
"supra_humaneval": {
|
| 107 |
+
"route_accuracy": 0.8292682926829268,
|
| 108 |
+
"code_accuracy": 0.676829268292683,
|
| 109 |
+
"math_accuracy": 0.8414634146341463,
|
| 110 |
+
"complexity_exact": 0.16463414634146342,
|
| 111 |
+
"complexity_within_1": 1.0,
|
| 112 |
+
"ms_per_prompt": 14.334112498536923,
|
| 113 |
+
"parse_failures": 4
|
| 114 |
+
},
|
| 115 |
+
"supra_mbpp": {
|
| 116 |
+
"route_accuracy": 0.994,
|
| 117 |
+
"code_accuracy": 0.882,
|
| 118 |
+
"math_accuracy": 0.842,
|
| 119 |
+
"complexity_exact": 0.206,
|
| 120 |
+
"complexity_within_1": 1.0,
|
| 121 |
+
"ms_per_prompt": 14.334112498536923,
|
| 122 |
+
"parse_failures": 4
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
"in_corpus_val": {
|
| 126 |
+
"val_rows": 7617,
|
| 127 |
+
"glint": {
|
| 128 |
+
"route_accuracy": 0.939608769856899,
|
| 129 |
+
"code_accuracy": 0.991466456610214,
|
| 130 |
+
"math_accuracy": 0.978075357752396,
|
| 131 |
+
"complexity_exact": 0.7923066824209006,
|
| 132 |
+
"complexity_within_1": 0.9883156098201391,
|
| 133 |
+
"ms_per_prompt": 0.0748591950898926,
|
| 134 |
+
"parse_failures": 0
|
| 135 |
+
},
|
| 136 |
+
"glint_trap": {
|
| 137 |
+
"route_accuracy": 1.0,
|
| 138 |
+
"code_accuracy": 1.0,
|
| 139 |
+
"math_accuracy": 1.0,
|
| 140 |
+
"complexity_exact": 1.0,
|
| 141 |
+
"complexity_within_1": 1.0,
|
| 142 |
+
"parse_failures": 0
|
| 143 |
+
},
|
| 144 |
+
"supra": {
|
| 145 |
+
"route_accuracy": 0.8575554680320336,
|
| 146 |
+
"code_accuracy": 0.9761060785085992,
|
| 147 |
+
"math_accuracy": 0.9400026257056584,
|
| 148 |
+
"complexity_exact": 0.4629119075751608,
|
| 149 |
+
"complexity_within_1": 0.923723250623605,
|
| 150 |
+
"ms_per_prompt": 14.768008985689887,
|
| 151 |
+
"parse_failures": 1
|
| 152 |
+
},
|
| 153 |
+
"supra_trap": {
|
| 154 |
+
"route_accuracy": 0.908695652173913,
|
| 155 |
+
"code_accuracy": 0.9869565217391304,
|
| 156 |
+
"math_accuracy": 1.0,
|
| 157 |
+
"complexity_exact": 0.33043478260869563,
|
| 158 |
+
"complexity_within_1": 0.8391304347826087,
|
| 159 |
+
"parse_failures": 1
|
| 160 |
+
},
|
| 161 |
+
"note": "labels from the glint pipeline. glint is in-distribution here, supra is not. read the holdout numbers for the fair comparison."
|
| 162 |
+
},
|
| 163 |
+
"supra_own_rows": {
|
| 164 |
+
"val_rows": 91,
|
| 165 |
+
"glint": {
|
| 166 |
+
"route_accuracy": 0.8901098901098901,
|
| 167 |
+
"code_accuracy": 0.9010989010989011,
|
| 168 |
+
"math_accuracy": 0.8131868131868132,
|
| 169 |
+
"complexity_exact": 0.5054945054945055
|
| 170 |
+
},
|
| 171 |
+
"supra": {
|
| 172 |
+
"route_accuracy": 0.967032967032967,
|
| 173 |
+
"code_accuracy": 1.0,
|
| 174 |
+
"math_accuracy": 0.978021978021978,
|
| 175 |
+
"complexity_exact": 0.8571428571428571
|
| 176 |
+
},
|
| 177 |
+
"note": "supra's own 992-row dataset, held out of glint training. these rows are supra's TRAINING data, so supra is scoring a memorised set. published because leaving it out would be dishonest."
|
| 178 |
+
}
|
| 179 |
+
}
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|