What a Decision Probability Actually Computes: Logits to Labels

The same {"department":"technical"} can come from generation, a classifier or label scoring. Work through softmax, candidate sets, multi-token labels and schema masks to see what 0.88 means.

Hands compare an ochre heart-shaped leaf with several green specimens as a closely related new candidate enters the set.

Series: Decision APIs in .NET, part 2 of 10. Previous: Part 1, one judgment inside a working support desk. Next: Part 3, designing the decision contract.

Three services route the same support ticket and return the same object:

{"department": "technical", "probability": 0.88}

The first generated those characters token by token under a JSON schema. The second ran a classification head with one output per department. The third scored each complete department name as a string and compared the results. The client deserializes three identical records. Yet the 0.88 means something different in each case, fails in different ways and costs a different amount of compute to produce.

Before a router treats that number as "the probability this ticket belongs to technical support", it needs to know which calculation produced it and over which alternatives. This article derives the mechanisms with small, fully specified numbers. Every figure below was computed by the C# program in the article; the token probabilities are a teaching construction, not a measurement of any commercial model.

  • Softmax turns scores into shares of a chosen set of alternatives. Add a candidate and every share changes, even when the model's view of the original candidates did not.
  • A multi-token label is a path. Its probability is the product of every conditional step, including the end marker; the first token alone can reverse the ranking.
  • Constrained decoding guarantees a valid value, not a correct one. Renormalizing after a mask can turn 8% of the original mass into an apparent 80%.
  • Ask of every confidence number: probability of which event, conditional on which candidates, computed by which procedure?

A probability is a share of a denominator someone chose

At each output position a language model assigns a score, called a logit, to every token in its vocabulary. A token may be a word, a fragment of a word, punctuation or another encoded unit. Logits are not probabilities: they can be negative or much larger than one. Softmax turns a finite vector of scores into non-negative values that sum to one:

p(i) = exp(z(i)) / sum over j of exp(z(j))

Take two candidates with logits 2 and 0. Their exponentials are about 7.389 and 1, so the first gets 7.389 / 8.389 = 0.880797 and the second 0.119203. Nothing in the arithmetic knows whether the candidates are departments, Yes and No tokens or chess moves. The meaning comes from the task around it.

Now add a third candidate with logit 2. The first candidate's logit has not moved, yet its share falls to 0.468311. What happens when a taxonomy grows over time? Use the slider to add candidates one at a time.

Explorable

The logit for technical never changes. Its probability falls from 0.88 to 0.26.

CANDIDATE LOGIT SHARE OF THE DENOMINATOR P technical 2.0 0.881 billing 0.0 0.119 returns 2.0 not in set other -1.0 not in set firmware 2.5 not in set sum of exp(logit) 8.389
  1. Two candidates, logits 2.0 and 0.0. Technical takes 0.881 of a denominator of 8.389. This is the number a two-label router would display.
  2. Adding returns at the same logit as technical splits the mass between them. Technical drops to 0.468 without any change to its own score.
  3. A weak candidate barely moves anything: other at logit -1.0 takes 0.023, and technical moves only from 0.468 to 0.458.
  4. A new firmware team at logit 2.5 becomes the most likely label. Technical is now 0.261, a correct answer to a different question.

Logits are illustrative. Probabilities and denominators were computed with the stable softmax shown below.

This is why a candidate set needs a version. A technical=0.88 from a two-label task and a technical=0.47 from a three-label task are not evidence of a model regression; they answer different conditional questions. If the new label describes a real category that used to be forced into the old ones, the new task can be more useful despite lower-looking numbers. Part 3 makes that version a field of the contract.

Compute softmax without overflow, and know what you discarded

Exponentiating a large logit directly overflows: Math.Exp(1002) returns Infinity in .NET, and the division that follows produces NaN. Subtracting the maximum score first does not change the result, because the common factor exp(-max) cancels between numerator and denominator:

