Teaching an open model to say how sure it is

A single forward pass, one LoRA epoch, and a size study from 0.8B to 27B.

We turned open Qwen models into typed decision models that return calibrated probabilities for yes/no, pick-one and rating questions in one forward pass, with no generated text. One LoRA epoch makes every model from 2B up accurate and well calibrated on familiar tasks. Calibration on task types it never saw is where size matters, and where the work is still unfinished.

01 · What we were trying to find out

A lot of what language models do inside software isn’t writing. It’s deciding. Is this ticket urgent? Which team gets it? Is this shell command safe to run? For decisions like these you don’t want a paragraph back. You want a probability, and you want that probability to mean something: when the model says 80%, it should be right about 80% of the time.

In September, TypeSafe launched Jev, a closed “System One” model built for exactly this. You send it a piece of state and a set of typed questions: yes/no, choice (pick one of up to 255 options) or score (a 2-to-10-level rating). It answers all of them at once with probabilities instead of text. It’s fast and cheap, and it’s pitched as calibrated. Independent tests have been less kind to that last claim. One study measured a calibration error of 0.107 on synthetic support tickets, about four times its noise floor. TypeSafe’s own docs show a question and its negation getting probabilities that add up to 1.19.

We didn’t benchmark Jev itself; that’s for a later post, once we have an account. Jev set the question:

Can an open model on a single consumer GPU return typed, calibrated probabilities in one forward pass, and does that calibration hold on kinds of tasks it never trained on?

02 · The setup

The readout: probabilities without generating anything

The core trick is simple. For each question we build a prompt: the state, the question, and the options, each tagged with a one-token label (A, B, C… for choices, 0–9 for rating levels, yes/no for yes/no). Then we run one forward pass and look at the model’s next-token distribution at the very last position. The probability mass on each label is the answer. We renormalize over the labels, and that’s the distribution we report.

Nothing is decoded, so there’s no sampling and no parsing of free text, and a 77-option question costs the same single pass as a yes/no one. It also means we measure the model’s own probabilities rather than probabilities it writes out as text, and that turns out to matter a lot (§4).

One architectural wrinkle changed the plan. The backbone family, Qwen3.5, is a hybrid: three of every four layers are Gated DeltaNet linear attention, which carries a single running state along the sequence rather than an attention matrix. The usual way to ask many questions about one state in a single packed sequence is a tree attention mask, and that simply doesn’t exist for those layers. So each question gets its own pass for now. Sharing the encoded state across questions is an optimization for later.

What we measured on

We built an eval set from public datasets, grouped into seven task families:

FamilySourceQuestion typeSeen in training?
EntailmentMultiNLIchoice + yes/noyes
Reading comprehensionRACEchoice (4)yes
News topicAG Newschoice (4)yes
Star ratingsYelp reviewsscore (5 levels)yes
Negation pairsBoolQtwo yes/noyes
Banking intentBanking77choice (77)never
Offensive tweetsTweetEvalyes/nonever

That’s 2,701 questions. The two bold families are the real test. Nothing like them appears in training: no intent classification and no toxicity data of any kind. Getting calibration right on familiar tasks is the easy half; the question is whether it transfers.

What we trained

One LoRA epoch (rank 16) on 29,001 questions from the training splits of nine families: the five in-distribution ones, plus DBpedia topics with 2-to-14 random options, science multiple choice, Amazon star ratings and sentiment pairs. The loss is a sum of proper scoring rules on the readout’s own distribution: log loss, a Brier term, a ranked-probability term for ratings, and a penalty whenever a question and its negation don’t add up to 1. Proper scoring rules are the standard tool for this job, because the loss is minimized exactly when the stated probabilities match reality.

Then we ran that identical pipeline on five models: Qwen3.5 at 0.8B, 2B, 4B and 9B, and Qwen3.8 at 27B.

03 · How we measured it

Accuracy is the plain fraction of questions where the top answer was right.

Calibration error (ECE) is the headline number. Group every answer by the model’s confidence in its top choice, 15 bins from 0 to 1, and within each bin compare the average confidence to the actual accuracy. ECE is the weighted average of those gaps:

