embedding

Blog/·18 min

Last updated

What the physics engine is worth

Four things measured rather than asserted: what the engine is worth in labelled rows, how the systems do cold on six corpora none of them were fitted on, what a calibration survives when the data moves, and who this is for. Plus the 27B against the hosted model on vega-U's own board.


The short version

  • The physics engine is worth 8 to 63 labelled rows against a plain linear readout of the identical frozen features, on five of six corpora. On the sixth, a linear probe never caught it at any label count the corpus could supply.
  • Cold, the hosted model leads on the six corpora. On vega-U's own held-out board, where its largest reader appears, the 27B is 1.75 points behind it, inside the board's own sampling error, and ahead outright on the ordinal score rail, 0.497 against 0.398.
  • Fitting is what closes the gap, and it is cheap: a head on 40% of a corpus took the mean from 0.530 to 0.679 and past the hosted model outright on one corpus.
  • A change in the class mix breaks calibration; a change in the text does not. A calibration fitted on one corpus held on a separately collected one at 90.0% coverage against a 90% target. Sixteen labelled rows restore it and it stops moving by sixty-four, so the recurring refit is not field-engineering work.
  • We are better calibrated than the hosted model on all six corpora, by 6.5x on the one where our accuracy is worst.
  • On speed: a decision is 114 to 326 ms locally against 297 to 380 ms for the hosted call, with laya at 16 to 84 ms. No network, no per-request bill, and a fitted head adds 13.9 microseconds on top of the read.
  • Fitting and calibrating takes 26 seconds on 240 labelled rows, nearly all of it reading each row once at 109 ms. The head fit itself is 43 ms, the cross-validation 208 ms, and the temperature and conformal quantile together 6 ms. No GPU hour, no fine-tuning run, nothing in the language model is trained.
  • Three gaps this run opened are now closed. The abstain flag fired on zero of 860 fitted rows and could not have fired on any, so there is now a review band on a confidence line you set or fit to a precision target. The 703.8 ms CPU figure was mostly a thread setting: 135 ms with the right reader and thread count, a 5.2x cut, with identical answers. And recalibration and the drift check that tells you to run it are one command each.
  • The first customer is a regulated financial institution in a jurisdiction with a data-residency rule, because that is the one buyer for whom running the model inside the perimeter is the product rather than a preference.

Where this sits, as of today

Every other system in this class is one model, fine-tuned or post-trained. The decision capability lives inside that model's weights, so changing which model reads for you means shipping a different checkpoint. vega-U splits the two: a 14.3M engine in a shared space, and a small linear port per reader.

systemwhat it isswap the reader?
Jev 1.14, TypeSafe AIclosed weights, hosted from a single US region, trained with reinforcement learning for calibrated decisionsno
openjev, djev, lichenDiffusionGemma 26B-A4B behind a Jev-compatible /v1/systemone API, Apache-2.0, vLLM or MLXno, the LLM is the decider
Perplexity Decider v1 27Ba fine-tune of Qwen3.8-27B with a 250K contextno
Microsoft Decision-1a post-training of Qwen3.5-9Bno
Cloudflare Clefnon-autoregressive, closedno
Mapika Decider0.8B to 35B-A3B on Qwen3.5 and Gemma 4, Apache-2.0separate checkpoints, not attachment
layaa 421M ModernBERT-large encoder with a typed decision head, trained end to end, Apache-2.0no
vega-Ua 14.3M engine in a shared 1024-d space, byte-identical behind every reader, plus one linear port per readeryes

Two things that table should not be read as claiming. The mechanism is not new research: aligning one frozen model's representation space to another is model stitching, with prior art from relative representations, Procrustes latent translation and latent functional maps. What is unusual here is the shape: the decider is deliberately tiny and frozen, so the reader is the only part that changes, and the alignment is label-free and runs as one call on text you bring.

And the rows other than ours come from their own documentation and third-party write-ups rather than from runs of our own, so treat the descriptions as a map of the field rather than as a benchmark. The only systems measured anywhere on this page are vega-U, laya and Jev.

Four things about this model are worth measuring rather than asserting, and all four fit on one laptop in an afternoon.

  1. Ablate the physics engine against a linear softmax head with everything else held fixed, and say how much difference the engine actually makes.
  2. Run all three systems cold, zero-shot, with nothing tuned, on corpora none of them were fitted on.
  3. Find out what a calibration is worth once the data moves, how many labels it takes to put it back, and how often that has to happen.
  4. Say who this is for, and why they would run a model themselves rather than rent one.

The short version, before the tables: we lose the cold comparison on all six corpora, clearly. The physics engine is worth between 8 and 63 labelled rows against a plain linear readout of the identical features, which is a much smaller number than our own marketing implies. With a hundred to a couple of hundred of your own labels a local head matches or beats the hosted model on three of six, and on reading comprehension nothing we run gets close. Fitted and calibrated, which is how vega-U would actually ship, it passes the hosted model on one corpus and closes most of the gap on two more.

On the hosted model's own kind of ground, zero-shot, our largest reader is 1.75 points behind it and ahead of the other open model, on a board whose own sampling error is wider than that gap. It is ahead outright on the ordinal score rail, which is what a risk score or a severity band is. And it has never been fitted, which is the measurement that moved the 0.8B from 0.530 to 0.679.

Where else we win is mostly the probability rather than the answer, and there is more of it than we first wrote down. Some configuration of vega-U is better calibrated than the hosted model on all six corpora, by 6.5x on the one where our accuracy is worst. Our conformal guarantee came out at exactly its 90% target after the corpus changed underneath it, while the hosted model's fell under target at 87.0%. We are faster on all six and the run cost nothing on our side. And averaging two small readers' probabilities, which needs no new training, is level with or ahead of the hosted model on three of six. Those are set out after the losses.

What was run