public static double[] Softmax(double[] logits)
{
    if (logits.Length == 0 || logits.Any(x => !double.IsFinite(x)))
        throw new ArgumentException("A nonempty finite logit vector is required.");
    double max = logits.Max();
    double[] w = logits.Select(x => Math.Exp(x - max)).ToArray();
    double sum = w.Sum();
    return w.Select(x => x / sum).ToArray();
}

With logits 1002 and 1000 the method computes exp(0) and exp(-2) and returns 0.880797 and 0.119203, the same as for 2 and 0. At least one weight is exactly 1, so the denominator cannot underflow to zero for a non-empty finite vector. Rejecting NaN, infinity and an empty vector is an input rule, not a calibration technique. Very unlikely alternatives can still underflow to zero; if later code needs their log probabilities, keep values in log space rather than taking the logarithm of a rounded zero.

The bigger question is which logits went in. Keeping only two vocabulary entries and renormalizing them discards the mass on every other token. Google's ShieldGemma notebook does exactly this, deliberately: it takes the logits of the Yes and No tokens at the last prompt position, stacks them and applies a two-way softmax. A result of 0.99 between Yes and No can coexist with a model that mostly wanted to begin an explanation. That is a sound scoring method for a model trained and evaluated that way. It is not a general description of every endpoint that returns a probability.

A label is a path of tokens, and the path has an end

Real labels often span several tokens. Suppose, in a deliberately invented tokenizer, tech repair needs two tokens and billing needs one. Each also needs an end marker, so the scorer can tell billing from billing details. If a scorer looks only at the first position, which label wins, and is that the label with the highest probability?

Worked example

The first token favours tech repair 0.55 to 0.30. The complete labels favour billing 0.285 to 0.099.

prompt label starts tech p = 0.55 billing p = 0.30 other starts p = 0.15  repair p = 0.20 support, nical, ... p = 0.80 end p = 0.90 end p = 0.95 First-token view: tech 0.55 beats billing 0.30, so tech repair "wins". Each box shows the probability of that token given everything to its left. tech repair + end 0.55 x 0.20 x 0.90 = 0.099 billing + end 0.30 x 0.95 = 0.285
  1. The tree of possible label continuations. Each node holds a conditional probability: the chance of that token given the prompt and the tokens before it.
  2. A first-token scorer compares only the first column. tech at 0.55 beats billing at 0.30, so it reports tech repair as the winner.
  3. After tech, the required token  repair gets only 0.20. The other 0.80 goes to continuations that spell different labels, which the first-token view silently counted.
  4. Both labels need an end marker. Multiplying every step gives 0.099 for tech repair and 0.285 for billing: the ranking reverses.
  5. Normalized across the two complete strings, the distribution is 0.258 and 0.742. That is the probability conditional on the answer being one of these two labels.

Invented tokens and probabilities. Products and the normalized pair (0.2578125, 0.7421875) were computed by the program in this article.

The product of conditional probabilities is the probability of the exact complete answer. Long products underflow, so scorers add log probabilities instead and apply the stable softmax to the totals:

log P(label | input) = sum over t of log P(token t | input, earlier label tokens)

tech repair + end:  log 0.55 + log 0.20 + log 0.90 = -2.312635
billing + end:      log 0.30 + log 0.95             = -1.255266
softmax of the two totals                          -> 0.2578125, 0.7421875

A scorer supplies each candidate's earlier tokens while it evaluates the next one, which is usually called teacher forcing. That is different from sampling a free answer and hoping its tokens reveal the likelihood of every alternative. It also means scoring costs one forward pass per candidate path unless the implementation shares the common prefix.

The end marker matters because labels share prefixes. Scoring the prefix billing without the terminator treats billing details, billing address and every other continuation as if they were the intended label. A real scoring protocol must fix the answer template, the leading whitespace and the stop symbol, and changing any of them can change the result.