ECE=∑bnbN ∣ confb−accb ∣\text{ECE} = \sum_{b} \frac{n_b}{N}\,\bigl|\,\text{conf}_b - \text{acc}_b\,\bigr|

An ECE of 0.05 means the model’s stated confidence is off by about five percentage points on average. One caveat matters a lot here: with only 300 questions per family, even a perfectly calibrated model shows an ECE of about 0.02–0.06, just from sampling noise. We report that noise floor next to every number, and anything near it is indistinguishable from perfect.

The easiest way to see calibration is a reliability curve: stated confidence on the x-axis, actual accuracy on the y-axis. A calibrated model sits on the diagonal. A curve that sags below the diagonal belongs to an overconfident model.

Two reliability plots for the 9B model. On trained task types, both curves track the diagonal, with ECE 0.055 before training and 0.020 after. On unseen task types, the before-training curve sags well below the diagonal (ECE 0.136) and the after-training curve sits close to it (ECE 0.039).
The 9B before and after one LoRA epoch. On familiar tasks it was already close to the diagonal. On unseen task types it was badly overconfident before training: when it said 70%, it was right about half the time. After training it’s close to the line on both.

Negation error checks internal consistency. We ask “is the answer yes?” and “is the answer no?” about the same passage, and measure how far the two probabilities are from summing to 1. The target is under 0.05.

04 · Two baselines, and a surprise

The obvious baseline is to ask a strong model for its probabilities. We prompted a 27B model to write them out as JSON. It was accurate, and overconfident on every question type, with calibration errors of 0.06–0.11. Its probabilities were also coarse: it answered 0.95 or 0.05 almost every time. A model writing a number is telling you what a confident answer sounds like, not reading its own uncertainty.

The single-pass readout, before any training, was already better calibrated than that on yes/no and choice questions (ECE 0.03–0.06 on the 9B), just by reading the probabilities the model already has. Its weak spots were accuracy, rating questions (badly overconfident) and consistency. On negation pairs it had a yes-bias: “is the answer yes?” and “is the answer no?” both leaned yes, giving a negation error of 0.21.

The surprise concerned which checkpoint to start from. A common argument says the chat-tuned version of a model has had its calibration damaged by preference training, so you should start from the raw pretrained “base” model. We tested both on the 9B, and the argument didn’t hold here: the chat-tuned model was better calibrated than the base model, and more accurate, with every question type pooled. Everything that follows starts from the chat-tuned models.

05 · What one LoRA epoch did, from 0.8B to 27B

Two panels of before-and-after accuracy by model size. Left, choice questions on trained task types: after training every size lands between 0.85 and 0.93, up from 0.59 to 0.87 before. Right, the unseen 77-way banking intent task: 0.8B and 2B stay near chance before and after (0.04 to 0.21), while 4B, 9B and 27B reach 0.72, 0.75 and 0.81.
Hollow circles are before training; filled are after one LoRA epoch (two dots where a model was trained with two seeds). On familiar tasks, training closes most of the gap between sizes. On the unseen 77-way task, the small models never get started, and size is what matters.

Here’s the scorecard after training. Every cell is accuracy / calibration error; the 4B and 9B rows are the mean of two training seeds.

After LoRAYes/noChoiceRatingNegation errorTraining time
0.8B0.86 / 0.0210.85 / 0.0230.63 / 0.0590.049~12 min†
2B0.90 / 0.0160.89 / 0.0270.62 / 0.0720.03816 min
4B0.92 / 0.0370.90 / 0.0300.66 / 0.0730.02334 min
9B0.93 / 0.0280.91 / 0.0230.68 / 0.0460.02451 min
27B0.94 / 0.0180.93 / 0.0210.70 / 0.0510.019152 min
27B, prompted0.91 / 0.0370.87 / 0.0730.63 / 0.1140.046—

Trained task types only. Rating questions have 300 items, so their ECEs sit on a noise floor of about 0.06. Times are one epoch on RTX 5090s: the 0.8B–9B on two cards, the 27B split across two. †The 0.8B’s run wasn’t timed; the estimate is from its logged throughput.

Three findings stand out.

1. On familiar tasks, the recipe does most of the work