Six corpora, 2,155 rows in all, none of them on vega-U's published benchmark board. Every corpus is posed as the same kind of question, a choice with one line of criteria per option, so one scoring path covers all of them and the hosted model is asked the identical thing. Classes are balanced where the corpus allowed it, because accuracy on a corpus that is mostly one class rewards answering that class always, and one of the systems here does exactly that on two of these corpora.

corpusdomainrowsoptionstestmajority
tweet-sentimentconsumer and social text399315933%
tweet-sentiment-shiftedthe same question, a different collection: the drift twin300312033%
finance-sentimentfinancial news headlines399315933%
moderationtrust and safety moderation399315933%
boolqreading a passage to answer a question about it400216050%
counterfactuale-commerce reviews, a label nobody guesses from the topic258210378%

One fixed, stratified test split per corpus carries every number below, so the zero-shot rows and every fitted head are scored on identical cases. The rest of each corpus is the pool the heads draw their labels from. Seed 7, inputs truncated to 1,200 characters before any system sees them, the hosted model pinned to jev-1.14.0 rather than jev-latest so a re-run scores the same model.

Hardware: one Apple M5, 16 GB, macOS 26.5.2. One process at a time, two BLAS threads. The Jev calls went one at a time over the public API from this machine and cost $0.0373 in total, with 0 failed requests.

A correction we owe you first

While setting this up we found that every local latency figure we have published, including the ones in the guard benchmark and on the benchmarks page, was described as measured on CPU. It was not. vegaml.load() with no device argument prefers CUDA, then Apple's Metal, then CPU, and on this machine that means Metal. Same cases, same sheet, one process at a time:

deviceloadshort, 40 caseslong, 20 cases
Metal (mps)5.0 s119 ms232 ms
CPU, 2 threads3.6 s704 ms820 ms

CPU is 5.9x slower on short text and 3.5x on long passages. So “runs on CPU” is true, and “119 ms on CPU” was not. The honest figure for a server with no GPU is about 704 ms a decision on this class of machine. We have corrected the other pages rather than quietly leaving them.

One: what the physics engine is worth

The ablation holds everything fixed except the readout. The same frozen Qwen3.5-0.8B reader produces the same pooled feature vector for the same (state, question) pair, and then either

Because the engine needs no labels and the head does, the comparison has to be a curve rather than a pair of numbers. The question is not “which is better”, it is how many labelled rows the engine is worth. Five seeds per point, the subsample stratified, the test split fixed.

engine, zero-shotlinear softmaxPCA + linearchance
tweet-sentiment
.25.50.751engine 0.616663240
tweet-sentiment-shifted
.25.50.751engine 0.583630126
finance-sentiment
.25.50.751engine 0.547663240
moderation
.25.50.751engine 0.421663240
boolq
.25.50.751engine 0.575864240
counterfactual
.25.50.751engine 0.43783299

x: labelled rows given to the head · y: accuracy on the held-out split, axis from 0.25

corpusenginelinear head catches itbest headat
tweet-sentiment0.61630 rows0.686240 rows
tweet-sentiment-shifted0.58363 rows0.665126 rows
finance-sentiment0.54730 rows0.836240 rows
moderation0.42115 rows0.648240 rows
boolq0.575never, within the cap0.550240 rows
counterfactual0.4378 rows0.90199 rows

So, stated plainly: on five of six corpora the physics engine is worth between 8 and 63 labelled rows against a linear readout of the identical features. That is the honest size of it. It is not nothing, and it is not the order of magnitude a reader would infer from the way we usually describe the engine.

The sixth corpus is the interesting one. On boolq no head reached the engine's accuracy at any label count we could afford, so there the settling computation is extracting something a linear probe on the same vectors does not get to. Both are poor in absolute terms, 0.575 against a 0.550 ceiling for the heads on a two-option question, so read it as a direction rather than a win.

What the engine gives that a head does not

Accuracy is the wrong column to settle this on, and this is the part of the ablation we would actually defend. A linear head has a confidence in the sense that a softmax has a maximum. It has no calibration, no conformal set and no abstain bit. Compare the calibration error of the two readouts at the point where their accuracy is comparable:

corpusengine ECEhead ECE, 8-16 rowshead ECE, capabstains
tweet-sentiment0.0990.3970.21775%
tweet-sentiment-shifted0.1100.4100.20969%
finance-sentiment0.0850.4600.08899%
moderation0.0200.5220.262100%
boolq0.1370.4370.46855%
counterfactual0.1140.2880.087100%

A head fitted on a dozen rows is confidently wrong: its calibration error sits between 0.288 and 0.522 at the low end of the curve, against an engine that is between 0.020 and 0.137 with no labels at all. On the moderation corpus the engine is the best-calibrated thing in this entire post at an ECE of 0.020, while being the least accurate at 0.421. It is accurately reporting that it does not know, which is a real property and a poor headline.

That is the trade the engine actually makes, and it is worth stating in one line: the engine buys you a usable probability with zero labels, not accuracy. If you have a few hundred labels and do not need the probability, a linear head on the same features is cheaper, faster and often better, and it is in the package as TTTHead.

Two: cold, out of the box

Nothing fitted, nothing tuned, the same sheet and the same truncated strings for all three. This is the table we would have preferred not to publish.

The reader here is the 0.8B, with gemma-1b beside it, because those are the two that run on the machine this was measured on. The 27B is further down, on vega-U's own board, which is the only place a reader that size appears.

tweet-sentiment(chance 33%, majority class 33%)

systemaccmacro F1ECEAURCacc @50%top pickms
jev 1.14.0*0.6920.6700.1810.1420.88747%323
laya-typed0.6600.6640.0910.2500.76240%16
vega-U engine, 0.8B reader0.6160.6170.0990.2680.70049%115

tweet-sentiment-shifted(chance 33%, majority class 33%)

systemaccmacro F1ECEAURCacc @50%top pickms
jev 1.14.0*0.7000.6850.1870.1710.80047%297
laya-typed0.6670.6670.0840.1910.78334%19
vega-U engine, 0.8B reader0.5830.5900.1100.2920.66744%114

finance-sentiment(chance 33%, majority class 33%)

