Repository navigation
[Engine] Jev System One decision endpoint (/v1/systemone): typed decisions with calibrated probabilities - #2639
justindressler wants to merge 42 commits into
Conversation
… prompt (10 failing)
serve/jev.py: frozen D3 prompt, donor 422 contract, jmath readout (M1 identity), engine-turn discipline (svc.fifo), final_row_logits readout boundary (BROW on the engine side). server.py: route + import. 10/10 tests; full-suite parity vs baseline verified (0 new failures; 14 pre-existing regex-module errors, flaky sampler tests vary run to run on both trees).
…fail-closed) + jev.handle wired to real contract
…act (CUDA test target)
…, CUDA+Metal), --jev-brow flag, serve-loop BROW handler; golden battery fixture
…minism; CMake CPU guard fixed (15/15)
…allowed (review r1)
…ly order, /admin/calib hot-swap, B1.1 log rows - serve/jev_calib.py: donor src/server.cpp:518-633 surface - load (calib.json/v1.json, corrupt=identity), swap, apply (platt_v1 noul / T_choice_v1 / T_score_v1, argmax-safe, renormalized), tuple_of, async 4096-row log queue with 200ms flusher - serve/jev.py: donor order (server.cpp:1134-1142) - softmax -> round 4dp -> renorm -> calibrate -> answers_from_calibrated; calib id surfaced per answer; B1.1 rows (ts/tuple/qid/type/raw_p unrounded; label_mass omitted - BROW is sparse, fit.py never reads it) - serve/server.py: POST /admin/calib (Bearer STRATA_JEV_ADMIN_KEY, empty=open like llama-swap's control plane); startup swap() + daemon flusher + atexit drain - tests: test_jev_calib (14) math+wire+hot-reload; test_systemone re-pinned to the donor 4dp contract. 29/29 green (mock, no GPU); full-suite regression running
…he live M3.2 bench 1. question_block now the donor render_qblock verbatim (server.cpp:305-334): full Options:/Levels: list with criteria descriptions, 'Question:'/'Answer:' anchors, noul Yes/No-means lines. The M3.0 render listed NO options -> the model picked labels blind (ARC 23% = chance). 2. enable_thinking=False on the decision render (D3, frozen): the readout position must predict the label, not open <think>. 3. render_state: string states pass through VERBATIM (server.cpp:266-268); objects stay pretty JSON. The port JSON-quoted every string state. Live QFN before -> after: choice 23%->100%, noul 49%->88%, score 24%->55% (donor 27B baseline: 99/90.5/56.9). 29/29 unit tests green.
…on yes/no branches, softmax(d) - question_head + v_branch: donor render_qhead/render_vbranch verbatim (server.cpp:337-353): shared 'Question:/Options:' head, then one 'Proposed answer: <name> ... Answer:' branch per option - d_i = s_yes - s_no per branch (BROW readout), p = softmax(d), 4dp + renormalize, choice temperature applies (donor :1150-1163, calib_apply) - response: probabilities keyed by option NAME, 'choice' = name string (donor A2.6 shape); usage.readout_tokens counts one BROW per branch - X-Jev-Mode header passthrough in server.py; V scopes to choice only, noul/score fall through to the L path (donor :767) - log rows: tuple ...|V|choice with v_d (donor B1.1 V schema) - K sequential branch decodes share the state+head prefix - Strata's engine-side KV prefix mounting reuses it (M3.0 alignment, no EngineSeq) - tests: test_jev_vmode (6) - name-keyed probs, branch prompt shape, shared-head prefix, per-option readouts, v_d log rows, L unchanged. 35/35 green with calib/systemone/golden.
…n, one wire request) - POST /v1/systemone with image_b64 (+ optional vision_prompt, default 'Describe this game screen:') -> jev.handle_image - stage 1: Strata's existing per-preset vision path (svc.prepare encodes the image and expands the image pads), one bounded summary generation (<=160 tok, thinking off) - stage 2: the summary becomes the decision state; the standard L/V readout answers. Response: answers + image_summary + stages_used ['vision','decision'] (donor PLAN B V1 contract, server.cpp:2419-2603; the donor's V2 single-seq fork crashed ggml and stays NOT ported) - fail-closed: empty summary -> 502 and the decision stage never runs; no vision encoder -> 400; short b64 -> 422 - tests: test_jev_image (6) - two stages one request, summary rides into the decision state, vision_prompt accepted, fail-closed paths, validation first. 41/41 green with vmode/calib/systemone/golden.
…e jev extension
M3.9 (donor amendment 7 mode 1): "then": {"generate": {...}} on /v1/systemone -
decide first, then generate with the answers folded into the LAST USER turn as
'Jev decisions (JSON): ...' (a trailing system message throws in the chat
template; donor folds too). Sampling mirrors the chat endpoint (temp 0.6,
top_p 0.95, top_k 20, max_tokens 512 defaults; stop strings honored).
Response = decision body + then{text, finish_reason, usage}.
M3.3 (donor A3.4): "jev": {state|messages, questions} on /v1/chat/completions -
jev.questions required; state XOR messages (400 on both/neither, donor
messages-must-be-empty rule); stream refused with the jev extension; the
conversation becomes the decision state (v1-chat, donor 3.5). Response wraps
the decision body under "jev" with an OpenAI-style envelope.
Tests: test_jev_then (9) - two stages one request, answers in the gen prompt,
sampling defaults, error propagation, chat-surface validation arms, plain chat
unaffected. 50/50 green with image/vmode/calib/systemone/golden.
…ns, no cross-talk, FIFO serializes engine turns The donor's DoD arm on the serve port: 7 threads, 3 noul questions each - every client gets exactly its own 3 answers (qid-keyed, no foreign qids), 21 readout turns, and an in-flight counter on engine.generate proves the FIFO never lets two engine turns overlap (the serve-side stand-in for the donor's per-decode-step ENG.mu, which M3.4 maps to svc.fifo - already held across each request's whole readout loop). 52/52 green.
…ns, calibration, examples Wire surface (request/response/422 contract), the four extensions (V mode, image two-call, decide-then-generate, chat-surface jev), calibration math + hot-swap + the measured QFN tables (raw vs calibrated, ms/item, donor-parity numbers), concurrency arm, and curl examples for every path.
…enizer
Live QFN run caught two: the engine streams negative sentinel ids
(verification artifacts) which the byte tokenizer's token_bytes rejects, and
per-token decode re-decodes for stops. Now uses the server's own incremental
Detokenizer (split-multibyte safe), skips None/negative ids, and checks stops
against the last few decoded pieces.
Live-verified on QFN through llama-swap after the fix:
- then.generate: decision + 'Jev selects the second option ... 75.93%' (the
answers folded into the prompt, 33-token completion, finish stop)
- chat-surface jev: {object: jev.response, jev: {answers... noul 0.9995}}
…cide Justin's ask: can the image embeddings themselves drive the readout, without the summary in between? Yes on Strata - the donor's V2 needed a seq fork off image embeds (crashed ggml on hybrid attention); our readout is BROW on the final decoded row, so one prompt [image + question_block], one decode step, label logits off that row. No new engine primitive. - image_mode: 'summary' (default, two-call) | 'direct' (single readout); invalid values 422 on image_mode - the question text sits AFTER the image pad so the readout position attends the full image; svc.prepare encodes + expands pads (Strata's normal path) - no outer fifo hold: prepare takes its own engine turn (deadlock found by the new test - same lock, held twice) - response: answers + usage + stages_used ['image-direct']; calib log rows under a ...|D|<qtype> tuple (D = direct image readout) for a separate fit Tests: direct-mode shape (one turn, question after image), bad mode 422. 54/54 green.
…mage was black without them The live vision A/B caught it: chat-path control answered 'Red' (reasoning described the white box and the RED text), while both image arms saw a 'completely black' image. prepare() stages the combined embeddings file on svc.embeddings.path, but it is the CALLER that must pass embeddings=... to engine.generate - that is what turns GEN into GENI and makes the engine load the image. The decision handlers called generate() bare, so the pads attended nothing. Both handlers now take svc.embeddings.path (run()'s exact pattern), clear it, and delete the file when done, as run() does. Also the summary loop's second run-in with negative sentinel ids (fixed the same way as handle_then: Detokenizer + skip None/<0). Also why the first bench arm died at 400: same crash, earlier code. 52/52 green after; live A/B rerun in flight.
…review
P0-1 (blocker): calibration was one GLOBAL Calib - fitting an image tuple
would have silently changed text decisions. Calibs are now keyed by readout
letter ('' text legacy, L, V, D, S) with per-mode files calib-<mode>.json and
fallback to the text fit when a mode has none. state_json reports every mode.
P0-2 (blocker): summary-mode image decisions logged under the text 'L' tuple,
indistinguishable from text rows. handle() now detects an image_summary state
and logs/applies under 'S'.
P0-3 (major): direct mode silently ignored X-Jev-Mode: V. It now 422s with a
clear message (D-V does not exist; the plan's assumed behaviour), pinned by a
test.
Tests: PerTupleCalib (image fit leaves text identity; fallback; swap loads
per-mode files) + DirectVMode 422. 54/54 green.
…wapped
Found by the small image calibration suite + Opus validation (2026-10-15):
image noul raw acc 0.0% with a perfect-inversion Platt fit (a=-2.80) while
choice/score on the same images were correct. Root cause, two compounding bugs
in the noul criteria path:
1. question_block unpacked descriptions as (true_d, false_d) but they are
stored in [No, Yes] label order - so 'Yes means:' printed the FALSE
description. The model read the reversed definitions and answered
consistently - every noul with criteria was inverted.
2. The ': ' separator was baked into the stored descriptions AND re-added by
question_block -> doubled colon ('Yes means: : ...').
Text noul (BoolQ) never sends criteria, so M3.2's 88-90% never exercised the
path; the image suite was its first caller. Fixed: descriptions stored bare in
[No, Yes] order, question_block renders Yes/no lines in donor order.
Also: fit_platt in the suite now REJECTS a <= 0 (a negative slope means the
labels/prompt are wrong, never a legitimate calibrator). New golden tests pin
the exact noul question-block text (Yes means true, No means false, no doubled
colon) and the choice description shape. 54/54 green.
- calib/bench_suite.py: live fit+eval suite (text/image/real); standard top-label ECE15 + mean NLL (the first suite's gold-class-pinned ECE variant was wrong - Justin caught the tables); per-item jsonl logs for adversarial audit; refuses Platt a<=0 and flags T at search boundaries - calib/convert_benchmarks.py: POPE-adv/CV-Bench/CountBenchQA converters with the banned-field selftest (never sends prompt/text/rationales), sha256 image manifest, image-disjoint dev/test, POPE 50/50 by-image sampling - calib/calib-L.json + calib-D.json: the ACTIVE per-tuple fits (2026-10-15, corrected harness): L a/c/T from BoolQ/ARC/SST-5 dev; D from POPE/CVB/CB dev. S stays identity until its own dev run - calib/README.md: refit procedure + donor reference (jevcpp 27B calib.json, tuple p-jevcpp-qwen38-27b|q4_k_xl|timber|v1|V|all, T_choice 1.5861) Live-verified after hot-swap: TEXT-L noul platt_v1, TEXT-L score T_score_v1, IMAGE-D noul platt_v1, IMAGE-D score T_score_v1, IMAGE-S identity (own tuple pending) - every mode answers under its OWN calibrator now.
…ev-vs-jev The PR document package: - pre/post calibration tables (held-out, offline replay of identical items; includes the old-global-calib comparison showing why per-tuple matters) - benchmark vs the donor's OWN m2_baseline.json (same tasks/metrics code): QFN IQ3_XXS MoE matches the 27B within noise on noul/choice, -4.9pp on score - performance matrix measured live (p50/p95/rps: text 272ms, image-direct 275ms, image-summary 1625ms) vs donor m1_perf 71ms/14rps - honest 3.8x gap + what buys it - test strategy: 54/54 unit, live suites with guards, the 1-in-10 adversarial audit protocol + results, banned-field converter selftest, Opus vision plan - artifacts bundled in docs/bench-artifacts/ (reports, item logs, audits, convert manifest)
…er-tuple logs, B1.4 tuple refusal R1 (blocker): the per-tuple fallback contradicted itself — code fell back to the TEXT fit for V/D/S while the comment and README promised identity. Strict now: only ''/L share the legacy calib.json fit; V/D/S use their own calib-<mode>.json or identity. (The S handoff was already threaded: handle() detects image_summary states and applies/logs under 'S'.) G3 (blocker): the donor's fit.py pools a single log by type — running it on a mixed L/V/D/S log file would fit text+image rows into ONE calibrator, the exact global-fit failure the per-tuple split fixed. log_flush now writes one file per readout tuple: data/log-<mode>.jsonl. G13: B1.4 tuple refusal — a calib-<X>.json whose tuple names a different readout letter is refused at load (logged, identity), so copying calib-D.json to calib-S.json cannot apply silently. Legacy calib.json/v1.json keep no mode binding. R7 (verified, no change): the [x / t ...] before apply() in the direct path is the donor's 4dp-round renormalization (softmax -> round -> renormalize -> calibrate), NOT a second temperature. Tests: OpusPrereviewFixes (4 new) — S/D never inherit the text fit, L does; mismatched tuple refused; matching tuple accepted; log split by tuple. Prior tests updated to the new contracts (state_json modes shape, log-<mode> files). 61/61 green.
… Not-ported section R3: the donor's b13_eval ECE scores each bin at its CENTER, our harness at the bin MEAN — both top-label/15-bin but not directly comparable. Added a column recomputing our logged raw_p under the donor's exact formula (BoolQ .035, SST-5 .156 — same direction under either definition), stated the n=100 SE on the -4.9pp score gap, and said plainly that calibrated SST-5 ECE stays ~5x the donor's. Rank 8: 'Not ported / diverged' section — mode gating, fork/batch/naive differential, V2 (superseded by direct mode), chat-KV reuse, B2 LoRA, golden HTTP variants — each with its one-line rationale, plus the open pre-PR list.
…(Opus G10) G2 (security): with no ADMIN_KEY configured, /admin/calib was OPEN — anyone could hot-swap calibrators and read state. Now 404s like the donor's unconfigured --admin-key routes (404, not 401: probing must not learn the surface could exist). Keyed operation unchanged. Test added. G10: calib/token_audit.py — donor A1.3 parity for the QFN tokenizer. Loads the real pack tokenizer (same path as serve/server.py main), checks all 38 labels (A-Z, Yes/No, 0-9) for single-token, collision-free, prefix-free. Result: PASS (reports/token_audit_qfn.json, donor schema). First run failed against the byte stand-in — the audit must point at the pack, which the script now takes as its second arg. 62/62 green.
…l negation, ambiguity (Opus G8) Donor parity for reports/a211_metamorphic.json: (1) REORDER — permute the questions in one request; per-question probabilities must be invariant (Jensen-Shannon < 1e-9, argmax never flips). The port renders each question as its own prompt so this is by construction — but the Yes/No-swap bug proved construction is not evidence, so the test PROVES it. (2) NOUL NEGATION — pos/neg question pairs must flip yes-probability; the >=4/5 gate binds on the GPU arm (mock answers by token, documented). (3) AMBIGUITY — unanswerable questions must not gain confident mass; |p-0.5| proxy for the donor's label mass (BROW is sparse). 3/3 green on the mock; GPU arm reuses the same file with RUN_BASE/RUN_KEY.
… (Opus ranks 3/5/6 + R1)
calib/blockers_gpu.py + artifacts in docs/bench-artifacts/blockers/:
NOISE FLOOR (30 items x 5 runs x {idle, 8-decision, 4chat+4decision}):
- argmax flips: ZERO in every scenario (stability gate passes)
- p99 |dp|: idle noul .070 / choice .039; dec8 .043/.044; chat4dec4 .039/.022
- The MoE drift floor is ~0.04-0.07 abs-prob — 14x the donor's dense 5e-3
gate. Every suite delta must be read against THIS floor: POPE +1.6pp (1
item at n=60) is within it, exactly as Opus R6 suspected.
SOAK: text 2000 req (1200 dec1q / 400 dec3q / 400 chat) = 1 error; image
direct 200 req = 0 errors. VRAM drift 23228->23222 MB (0.026%, donor: 0.07%).
The 1 error is a 503 at the moment the engine self-stopped mid-soak
(strata 'stopping' in logs — TTL/idle unload raced the soak, NOT a jev
crash); restarted cleanly, soak continued green. Documented as infra race.
S-FIT (summary-mode dev): a=.4892 c=.8313 (Tc .05 boundary — choice n=20
degenerate, same as CVB; Ts 1.8702). calib-S.json committed + live: S answers
now platt_v1, D unchanged. Honest finding: summary raw noul acc .75 vs direct
.867 on the same POPE dev — DIRECT BEATS SUMMARY for perception.
CIs/Brier (bootstrap 1000): POPE .067->.065, CVB .200->.179 (CIs overlap
raw), CountBench .018->.005 calibrated (3.7x better, CI clean). Supports
shipping D|choice as-is with CIs stated, not identity.
…donor's dense gate Replaces the one-line 'determinism caveat' with the measured floor (p99 |dp| .04-.07, zero argmax flips in 720 repeats) AND the mechanism, verified in engine source: atomicAdd expert slot assignment (moe_fused.cu place_kernel) -> row order = warp-dispatch order -> non-associative float sums differ per run -> compounds across layers -> IQ3_XXS keeps it above dequant noise -> softmax amplifies near p .5-.9. Same floor across idle/ dec8/chat4dec4 (per-request kernel scheduling, not load). Donor's 5e-3 was a dense-model gate. Mitigation note: deterministic placement = upstream engine follow-up. Reviewer guidance: single-item deltas under the floor are not signal.
…is the 260ms 3-arm + floor decomposition (live): /health p50 1ms, /v1/models 1ms, systemone 262ms -> the serve layer is innocent; the cost is the engine turn (prefill-dominated). Fresh TCP 47ms, proxy hop 44ms (keep-alive direct p50 255ms). Streaming was never in the decision path (Content-Length bodies, donor refuses stream:true). Donor gap = no prefix amortization (it forked a cached prefix; we re-prefill) — state-KV reuse projects 60-80ms, queued as the suffix-reuse follow-up. Repeat-identical requests already hit the engine's prefix mount (278-300ms even at 470-token states).
…ev (Justin's catch) Two missing examples, both with REAL captured responses (not illustrative): - multi-question: mixed noul/choice/score on one state; shows each answer carrying its own calibrator id (platt_v1 / T_choice_v1 / T_score_v1) and the per-readout usage block; notes cost scales ~linearly until prefix reuse - inline jev: the chat-surface extension with the conversation AS state (v1-chat template, no state field); shows object: jev.response shape and that any OpenAI client gets decisions without a second endpoint
…closed (it was stale)
…variants + calibration-literature citation Ecosystem survey (/opt/data/research/decision-readout-survey.md) adoptions: 1. margin (top1−top2 probability) on every noul/choice/score answer — the cascade-routing number from the classifier literature (separates 'confident' from 'no-alternatives'; route low-margin to escalation). noul margin = |p_yes − p_no|. 2. LmEvalSmokeInvariants in the golden battery: per-answer probabilities must be distinct, in (0,1), sum to 1, and carry margin — identical values are lm-eval's documented signature of a broken readout split (the BOS class of bug). Also asserts the new margin field. 3. JEV-PR.md: one-paragraph citation of the calibration literature (verbalized-confidence failure, ECE>0.37 Groot ACL-24; Platt 30-60% ECE cuts matching our measured −53%) justifying the logit+Platt architecture. 68/68 green (62 + metamorphic 3 + invariants 1 + margin asserts folded).
…er question Justin's ask: confirm the decode-cost assumption instead of estimating it. usage.timing_ms.readout = wall time of the per-question engine turn (generate(ids,1) + final_row_logits), rounded 0.1ms. L-mode main loop and both image paths instrumented (prepare/generate splits in the direct and summary stages). MEASURED (live QFN, post-cold-reload, 67-token prompts): - fresh question: readout ~975-1135 ms (prefill of 67 tokens + 1 decode + BROW) - identical repeat: readout ~348-451 ms (engine prefix mount skips prefill) - => the DECODE-ONLY share is roughly (repeat total) minus fixed overhead: 348-451 ms includes mount-check + decode + BROW + wire; the pure decode step is a fraction of that. Fresh-vs-repeat delta ~600-700 ms = the 67-token prefill — prefill dominates, as the latency budget stated. - first-request-after-reload shows ~1s cold JIT/lazy-init tail (q0 1083 vs settled ~1000). Also corrects the earlier 260ms engine-turn estimate: that was the settled identical-repeat number INCLUDING the mount path; fresh-question turns are ~1s at short states. The PR's budget table stays directionally right (prefill-dominated) but its 'engine turn ~260ms' line is the mounted steady-state, now labeled as such in notes; doc table update queued.
…asured per question" This reverts commit 53307a1.
… (fresh ~1s vs repeat ~0.4s at 67 tok) The instrumented-build measurements (53307a1, since reverted) showed the table's p50s are the mounted/repeat steady state; fresh-question turns are ~1s even at 67 tokens. Stated plainly so nobody reads 272ms as the fresh-prompt cost. Prefill dominates both regimes -> the follow-up.
… parity; Justin's call) - jev_calib.MODES: env STRATA_JEV_MODES='chat,decision' (default) | 'decision' | 'chat'; unknown tokens raise at import -> serve refuses to start (fail-closed) - Router gates: /v1/systemone + chat jev extension under 'decision'; then.generate under 'chat'. Gated surfaces 404 not_found_error (probe learns nothing). Donor /admin/modes runtime-flip deliberately NOT ported: restart is the policy boundary - 4 new tests (ModeGating) incl the env-leak regression this work itself hit (a leaking test env var gated later modules — cleanup now deletes) - 72/72 green; JEV-PR.md usage section + 4d row updated (ported, not deferred)
…ustin's ruling) undefined STRATA_JEV_MODES now means chat only: a serve process that never mentions jev behaves exactly like stock Strata; the decision surface must be explicitly opted into (chat,decision / decision). Deliberate divergence from the donor (whose defaults were both-on) recorded in JEV-PR 4d. Live presets carry STRATA_JEV_MODES=chat,decision explicitly so current behavior is unchanged for existing callers. 72/72 green.
…o actually send direct The chat-only default broke 53 tests - correctly: they now carry the JEV_TEST_MODES_BOOTSTRAP env (setdefault chat,decision) before serve import, which IS the new contract: decisions must be opted into. Also fixed test_direct_rejects_v_header: it was posting REQ without image_mode=direct, so it exercised the summary path where V is legal (200). Now sends direct+V and gets the intended 422. 73/73 green.
…-on after the flip to chat-only Found in the pre-PR audit: usage block, prose, and the 4d row all still claimed STRATA_JEV_MODES defaults to chat,decision. Now: unset = chat only, decisions opt-in, jevport presets set chat,decision explicitly; divergence from donor recorded with the repo's own opt-in rule as rationale. Also moved the mode-gating comment block inside the code fence (it was rendering as markdown headings).
…r-file paths - new section 6: engine diff scope (5 files +149, additive/opt-in, one unconditional int store), default-mode behavior verified env-unset, and an explicit NOT-VERIFIED list (HIP/SYCL/Metal builds, layer-split brow_row, pre-existing NoLeakedSampler isolation failure that reproduces on an unmodified Strata-043 tree) - 54/54 -> 73/73 jev modules (858 full discovery) - donor reference paths no longer imply files that live in this repo
…, testing-status wording - new Introduction: what a System One decision model is and does, then Jev (TypeSafe's commercial API, the wire followed), Laya (RLCD-trained encoder, per-type/option-count temperature refit; claims checked against its README), jevcpp (the reference implementation), and what this PR adds to Strata - new section 7 References: Jev/Laya/jevcpp + open Jev-compatible projects from the jevcpp plan; Platt 1999; Guo et al. ICML 2017; Groot & Valdenegro-Toro TrustNLP 2024 (arXiv:2405.02917, citation verified, the unsourced ECE>0.37 figure removed); lm-evaluation-harness/HELM, OpenAI logprobs cookbook, constrained-decoding systems, game-agent surveys; benchmark datasets - section 6: HIP/SYCL/Metal stated as not tested in this PR (nothing more) - removed internal /opt/data review paths; corrected the engine-capability sentence (window_logprobs already existed)
|
Wow, the other day I was actually thinking about using jev to detect whether the stop token in the answer is as expected, so as to prevent the task from ending illegally. Turns out it's already there now, haha. |
|
Thanks for the pull request. We're resetting the tracker for 0.2.0-beta. There are more than 1,300 open issues and pull requests, too many for us to review properly, so we're closing everything that isn't being worked on right now. Closed here does not mean rejected. To reopen it takes about two minutes:
The rules are in CONTRIBUTING.md. |
|
Rebased onto What changed in the move: 42 commits replayed with no conflicts, and the feature code is unchanged Not run: HIP, SYCL, Metal, Windows, macOS, and the app's UI tests. Those are listed in #3643 as not run |
Problem and result
Problem. Strata can dump logprobs for verification, but there is no per-request way to
read the logits of arbitrary label tokens at the final position. Without that, the engine
cannot serve a System One decision contract — a caller that wants a typed answer with a
calibrated probability (
noulyes/no,choicepick-one,scoreordinal) has to make themodel generate prose and then parse it, which is slow, unparsable in the failure case, and
gives a confidence the model chose to write rather than a number the caller can threshold.
Result. An opt-in engine wire (
--jev-brow) that returns the final-row logits forexactly the label tokens asked, plus a serve-layer
POST /v1/systemoneendpoint that appliesthe Jev readout math and per-tuple post-hoc calibration (Platt for
noul, temperature forchoice/score). One decode step produces a typed answer with a probability and a margin;the same model keeps serving chat, can answer decisions inside a chat call, and can decide
then narrate. Everything is gated by
STRATA_JEV_MODES; with it unset, the process behavesexactly like stock Strata (a decision surface that is not enabled returns
404, so probinglearns nothing).
Related discussion
reference implementation's wire and math onto the Strata engine. Opening it as a PR rather
than asking first is deliberate, and a maintainer can redirect it to an issue if that is
the preferred shape.
had considered — that is the same readout, used for a different purpose.
through
/reopen.Approach
Verifier::brow_rowinverify.hpp/.cppplusthe
--jev-browflag; final-row logits for a supplied token list, fail-closed on anymismatch. No change to existing decode paths.
/v1/systemonewith the donor's error contract (422+{"error": {"field": ...}}), the frozen D3 prompt block, and readout math ported fromjevcpp. Calibration is post-hoc and argmax-preserving, per tuple (
mode|qtype|set), hot-swappable through
POST /admin/calib, with donor-schema log rows for the offlinefit.py.STRATA_JEV_MODES=chat,decision(default for the jevport presets),decisionalone, or unset for stock behaviour; unknown tokens refuse to boot./v1/chat/completionsunless"jev"is present; nonew default; model format unchanged. Divergences from the donor plan are listed in §4d of
the detail below, each with the reason.
Validation
Revision: branch
jev-portheadfbffc99a, base296b59d2(v0.1.43 lineage).Environment: RTX 3090 24 GB, QFN = Qwen3.8-Flash-Next IQ3_XXS MoE, 32k ctx, calibration
tuple
p-jevcpp-qwen38-27b|q4_k_xl|timber|v1|V|all.Passed:
11 skipped.
NLL and ECE15 pre→post — table in §4a below.
quant): §4b below.
inline-chat shapes: §2 below.
Failed:
Not run:
main(v0.2.1.4,9de04cd4). This branch is 42 commits behindand
mergeable_stateisdirty, so a rebase is required before the numbers above can bere-claimed on the current tree. Queued next.
Limits
QFN/IQ3_XXS/32k. Another model or quant needs its own refit before the probabilities mean
anything — the PR ships the fitting path, not universal numbers.
lands (that is the separate suffix-reuse PR, [Engine] suffix-reuse: conversation KV prefix mount, quant-safe splices, state checkpoint ladder #2351).
image_mode: directreads raw image embeddings; it was validated on the QFN vision pathonly.
Background and design detail
What a System One decision model is. Most uses of a language model are
"System Two": the model writes text and the caller reads, parses and trusts it. A
System One decision model does the opposite. It receives a state (text, JSON, or an
image) and a set of typed questions, and returns typed answers with calibrated
probabilities: a yes/no (
noul), a pick-one (choice), or an ordinal rating (score).It never generates prose to decide. Nothing has to be parsed, and the confidence is a number
the caller can threshold, route on, or log, rather than a sentence the model chose to write.
That makes it suited to fast, repeatable, auditable decisions inside a loop: routing,
guardrails, triage, scoring, and game or agent control.
Jev. "Jev" is TypeSafe's commercial System One decision API:
POST /v1/systemonewith{state, questions}, answers read from token probabilities rather than generated text. Itsrequest/response shape is the wire this work follows. Open implementations of the same idea
already exist (see References); the goals here are correctness, speed and honest
probabilities, not novelty.
Laya. Laya (
convaiinnovations/laya, Apache-2.0) approaches the same contract with adifferent mechanism: a small purpose-trained encoder (ModernBERT-large, 421M parameters;
non-autoregressive) that answers all questions in one forward pass (~33 ms reported),
trained with reinforcement learning against strictly proper scoring rules (RLCD) so that
honest probabilities are what maximises reward. Two things in its documentation shaped this PR: its probabilities ship
over-confident until a temperature is refit per question type and option count
(reported mean ECE 0.466 → 0.081, with the instruction to do this on your own data before
trusting them), and its comparison against Jev uses third-party published Jev figures
that it did not measure itself. We kept the first idea (per-tuple calibration, refit on
local data) and measured our own numbers on our own harness. The mechanism differs: a
general LLM read at its label logits, with no task-specific training.
jevcpp. The reference implementation this port follows: a llama.cpp-based server that
serves the Jev wire from a generative model by reading the label logits at the final
prompt position (Qwen3.8-27B, with a shipped calibration file and a metamorphic/golden test
set). Its plan and amendments define the request contract, the frozen prompt template, the
readout math and the test strategy that this PR ports.
What this PR does. It brings that System One surface to Strata. Strata's engine could
already dump logprobs for verification, but had no per-request way to read the logits of
arbitrary label tokens. This PR adds an opt-in
--jev-browengine wire (BROW) that returnsthe final-row logits for exactly the label tokens asked, and a serve-layer
/v1/systemoneendpoint that applies the Jev readout math and per-tuple post-hoccalibration (Platt scaling for yes/no, temperature scaling for choice/score) on top. The
same model keeps chat, and can answer decisions inside a chat call, from an image directly,
or decide-then-narrate. It is opt-in per process (
STRATA_JEV_MODES).Donor reference: jevcpp on Qwen3.8-27B (q4_k_xl), shipped calibration tuple
p-jevcpp-qwen38-27b|q4_k_xl|timber|v1|V|all(T_choice 1.5861). This port runs onQFN = Qwen3.8-Flash-Next IQ3_XXS MoE (RTX 3090, Strata engine v0.1.43-lineage, 32k ctx).
1. Features (the full jevcpp surface)
POST /v1/systemone{state, questions}— noul (yes/no), choice (letters), score (0-10 levels)enable_thinking=false, verbatim string states)--jev-browengine flag; final-row logits, fail-closedcalib-<mode>.json;POST /admin/calibhot-swap; donor-schema log rows forfit.pyX-Jev-Mode: V— per-option yes/no branches,p = softmax(s_yes − s_no), name-keyedimage_b64+image_mode:summary(vision → 160-tok summary → decide) ordirect(raw image embeddings + question, ONE readout)"then": {"generate": {...}}— answers folded into the last user turn, one wire requestPOST /v1/chat/completionswith `"jev": {stateError contract: donor's
422+{"error": {"field": ...}}. Streaming refused with thejev extension,
stateXORmessages,image_modevalidated, D+V refused with 422.2. Usage
Full surface + examples:
docs/JEV-SYSTEMONE.md. Refit calibration:calib/README.md(the suite IS the procedure). Mode gating:STRATA_JEV_MODESenv ("decision"= decision-only — no generation pathexists on the instance at all; outputs are probabilities over the caller's own
labels. Unset =
"chat"only: decisions are opt-in, and the jevport presets set"chat,decision"explicitly. Fail-closed on unknown tokens and on gatedroutes).
Why logit readout instead of asking the model for confidence: models asked to state their
own confidence are mostly overconfident with high calibration error (Groot &
Valdenegro-Toro, TrustNLP 2024, see References). Reading label probabilities from the
logits and calibrating them post-hoc on held-out data (Platt / temperature scaling) is the
standard alternative, and it is what this PR does; its measured effect is in section 4a.
Laya reaches the same conclusion from the training side: its probabilities also need a
temperature refit on local data before they can be trusted.
3. Test strategy
Unit (mock engine) — 73/73 green (jev modules; full serve/ discovery 858 run, 0 fail, 11 skipped): readout math invariants, prompt rendering
(byte-pinned noul/choice blocks — the Yes/No-swap regression is now a golden test),
per-tuple calibration isolation, V-mode softmax, image two-call/direct shapes,
decide-then-generate, chat-surface validation arms, 7×3 concurrency + FIFO
serialization.
Live suites (
calib/bench_suite.py): dev-split fit → held-out test eval,standard top-label ECE15 + mean NLL, per-item JSONL logs. Guards: Platt slope
a≤0 refused (label/prompt inversion detector — it caught a real bug), temperature
at search boundaries flagged.
Adversarial audit protocol (Justin's rule, 2026-10-15): 1-in-10 of all test
items re-verified by hand — text golds re-read from source passages, image items
regenerated from committed seeds with sha256 match against the logged hash, sampled
error images independently re-counted with vision. Result across text+image+real
suites: 0 scoring hallucinations, 0 gold errors, 1 documented CV-Bench ambiguity.
Artifacts:
audit-*.json,audit-imgs/,*-item-log.jsonl(920 rows).Real-benchmark converters (
calib/convert_benchmarks.py): POPE adversarial,CV-Bench, CountBenchQA via HF datasets-server; banned-field self-test (answer-bearing
fields never enter requests), sha256 image manifest, dev/test image-disjoint, POPE
sampled by image at exactly 50/50.
Vision strategy (externally reviewed before implementation):
6-arm design (D/S/S-QC/S-V/blind/shuffled — the last two are contamination
detectors), circular rotation for choice position bias, phased screen→full→frozen
PR gate.
4. Benchmarking results (all live, QFN IQ3_XXS, RTX 3090)
4a. Pre/post calibration (held-out test, offline replay of identical items)
*CVB n=20, bin-noise territory; NLL improves, acc flat. Fitted: L a=.6127 c=.6539
Tc=1.0557 Ts=2.263 · D a=1.0571 c=−.172 Tc=1.5456 Ts=.6555.
Also shown in
calib-prepost-ab.json: the OLD single global calibrator made imagetuples WORSE than raw (CountBench ECE .091→.451!) — the per-tuple fix is the story.
4b. Benchmark vs jevcpp's own benchmark (donor m2_baseline, same 3 tasks)
Donor: Qwen3.8-27B q4_k_xl, dense, its own server. Port: QFN IQ3_XXS MoE, Strata.
Same task sets (BoolQ / ARC / SST-5 slices from jevcpp's eval data), same metrics
code, post-calibration numbers:
*Both ECE variants are top-label, 15 bins; the donor scores each bin at its CENTER
((b+0.5)/15), this PR's harness scores the bin MEAN confidence. The donor-definition
column recomputes our logged raw_p under the donor's exact b13_eval formula so the
comparison is apples-to-apples. Under either definition the direction is the same.
The 26B-smaller IQ3 MoE matches the donor within ~1 standard error on noul and choice
(at n=100, SE≈3-5pp); score is −4.9pp (≈1 SE; the confounds are model size, MoE vs
dense, and quant — not separable at this n). Donor noul calibration HURT it
(0.905→0.890 acc); ours helps (0.890→0.910). SST-5 calibrated ECE remains ~5× the
donor's under both definitions — T_score=2.263 is doing damage control on a weak
5-way readout, not achieving donor-grade confidence. Stated plainly.
4c. Performance (warm, single client, n=10 per cell)
These cells are the warm-repeat regime (identical or near-identical prompts;
the engine's prefix mount fires). Instrumented per-turn measurements
(temporary build, since reverted) showed the two regimes explicitly, at a
67-token prompt: fresh question ≈ 1.0 s end-to-end (~0.97–1.14 s prefill +
decode + readout); identical repeat ≈ 0.35–0.45 s (mount skips prefill).
Multi-question requests and benchmark items are mostly fresh-prompt, so
expect the fresh column when prompts vary; the p50s above reflect settled
repeat traffic. Prefill dominates in both — that is the case for the
state-KV-reuse follow-up.
vs donor (27B dense, its m1_perf matrix, 1q-1c-warm): 71 ms p50 / 14.0 rps.
Measured budget of the port's ~350 ms (proxy, fresh connection), decomposed live:
Streaming is not a factor: systemone responses are single JSON bodies with
Content-Length (donor contract refuses
stream: true). The gap vs the donor isthe un-prefixed engine turn, not protocol overhead — the donor amortized prefill
across forks (prefix-once + seq_cp); this port re-prefills per request. State-KV
reuse / prefix-LRU (the separate suffix-reuse work, out of scope here) collapses
the prefill share and projects decisions to ~60–80 ms. Repeat-identical requests
already hit the engine's prefix mount: 278–300 ms via proxy, and long states
(~470 tok) repeat at the same cost, confirming the mount fires when the prompt is
a full prefix. Image direct ≈ text latency (encoder cached); summary costs ~6×
direct (full 160-token generation).
Determinism / noise floor (measured, mechanism identified): repeat-decision
probability drift on this engine is p99 |Δp| ≈ 0.04–0.07 with zero argmax
flips in 720 repeated decisions across idle, 8-decision and mixed 4-chat+4-
decision load (
blockers-run/noise-floor.json). The donor's 5e-3 gate was aDENSE-model gate and never applied here. Mechanism (verified in engine
source): MoE expert placement uses
atomicAddslot assignment(
src/prefill/moe_fused.cuplace_kernel), so row order inside each expert iswarp-dispatch order — nondeterministic per run; float addition is non-
associative, so the expert GEMM sums differ in the last ulp and the divergence
compounds across layers; IQ3_XXS keeps the wobble above dequant noise and the
readout softmax amplifies it near p≈0.5–0.9 (Δp ≈ p(1−p)·Δd). It is a property
of the model+engine pair, not of load — the three load scenarios measured the
same floor. Decision margins (>0.9 typical) exceed the floor by an order of
magnitude; fitted calibrators average it out over the dev split. A
deterministic-placement option (sort rows by source index after the atomic
fill) would lower it, as an upstream engine follow-up — out of scope here.
Single-item probability deltas smaller than this floor (e.g. POPE +1.6pp at
n=60) must not be read as signal.
4d. Not ported / diverged (deliberate, vs the donor plan)
--modes chat,decisiongatingSTRATA_JEV_MODESenv (A0.1) with a deliberate divergence: unset = chat only (donor defaulted both-on). Opt-in keeps an unconfigured Strata process behaviorally identical to stock, per the repo rule that a new feature is opt-in and the default path stays unchanged. Values:"chat"/"decision"/"chat,decision"; fail-closed 404 on gated routes, refuse-to-start on unknown tokens./admin/modesruntime flip not ported — restart is the policy boundary hereimage_mode: direct— same single-readout goal, no fork, D-tuple-calibrated, benched on POPE/CVB/CountBenchAll 10 pre-PR blockers from the Opus review
(pre-PR external review) are closed: strict per-tuple
fallback, per-tuple log files, B1.4 tuple refusal, fail-closed /admin/calib,
token audit (PASS), a211 metamorphic battery, noise floor (see 4c), soak
(2,200 req, 0 jev errors, VRAM drift 0.026%), S-tuple fit (live), bootstrap
CIs + Brier (
blockers-run/cis-brier.json).5. Artifacts
calib-prepost-ab.json— 4a's numbers + method (offline replay, identical items)perf-matrix.json— 4c (live measured)text-suite.md,image-suite.md,real-suite.md+*-item-log.jsonl— suite reportsconvert-manifest.json— real-benchmark provenance (sha256 per image)audit-text-sample.json,audit-image-sample.json,audit-real-sample.json— 1-in-10 auditsjevcpp/reports/m2_baseline.json,jevcpp/reports/m1_perf.json,jevcpp/calib/calib.json(all in the donor tree, not this repo)6. Scope of change, compatibility, and testing status
Engine diff (5 files, +149 lines, additive):
Verifier::brow_row(verify.hpp/.cpp),the
--jev-browflag andBROWserve-loop handler (generate.cpp), a CMake test target(
verify_brow_test, CUDA builds only), and its test. Everything is opt-in: the handlerruns only when
--jev-browis passed and the server sendsBROW; no existing requestpath changes. One line runs on every verify window regardless of the flag
(
last_row_T_ = T;inVerifier::run), an int store with no behavior change.Default behavior:
STRATA_JEV_MODESunset = chat only. Verified end to end with theenv var absent:
/v1/systemoneand the chatjevextension return 404, plain chatreturns 200. A stock Strata process is behaviorally unchanged.
Tested: CUDA build and GPU runs on an RTX 3090 (QFN IQ3_XXS, Qwen3.8-Flash-Next); the
full
serve/test discovery (858 tests) passes with the jev modules opted in.Not tested in this PR: HIP, SYCL and Metal builds.
brow_rowis written for thosebackends (the HIP compat headers map
cudaMemcpy*tohipMemcpy*; a Metal branch isincluded), but none of them was built or run as part of this PR.
7. References and prior work
Decision-model products and implementations
/v1/systemonewire thisfollows.
convaiinnovations/laya(Apache-2.0).https://github.com/NandhaKishorM/laya, model card
convaiinnovations/laya. Source of theRLCD training description, the one-forward-pass encoder design, and the per-(question
type, option count) temperature-refit finding. Its README cites third-party Jev
measurements of 236–276 ms p50 (AbdelStark/jev-benchmarks, nibzard/decision-model-benchmark).
PLAN.md,PLAN.amendments.md,DECISIONS.md,m2_baseline.json,m1_perf.json,calib.json(donor tree, not part of this repo). Its plan also lists open Jev-compatible projects as
prior art: DIY-Jev (Rust),
llm2jev,verdict,jevper,jev-compatible-server.Calibration
Regularized Likelihood Methods, 1999: the Platt scaling used for yes/no.
ICML 2017: temperature scaling (choice/score) and the Expected Calibration Error metric.
in Large Language and Vision-Language Models, TrustNLP 2024 (arXiv:2405.02917): LLMs and
VLMs asked to state their own confidence show high calibration error and are mostly
overconfident, the reason decisions are read from logits and calibrated externally.
Readout design (surveyed in
docs/research notes, 2026-10-15)lm-evaluation-harnessand HELM: loglikelihood multiple-choice scoring,single-token letter labels,
acc/acc_norm, and the smoke-test invariants adopted inthe golden battery.
top_logprobsclassification and top-1 − top-2 margin as the routing signal(returned on every answer here).
approach this design avoids, and its documented whitespace/prefix pitfalls.
The Latent Bridge (arXiv:2606.24470): why a single-pass fast path matters for
frame-rate decision loops.
Benchmarks used for calibration and evaluation: BoolQ, ARC, SST-5 (text); POPE
(adversarial), CV-Bench, CountBenchQA (image).