Before training, choice accuracy ran from 0.59 (0.8B) to 0.87 (27B). After one epoch, every model from 2B up lands between 0.89 and 0.93, with calibration errors around 0.02–0.04, mostly at the noise floor. Rating questions go from near chance at the small end (0.21 for the 0.8B, against a chance level of 0.20) to 0.62–0.70 at every size. And the negation problem is solved everywhere: 0.16–0.41 before training, under 0.05 after, at every size.

Practical reading: if you know which kinds of decisions you need, a 2B model and a quarter hour of training get you calibrated, typed answers that match the 27B we prompted.

2. On unseen task types, there’s a size threshold

The 77-way banking intent task is where the sizes separate. The 0.8B and 2B are near chance both before and after training (0.04–0.21). The 4B, 9B and 27B reach 0.71–0.81. Training adds 2–9 points for those larger models, but it doesn’t create the ability in a model that lacks it. Somewhere between 2B and 4B, the model starts being able to read 77 option labels and pick the right one.

3. Training fixes overconfidence on unseen tasks, for the larger models

Calibration error on the two unseen task types by model size, before and after training, with a target line at 0.05. On offensive tweets, trained models sit between 0.03 and 0.08. On 77-way banking intent, the 0.8B and 2B stay far above target at about 0.33, the 4B and 9B land near 0.09 after training, and the 27B reaches 0.06.
Calibration error on the two task types that never appeared in training. The 9B roughly halves its error on both, and does so on both training seeds. No size gets the 77-way task under the 0.05 target; the closest is the 27B at 0.062.

Before training, the 9B and 27B were confidently wrong on unseen tasks, with calibration errors of 0.13–0.19 on 77-way intent. One epoch on other tasks cut that roughly in half: 0.192 → 0.093 for the 9B and 0.133 → 0.062 for the 27B. For the 9B we trained twice with different seeds and got 0.093 and 0.092. The 27B’s result is a single run.

Nobody hit the goal, though. The target was under 0.05 on unseen task types, and the best result on the 77-way task is 0.062. That’s the open problem the rest of this project is about.

06 · The lessons that weren’t in the plan

A finding that didn’t survive a second seed

After the first round, the 4B looked like it had an interesting story: training made its calibration on unseen tasks worse (0.049 → 0.071 on tweets, 0.079 → 0.106 on intent). It fit a tidy narrative about mid-sized models over-fitting to their training tasks. We ran a second seed before believing it. Seed 2 went the other way: 0.049 → 0.033 and 0.079 → 0.076. The effect was noise.

Lesson: on 300-question eval sets, any calibration difference of about 0.03 or less needs at least two seeds before it counts as a finding. Its sign can flip with the next run. The 9B’s improvement, by contrast, reproduced to within 0.003. That’s what a real effect looks like at this sample size.

Two of our “findings” were measurement bugs

The first time we measured negation, the model said “yes, 95%” to both “is the answer yes?” and “is the answer no?”, which looks like a spectacular consistency failure. It was our prompt. We’d asked “is the answer no?” and then asked for “the probability that the answer is yes”, which is ambiguous. With explicit criteria on each question, the pairs came out as complements.

Separately, a numerical comparison made the fast GPU kernels for the linear-attention layers look wrong: probabilities differed by up to 0.05 from the reference implementation. In full precision they agreed to 0.001. The gap came entirely from bf16 arithmetic, the same for both implementations.

Lesson: when a result is dramatic, rule out the measurement first. Both of these would have made a good paragraph, and both were false.

The extra loss terms didn’t earn their keep

We trained the 9B a second time using only plain log loss, dropping the Brier, ranked-probability and consistency terms. It matched the full objective within noise on every metric, including negation consistency (0.022 both ways). The consistency penalty turned out to be redundant: the training data already contains both halves of every negation pair with correct labels, so ordinary log loss teaches the model that “no” is the complement of “yes”.

Four GPUs aren’t faster than two when they talk over PCIe

For the 27B we tried sharding the model across all four cards (FSDP). It trained correctly, matching our verified two-GPU path to within 0.003 on every loss term, but it ran at 1.0 items per second against 2.3 for simply splitting the model across two cards. Our cards talk through the host’s PCIe bridge, with no direct GPU-to-GPU link, and FSDP moves every layer’s weights between cards on every pass. We kept the two-card split.

07 · Try it