systemaccmacro F1ECEAURCacc @50%top pickms
jev 1.14.0*0.8490.8440.0730.0360.97538%331
laya-typed0.7550.7590.1760.1020.92546%30
vega-U engine, 0.8B reader0.5470.5390.0850.3080.70045%180

moderation(chance 33%, majority class 33%)

systemaccmacro F1ECEAURCacc @50%top pickms
jev 1.14.0*0.7610.7640.1280.1270.87545%343
laya-typed0.6670.6670.1620.2060.72557%38
vega-U engine, 0.8B reader0.4210.3850.0200.5580.45076%222

boolq(chance 50%, majority class 50%)

systemaccmacro F1ECEAURCacc @50%top pickms
jev 1.14.0*0.9190.9190.0640.0300.97553%349
laya-typed0.6250.5940.1700.2360.72578%84
vega-U engine, 0.8B reader0.5750.5050.1370.2890.67588%326

counterfactual(chance 50%, majority class 78%)

systemaccmacro F1ECEAURCacc @50%top pickms
jev 1.14.0*0.8450.8080.0800.0421.00066%380
laya-typed0.7770.5380.1050.1450.90494%27
vega-U engine, 0.8B reader0.4370.4370.1140.5760.40479%173

Jev wins the accuracy column on all six. laya is second on all six. Our zero-shot engine is last on all six, and on two of them it is worse than guessing the majority class: 0.421 on moderation and 0.437 on counterfactual, where the majority class alone would score 33% and 78%. The “most-picked option” column shows why: on those two it answers one option 76% and 79% of the time. A guard against exactly this is the degenerate() check we shipped for the guard work, and it would fire here.

The gap on boolq is the largest and the most instructive: 0.919 against 0.575. That is a reading-comprehension task over a passage, and the reader doing the reading for us there is a 0.8B. Our own per-source board says the same thing in the other direction: long-context accuracy goes from 0.831 to 0.941 between the 0.8B and the 27B reader. The answer to boolq is a bigger reader, which costs a GPU, not a better engine.

Two things do go our way, and both are about the probability rather than the answer. Jev's calibration error on the two social corpora is 0.181 and 0.187, against 0.099 and 0.110 for the engine. That is not a fluke of our setup: the independent RLCDAlignBench study reports a median per-benchmark ECE of 0.168 for Jev against a null of 0.074, with 24 of 31 benchmarks above the null's 95th percentile, and attributes it to a base-rate mismatch rather than to bad ranking. Our 0.18 lands on their 0.168. Their framing is the useful one: Jev's probabilities rank extremely well, which is what its AURC column here shows, and they do not transfer as thresholds.

And on speed, the local models win by the length of the network. laya answered in 16 to 84 ms and the vega-U engine in 114 to 326 ms, both on this laptop's GPU, against 297 to 380 ms for the hosted call. Note that the hosted figure is a third measurement and a third answer: the same pinned model answered at a p50 of 775 ms in our guard run and 412 ms in the one before it. Accuracy reproduces across those runs and latency does not, so a single run of hosted latency should not be quoted as a specification, including by us.

How many of your own labels it takes to catch the API

This is the number that decides whether anyone would run a model themselves rather than rent one. The curve above, read against the hosted row:

corpusjevbest local headlabelsgap
tweet-sentiment0.6920.686240-0.6 pts
tweet-sentiment-shifted0.7000.665126-3.5 pts
finance-sentiment0.8490.836240-1.3 pts
moderation0.7610.648240-11.3 pts
boolq0.9190.550240-36.9 pts
counterfactual0.8450.90199+5.6 pts

On counterfactual a linear head on frozen features passes the hosted model at 64 labelled rows. On finance-sentiment and tweet-sentiment it lands within about a point with a couple of hundred, and both curves are still rising at the cap. On moderation it closes about half the gap, and on boolq it closes none of it.

We are not going to round that up into a claim. What it supports is a narrow statement: for a short-text classification decision where you have a few hundred labelled examples of your own, a model on your own hardware is competitive with the hosted one, and the remaining gap is worth less than the deployment properties to some buyers and worth more to others. For reading comprehension over documents with a small reader, it is not competitive at all.

The 27B against the hosted model, on vega-U's own board

The corpora above are the ones this post chose, and the reader on them is the 0.8B because that is what fits on a laptop. vega-U also publishes its own zero-shot board, four sources it never trained on, and that board is where the larger readers appear. It is the only ground on which a 27B reader and the hosted model can be compared, so it carries the headline comparison.

Three of its four sources rebuild verbatim from the training notebook's own converters: the same public dataset and split, the same answer type, the same instruction wording and the same criteria. That gives 1,200 items on which laya and the hosted model could be run for the first time.

systemoverallemotionprompt injectionsst5 (score rail)
jev 1.14.00.6170.5980.8550.398
vega-U, 27B reader0.5990.5700.7300.497
laya-typed0.5390.5750.7380.305

1.75 points separate the hosted model and our largest reader, zero-shot, on ground the hosted model has no particular advantage on. On the corpora earlier in this post, with the 0.8B doing the reading, the same comparison was 11.5 points. The 27B is also 6.0 points clear of the other open model.

By source rather than overall, which is the more useful read. The 27B takes the score rail outright, 0.497 against 0.398 on a five-level ordinal question, with the 4B close behind at 0.492: reader size buys the ordinal task, and the ordinal task is what a risk score or a severity band actually is. The hosted model's remaining lead is almost entirely prompt injection, 0.855 against 0.730, the one source here that is a security judgement rather than a linguistic one. Emotion is within three points.

Read with the table

What would move this number, in both directions. The hosted row was measured on the rebuilt board and the 27B row is its published figure on vega-U's own sample of the same corpora, so they are not the same items. Two readers appear in both and say how far apart the samples sit: the 0.8B scored 0.541 on the rebuild against 0.520 published, and gemma 0.517 against 0.469. This sample is the easier one, by two to five points, which is wider than the gap itself. Read the two as level rather than as a near-miss, and do not read it as a win either.

