A typed-decision model: Qwen/Qwen3.5-0.8B-Base with its tower fine-tuned and a
trained option-scoring head. It answers choice / noul / score questions about a
document in one forward pass, and speaks the
Jev HTTP API.
This checkpoint is seed 17, the seed fixed in advance as primary, not the best of the three:
| typed-decisions | reversed order | MMLU-Pro 1k | reproduces its training run |
|---|---|---|---|
| 0.6175 | 0.621 | 0.269 | 1.0000 |
Re-computed from these exact files after upload-time reload. Kernel stack fla-0.5.2/torch-2.7.1+cu128; full recipe in meta.json.
import sys; sys.path.insert(0, 'code') # after snapshot_download of this repo
from load_release import load_release
model, tok, enc, meta = load_release('.', device='cuda')
These models are trained by an autonomous research loop, so telling us what is wrong with one is the most useful thing you can do. A case where it is confidently wrong becomes a test case and often a hypothesis; a variant you tried that failed removes a branch nobody has to pay for again. Either one belongs in this repository's discussions.
Everything below is this release's own record, unchanged.
The code, this release's record, and every experiment behind it live at github.com/Shanghua-Gao/RSI-Jev.
These models are trained by an autonomous research loop, so telling us what is wrong with one is the most useful thing you can do. A case where it is confidently wrong becomes a test case and often a hypothesis; a variant you tried that failed removes a branch nobody has to pay for again. Either one belongs in this repository's discussions.
Everything below is this release's own record, unchanged.
Describes release v1.0 Β· updated 2026-09-25
Two typed-decision models, 0.8B and 2B. Given a document and a set of typed questions β yes/no, pick-one-of-k, rate-on-a-rubric β they return a calibrated probability for every option in one forward pass, with no generated text.
On the LocalLLaMA/typed-decisions test set β 400 documents, 2,000 decisions β
the 2B model scores 0.662 pooled top-1 and the 0.8B 0.614, against a
majority baseline of 0.5185 and Jev's 0.727.
To download and use a checkpoint, see the root README; for the
HTTP API, ../serve/README.md. This card is the evidence
behind both.
A typed decision is a document (the state) plus one or more questions, each of a declared type:
| type | question | the model returns |
|---|---|---|
noul | yes / no | P(true) |
choice | pick one of k named options | a probability per option |
score | rate against a rubric of 2β10 levels | a probability per level, and their expectation |
Pooled top-1 is the fraction of all 2,000 test decisions whose highest-probability option matches the teacher's modal label. It is the headline throughout, because a model can look good on one question type and fail another.
| base | Qwen3.5-0.8B-Base / Qwen3.5-2B-Base |
| trained | all tower weights except the frozen input embedding (505 M at 0.8B), plus the 7.3 M option_xattn scorer |
| data | n4ze3m/typed-decisions-synth, 6,977 documents carrying the teacher's full distribution per question |
| objective | soft cross-entropy on those distributions, options shuffled per example |
| schedule | 1,500 steps Γ batch 16; tower 5e-6 cosine, head 1e-4 constant. ~13 min/seed on one H100 at 2B, 10 at 0.8B |
| precision | tower bf16 autocast + gradient checkpointing, fp32 master; scorer fp32 outside autocast |
| seeds | three (17/29/43), judged on the mean; the released checkpoint is seed 17, fixed in advance as primary |
| stack | fla-0.5.2 / torch-2.7.1+cu128 β numbers only compare within one kernel stack |
The scorer is a cross-attention head: the hidden state at the decision cue is the query, the mean-pooled option spans are keys and values, and the attended result projects to one logit per option. Because it attends over the whole option set, an option's logit depends on what it competes against.
Four of those rows were forced by a failure. Fine-tuning the tower rather than freezing it, because every frozen variant stalled below the majority baseline whatever the head or the data. The scorer in fp32 outside autocast, which took seven failed experiments to establish. Options shuffled per example, because a causal encoder shows option k only options 1β¦kβ1, and a head trained on a fixed order collapses onto position β one early run picked the last option on 800 of 800 score questions. Three seeds, because the sd across them is 0.011 and single-seed differences smaller than that are noise. All of it is in Β§10.
The benchmark is held out completely, verified rather than asserted: no training document shares a state with the benchmark's train or test split, and none shares even a single 12-word phrase with either.
Full spec in each checkpoint's meta.json; code and a reproduction recipe in
../rsijev/README.md.
| model | base | as written | options reversed | MMLU-Pro 1k | download |
|---|---|---|---|---|---|
| RSI-Jev-v1.0-2B | Qwen3.5-2B-Base | 0.6525 | 0.6525 | 0.3550 | π€ download |
| RSI-Jev-v1.0-0.8B | Qwen3.5-0.8B-Base | 0.6175 | 0.6210 | 0.2690 | π€ download |
Two benchmarks, not three: the middle columns are the same 2,000 questions scored twice, once in the dataset's option order and once reversed. A model answering by position would score differently on the two; the gap here is at most 0.0035. MMLU-Pro is a separate guard metric (Β§4). All figures pooled top-1. Apache-2.0, following their base models.
Three seeds, full test set:
| typed-decisions pooled top-1 | sd | MMLU-Pro 1k | score-mode Brier | |
|---|---|---|---|---|
| 2B | 0.6622 | 0.011 | 0.3573 | 0.111 |
| 0.8B | 0.6140 | 0.007 | 0.2553 | 0.126 |
Against the reference points that give those numbers meaning:
| reference | typed-decisions pooled top-1 | what it tells you |
|---|---|---|
| untrained Qwen3.5-2B-Base | 0.428 | what the training is worth: +0.23 |
| always guess the majority | 0.5185 | the floor any useful model must clear |
| perfect scenario understanding | 0.704 | the dataset card's realistic ceiling |
| Jev | 0.727 | the target; we are 0.065 short |
Where the errors are. Pooled top-1 hides that the benchmark's own teacher is unsure about most of it: 61% of test items have a gold max-probability below 0.67 and carry 75% of the errors. Scored only where the teacher was confident, the 2B model reads 0.784 at gold max-prob β₯ 0.67 and 0.886 at β₯ 0.9. It fits the synthetic teacher it was trained on at 0.850 on held-out synth against 0.662 on the benchmark, so the gap is teacher mismatch rather than underfitting. A useful reference point measured alongside: zero-shot Qwen3.5-4B with a 1,024-token thinking budget scores 0.677 here and 0.964 on the items its teacher was sure of β usable at training time only, since Jev is System One and generates nothing.
Verification. Each released checkpoint was reloaded from disk and re-scored;
all 3,000 of its per-question predictions matched the training run that produced
it β agreement 1.0000 for both. A checkpoint that does not reproduce its own
run is not published. The released checkpoints are fresh runs at seed 17, so the
2B one reads 0.6525 where the experiment that set the headline read 0.6515: two
questions out of 2,000, from non-deterministic GPU kernels. Both are reported
rather than the friendlier one. Served end to end on GPU, a four-question request
including a 64-option choice returns well-formed answers; the worked example and
the answer it measurably returns are in ../serve/README.md.
Sections 5 to 7 are measurements of these checkpoints as served. Each table names
the script that prints it, so any of them can be re-run. Everything is an
NVIDIA DGX Spark (NVIDIA GB10, 122 GiB unified) unless another machine is
named, and all of it moves with GPU load β up to Β±20% between runs.
python scripts/bench.py --model 2b, bf16, median of ten, asking one document 1, 2, 4, 8, 16
and 32 questions. A single per-decision figure would hide the shape of it: reading the document
is paid once, and each decision after that is nearly free.
| document | read once | per decision | RΒ² | 32 together | 32 apart |
|---|---|---|---|---|---|
| 80 tokens | 56 ms | 10.0 ms | 0.994 | 375 ms | 1,869 ms |
| 404 tokens | 100 ms | 9.8 ms | 0.984 | 418 ms | 2,774 ms |
| 1,052 tokens | 188 ms | 10.8 ms | 0.988 | 534 ms | 5,635 ms |
The fixed half grows with the document; the marginal cost of one more decision stays near 10 ms whatever the document. That is the whole argument for asking a batch: thirty-two decisions about one 404-token document take 418 ms together against 2,774 ms one request at a time, because the document is encoded once and every question continues from that cache. Four questions on those same three documents cost 108 / 148 / 241 ms at 2B and 89 / 125 / 198 ms at 0.8B β the smaller model is about 1.2x faster, which turns out not to be enough to build a cascade on (below).
Both versions stay resident at once in 5.1 GiB of 122 (1.45 + 3.61), so model size is not
the constraint here, and all of it is on the plain PyTorch fallback rather than fla's fused
kernels β a floor rather than a ceiling. For contrast, an A100 MIG 3g.20gb answers the
four-question requests in 90 / 93 / 119 ms at 2B.
Speed is worth little if a confident answer is wrong. python scripts/calibration.py bins all 2,000
held-out decisions at 2B by the probability the model gave them, none of it seen in training:
| model says | decisions | actually right |
|---|---|---|
| 90β100% | 29 | 100.0% |
| 70β90% | 413 | 86.7% |
| 50β70% | 697 | 72.0% |
| under 50% | 861 | 48.0% |
Every bin lands at or above its stated confidence, so the model is conservative rather than overconfident β the useful direction for anything routing on a threshold β and zero of 2,000 decisions were confidently wrong (90%+ and incorrect). Overall accuracy is 65.1% against 51.9% for always guessing the majority. The top bin holds only 29 decisions and pins down little on its own; the 413- and 697-decision bins are the ones carrying the claim, and a bin's count moves by one or two between runs as a decision crosses a boundary.
That is worth something only if you use it. python scripts/routing.py ranks every decision by
the probability behind it and keeps the top slice β the question a deployment actually asks,
which accuracy alone cannot answer:
| act on the top | decisions | of those, right |
|---|---|---|
| 100% | 2,000 | 65.1% |
| 80% | 1,600 | 71.5% |
| 60% | 1,200 | 77.0% |
| 40% | 800 | 80.1% |
| 20% | 400 | 88.0% |
Strictly monotone: act on everything and 65% are right, act on the 40% it is surest about and 80% are right, with the rest going to a person. That trade exists only because the probability means something. The sweep is 2,000 decisions in 65 seconds on the Spark, both models resident, nothing generated.
The same script tries the obvious next idea β let the 0.8B answer and escalate what it is unsure about to the 2B β and it loses to simply running the 2B, which costs only 1.17x the 0.8B here, nowhere near the 5β10x a cascade needs. Repeat runs move the routing rows by up to 0.2 points β non-deterministic GPU kernels, not a different model.
| NVIDIA DGX Spark (GB10) | where this is developed and measured. Both versions resident in 5.1 GiB of 122, bf16, ~10 ms per decision. fla is not required; without it PyTorch falls back to a reference DeltaNet path, which is what the numbers above were taken on |
| NVIDIA A100 / H100 / consumer | works the same. Resident weights, measured: 0.8B 1.45 GiB bf16 / 2.83 GiB fp32, 2B 3.61 GiB bf16 / 7.12 GiB fp32 |
| Apple Silicon | --device cpu works and is the safe choice. --device mps may be faster, but PyTorch has no MPS kernels for the Qwen3.5 DeltaNet layers, so they fall back to a reference implementation β we have no Mac to test on, so MPS is unverified. Reports welcome, good or bad |
| CPU only | works, just slow: the 0.8B answered the demo's five-question example in 8.1 s against 142 ms on the GB10 β about 57x, for the same answers: every label identical, every probability within 0.003. Use --versions 0.8b |
The scorer always runs in fp32 β that is the whole lesson of Β§10 β so there is no quantised path yet.
Both are serving-code changes. The weights are unchanged, and so is every accuracy and calibration figure above.
The prefix cache was gated on a constant measured on an A100 with fused
kernels. Break-even is a property of the machine, not the model: on the GB10's plain
torch path, caching won at every size measured (1.09x at 161 tokens to 3.24x at
1,052), and the constant was refusing a 2x speed-up at 404 tokens. The gate now
depends on whether the fused kernels are present, with RSIJEV_MIN_SAVED_TOKENS
to override. The symptom had been sitting in our own published table β a
404-token document timed slower than a 1,052-token one.
confidence was the wrong statistic β and this one changes what the API
returns. The field on a choice or score answer was computed as
1 - H(p)/ln K, chosen when the statistic was undocumented. It is in fact the
peak statistic, (K * p_max - 1) / (K - 1) β TypeSafe documents it for three
options as "(3 x largest probability - 1) / 2". The two agree exactly at a uniform
distribution and at one-hot and nowhere else, which is why 25 wire-contract tests
passed: they pinned only those two points. The gap on real answers is large β a
four-option choice peaking at 0.90 reads 0.869 correctly against 0.692 as
shipped β so anything that thresholded on confidence before this fix was acting
on the wrong number. Accuracy and calibration are unaffected because the
numbers in this record rank by the probability of the chosen option, a different
quantity that did not change. noul answers carry no confidence, which was
always correct here and matches the reference.
choice key can move the
answer; the reference implementation hides keys and is invariant to this.Five attempts came before this one, none released. They are recorded because the release is only interpretable next to them.
| attempt | what changed | typed-decisions pooled top-1 | why it was not the release |
|---|---|---|---|
| zero-shot control | none β the frozen base read out by its own logprobs | 0.3775 | below the majority baseline; "always true" on noul is a token prior, and choice swings 0.3775 β 0.3300 on option order alone |
| frozen + trained readout | a trained cross-attention head on a frozen tower | 0.4850 | still below the baseline. Every frozen variant β readout layer, per-mode taps, residual readout, a KL anchor to the base, symmetrised inference β stalled at 0.47β0.49 |
| frozen + synthetic data | the same head, trained on the synthetic corpus | 0.473β0.483 | the data moved MMLU-Pro (0.184 β 0.240) and Brier (0.335 β 0.218) but not the plateau. The frozen tower was the limit, not the data |
| tower fine-tuned, 0.8B | train the tower itself | 0.6097 | first attempt above the baseline, +0.125 β but three failures to get it running: 79 GB out-of-memory, NaN at a constant tower lr of 2e-5, then logits of 4.7 million at head lr 1e-3 |
| same recipe at 2B | base 0.8B β 2B | 0.6598 | scale helped (+0.05, MMLU-Pro 0.353) but one seed in three diverged, and seven remedies failed to fix it |
That last instability is where the release came from. Seven remedies were tried, each at two seeds against a stability rule fixed in advance: a cosine schedule on the head, head learning rates 5e-5 and 2.5e-5, parameter-free input normalisation, label smoothing 0.02, head weight decay 0.1. Every one reduced the logit spikes; none removed them.
They failed because each treated the optimiser, and the optimiser was not the
problem. Under bf16 autocast, nn.Linear and nn.MultiheadAttention compute
in bf16 whatever dtype their weights are stored in. The scorer β documented,
intended and reported as fp32 β had been computing its attention softmax,
projections and gradients in bf16 in every attempt that trained the tower.
The evidence had been in the logs throughout: attempts with a frozen tower never spiked, and they run without autocast; every tower-training attempt could spike, and which seed did was luck; the head's weight norm moved 0.1% while its logits reached 12,096; and the largest input-feature norms reaching the head stayed flat. A head whose weights barely move cannot produce logits of 12,096 through optimisation. That mismatch is what pointed at precision.
| 2B, max training logit after step 150 | bf16 scorer | fp32 scorer (v1.0) |
|---|---|---|
| seed 17 | 6,464 | 7.8 |
| seed 29 | 12,096 | 7.6 |
| seed 43 | β | 10.8 |
Both columns are runs on the same kernel stack at the same head learning rate, which is the only way they are comparable. A consequence worth stating: head learning rate 1e-3 β the value that diverged at 0.8B and forced the reduction to 1e-4 β is stable once the scorer is fp32. Every learning-rate reduction on the way here had been compensating for the same bug.
A metric can certify a loser. On score, a head reading Spearman +46 had
worse log score and Brier than the untrained control. Spearman is invariant to
monotone reshaping, so it rewarded ordering while the probabilities themselves
got worse. Score mode has been judged on proper scoring rules first ever since.
A readout can be provably blind and still look good. The score options
" 0", " 1", " 2", " 3" all begin with the same space token, so a readout
that looks only at the first token carries no option identity at all β and still
returns a respectable-looking number. The control now reports first-token
collisions, and any arm with collisions is invalid.
A typed-decision model: Qwen/Qwen3.5-0.8B-Base with its tower fine-tuned and a
trained option-scoring head. It answers choice / noul / score questions about a
document in one forward pass, and speaks the
Jev HTTP API.
This checkpoint is seed 17, the seed fixed in advance as primary, not the best of the three:
| typed-decisions | reversed order | MMLU-Pro 1k | reproduces its training run |
|---|---|---|---|
| 0.6175 | 0.621 | 0.269 | 1.0000 |
Re-computed from these exact files after upload-time reload. Kernel stack fla-0.5.2/torch-2.7.1+cu128; full recipe in meta.json.
import sys; sys.path.insert(0, 'code') # after snapshot_download of this repo
from load_release import load_release
model, tok, enc, meta = load_release('.', device='cuda')
These models are trained by an autonomous research loop, so telling us what is wrong with one is the most useful thing you can do. A case where it is confidently wrong becomes a test case and often a hypothesis; a variant you tried that failed removes a branch nobody has to pay for again. Either one belongs in this repository's discussions.
Everything below is this release's own record, unchanged.
The code, this release's record, and every experiment behind it live at github.com/Shanghua-Gao/RSI-Jev.
These models are trained by an autonomous research loop, so telling us what is wrong with one is the most useful thing you can do. A case where it is confidently wrong becomes a test case and often a hypothesis; a variant you tried that failed removes a branch nobody has to pay for again. Either one belongs in this repository's discussions.
Everything below is this release's own record, unchanged.
Describes release v1.0 Β· updated 2026-09-25
Two typed-decision models, 0.8B and 2B. Given a document and a set of typed questions β yes/no, pick-one-of-k, rate-on-a-rubric β they return a calibrated probability for every option in one forward pass, with no generated text.
On the LocalLLaMA/typed-decisions test set β 400 documents, 2,000 decisions β
the 2B model scores 0.662 pooled top-1 and the 0.8B 0.614, against a
majority baseline of 0.5185 and Jev's 0.727.
To download and use a checkpoint, see the root README; for the
HTTP API, ../serve/README.md. This card is the evidence
behind both.
A typed decision is a document (the state) plus one or more questions, each of a declared type:
| type | question | the model returns |
|---|---|---|
noul | yes / no | P(true) |
choice | pick one of k named options | a probability per option |
score | rate against a rubric of 2β10 levels | a probability per level, and their expectation |
Pooled top-1 is the fraction of all 2,000 test decisions whose highest-probability option matches the teacher's modal label. It is the headline throughout, because a model can look good on one question type and fail another.
| base | Qwen3.5-0.8B-Base / Qwen3.5-2B-Base |
| trained | all tower weights except the frozen input embedding (505 M at 0.8B), plus the 7.3 M option_xattn scorer |
| data | n4ze3m/typed-decisions-synth, 6,977 documents carrying the teacher's full distribution per question |
| objective | soft cross-entropy on those distributions, options shuffled per example |
| schedule | 1,500 steps Γ batch 16; tower 5e-6 cosine, head 1e-4 constant. ~13 min/seed on one H100 at 2B, 10 at 0.8B |
| precision | tower bf16 autocast + gradient checkpointing, fp32 master; scorer fp32 outside autocast |
| seeds | three (17/29/43), judged on the mean; the released checkpoint is seed 17, fixed in advance as primary |
| stack | fla-0.5.2 / torch-2.7.1+cu128 β numbers only compare within one kernel stack |
The scorer is a cross-attention head: the hidden state at the decision cue is the query, the mean-pooled option spans are keys and values, and the attended result projects to one logit per option. Because it attends over the whole option set, an option's logit depends on what it competes against.
Four of those rows were forced by a failure. Fine-tuning the tower rather than freezing it, because every frozen variant stalled below the majority baseline whatever the head or the data. The scorer in fp32 outside autocast, which took seven failed experiments to establish. Options shuffled per example, because a causal encoder shows option k only options 1β¦kβ1, and a head trained on a fixed order collapses onto position β one early run picked the last option on 800 of 800 score questions. Three seeds, because the sd across them is 0.011 and single-seed differences smaller than that are noise. All of it is in Β§10.
The benchmark is held out completely, verified rather than asserted: no training document shares a state with the benchmark's train or test split, and none shares even a single 12-word phrase with either.
Full spec in each checkpoint's meta.json; code and a reproduction recipe in
../rsijev/README.md.
| model | base | as written | options reversed | MMLU-Pro 1k | download |
|---|---|---|---|---|---|
| RSI-Jev-v1.0-2B | Qwen3.5-2B-Base | 0.6525 | 0.6525 | 0.3550 | π€ download |
| RSI-Jev-v1.0-0.8B | Qwen3.5-0.8B-Base | 0.6175 | 0.6210 | 0.2690 | π€ download |
Two benchmarks, not three: the middle columns are the same 2,000 questions scored twice, once in the dataset's option order and once reversed. A model answering by position would score differently on the two; the gap here is at most 0.0035. MMLU-Pro is a separate guard metric (Β§4). All figures pooled top-1. Apache-2.0, following their base models.
Three seeds, full test set:
| typed-decisions pooled top-1 | sd | MMLU-Pro 1k | score-mode Brier | |
|---|---|---|---|---|
| 2B | 0.6622 | 0.011 | 0.3573 | 0.111 |
| 0.8B | 0.6140 | 0.007 | 0.2553 | 0.126 |
Against the reference points that give those numbers meaning:
| reference | typed-decisions pooled top-1 | what it tells you |
|---|---|---|
| untrained Qwen3.5-2B-Base | 0.428 | what the training is worth: +0.23 |
| always guess the majority | 0.5185 | the floor any useful model must clear |
| perfect scenario understanding | 0.704 | the dataset card's realistic ceiling |
| Jev | 0.727 | the target; we are 0.065 short |
Where the errors are. Pooled top-1 hides that the benchmark's own teacher is unsure about most of it: 61% of test items have a gold max-probability below 0.67 and carry 75% of the errors. Scored only where the teacher was confident, the 2B model reads 0.784 at gold max-prob β₯ 0.67 and 0.886 at β₯ 0.9. It fits the synthetic teacher it was trained on at 0.850 on held-out synth against 0.662 on the benchmark, so the gap is teacher mismatch rather than underfitting. A useful reference point measured alongside: zero-shot Qwen3.5-4B with a 1,024-token thinking budget scores 0.677 here and 0.964 on the items its teacher was sure of β usable at training time only, since Jev is System One and generates nothing.
Verification. Each released checkpoint was reloaded from disk and re-scored;
all 3,000 of its per-question predictions matched the training run that produced
it β agreement 1.0000 for both. A checkpoint that does not reproduce its own
run is not published. The released checkpoints are fresh runs at seed 17, so the
2B one reads 0.6525 where the experiment that set the headline read 0.6515: two
questions out of 2,000, from non-deterministic GPU kernels. Both are reported
rather than the friendlier one. Served end to end on GPU, a four-question request
including a 64-option choice returns well-formed answers; the worked example and
the answer it measurably returns are in ../serve/README.md.
Sections 5 to 7 are measurements of these checkpoints as served. Each table names
the script that prints it, so any of them can be re-run. Everything is an
NVIDIA DGX Spark (NVIDIA GB10, 122 GiB unified) unless another machine is
named, and all of it moves with GPU load β up to Β±20% between runs.
python scripts/bench.py --model 2b, bf16, median of ten, asking one document 1, 2, 4, 8, 16
and 32 questions. A single per-decision figure would hide the shape of it: reading the document
is paid once, and each decision after that is nearly free.
| document | read once | per decision | RΒ² | 32 together | 32 apart |
|---|---|---|---|---|---|
| 80 tokens | 56 ms | 10.0 ms | 0.994 | 375 ms | 1,869 ms |
| 404 tokens | 100 ms | 9.8 ms | 0.984 | 418 ms | 2,774 ms |
| 1,052 tokens | 188 ms | 10.8 ms | 0.988 | 534 ms | 5,635 ms |
The fixed half grows with the document; the marginal cost of one more decision stays near 10 ms whatever the document. That is the whole argument for asking a batch: thirty-two decisions about one 404-token document take 418 ms together against 2,774 ms one request at a time, because the document is encoded once and every question continues from that cache. Four questions on those same three documents cost 108 / 148 / 241 ms at 2B and 89 / 125 / 198 ms at 0.8B β the smaller model is about 1.2x faster, which turns out not to be enough to build a cascade on (below).
Both versions stay resident at once in 5.1 GiB of 122 (1.45 + 3.61), so model size is not
the constraint here, and all of it is on the plain PyTorch fallback rather than fla's fused
kernels β a floor rather than a ceiling. For contrast, an A100 MIG 3g.20gb answers the
four-question requests in 90 / 93 / 119 ms at 2B.
Speed is worth little if a confident answer is wrong. python scripts/calibration.py bins all 2,000
held-out decisions at 2B by the probability the model gave them, none of it seen in training:
| model says | decisions | actually right |
|---|---|---|
| 90β100% | 29 | 100.0% |
| 70β90% | 413 | 86.7% |
| 50β70% | 697 | 72.0% |
| under 50% | 861 | 48.0% |
Every bin lands at or above its stated confidence, so the model is conservative rather than overconfident β the useful direction for anything routing on a threshold β and zero of 2,000 decisions were confidently wrong (90%+ and incorrect). Overall accuracy is 65.1% against 51.9% for always guessing the majority. The top bin holds only 29 decisions and pins down little on its own; the 413- and 697-decision bins are the ones carrying the claim, and a bin's count moves by one or two between runs as a decision crosses a boundary.
That is worth something only if you use it. python scripts/routing.py ranks every decision by
the probability behind it and keeps the top slice β the question a deployment actually asks,
which accuracy alone cannot answer:
| act on the top | decisions | of those, right |
|---|---|---|
| 100% | 2,000 | 65.1% |
| 80% | 1,600 | 71.5% |
| 60% | 1,200 | 77.0% |
| 40% | 800 | 80.1% |
| 20% | 400 | 88.0% |
Strictly monotone: act on everything and 65% are right, act on the 40% it is surest about and 80% are right, with the rest going to a person. That trade exists only because the probability means something. The sweep is 2,000 decisions in 65 seconds on the Spark, both models resident, nothing generated.
The same script tries the obvious next idea β let the 0.8B answer and escalate what it is unsure about to the 2B β and it loses to simply running the 2B, which costs only 1.17x the 0.8B here, nowhere near the 5β10x a cascade needs. Repeat runs move the routing rows by up to 0.2 points β non-deterministic GPU kernels, not a different model.
| NVIDIA DGX Spark (GB10) | where this is developed and measured. Both versions resident in 5.1 GiB of 122, bf16, ~10 ms per decision. fla is not required; without it PyTorch falls back to a reference DeltaNet path, which is what the numbers above were taken on |
| NVIDIA A100 / H100 / consumer | works the same. Resident weights, measured: 0.8B 1.45 GiB bf16 / 2.83 GiB fp32, 2B 3.61 GiB bf16 / 7.12 GiB fp32 |
| Apple Silicon | --device cpu works and is the safe choice. --device mps may be faster, but PyTorch has no MPS kernels for the Qwen3.5 DeltaNet layers, so they fall back to a reference implementation β we have no Mac to test on, so MPS is unverified. Reports welcome, good or bad |
| CPU only | works, just slow: the 0.8B answered the demo's five-question example in 8.1 s against 142 ms on the GB10 β about 57x, for the same answers: every label identical, every probability within 0.003. Use --versions 0.8b |
The scorer always runs in fp32 β that is the whole lesson of Β§10 β so there is no quantised path yet.
Both are serving-code changes. The weights are unchanged, and so is every accuracy and calibration figure above.
The prefix cache was gated on a constant measured on an A100 with fused
kernels. Break-even is a property of the machine, not the model: on the GB10's plain
torch path, caching won at every size measured (1.09x at 161 tokens to 3.24x at
1,052), and the constant was refusing a 2x speed-up at 404 tokens. The gate now
depends on whether the fused kernels are present, with RSIJEV_MIN_SAVED_TOKENS
to override. The symptom had been sitting in our own published table β a
404-token document timed slower than a 1,052-token one.
confidence was the wrong statistic β and this one changes what the API
returns. The field on a choice or score answer was computed as
1 - H(p)/ln K, chosen when the statistic was undocumented. It is in fact the
peak statistic, (K * p_max - 1) / (K - 1) β TypeSafe documents it for three
options as "(3 x largest probability - 1) / 2". The two agree exactly at a uniform
distribution and at one-hot and nowhere else, which is why 25 wire-contract tests
passed: they pinned only those two points. The gap on real answers is large β a
four-option choice peaking at 0.90 reads 0.869 correctly against 0.692 as
shipped β so anything that thresholded on confidence before this fix was acting
on the wrong number. Accuracy and calibration are unaffected because the
numbers in this record rank by the probability of the chosen option, a different
quantity that did not change. noul answers carry no confidence, which was
always correct here and matches the reference.
choice key can move the
answer; the reference implementation hides keys and is invariant to this.Five attempts came before this one, none released. They are recorded because the release is only interpretable next to them.
| attempt | what changed | typed-decisions pooled top-1 | why it was not the release |
|---|---|---|---|
| zero-shot control | none β the frozen base read out by its own logprobs | 0.3775 | below the majority baseline; "always true" on noul is a token prior, and choice swings 0.3775 β 0.3300 on option order alone |
| frozen + trained readout | a trained cross-attention head on a frozen tower | 0.4850 | still below the baseline. Every frozen variant β readout layer, per-mode taps, residual readout, a KL anchor to the base, symmetrised inference β stalled at 0.47β0.49 |
| frozen + synthetic data | the same head, trained on the synthetic corpus | 0.473β0.483 | the data moved MMLU-Pro (0.184 β 0.240) and Brier (0.335 β 0.218) but not the plateau. The frozen tower was the limit, not the data |
| tower fine-tuned, 0.8B | train the tower itself | 0.6097 | first attempt above the baseline, +0.125 β but three failures to get it running: 79 GB out-of-memory, NaN at a constant tower lr of 2e-5, then logits of 4.7 million at head lr 1e-3 |
| same recipe at 2B | base 0.8B β 2B | 0.6598 | scale helped (+0.05, MMLU-Pro 0.353) but one seed in three diverged, and seven remedies failed to fix it |
That last instability is where the release came from. Seven remedies were tried, each at two seeds against a stability rule fixed in advance: a cosine schedule on the head, head learning rates 5e-5 and 2.5e-5, parameter-free input normalisation, label smoothing 0.02, head weight decay 0.1. Every one reduced the logit spikes; none removed them.
They failed because each treated the optimiser, and the optimiser was not the
problem. Under bf16 autocast, nn.Linear and nn.MultiheadAttention compute
in bf16 whatever dtype their weights are stored in. The scorer β documented,
intended and reported as fp32 β had been computing its attention softmax,
projections and gradients in bf16 in every attempt that trained the tower.
The evidence had been in the logs throughout: attempts with a frozen tower never spiked, and they run without autocast; every tower-training attempt could spike, and which seed did was luck; the head's weight norm moved 0.1% while its logits reached 12,096; and the largest input-feature norms reaching the head stayed flat. A head whose weights barely move cannot produce logits of 12,096 through optimisation. That mismatch is what pointed at precision.
| 2B, max training logit after step 150 | bf16 scorer | fp32 scorer (v1.0) |
|---|---|---|
| seed 17 | 6,464 | 7.8 |
| seed 29 | 12,096 | 7.6 |
| seed 43 | β | 10.8 |
Both columns are runs on the same kernel stack at the same head learning rate, which is the only way they are comparable. A consequence worth stating: head learning rate 1e-3 β the value that diverged at 0.8B and forced the reduction to 1e-4 β is stable once the scorer is fp32. Every learning-rate reduction on the way here had been compensating for the same bug.
A metric can certify a loser. On score, a head reading Spearman +46 had
worse log score and Brier than the untrained control. Spearman is invariant to
monotone reshaping, so it rewarded ordering while the probabilities themselves
got worse. Score mode has been judged on proper scoring rules first ever since.
A readout can be provably blind and still look good. The score options
" 0", " 1", " 2", " 3" all begin with the same space token, so a readout
that looks only at the first token carries no option identity at all β and still
returns a respectable-looking number. The control now reports first-token
collisions, and any arm with collisions is invalid.