Dividing a label's log-likelihood by its token count is a common fix for long labels being penalized. It ranks by average log probability, which is a different criterion. With path probabilities 0.55, 0.90, 0.90 for a three-token label and 0.50, 0.95 for a two-token label, the totals are 0.4455 and 0.475, so the short label is more probable. The per-token averages are -0.270 and -0.372, so the long label wins. Use the heuristic if it evaluates better, but do not call the result the probability of the label.

A cleaner design gives labels short, neutral identifiers and puts the descriptions in the prompt. Even then, tokenization, leading spaces and the distribution of prompts still matter. Inspect the actual tokenizer before assuming two labels are one token each.

Constrained decoding changes what can be emitted, not what is true

A constrained decoder prevents invalid continuations under a grammar or JSON schema. Conceptually it masks disallowed tokens, then samples or selects from what remains. At the start of an enum value it permits only tokens that begin technical or billing; after the first branch, only continuations consistent with that branch survive. What does the renormalized distribution say about the model's belief?

Before and after

The schema keeps 10% of the mass. Renormalizing turns 0.08 into 0.80.

BEFORE THE MASK I The tech billing 0.62 0.28 0.08 0.02 schema: value must start an enum string 0.90 of the mass begins an explanation AFTER THE MASK, DIVIDED BY 0.10 tech billing 0.80 0.20 Valid by construction. Whether technical is right is still an open, evaluable question.
  1. At the start of the value, the unconstrained model puts 0.90 of its mass on tokens such as I and The: it would rather explain than answer.
  2. The schema mask removes every token that cannot begin an allowed enum string. Only tech and billing remain, holding 0.10 of the original mass.
  3. Renormalizing divides by 0.10. Technical becomes 0.80 and billing 0.20, a confident-looking split made from 8% and 2%.
  4. The output is guaranteed to parse. That guarantee says nothing about whether technical is the right team for this ticket.

Illustrative next-token probabilities; the renormalized values follow by division.

That may be exactly the decoding procedure you want. It is not independent evidence of correctness. OpenAI's Structured Outputs guide says the same from the provider side: the schema constrains completed, non-refusal output, but the application must still handle refusals, incomplete responses and API errors, and validate any business rules the schema cannot express.

Do not try to rebuild a full candidate distribution from returned log probabilities either. The Chat Completions top_logprobs parameter returns at most 20 alternatives per generated position, and the reference notes that fewer may come back. Those are alternatives for the tokens that were generated, not scores for every multi-token label you care about. Reading the first position of a generated enum gives you the first-token view from the previous section, with its reversal risk.

A classification head has a different output space

A conventional classifier maps an input representation h to one score per class with a learned matrix and bias:

z = W h + b
p = softmax(z)

Its output dimension is the class set, so no label is ever spelled through a vocabulary, and training can reward the correct class directly. Computing h may still mean running a transformer over the whole ticket, so a short output does not imply a cheap request. The cost of the head itself is usually small next to the cost of reading the input.

The trade-off is flexibility. Adding a genuinely new class, such as the firmware team in the slider above, normally requires new training data and a new head. A model that reads label descriptions from the prompt can absorb a changed candidate set without retraining, at the price of the tokenization and normalization issues above. A stable, well-labelled routing task is an excellent place to try a small classifier first; a taxonomy that changes monthly with few examples per class may justify the more flexible approach.

Specialized decision models add a further variable. TypeSafe describes its Jev model as a new architecture with a parallel sampler, trained with what it calls Reinforcement Learning for Calibrated Decisions, and states that higher confidence means higher accuracy. Those are provider claims; the announcement publishes no calibration measurements, and a C# client cannot inspect the internals. Whether the probabilities are calibrated on your tickets is something you measure, as Part 4 does.

What a native decision API actually hands you

Both native interfaces return a full distribution for a choice and a separate confidence. They are not the same number. System One documents confidence as a value computed from how the probabilities are spread, high for a single peak and low for a flat shape, and its own documentation example shows a choice with probability 0.40 and confidence 0.20. OpenAI's Decisions guide says choice and score answers return a distribution and a separate confidence field, without publishing a formula. A client should keep both, and must not treat confidence as the probability of the selected choice.