For 48 hours after this post goes up, the 4B model (with its second-seed adapter) is live on one of our GPUs. The 4B is the sweet spot of the study: nearly the 9B’s accuracy on familiar tasks, and it crosses the size threshold on unseen ones. Write some state (a support ticket, a request for an agent, any JSON blob), ask it typed questions, and watch the probability distributions come back. The request and response use the same shape as Jev’s API.

A few things to know. It answers in about a quarter of a second, with one forward pass per question and no text generated. It’s rate-limited and doesn’t store what you send, but don’t paste anything private. Choice questions are capped at 26 options, so the 77-way intent task won’t fit. And it’s the model from the table above, with the same strengths and blind spots: it’s good at the kinds of decisions it trained on, and less sure elsewhere.

08 · Where we go next

  • More unseen task types, and more seeds. Two unseen families is a thin base for the transfer claims, and three of our five sizes were trained once. Both are cheap to fix, and both come before any stronger claim.
  • Close the gap on unseen tasks. The 0.05 target isn’t met on the 77-way task. The obvious lever is a broader training mixture (more kinds of decisions, not more of the same), which is what the size study suggests matters.
  • Separate size from generation. Our 27B is a newer model generation than the rest, so the top of the curve changes two things at once. A same-generation ~27B would untangle that.
  • Measure Jev itself. Our evaluation harness already speaks Jev’s API. With an account, the head-to-head comparison is a single command, which is the comparison this whole project was set up to make.

All of this ran on a homelab box with four RTX 5090s. Numbers are from held-out evaluation; seeds and noise floors are noted where they matter. Built on the open Qwen3.5 and Qwen3.8 models, the flash-linear-attention kernels, and public datasets from the MultiNLI, RACE, AG News, Yelp, BoolQ, Banking77 and TweetEval authors. Thanks to all of them.

Ugg want talking-rock that answer quick questions. Not long story. Just “yes or no?”, “which one?”, “how much, 1 to 5?”. And Ugg want rock to say HOW SURE. When rock say “80% sure”, rock should be right 8 times out of 10. Not lie about sure-ness.

Shiny closed rock called Jev do this. But nobody can look inside shiny rock, and some tribes checked it: shiny rock say sure-numbers that are a bit off. Ugg ask: can Ugg make open rock that do same thing, on Ugg’s own cave fire?

Ugg trick: no talking at all. Ugg show rock the question and label each answer A, B, C. Rock think ONE time. Ugg peek at how much rock lean toward A, toward B, toward C. That lean IS the answer. Very fast. No words.

First Ugg try easy way: just ASK big rock “how sure are you?” Big rock say “95%!” almost every time. Big rock is show-off. Peeking at the lean works much better than asking.

Then Ugg give rocks one round of practice (LoRA) on many question kinds. Ugg do this for five rock sizes: tiny (0.8B) to huge (27B).

What Ugg learn:

  • Question kinds rock PRACTICED: every rock 2B and bigger get very good (about 9 of 10 right) and honest about sure-ness. Even small rock. Practice do most of the work.
  • “Is it yes?” and “is it no?” should add to 100%. Before practice rocks say “yes” to both, like dummy. After practice, all rocks fixed.
  • Question kinds rock NEVER saw: here size matter. Tiny rocks guess randomly on a 77-choice question, even after practice. Rocks 4B and bigger can do it. Something happen between 2B and 4B.
  • Bigger rocks get more honest on never-seen questions after practice. But none reach the goal yet. Best rock still off by 6 points on the 77-choice question. Goal was less than 5.

Traps Ugg fell in:

  • Ugg thought medium rock (4B) got WORSE on never-seen questions after practice. Ugg try again with different starting luck. Now it got better! It was just luck. Always try twice before telling tribe.
  • Ugg thought rocks said “yes” to both “is it yes?” and “is it no?” Actually Ugg asked the question in a confusing way. Ugg’s fault, not rock’s.
  • Four fire pits slower than two, because fire pits pass rocks back and forth through slow tunnel.

You can poke the 4B rock yourself for two days (see “Try it”). After that, rock go back to sleep.

Next: give rocks more KINDS of questions to practice, try more luck-rolls, and finally measure shiny Jev rock head to head.