The board's fourth source, natural_instructions, is excluded because it needs a keyword filter over 227 tasks and would not be a verbatim rebuild. It was the 27B's strongest source at 0.9900, so leaving it out lowers the 27B's figure rather than flattering it: its published four-source overall is 0.629.

And the 27B has never been fitted. Everything in the row above is zero-shot. On the corpora earlier in this post, fitting lifted the 0.8B from a 0.530 mean to 0.679 and past the hosted model on one corpus outright. The 27B fitted and calibrated is the obvious next measurement and it needs a GPU this laptop does not have.

What changes once there is customer data

Everything above is zero-shot or a bare linear probe. Neither is the product. What vega-U ships as its strong configuration is a head fitted on the customer's labels and a calibration fitted on labels the head never saw, so that is the row that belongs beside the hosted model.

The split is 40/20/40 over each corpus: 40% fits the head, 20% fits the temperature and the conformal quantile, and the remaining 40% is the same test third every other row on this page is scored on. Fitting the head and the calibration on the same rows is the trap the middle slice exists to avoid. The temperature, the quantile and the abstain bit come from vegaml's own calibration code rather than a reimplementation, so what is measured is the shipped path.

corpuszero-shotfittedjevgap to jev5-fold cv
tweet-sentiment0.6160.6600.692-3.1 pts0.704
tweet-sentiment-shifted0.5830.6420.700-5.8 pts0.567
finance-sentiment0.5470.7610.849-8.8 pts0.704
moderation0.4210.6290.761-13.2 pts0.623
boolq0.5750.5000.919-41.9 pts0.481
counterfactual0.4370.8830.845+3.9 pts0.845

One reconciliation before the numbers, because two tables in this post now report a fitted accuracy and they disagree. The ablation curve's last point gives the head the whole training pool, 240 rows on a 400-row corpus. The fitted row gives the head two thirds of that pool and spends the other third on the calibration. So the same labelled budget buys either a slightly more accurate head or a slightly less accurate head with a probability you can threshold, and on tweet-sentiment that is 0.686 against 0.660. That is the trade, and it is a real one rather than a rounding artefact.

Against the zero-shot engine, fitting is transformative on five of six. On counterfactual it reaches 0.883 and passes the hosted model. On finance-sentiment it goes from 0.547 zero-shot to 0.761, and on moderation from 0.421 to 0.629. The hosted model is still ahead on four of six.

boolq is where fitting fails outright. The head lands at 0.500, which is below the zero-shot engine's 0.575, and its cross-validated accuracy on the training rows is 0.481, under the 0.5 chance line for a two-option question. A head that does not beat chance has learned nothing, and Vega.fit discards exactly that case rather than serving it, so in production this row would not exist: the engine would answer. We are printing it because we ran it.

Fitting erases the difference between readers

vega-U ships several readers of the same engine, so the same fit was run on a second one, Gemma 3 1B, with the identical split, head and calibration recipe. Zero-shot the two readers disagree by as much as 32 points on a single corpus. Fitted, they land within 0.7 of a point of each other on the mean.

corpus0.8B fittedgemma-1b fitted0.8B cvgemma cv
tweet-sentiment0.6600.6670.7040.755
tweet-sentiment-shifted0.6420.6000.5670.525
finance-sentiment0.7610.7990.7040.730
moderation0.6290.6290.6230.597
boolq0.5000.4940.4810.494
counterfactual0.8830.8450.8450.816

Gemma is ahead on tweet-sentiment and finance-sentiment and its cross-validated accuracy on the training rows beats the 0.8B's on three of six. That is worth more than a reader comparison: it says the zero-shot gap between readers was mostly the readout rather than the features. The pooled features of a 1B carry nearly as much task signal as the 0.8B's, and replacing the zero-shot readout with a head trained on labels recovers almost all of it.

What the calibration bought

corpusTECE fittedECE calibratedqhatabstention
tweet-sentiment2.3170.1650.0920.863impossible, needs qhat < 0.667
tweet-sentiment-shifted2.5330.1190.1420.800impossible, needs qhat < 0.667
finance-sentiment1.1560.0910.0520.728impossible, needs qhat < 0.667
moderation2.0130.1670.0470.802impossible, needs qhat < 0.667
boolq20.000rail0.3590.0450.548impossible, needs qhat < 0.500
counterfactual1.2040.0600.0370.508impossible, needs qhat < 0.500

Calibration helped here, which is the opposite of what we found on the guard task. Calibration error improved on five of six, and on two of them by a lot: moderation from 0.167 to 0.047 and boolq from 0.359 to 0.045. It got worse on tweet-sentiment-shifted. Accuracy is identical in both rows by construction, because temperature scaling is monotonic and cannot change which option wins; what moves is the probability attached to it.

Two caveats that belong next to that table. boolq's temperature railed at 20.0, the ceiling of the search range, which is the optimiser failing to find an optimum on a head that learned nothing rather than a fit. And abstention never fired on any corpus. An answer abstains when the conformal set comes out empty, the set keeps every label clearing 1 - qhat, and the winner always holds at least 1/k of the mass, so an empty set needs qhat below 0.667 on a three-option question and below 0.500 on a two-option one. Every qhat here came out above its threshold. The escalation band that the whole design argues for is, on these six corpora, not reachable at alpha 0.1.

Three: what a calibration survives, and how often to refit

Calibration was fitted on a held-out slice of each corpus, a temperature and a split conformal quantile at alpha 0.1, and then evaluated three ways: on an i.i.d. slice, on a slice of the same corpus with the class mix deliberately skewed to about 80% one class, and on a different corpus asking the identical question with the identical options.

corpusfitted onTECE i.i.d.ECE skewedcov i.i.d.cov skewed
tweet-sentiment120 rows1.8890.1100.22193.1%95.5%
tweet-sentiment-shifted90 rows1.6020.0800.15294.2%94.0%
finance-sentiment120 rows1.0740.0950.19596.2%95.5%
moderation120 rows0.5750.0330.30895.0%93.9%
boolq120 rows13.1680.0560.26998.1%100.0%
counterfactual77 rows20.000rail0.066n/a88.3%n/a

