Aron Homberg - Independent Researcher - 2026
Scope. This document explains the inference method implemented by
kyr0/Bonsai-Llama-Jev: how an ordinary causal language model (LM) can be exposed as a Jev-like typed-decision engine and System One API service without sampling, without generating an answer sentence, and without changing the model weights.The central idea is simple: stop after prompt evaluation, read the model's native next-token logits for a small set of answer-label tokens, normalize those logits into a categorical distribution, and reduce that distribution into a typed result.
This is an inference/readout transformation, not a new neural architecture and not, by itself, a calibration method. It reproduces the shape of a typed-decision API. It does not establish that the loaded model has Jev's weights, training procedure, or probability calibration.
Any causal language model already computes a categorical score over its vocabulary at every next-token boundary; a Jev-like typed-decision engine turns runtime-defined semantic answers into token verbalizers, stops inference after prefill, reads only those native logits, converts them with stable restricted softmax and chain-rule branch scoring, and deterministically reduces the resulting distribution into choice, score, or noul — without sampling and without generating an answer token. This paper breaks this method down. It explains, how any causal language model can be turned into a Type-Decision Engine. Demonstrated as a paper with code, the target audience of this paper is primarily: Applied AI engineers, AI research scientists and curious tech-affine readers.
The reader should be able to follow the code. Therefore, the repository lineage matters as the typed-decision behavior implementation at hand is a server/runtime modification to a fork of llama.cpp.
Provenance as of 2026-09-22:
kyr0/Bonsai-Llama-Jev was forked from PrismML-Eng/llama.cpp.3ae4f51087d8d9292eda16ee00cec54e798ea576.8b1bb68f41a2a78e1e9ca5bd6155694e6a74ed7f — works9203183e6c54fda23872f230e33c1985b713c10c — server/runtime hardening, generation-cap handling, build/start/e2e work.The important code archaeology is:
PrismML llama.cpp
|
| inherited model/runtime implementation
v
3ae4f510...
|
| fork-local typed-decision implementation
v
8b1bb68...
|
| runtime/e2e hardening
v
9203183...
The core files introduced / changed for typed decisions are:
tools/server/SYSTEMONE.md
tools/server/server-context.cpp
tools/server/server-context.h
tools/server/server-task.h
tools/server/server.cpp
tools/server/tests/unit/test_chat_completion.py
The decisive implementation is in server-context.cpp: a new SERVER_TASK_TYPE_SYSTEMONE runs the normal model prompt forward pass, extracts selected logits at SLOT_STATE_DONE_PROMPT, sends those logits back to the HTTP-side decision logic, releases the inference slot, and returns before normal token generation starts.
This method crosses language-model, probability, and inference-runtime terminology. The terms below are used precisely throughout this document.
| Term | Meaning here |
|---|---|
| LM | Language model. |
| LLM | Large language model. Size is irrelevant to the method; the same readout principle applies to smaller causal LMs. |
| Causal / autoregressive LM | A model trained/inferred so token position predicts a distribution for the next token from earlier tokens. |
| Token | Integer symbol consumed/emitted by the model. A token is not necessarily one character or one word. |
| Tokenizer | Deterministic mapping between text and token IDs. Its context-sensitive segmentation is why answer-label validation is required. |
| Vocabulary | Finite set of token IDs the LM can predict. |
| Hidden state | Final internal vector at prompt position , before the LM output head. |
| LM head | Existing output projection that maps a hidden state to one score per vocabulary token. |
| Logit | Pre-softmax model score. In neural-network usage this usually means an unnormalized score; it should not be confused with the strict binary-statistics definition of log-odds. |
| Softmax | Function converting real-valued scores into positive normalized weights summing to 1. |
| Restricted softmax | Softmax computed only over the answer-token subset rather than over the whole vocabulary. |
| Verbalizer |
Mapping from a semantic class such as technical to one or more model tokens such as B.
|
| Prefill | Evaluation of the known input prompt. It produces model state/KV cache and logits at the prompt boundary before any generated continuation token. |
| Decode | Repeated continuation phase in which new output tokens are selected/appended and the model advances from them. |
| Sampler | Generation component that transforms/selects from logits using operations such as temperature, top-, top-, penalties, random sampling, or greedy selection. |
| KV cache | Cached attention Key/Value tensors for already-evaluated prefix tokens, allowing suffix continuations to avoid recomputing the full prefix. |
| Argmax | Index of the largest value. Used here to select the highest-probability semantic candidate after probability computation. |
| System One | TypeSafe API surface for asking typed questions over supplied state. “System One compatible” here refers to the request/response contract, not a claim about proprietary Jev internals. |
| Choice | Unordered finite-class primitive: return one selected semantic option and a distribution across options. |
| Score | Ordered finite-level primitive: return the probability-weighted expected level plus the distribution. |
| Noul | TypeSafe yes/no primitive: return the probability of “yes”. |
| Calibration | Empirical property that predicted probabilities correspond to observed frequencies/correctness rates on a defined population. |
| NLL | Negative log-likelihood, a proper probabilistic scoring rule used when evaluating predicted class probabilities. |
| Brier score | Squared-error proper scoring rule for probabilistic predictions. |
| ECE | Expected Calibration Error: a binned summary of the gap between predicted probability and observed frequency; its value depends on the binning scheme. |
| GGUF | Model/container format used by llama.cpp-family runtimes. It is not part of the decision mathematics. |
Two distinctions are especially important:
An ordinary causal LM is already a next-token classifier over its vocabulary.
Given a tokenized prefix
the model computes a final hidden representation . Its language-model output head maps that hidden vector into one real-valued score for every vocabulary token:
Where:
The ordinary next-token distribution is:
Transformer decoders conventionally end in an output projection and softmax over possible symbols.
The typed-decision engine exploits a consequence that is easy to miss:
The LM already computed scores for every possible next token. If we encode each semantic answer as a known answer token, classification is already present in the final logits. We do not need the model to generate text explaining its choice.
This is related to the verbalizer idea in prompt-based classification: semantic classes are mapped to token-level labels that the LM can score. PET (Pattern-Exploiting Training, Schick & Schütze, EACL 2021) is a well-known example of this general class-to-token interface.
Let:
choice, score, or noul).Then:
The model parameters are unchanged.
Thus:
No classifier head must be trained. No adapter must be attached. No sampler is necessary.
Saying “we implemented a different neural forward pass” is therefore slightly imprecise. The neural forward graph remains the same. What changes is the server execution path:
A normal generation engine conceptually performs:
prompt
↓
prefill
↓
logits z₀
↓
temperature / top-k / top-p / penalties / ...
↓
sample or argmax y₁
↓
append y₁
↓
decode forward
↓
logits z₁
↓
select y₂
↓
...
Generated text follows the autoregressive factorization:
A generation engine therefore contains a serial decode loop.
For a one-token label set:
state + question + candidates
↓
deterministic prompt
↓
prefill
↓
final-position logits z
↓
gather z[A], z[B], z[C], ...
↓
restricted softmax
↓
typed reduction
↓
JSON response
There is no generated token between the model and the typed answer.
In the current implementation:
SERVER_TASK_TYPE_SYSTEMONE
↓
normal prompt evaluation
↓
SLOT_STATE_DONE_PROMPT
↓
llama_get_logits_ith(...)
↓
copy logits for task.systemone_tokens
↓
queue result
↓
slot.release()
↓
return
The normal sampler chain is never entered.
Consequences:
top-k;top-p;"A" and then an explanation;usage.output_tokens == 0 for this local method.The only argmax is later applied to the semantic candidate probability vector. That is classification, not greedy text decoding.
Removing sampling removes an explicit stochastic operation. It does not guarantee bit-identical outputs across:
Cross-backend equivalence still requires numerical tolerances.
Suppose the application asks:
{
"state": "The Stripe integration has failed for three days and sales are being lost.",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"billing": "Payments and subscriptions",
"technical": "Bugs and integrations",
"sales": "Pricing and account questions"
}
}
}
}
The engine does not ask the model to generate "technical".
Instead:
A -> billing
B -> technical
C -> sales
The semantic names/descriptions enter the prompt; the output boundary uses a tiny label alphabet.
semantic class verbalizer
------------------ ----------
billing A
technical B
sales C
The current repository uses:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
giving 62 single-character labels.
The direct readout is exact only if an answer character really is one token at the exact prompt boundary.
Let:
Require:
That means:
A adds exactly one token;A;The repository explicitly verifies these properties.
Conceptually:
prefix = tokenize(prompt);
extended = tokenize(prompt + "A");
require(extended.size() == prefix.size() + 1);
require(extended[0:prefix.size()] == prefix);
require(piece(extended.back()) == "A");
require(token_id_for_A_is_unique);
This matters because BPE/unigram tokenizers can merge punctuation, whitespace, and following text. It is possible that:
tokenize("answer:")
is not a prefix of:
tokenize("answer:A")
The current engine therefore fails with HTTP 422 rather than silently scoring the wrong token.
Hence “any LM” should be qualified:
The current implementation supports a causal next-token LM whose tokenizer/chat-template combination provides usable singleton answer tokens at the output boundary.
A generalized prefix-trie implementation can remove that restriction.
Assume candidates use one-token verbalizers:
The LM returns full-vocabulary logits . We copy only:
Compute:
This is a restricted softmax over answer tokens. Softmax converts arbitrary real scores into positive normalized weights summing to one.
Full-vocabulary probability:
Condition on the next token being one of the allowed candidate labels :
Substitute full softmax:
where:
The vocabulary denominator cancels:
Therefore:
test_systemone_matches_native_probabilities:
A/B token IDs;A and B;/v1/systemone.Agreement is checked to approximately .
Thus, for single-token candidates, System One is exposing the backbone's existing probability geometry under a restricted label space.
Naive exponentiation can overflow.
Use:
and:
The result is unchanged because the common factor cancels.
This is the standard max-subtraction/log-sum-exp stabilization technique.
The current implementation performs probability arithmetic in double, although copied logits arrive as float.
Suppose:
A / billing: 2.1
B / technical: 0.9
C / sales: -0.1
Subtract :
Exponentiate:
Normalize:
Therefore:
The choice is candidate A.
No answer token was generated.
For , labels use multiple characters, because the set of single-character labels is exceeded (see 6.).
Let the label width be , with radix sizes:
Capacity:
Require:
The implementation searches for radix sizes that minimize capacity and then prefix-readout cost.
For 67 candidates:
radices = 2 × 34
capacity = 68
For label BA, the second character must be scored conditional on B.
By the probability chain rule:
More generally:
This is the standard probability chain rule.
The current engine uses a restricted categorical probability at every branch:
For candidate :
Finally:
For multi-character labels this is best understood as a forced categorical decision tree.
It is not exactly the raw unrestricted language-model probability of the complete label string, because every branch is renormalized over valid answer symbols:
The engine asks:
Among valid answer symbols at this branch, how does the model distribute support?
That is the desired quantity for a typed classifier.
For radices :
with empty product .
Examples from the implementation:
| Candidates | Radices | Capacity | Readouts |
|---|---|---|---|
| 27 | 27 | 27 | 1 |
| 53 | 53 | 53 | 1 |
| 62 | 62 | 62 | 1 |
| 63 | 3 × 21 | 63 | 4 |
| 67 | 2 × 34 | 68 | 3 |
| 100 | 2 × 50 | 100 | 3 |
| 677 | 17 × 40 | 680 | 18 |
| 3844 | 62 × 62 | 3844 | 63 |
| 3845 | 2 × 37 × 52 | 3848 | 77 |
Therefore “no decoding” does not mean “one neural evaluation for arbitrary candidate counts.”
For (single-character label case), one final-position readout is enough per question.
For wider labels, deterministic prefix branches require additional suffix evaluations. They are still not sampled/generated outputs.
The KV cache stores previously computed attention keys and values.
Without reuse:
prompt + A
prompt + B
prompt + C
would each repeat the entire prompt computation.
With reuse:
prefill(prompt) -> KV(prompt)
branch A -> suffix only
branch B -> suffix only
branch C -> suffix only
The current server sets:
task.params.cache_prompt = depth > 0;
for deeper label-prefix readouts.
Cache reuse changes performance, not probability semantics. Cache eviction or slot scheduling can still force reevaluation.
The alphanumeric scheme is practical, but a fully portable engine should support candidate verbalizers of arbitrary token length.
Construct a prefix trie:
root
/ \
token 91 token 42
/ \ \
17 88 5
At every node:
Suggested abstraction:
struct DecisionCandidate {
std::string semantic_id;
std::vector<llama_token> verbalizer;
};
struct DecisionTrieNode {
std::map<llama_token, NodeId> edges;
std::optional<size_t> candidate;
};
The runtime then needs only:
SelectedLogits score_next(
PrefixState prefix,
span<const llama_token> allowed_tokens);
After obtaining:
the API primitives are deterministic reductions.
TypeSafe's public Choice contract similarly exposes the selected option, a probability per supplied option, and confidence.
For ordered levels :
This is ordinary statistical expected value.
TypeSafe documents exactly this probability-weighted-level interpretation; for example, probabilities over levels yield score .
TypeSafe defines Noul as a yes/no question returning the probability of “yes”.
Internally:
yes
no
with:
The current implementation computes:
Since:
this simplifies to:
confidence field is a local heuristicLacking any better known option, we currently compute:
This is a deterministic concentration heuristic.
It is not a theorem that:
For binary winner probability :
Thus:
For the previous three-class example:
so:
TypeSafe's public docs describe confidence as derived from how spread/peaked the probability distribution is; with no further information about Jev’s implementation, we cannot establish this local formula as Jev's production formula.
Downstream systems should distinguish:
The direct readout is exact relative to the model and prompt:
That does not imply:
The readout inherits:
Thus, Softmax over allowed answer tokens is conditional on supplied alternatives and is not automatically operationally calibrated confidence.
Zhao et al. show that language-model classification can be highly sensitive to prompt format, example ordering, and answer biases, motivating contextual calibration. This should be considered as well.
The safest interpretation is:
Given this prompt and these alternatives, how does this backbone distribute its answer preference among them?
If the candidate set is incomplete, the probabilities still sum to one.
Use other, none, insufficient evidence, or an abstention class when the task is genuinely open-set.
Suppose the engine produces scores . A post-hoc temperature parameter can transform them:
Fit on a held-out calibration set by minimizing NLL.
Guo et al. found temperature scaling to be a simple and often effective post-hoc calibration method for neural classifiers.
These may use mathematically similar divisions by , but serve different purposes.
Sampling temperature
logits -> temperature -> token-selection sampler
belongs to generation.
Calibration temperature
decision scores -> fitted T -> deterministic probabilities
is a deterministic probability mapping.
Therefore a calibrated decision engine can still correctly claim no sampling.
Do not fit calibration parameters on the final test set.
Use:
training/model data
↓
calibration/validation split
↓
untouched test split
Operationally the decision function is not only:
but:
where includes:
The current implementation builds a structured payload conceptually resembling:
{
"evidence": "...",
"criterion": "...",
"options": [
{
"letter": "A",
"name": "billing",
"description": "Payments and subscriptions"
}
]
}
and applies the model's normal chat template with thinking disabled.
Mapping a semantic candidate to A instead of B changes the rendered prompt.
Thus candidate permutations should be treated as a semantic-stability test.
Control tokens, whitespace and the chat template itself remain important.
They determine:
Tokenizer validation must therefore operate on the fully rendered prompt.
In Bonsai-Llama-Jev, the System One API is added alongside the existing OpenAI API and llama.cpp native HTTP APIs:
server.cpp registers:
POST /v1/systemone
with 422-style request validation.
Question types:
choice
score
noul
systemone_question in server-context.cpp:
Each used answer character must:
server-task.h introduces:
SERVER_TASK_TYPE_SYSTEMONE
plus:
llama_tokens systemone_tokens;
and need_logits() returns true for the task.
At prompt completion, the key path is effectively:
const float * logits =
llama_get_logits_ith(ctx_tgt, slot.i_batch - off);
for (llama_token token : slot.task->systemone_tokens) {
result->logits.push_back(logits[token]);
}
queue_results.send(...);
slot.release();
return;
That return is the decisive boundary.
The HTTP-side path:
output_tokens = 0.A portable engine can expose one primitive:
score_allowed_next_tokens(prefix, allowed_token_ids) -> logits[]
Then:
function decide(state, questions):
answers = {}
for question in questions:
candidates = compile_candidates(question)
prompt = render_prompt(state, question, candidates)
verbalizers = compile_and_validate_verbalizers(prompt, candidates)
trie = build_prefix_trie(verbalizers)
path_weight = array(candidate_count, 0.0)
queue = [(root, tokenize(prompt), 1.0)]
while queue not empty:
node, prefix_tokens, node_weight = queue.pop()
outgoing = node.outgoing_token_ids
logits =
prefill_or_cached_suffix_forward(
prefix_tokens,
outgoing
)
local_p = stable_softmax(logits)
for each edge(token -> child):
child_weight =
node_weight * local_p[token]
if child is terminal:
path_weight[child.candidate] += child_weight
else:
queue.push(
child,
prefix_tokens + [token],
child_weight
)
p = normalize(path_weight)
answers[question.id] =
typed_reduce(question.type, p)
return {
answers,
usage: {
input_tokens: logical_input_usage,
output_tokens: 0
}
}
Normal generation of output tokens costs approximately:
One-token typed decision:
gather + softmax + reducer is tiny relative to a large Transformer forward.
The system also avoids:
For independent questions, tasks can also be batched by the server.
A request may contain many questions.
The current server can schedule/batch their work together, but each question has its own criterion/options and therefore its own compiled prompt.
Thus:
parallel/batched inference
does not imply:
one shared hidden state answers every question
For one-character labels:
For multi-character labels:
Use:
final prompt position
↓
next-token logits
This is the current implementation.
Use:
encoder(state/question/options)
+
decoder start state
↓
decoder token logits
The restricted-softmax logic still applies.
For a BERT-like model:
prompt containing [MASK]
↓
hidden state at [MASK]
↓
vocabulary logits
↓
restricted verbalizer softmax
An embedding model has no token-prediction head, so this exact method does not apply. It requires a classifier, reranker, or similarity rule.
Thus “any LM” more precisely means:
any model exposing an appropriate token-prediction distribution through its inference runtime.
For multimodal causal models:
image/audio/text state
↓
multimodal prefill
↓
final LM logits
↓
same selected-token readout
↓
same typed math
The current repo's System One media extension uses the existing multimodal parser/projector.
A text-only model remains text-only.
Every semantic candidate has exactly one verbalizer path.
Every verbalizer is exactly the token path evaluated at the real prompt boundary.
For every selected token , decision mode returns the model's native .
Decision mode emits no sampled model token.
Changing:
top_k
top_p
min_p
sampling temperature
penalties
must not alter decision probabilities.
Cache reuse may change latency but not semantic output beyond numerical tolerance.
Typed-decision support must not alter ordinary chat/completion behavior.
Verify:
Test across:
For every prefix:
Test:
" A" tokens;Fail rather than approximate.
Vary all sampler controls.
Result should remain unchanged.
Permute option order, map outputs back to semantic IDs, then measure:
Compare cache reuse on/off.
Expected:
same probabilities
different compute cost
Stress:
On untouched labeled data report:
No.
Greedy decoding selects a token, appends it, and continues decoding.
Decision mode reads scores and returns structured data.
No.
Temperature 0 is usually a generation-path convention.
This method bypasses the generation sampler.
Not without calibration evidence.
It is a restricted model probability under a particular prompt/candidate set.
No.
Structural compatibility and semantic decision quality are separate.
Not necessarily.
They are separately compiled readouts that can be batched.
No.
The model weights/graph stay intact. The runtime exposes a different readout and termination point.
┌────────────────────────────────────────────┐
│ 1. Typed API / validation │
│ state, model, questions │
└──────────────────┬─────────────────────────┘
v
┌────────────────────────────────────────────┐
│ 2. Decision compiler │
│ semantics -> prompt + verbalizers │
│ tokenizer validation │
└──────────────────┬─────────────────────────┘
v
┌────────────────────────────────────────────┐
│ 3. Inference primitive │
│ prefill / cached suffix │
│ selected native logits │
│ NO sampler / NO decode loop │
└──────────────────┬─────────────────────────┘
v
┌────────────────────────────────────────────┐
│ 4. Probability + reducer │
│ stable softmax / chain rule │
│ choice | score | noul │
│ optional calibration │
└────────────────────────────────────────────┘
The backend does not need to know the meaning of choice, score, or noul.
It only needs:
struct LogitReadoutRequest {
TokenSequence prefix;
std::vector<TokenId> selected_tokens;
PrefixCachePolicy cache_policy;
};
struct LogitReadoutResult {
std::vector<float> logits;
size_t logical_input_tokens;
};
Semantics:
evaluate prefix
do not sample
do not generate
return z[token_id] for requested token IDs
In llama.cpp, the underlying capability already exists through:
llama_get_logits_ith(...)
The implementation task is exposing it at the correct server lifecycle boundary.
It transforms:
Properties:
The most accurate description is:
a discriminative readout layer over a generative language-model backbone
rather than “prompting an LLM to output JSON.”
Energy consumption and time spent is reduced. Therefore, also cost is reduced.
The method is useful for every non-open question format. If a question can be formulated or re-formulated as a closed question, the method applies.
It establishes:
It does not by itself establish:
Cloudflare currently describes Jev as a structured evaluation model that answers typed Noul/Choice/Score questions with calibrated answers, probabilities, and confidence. That is a product-level contract/claim, not a public architectural specification.
The local engine should therefore be described as Jev-like / System-One-compatible, not as a reproduction of undisclosed Jev internals.
Should you plan to implement this method in any inference engine, the following tasks need to be done:
choice, score, noul.double.kyr0/Bonsai-Llama-Jev8b1bb68f41a2a78e1e9ca5bd6155694e6a74ed7f9203183e6c54fda23872f230e33c1985b713c10ctools/server/SYSTEMONE.mdtools/server/server-context.cpptools/server/server-task.htools/server/tests/unit/test_chat_completion.pybonsai/openjev, docs/METHOD.md — generation-free direct final-position answer-token readout.The downloadable Markdown version contains conventional direct links for these references.
If you use the Bonsai-Llama-Jev inference engine, it’s method for turning causal language models into a typed-decision engine or it’s Qtype-stratified temperature scaling method, please cite my work:
@software{homberg2026bonsaillamajev,
author = {Homberg, Aron},
title = {Turning Causal Language Models Into Typed-Decision Engines},
year = {2026},
version = {5},
publisher = {GitHub},
url = {https://github.com/kyr0/Bonsai-Llama-Jev},
license = {MIT}
}