Skip to content

feat(serve): typed decisions through /v1/systemone and /v1/decisions - #119

Merged
iamwavecut merged 1 commit into
masterfrom
dev/systemone-decisions
Oct 22, 2026
Merged

iamwavecut merged 1 commit into
masterfrom
dev/systemone-decisions

Conversation

@iamwavecut

Copy link
Copy Markdown
Owner

Closes #64.

What

POST /v1/systemone (TypeSafe's System One API, in the shape llama.cpp, vLLM and Ollama serve) and POST /v1/decisions (OpenAI's Decisions API, text only) answer choice, noul/predicate and score questions about a state with the served chat model, no training and no startup option.

  • Each question is one read: a fixed system turn asking for one label, then a user turn with the state, the question and its options under one-token labels (A.. for a choice, yes/no for a noul, 0.. for a score), thinking off, one output token, greedy, no penalties, no graft.
  • The answer comes from the first reply token's exact log probabilities of the labels: a new Op readout_logprobs (l[id] - logsumexp(l) over the public vocabulary, one CTA, FP64 oracle test) runs on the head's logits in the Qwen3.5 core's prefill (both the chunked card path and sample_from_hidden), carried through ExecutionOptions::readout_tokens → plan → round buffers → RawTokenLogprob::readout_values → TokenLogprob::readout. Flash-Next and Gemma 4 leave it empty and the serve layer reads the labels from the token's top-20 alternatives (diagnostics.<id>.readout = "top_k", a missing label bounded by the least likely alternative).
  • Probabilities are the softmax over the options (a noul's yes+Yes and no+No added up); confidence is the winner's probability over the whole vocabulary; diagnostics.<id> carries label_mass, argmax_is_label and readout. score is the probability-weighted level index with a legend.
  • Strict parsing as vLLM: unknown fields, joint, thinking on → 400; images/files/audio/videos → 501; 1–64 questions, 2–26 options, 2–10 levels; a label that is not one tokenizer token → 400.
  • The reads run a lane's worth (--max-concurrency) at a time, so other clients get the queue between windows, and every read starts with the state, which the prefix cache prefills once.
  • Request log protocols systemone / openai_decisions; GET /v1 index entries; docs in docs/serving.md (Typed decisions), README and the documentation map.

Per-request calibration (temperature, permutations) is deferred by the user's decision.

Verification

Vast RTX 3090 (EPYC 7313, driver 595, CUDA 12.8 build, sm_86), Ternary Bonsai 2 27B, --max-concurrency 4, scripts/pods/decision_checks.sh + decision_bench.py (added):

  • ninfer_readout_logprobs_test (full 248320-row and 151936-row vocabularies, 1–32 ids, uniform and extreme logits, untouched slots past count, validation), ninfer_target_logprobs_test, ninfer_logprob_topk_test, ninfer_systemone_test (parsing of both dialects, labels, prompts, reading, responses), ninfer_rerank_test: pass.
  • Agreement: a choice's readout probabilities times label_mass equal the chat endpoint's top-20 log probabilities of the same prompt to 1e-5 (billing 0.71710 vs 0.71710; label mass 0.98292 vs 0.98292).
  • Both endpoints give the same choice probabilities for the same question; shapes, sums to 1, argmax = choice, expected score, 400/501 refusals checked.
  • Latency (wall, median of 3 warm = state cached; cold = state prefilled first):
state questions cold warm per question
370 tok 1 0.15 s 17 ms 17 ms
370 tok 4 0.60 s 62 ms 15 ms
370 tok 16 0.78 s 235 ms 15 ms
13.5K tok 1 5.3 s 39 ms 39 ms
13.5K tok 4 5.8 s 91 ms 23 ms
13.5K tok 16 6.1 s 325 ms 20 ms
  • Concurrency: 16 questions (2K-token cached state) take 0.26 s alone and 0.42 s beside a streaming 256-token chat completion; the chat decodes at 83.5 tok/s instead of 89.7 tok/s meanwhile.
  • Prompt tuning (chat top-20 probes on the same model): the user turn alone left the labels 60–90% of the first token's probability ("The", "Based" took the rest, and at 13.5K tokens "The" was the argmax for a choice); the system turn raises it to 98–99% for every type and length, and "in lower case" moves a noul's mass from Yes to yes. Both cases are read anyway.
  • Mac: host-only syntax check of every changed C++ file and the serve test built and run with clang; git diff --check.

Not verified / limits

  • The top-20 fallback (Flash-Next, Gemma 4) is covered by the unit test only; no GPU run on those models.
  • The head's logits are BF16: two options scored within one BF16 step share one probability (seen as A = C = 0.2884 before tuning). Documented.
  • Probabilities are a general chat model's under this prompt; not calibrated.
  • CI was not waited for; the CUDA build and tests ran on the rented 3090 (GCC 13), the host checks under Apple clang.

Rental: 1.0 h at $0.30/h on Vast (RunPod had no 3090/4090 community instance with a public IP); the instance is destroyed.

TypeSafe's System One API (as llama.cpp, vLLM and Ollama serve it) and OpenAI's
Decisions API, answered by the served chat model: one read per question, the
first reply token's exact log probabilities of the labels (A.. for a choice,
yes/Yes and no/No for a noul, 0.. for a score) read by a new readout_logprobs
Op on the head's logits in the Qwen3.5 core (ExecutionOptions::readout_tokens,
TokenLogprob::readout); the other cores read them from the top-20 alternatives.
A system turn keeps the first token on the labels (98-99% of its probability on
Bonsai 2 27B), the reads run a lane's worth at a time and share the state's
cached prefix, and the responses carry label_mass, argmax_is_label and readout
diagnostics.
@iamwavecut
iamwavecut merged commit 2ff87bd into master Oct 22, 2026
12 of 25 checks passed
@iamwavecut
iamwavecut deleted the dev/systemone-decisions branch October 22, 2026 00:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: one running instance with both Qwen chat and Jev-style decision capability

1 participant