The finding is clean and it is about which kind of drift matters.

A change in the class mix breaks calibration, every time. On all five corpora with a prior-shift case the calibration error got worse, and on two of them it roughly doubled or worse: moderation from 0.033 to 0.308 and boolq from 0.056 to 0.269. Conformal coverage mostly survives, because the quantile is deliberately conservative, but the probability stops meaning what it says. This is the same failure the RLCDAlignBench authors diagnose in the hosted model, which suggests it is a property of the problem rather than of either vendor.

A change in the text, with the same question, is survivable. The covariate-shift case moves from one Twitter sentiment corpus to a different one collected separately. The calibration fitted on the first held on the second: ECE 0.116 raw became 0.055 calibrated, and conformal coverage came out at 90.0% against a 90% target. That is the case we were least confident about and it is the one that held.

How many rows to put it back

Refitting on rows from the new distribution, holding the rest out:

rowsTqhatECE on the restcoverageset size
162.1900.8580.07398.9%2.54
321.9650.8510.07797.8%2.42
641.4470.8430.07592.8%2.14
1281.3510.8480.08592.4%2.12

Sixteen rows already lands the calibration error under 0.08, and it stops moving by about sixty-four. The cost of the sets tells the rest of the story: at 16 rows the conformal quantile is coarse and over-covers at 98.9% by keeping wider sets, and by 64 it is close to the target with tighter ones.

So, on how often to refit, with the hedges attached:

And the field-engineering share of that, stated plainly because it is a commercial question: the first calibration is ours, because it needs the question sheet, the split discipline and someone who has seen a railed temperature before. Two of the six fits in the table above railed at a search bound, which a customer would not recognise. The recurring recalibration should not be ours. It is one call on a labelled file, and if a customer still needs us for it in month three, we built the wrong thing.

What of this is field-engineering work, and what is not

Stated plainly, because it is a commercial question and the honest answer is not “all of it”. The first calibration is ours. It needs the question sheet written, the three-way split held, and someone who recognises a railed temperature: two of the six fits in the table above hit a search bound, which a customer would read as a number rather than as a failure.

The recurring recalibration should not be ours. It is one call on a labelled file, it needs sixty-four rows, and the trigger is watchable without labels at all. If a customer still needs us for it in month three, we built the wrong thing. What stays with us after month one is the part that is genuinely engineering: a new question sheet when the business adds a decision, a reader swap when the hardware changes, and reading the eval when a number moves.

What it adds up to

The losses are above and they are the larger part of this run. These are the places the same measurements put us ahead, every one of them read off the same test splits and the same cases as the tables that go against us.

Calibration, on all six corpora

Some vega-U configuration is better calibrated than the hosted model on every corpus here, and the fitted-and-calibrated gemma reader beats it on all six by itself.

corpusbest vega-U ECEwhich configurationjev ECEfactor
tweet-sentiment0.066gemma fit+cal0.1812.8x
tweet-sentiment-shifted0.068gemma fit+cal0.1872.7x
finance-sentiment0.0520.8B fit+cal0.0731.4x
moderation0.0200.8B zero-shot0.1286.5x
boolq0.037gemma fit+cal0.0641.7x
counterfactual0.0370.8B fit+cal0.0802.2x

The moderation row is the one worth quoting, because it is the row where our accuracy is worst. The zero-shot engine is 6.5x better calibrated there at an ECE of 0.020. A model that is 42% accurate and reports that honestly can sit behind an escalation band. One that is 76% accurate and overstates its confidence is harder to put a threshold on.

Accuracy, on the corpus that rewards reading the criteria

configurationcounterfactual accuracyvs jevECEAURC
vega-U 0.8B fitted0.883+3.9 pts0.0600.036
vega-U 0.8B fitted+calibrated0.883+3.9 pts0.0370.036
two readers, probabilities averaged0.854+1.0 pts0.062n/a
jev 1.14.00.8450.0800.042

Three configurations pass the hosted model on accuracy here, and the fitted row also takes AURC, 0.036 against 0.042, which means its confidence ordering ranks better as well as its answers being right more often. This is the corpus whose label is a linguistic property rather than a topic, which is exactly what a written criterion can describe.

Two small readers beat either one alone

We hold pooled features from two readers over the identical cases, so three combinations cost nothing more to test: fit on each alone, fit on the two concatenated, or average the two single-reader heads' probabilities. Averaging the decisions is the one that works.

varianttweet-sentweet-senfinance-smoderatioboolqcounterfameanlevel with jev
0.8B alone0.6600.6420.7610.6290.5000.8830.6791 of 6
gemma-1b alone0.6670.6000.7990.6290.4940.8450.6721 of 6
concatenated features0.6670.6580.7480.6290.5190.8640.6811 of 6
probability average0.6920.6670.8430.6160.4750.8540.6912 of 6
jev 1.14.0, zero-shot0.6920.7000.8490.7610.9190.8450.794

The probability average reaches a mean of 0.691 and is level with or ahead of the hosted model on three of six: a tie on tweet-sentiment at 0.692, ahead on counterfactual, and 0.6 of a point behind on finance-sentiment. Concatenating the features does almost nothing, which is the useful detail: the gain comes from two readers making uncorrelated errors, not from having more columns to fit on. Two 1B-class readers on a laptop is a cheaper route to that than one large one.

The guarantee survives the corpus changing, and the hosted one does not

Given the same recipe, the same 120 rows to fit it on and the same corpus to spend it on, whose probability survives? Jev exposes a distribution per answer, so the identical temperature-and-conformal fit runs on it unchanged and no system gets a recipe the others did not.

systemTECE, same corpuscoverage, same corpusECE, other corpuscoverage, other corpusset size
vega-U engine, 0.8B reader1.8890.11093.1%0.05590.0%2.02
laya-typed0.7300.05691.8%0.04291.7%1.83
jev 1.14.08.6340.13089.3%0.09587.0%1.68