For ordered rubrics both APIs return a score equal to the probability-weighted average of the level indices, alongside the per-level distribution. The average can fall between levels. Part 3 shows why the distribution has to travel with it.

Reading the number without its question

// Week 1: two labels.  Week 6: three labels.
if (todayTechnical < lastMonthTechnical - 0.2)
    alerts.Raise("routing model regressed");
// 0.88 -> 0.47 fires, although nothing got worse

The alert compares shares of two different denominators. It will fire every time the taxonomy grows, and stay silent when a real regression happens to coincide with a shrinking set.

Carrying the conditions with the number

public sealed record Assessment(
    string TaskVersion,          // which candidate set
    string Procedure,            // "native-choice", "label-scoring", ...
    ImmutableDictionary<string, double> Probabilities,
    double? Confidence);
// compare only assessments with equal TaskVersion and Procedure

Every probability travels with the candidate set and the method that produced it. Comparisons across versions become an explicit, reviewable decision.

Keep four comparisons separate

The same application can compare several mechanisms, but each comparison asks a different question and holds different things constant:

ComparisonHold constantObserve
Label qualityCases, rubric, label setConfusion matrix and reviewed errors
Probability qualityMeaning of each probability, labelsCalibration and proper scoring rules such as Brier loss
Whole-system behaviorAction policy, service requirementsCorrect actions, review load, failures
PerformanceInput sizes, hardware or service tier, concurrencyFull latency distributions, not single requests

A rule can produce a correct label with no probability. A generated enum may have the same limitation. If the downstream policy requires a distribution, those methods will send cases to review even when their labels are right. That is a mismatch of information, not proof of worse classification. The reverse shortcut misleads too: wrapping a rule's answer in a one-hot distribution makes it look maximally certain while saying nothing about its observed error rate.

A fair performance comparison counts the work each method needs. Scoring every candidate path can cost far more than emitting one enum, although batching and shared-prefix reuse change that. A classification head can be tiny relative to computing the input representation. Do not infer latency from the size of the JSON, and never time a recorded response against live inference.

Which mechanism gives you which number

MechanismWhat its probability meansWatch for
Generated enum under a schemaOften none; a first-token logprob at bestMask renormalization, first-token reversal
Two selected tokens (Yes/No)Share between those two tokens onlyDiscarded mass on every other token
Complete-label scoringP(label | one of the listed labels), given a fixed templateEnd marker, whitespace, cost per candidate
Classification headSoftmax over a trained class setRetraining when classes change
Native decision APIProvider-defined distribution plus a separate confidenceCalibration on your data is unmeasured until you measure it

Checklist

  • Every stored probability records its candidate set version and the procedure that produced it.
  • Probabilities are compared only across identical candidate sets; a taxonomy change starts a new baseline.
  • Multi-token labels are scored as complete paths with a fixed template and end marker, or replaced by short neutral identifiers.
  • Length-normalized scores are named as such and never displayed as probabilities.
  • Schema-valid output is still evaluated for correctness on labelled cases.
  • confidence and the selected choice's probability are stored as separate fields.
  • Softmax is computed with the maximum subtracted, and non-finite inputs are rejected.

When the router returns a number, the useful next question is now precise: probability of which event, conditional on which candidates, computed how? Part 3 turns those answers into a contract a program can validate, and Part 4 asks whether the numbers are good enough to choose an action. For the evaluation side of the same idea, see treating quality as a distribution, not a single score.

Series: Previous: Part 1. Next: Part 3, designing the decision contract.

Further reading

Written by

Marcin Borecki

Software architect and entrepreneur, Microsoft Regional Director since 2014, with CloudExpert since 2011. I help teams design, measure and ship .NET, Azure, data and AI systems that hold up in production.

Facing this problem in your own system?

I run focused investigations, architecture reviews and hands-on workshops for engineering teams.

See how I can help