At alpha 0.1 the point of the conformal set is that it holds the true answer 90% of the time. After the corpus changed, the vega-U engine came out at 90.0%, exactly on target, and the hosted model at 87.0%, under it, having also been under it at home at 89.3%. Its sets are the tightest at 1.68 options against our 2.02, which is worth having only if they hold.

Faster on every corpus, at no per-request cost

corpusvega-U 0.8Bvega-U gemma-1blayajevours vs hosted
tweet-sentiment115 ms80 ms16 ms323 ms2.8x
tweet-sentiment-shifted114 ms78 ms19 ms297 ms2.6x
finance-sentiment180 ms116 ms30 ms331 ms1.8x
moderation222 ms181 ms38 ms343 ms1.5x
boolq326 ms223 ms84 ms349 ms1.1x
counterfactual173 ms165 ms27 ms380 ms2.2x

Between 1.1x and 2.8x on the 0.8B and up to 4.0x on gemma, and the whole hosted side of this run cost $0.0373 against nothing on ours. The honest reading is that this is a property of where the model sits rather than of the silicon: ours is compute with no network, theirs is mostly a packet in flight.

The one corpus where the physics is irreplaceable

On boolq a linear head on the engine's own features never reached the engine's accuracy at any label count the corpus could supply, topping out at 0.550 against 0.575. On the other five the engine is worth 8 to 63 labelled rows and is then overtaken. There it is not overtaken at all.

And one that is in no table because the other systems do not expose it: an answer here carries abstain, unbound and a conformal prediction_set as fields, fitted on a held-out split, which is what lets a gateway route a middle band rather than threshold a number somebody guessed.

Four: who this is for, and why not the hosted one

The honest answer is narrower than it could be. Our first customer is a regulated financial institution in a jurisdiction with a data-residency rule: a bank, an insurer, an NBFC, a payment operator, a broker. Not because the model is better at finance, but because that is the one buyer for whom the deployment property is the product, and for whom the alternative is not a cheaper vendor but no vendor at all.

Everywhere else, the cold comparison above is the one that matters, and on the live-traffic corpus in our guard benchmark the hosted model beat us on every axis while being given no labels at all. We are not going to pretend otherwise to win an argument about where the weights live.

What actually rules a hosted API out, and what does not

The published descriptions of Jev agree on three facts that decide this: it is cloud only, it is served from the United States, and there is no self-hosted or on-premise edition. Third-party reviews put retention as “as long as necessary” per the privacy policy, with zero retention available to enterprise accounts on request, and say customer data is not used for training. Those are secondary sources, so a buyer should confirm the retention terms and the DPA with the vendor rather than with us or with a blog.

None of that is a criticism of the model. It is a statement about where bytes go, and for most workloads it is a non-issue. It becomes the whole issue in a short list of places:

rulewhat it requireseffect on a hosted US API
RBI payment data localisation, April 2018payment system data stored in India, covering customer identifiers, payment credentials and transaction data; third parties bound contractually to the same obligationstorage offshore is out. Google Cloud's reading of the RBI clarification is that processing abroad is not barred provided storage stays in India, so the question turns on what the vendor retains, which is exactly the term that is not published
IRDAI records regulationsrecords of policies issued and claims made in India kept in data centres located in India, carried from the 2015 regulations into the 2025 Maintenance of Information regulations; the 2026 cyber guidelines import CERT-In's 180-day log retention inside Indian jurisdictionthe same, for insurers, brokers, TPAs and repositories
DPDP Act 2023reasonable security safeguards, breach notification with no materiality threshold, and a penalty ceiling of ₹250 crore for failing the first and ₹200 crore for failing the secondnot a location rule, a liability rule. It prices the question of whose infrastructure held the personal data when it leaked
DORA, EUa model API is an ICT third-party service: register entry recording where data is processed and stored, contractual audit and exit rights, and an assessment of undue concentrationusable, but as a registered critical dependency with audit and exit terms a US vendor has to agree to in writing
RBI FREE-AI, August 2025a board-approved AI policy, customers told when they are dealing with AI and given a route to challenge an AI decision, AI-specific threats inside the cyber frameworkrecommendations to regulators and regulated entities rather than binding instructions, but they point at documentation, versioning and contestability of the model that decided

Two caveats on those rows that we are not going to bury. The dates are genuinely contested in the secondary sources: on DPDP, most compliance write-ups put full compliance at 13 May 2027 while several say the penalty provisions switch on from 13 November 2026, and we could not reconcile them, so check the Gazette notification. And the RBI published a draft Guidance on Regulatory Principles for Model Risk Management in June 2026 covering AI and machine learning models, which we found in only one source and could not confirm against the RBI's own site. Anyone making a deployment decision should read the primary text and take advice. We are a model vendor, not your counsel.

Why a frozen reader plus a 14.3M engine is the shape that fits

Four properties, none of which is an accuracy claim:

And one property that is not ours and should not be sold as if it were: the 27B reader that scored best on our board is a 27B model, and running it in a bank means buying the GPU to run it in a bank. The 0.8B that runs on a laptop is the one that makes the deployment argument, and it is also the weakest reader we publish.

The ₹1.64 crore email, and what would have stopped it

On 24 October 2026 Kochi City Police registered an FIR on a complaint from the Lulu Group's legal manager. Press reporting of that FIR describes a loss of about ₹1.64 crore, put at €150,500, to a business email compromise. The scheme ran from 2 to 29 September. Addresses resembling those of IE Park SRL, a long-standing Italian partner, carried invoices and new bank details for an outstanding balance on gaming equipment, and the money went to the accounts the impostors supplied. The case is under the Bharatiya Nyaya Sanhita and the IT Act, no suspects had been identified at the time of reporting, and the allegations have not been tested in court.

We are using it because it is the ordinary shape of the loss, not the exotic one. The FBI's IC3 counted 24,768 BEC complaints and $3.05 billion in reported losses in 2025, the second-costliest category it tracks, and its Recovery Asset Team froze more than $507 million in fraudulent domestic wires, which is the measure of how much of this turns on catching it in hours.

The controls that stop it are process, not models

This needs saying before anything about what we sell. The control that would have stopped this payment is a callback, and the detail of the callback is the whole control:

A decision model does not verify a bank account and cannot replace any line of that list. What it can do is cheap and specific: read every inbound message and answer “does this ask to change where money goes?” so that the callback workflow is triggered by the message rather than by whoever happened to open it. That is a two-way typed decision with a probability on it, which is the one thing that makes it routable: high confidence holds the payment, the middle band goes to the payments team, low confidence does nothing.

PAYMENT_CHANGE = {
    "type": "choice",
    "instructions": (
        "This message arrived in an accounts-payable inbox. Decide whether acting "
        "on it would change where money is sent. Judge the request, not the tone: "
        "a polite, well-formatted message from a known supplier is the usual shape "
        "of this request."
    ),
    "criteria": {
        "changes-payment": (
            "asks for payment to a different account, bank, country or intermediary "
            "than the one on file, supplies new banking details, attaches an invoice "
            "whose remittance details differ from the supplier's existing ones, or "
            "asks for an existing payment to be redirected, held or reissued"
        ),
        "ordinary": (
            "anything else: a quote, a statement, a query, a dispute, a reminder "
            "about an unpaid invoice that does not change the destination"
        ),
    },
}

We have not benchmarked that sheet, and we are not going to quote a number for it. There is no public corpus of real business email compromise that postdates these models; the spam corpora that exist are on our own training board, so a figure from them would be meaningless; and a corpus of BEC emails we wrote ourselves would measure our imagination rather than the model. The sheet above is the shape of the thing, offered as a starting point for someone who has their own mail archive and their own labels, which is the only data that can answer it. That is also the honest version of what a field engagement does: the labels are the customer's, and so is the measurement.

The rest of the BFSI surface

Every row below is the same primitive: a state, a typed question, a calibrated probability, and an explicit band that goes to a human. None of them needs generated text, which is why none of them needs a frontier model, and all of them involve records that a residency rule or a liability rule touches.

wherethe questionanswer typewhy typed and calibratedthe band
AML alert triageDoes this alert show a pattern worth a case file?score, 0 to 4the queue is ranked by expected level, not by a rule's binary output, and the ranking is what cuts the review hoursthe middle levels go to an analyst, the top escalates, the bottom auto-closes with a logged probability
KYC adverse mediaDoes this article concern this applicant, and does it describe a reportable matter?two booleans on one readtwo decisions from one pass over the article, each with its own probability, instead of a summary a human still has to readanything under the threshold goes to the analyst with the article attached
Claims first notice of lossWhich handler, how severe, and are there fraud signals?choice, score and boolean on one readone read of the report answers all three, and the severity's expected value sorts the queue better than its argmaxa flagged fraud signal never auto-settles
Complaints and mis-sellingIs this a regulated complaint, and about what?boolean plus choicemisclassifying a complaint is itself the reportable event, so the decision needs a confidence you can audit, not a keyword rulenear-threshold cases are reviewed before the clock starts
Disputes and chargebacksWhich reason code, and is the documentation sufficient?choice plus booleancodes are a fixed set with real definitions, which is exactly what criteria are forinsufficient documentation routes to the agent, never to an automatic refusal
Credit file notes and collectionsWhat did this interaction establish, and how likely is contact to succeed?choice plus scoreprioritising the next call is an ordering problem, and an ordering is only as good as the confidence behind itunbound answers mean the traffic drifted, which is the signal to recalibrate
Trade finance documentsDoes this document satisfy the stated condition?boolean per conditiona long document read once answers many conditions, which is where the 73k context and prefix caching pay for themselvesevery negative goes to a checker with the clause attached
Vendor payment changesDoes acting on this change where money goes?choicethe sheet above; the model triggers the callback, the callback is the controlhigh confidence holds the payment, the middle band goes to payments

What is measured on this page and what is not matters here. The six corpora above include one BFSI-domain task, financial headline sentiment, and nothing else in that table has been benchmarked by us. The rows are a map of where the primitive fits, not a claim about accuracy on any of them. The number that should decide a deployment is the one from the institution's own labels, which is what vegaml-eval exists to produce.

What this run told us to fix, and what the fix measured

Three gaps in everything above are ours rather than the benchmark's, and all three fall out of these numbers rather than out of anybody's opinion. They are closed in vegaml 0.14.0. What follows is what the fixes measured, on the same laptop, because a fix announced is not a fix.

The escalation band had no way to fire

The whole case for putting this inside a bank is that the uncertain middle goes to a human. So it matters a great deal that in the fitted runs above, the abstain flag fired on zero rows out of 860, and could not have fired on any of them.

The reason is a definition. Once you recalibrate on your own labels, which is the step this post spends a third of its length recommending, abstaining means the conformal prediction set came out empty. For that to be possible at all, the conformal threshold has to sit below 1 minus one over the number of options. It did not, on any corpus:

corpusoptionsfitted qhatneeds belowabstain ratecould fire?
tweet-sentiment30.8630.6670.0%no
tweet-sentiment-shifted30.8000.6670.0%no
finance-sentiment30.7280.6670.0%no
moderation30.8020.6670.0%no
boolq20.5480.5000.0%no
counterfactual20.5080.5000.0%no

Read with the table

The checkpoint's own published abstain flag is a different thing and does fire: it is a plain confidence cut, at a risk level we picked. The trouble is that it is ours and not the customer's, it is tied to no precision target, and it disappears the moment the probabilities are refitted. A fitted head is worse again: a head answer carries no conformal set and no abstain flag at all, so a deployment that fits heads has no escalation signal from the model whatsoever.

What ships instead reads confidence and never looks at the prediction set. You can set the line yourself, per question, with no labels involved. Or, with labels, you can ask for the thing you actually want, which is not a threshold but a standard: give it the precision the automatic band has to hold and it returns the widest band that holds it, along with what that costs in review load.

v.set_band({"payment_change": 0.8})          # a line you choose
out["answers"]["payment_change"]["route"]   # "auto" or "review"

cal = calibrate(v, examples, questions, target_precision=0.97)
cal.report["payment_change"]["band"]
# {"threshold": 0.83, "review_rate": 0.31, "precision": 0.983,
#  "precision_lb": 0.971, "held_out": {"precision": 0.975, ...}}

Two details decide whether that number survives contact with a pilot. The target is held on a one-sided 95% lower bound rather than on the precision the band happened to score, because the threshold is chosen on the same rows that then grade it, and picking the best-looking cut on a few hundred rows is exactly how a 97% target ships as a 91% deployment: twelve correct out of twelve certifies 0.816, not 1.0. And a target that nothing can hold is refused rather than approximated. Every answer routes to review, and the report names the target this much data could actually support. The failure mode we were careful to rule out is the one where the band quietly gets weaker than the number written on the contract.

The CPU number was mostly configuration

703.8 ms per decision on CPU against 118.7 ms on Metal is the figure published above, and a bank's on-prem box often has no GPU at all. Most of that gap turned out to be a setting. The right thread count belongs to the reader, not to the engine, and torch's default is wrong for the one we ship:

torch threadsQwen3.5-0.8B, the default readergemma-3-1b-it
1258.2 ms167.6 ms
2763.8 ms138.9 ms
4 (torch's default here)1268.1 ms135.2 ms
61852.0 ms142.7 ms
103612.1 ms146.3 ms

The shipped reader gets 4.9x slower going from one thread to four and 14.0x slower going to ten. It is not the engine and not the prompt length: Qwen3.5's linear-attention blocks fall back to a reference PyTorch implementation whenever the CUDA kernels are missing, which on a CPU they always are, and that fallback does not thread. A plain-transformer reader has no such fallback and behaves the ordinary way. It holds at length too, 547 against 1639 ms at 855 characters, and 1019 against 2853 ms at 3426 characters.

The thread count is free in the only sense that matters: one thread and four agreed on 12 of 12 choices with a confidence difference of 0.0000. The second lever is the reader, and it is free in a different sense: the engine behind every reader is the same 14.3M parameters, checksum-identical, so this is a configuration change rather than a different model. Fitted and calibrated across the six corpora, the gemma reader means 0.672 accuracy against the 0.8B's 0.679, at a calibration error of 0.059 against 0.069. Seven tenths of a point behind on accuracy, a point ahead on calibration, and 135.2 ms instead of 703.8 ms. That is 5.2x, with no quantization and no GPU in the box.

Read with the table

We built the quantized path as well, and it is not the speed answer. Dynamic int8 over every linear layer takes the reader's weights from 3.75 GB to 0.95 GB, which is the difference between fitting on a small box and not. It also ran 4.1x slower here and moved 2 of 12 answers, with confidence shifting 0.102 on average. The latency is a backend fact rather than an int8 fact: this laptop has qnnpack, while float32 here runs through Apple's matrix path at a measured 1,357 GFLOP/s, so there is nothing for int8 to beat. A Linux x86 box has fbgemm instead and the comparison there is different, which is precisely why it ships reporting which backend it chose rather than claiming a speedup we have not measured on your hardware.

Recalibration is one command, and it tells you when to run it

If a customer still needs us in month three we built the wrong thing, which is easy to write and worth checking. So recalibration is one command, and so is the question of whether it is due:

vegaml-calibrate fit --labels recent.jsonl --questions @questions.json \
    --target-precision 0.97 --out calibration.json

vegaml-calibrate check --answers last-week.jsonl --calibration calibration.json
# exits 1 when the traffic has moved off the calibration

The check needs no labels at all, which is the point: labels are what the refit costs, and this is the thing that tells you to go and collect them. It reads answers the deployment has already produced and compares the mix of them against the mix the calibration was fitted on, which the fit records automatically. It exits non-zero at the point of action, so a cron entry and a pager are the entire alerting setup.

What it watches is decided by the drift section above rather than by taste. A covariate shift, the same decisions written differently, left calibration intact and in fact improved it. A change in the mix of answers broke it on every corpus we tried. So the statistic is the population stability index over the answer mix, which is one counter per option and is already what model-risk teams in banking read, at its conventional bands: under 0.1 stable, 0.1 to 0.25 watch, 0.25 and over refit. Beside it sit mean confidence, because a reader that has started hedging on everything moves no mix at all, and review load, because a band whose review rate has doubled is costing a pilot money whatever the statistics say. An option the calibration never produced is not drift in degree but a different question, and always a refit. Under fifty answers for a question it reports the numbers and refuses a verdict, because a mix drawn from a handful of answers moves on its own, and paging on that is how a drift alert gets switched off for good.

The bill for acting on it is the one already in the short version: 26 seconds on 240 rows, almost all of it reading each row once, with the temperature and the conformal quantile together costing 6 ms. Sixteen labelled rows restored calibration after the prior shift and it stopped moving by sixty-four. That is a morning of labelling, not an engagement.

What this does not measure

Run it yourself

The harness is one file. It caches every stage, so each can be re-run without paying for the ones before it, and the corpora download to the ordinary HuggingFace cache rather than being copied into a repository.

pip install vegaml pandas pyarrow huggingface_hub
export JEV_API_KEY=...          # only needed for the hosted rows

python -I scripts/ablation_eval.py \
    --n 400 --n-twin 300 \
    --curve 8,16,32,64,128,240 --curve-seeds 5 \
    --stages features,engine,laya,jev,ablate,drift

Swap the task table at the top of the file for your own corpora and the same three measurements run against your data, which is the only version of this that should decide anything.

Sources

If one of these numbers is the one that matters to you, the harness takes your corpus in about ten lines of diff. We would rather you ran it than believed us.


More posts·embedding.in