Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
A latency model predicts that the next request will finish in 40 milliseconds. Should a caller rely on a 50-millisecond deadline? A prediction interval such as [32, 48] adds information the point prediction lacks, but even that range isn't a promise about this particular request.
Statistical Learning and Generalization established why untouched evidence matters. Traditional confidence intervals often assume normally distributed errors or asymptotic large-sample limits. When production traffic experiences heavy tails or bursty queues, those parametric assumptions break down. Split conformal prediction circumvents this issue by using a separate calibration split to wrap any frozen model's point predictions with statistically rigorous prediction intervals or prediction sets .[1]
Under an exchangeability condition on the data, split conformal prediction guarantees that future outcomes fall inside the prediction set with at least a chosen probability:
Here represents the allowed miscoverage rate (for example, for an 80% coverage target). This guarantee holds for any sample size , without requiring Gaussian errors, linear relationships, or parametric families.
All latency values below are synthetic teaching data. We start with predictions already made by a frozen model; this lesson calibrates their uncertainty rather than fitting another predictor.
Freeze the model before measuring its errors
Before calculating a radius, decide which rows are allowed to influence it. Three slices play different roles:
| Split | Role | Prohibited shortcut |
|---|---|---|
| Training | Fit the latency model | Report training residuals as independent calibration |
| Calibration | Measure errors from the already-frozen model | Tune the model repeatedly against calibration outcomes |
| Test or deployment | Check interval coverage on new requests | Include those outcomes in the original coverage claim |
Choose features, preprocessing, model settings, and the score rule before using calibration outcomes. Any model selection needs its own validation data within the training workflow. Once calibration starts, freeze that whole prediction procedure, not just its final layer.
For a 40-millisecond prediction, an observed 44 milliseconds gives an error of 4; an observed 53 gives an error of 13. A simple regression nonconformity score records that miss as the absolute residual . Larger scores mean the point prediction missed by more.
The finite-sample guarantee relies on exchangeable calibration and future examples, conditional on the fitted procedure: their joint distribution is unchanged by permuting their positions. Formally, for calibration points and one future test point:
for every permutation , where . Independent draws from the same distribution satisfy this condition. A queueing time series, diurnal traffic swings, or an altered prompt distribution may not. Shuffling an already shifted log doesn't restore exchangeability.[2]
A model was repeatedly tuned using the same nine examples later labeled as its calibration split. Why can't their residuals support the ordinary split-conformal guarantee?
Answer
Those outcomes influenced the model before calibration, so their residuals are no longer held-out measurements of a frozen predictor. Their apparent errors can be optimistically small, and the ordinary exchangeability argument for fresh request outcomes no longer follows.
Let nine errors choose the radius
Our nine held-out requests produce these sorted absolute errors, measured in milliseconds. Which position should set the radius for the next request?
For 80% target coverage, allow a miscoverage rate of . With nine calibration errors plus one future error, there are ten possible rank positions. Covering the first eight positions gives 8/10 = 80%. The correction counts the future request as well as the calibration requests.
In general, with calibration size , select the following residual quantile. Its order statistic, the value at a numbered position in the sorted sample, has rank:[1]
The ceiling brackets mean round upward. For example, an 85% target would require , not eight. At our 80% target, rank eight selects an 8 ms error. Call this selected radius . A new prediction of 40 milliseconds therefore gets:

The next cell constructs the absolute errors from frozen predictions and outcomes. Its helper selects an observed order statistic directly, avoiding interpolation between two residuals. Python index rank - 1 converts the one-based rank to a zero-based list position.
1from math import ceil, inf, isfinite
2
3def conformal_radius(scores, coverage):
4 if not 0 < coverage < 1:
5 raise ValueError("coverage must be strictly between 0 and 1")
6 ordered = sorted(scores)
7 if not ordered or any(not isfinite(s) or s < 0 for s in ordered):
8 raise ValueError("provide nonempty, finite, nonnegative scores")
9 rank = ceil((len(ordered) + 1) * coverage)
10 radius = ordered[rank - 1] if rank <= len(ordered) else inf
11 return rank, radius
12
13cal_predictions = [30, 35, 40, 45, 50, 55, 60, 65, 70]
14cal_observed = [32, 32, 43, 41, 55, 49, 67, 57, 82]
15residuals = sorted(abs(actual - guess)
16 for guess, actual in zip(cal_predictions, cal_observed, strict=True))
17rank, radius = conformal_radius(residuals, coverage=0.80)
18prediction = 40
19
20print("sorted absolute errors:", residuals)
21print(f"corrected rank: {rank} of {len(residuals)}")
22print(f"calibration radius: {radius} ms")
23print(f"prediction interval: [{prediction - radius}, {prediction + radius}] ms")1sorted absolute errors: [2, 3, 3, 4, 5, 6, 7, 8, 12]
2corrected rank: 8 of 9
3calibration radius: 8 ms
4prediction interval: [32, 48] msThe helper returns inf when the requested rank exceeds the available scores. That's deliberate, not a numerical failure. At 90% coverage the ninth error gives radius 12; at 95% the required rank is ten. The standard rule then uses , giving the entire real line rather than inventing a tenth error.[1]
Check the boundary in code. Raising the target can only preserve or widen the interval:
1for coverage in (0.80, 0.90, 0.95):
2 rank, q = conformal_radius(residuals, coverage)
3 print(f"target={coverage:.0%} rank={rank} radius={q} width={2*q}")1target=80% rank=8 radius=8 width=16
2target=90% rank=9 radius=12 width=24
3target=95% rank=10 radius=inf width=infDon't clip rank ten to nine: a largest-of-nine threshold supports a 90% lower bound in the no-ties case, not 95%. With 19 calibration errors, 95% first becomes possible with a finite observed threshold: . That's a mathematical minimum, not a recommendation for a stable calibration sample. Known bounds on the outcome could support other finite sets; the unavailable threshold here is specific to this residual-quantile construction.
Why does the example choose the eighth residual instead of simply multiplying nine by 0.8 and rounding down?
Answer
The finite-sample conformal rank is ceil((n+1)(1-alpha)). For nine calibration rows and 80% coverage, that is ceil(10 times 0.8), which selects the eighth order statistic.
Why the extra rank gives coverage
Temporarily imagine ten distinct scores, one from the future request and nine from calibration. Exchangeability makes the future request equally likely to occupy any of the ten sorted positions. Its score is no larger than the eighth calibration score exactly when it lands in positions one through eight.
You can inspect every possible test position without a random simulation. The following scores are a separate rank-only example, not additional latency measurements. Each pass holds out one score and calibrates on the remaining nine:
1scores = list(range(1, 11))
2covered_positions = []
3
4for index, test_score in enumerate(scores):
5 calibration = scores[:index] + scores[index + 1:]
6 _, q = conformal_radius(calibration, coverage=0.80)
7 if test_score <= q:
8 covered_positions.append(index + 1)
9
10print("covered test ranks:", covered_positions)
11print(f"covered positions: {len(covered_positions)}/{len(scores)}")1covered test ranks: [1, 2, 3, 4, 5, 6, 7, 8]
2covered positions: 8/10Our latency fixture has tied errors at 3 ms. Ties don't invalidate the lower coverage bound when the set includes the boundary (score <= q); they can make coverage conservative. Don't discard tied rows or promise exact 80% coverage. When scores are almost surely distinct (continuous nonconformity scores), conformal prediction also provides a tight upper bound on coverage:
The upper bound demonstrates that split conformal prediction isn't overly conservative. With and , coverage is sandwiched between 80% and . The rank argument doesn't require the predictor to be correct; a poor predictor will usually pay through less useful intervals.[3]
Watch coverage appear in new requests
The interval [32, 48] covers an actual 44-millisecond outcome and misses 53 milliseconds. That single request tells us what the rule does, not whether the rule is reliable. Coverage is measured across a stream of new comparable examples.
Keep the 80% radius 8 fixed and inspect four new synthetic request outcomes. Each request has its own interval centered at its prediction. Before running the cell, predict which outcome falls outside.

1predictions = [40, 42, 38, 44]
2observed = [44, 48, 35, 55]
3
4covered = [abs(actual - guess) <= radius
5 for guess, actual in zip(predictions, observed, strict=True)]
6for guess, actual, is_covered in zip(predictions, observed, covered):
7 print(f"predicted={guess} actual={actual} covered={is_covered}")
8
9print(f"observed coverage: {sum(covered)}/{len(covered)}")
10print(f"interval width: {2 * radius} ms")1predicted=40 actual=44 covered=True
2predicted=42 actual=48 covered=True
3predicted=38 actual=35 covered=True
4predicted=44 actual=55 covered=False
5observed coverage: 3/4
6interval width: 16 msA four-row observed coverage of 75% doesn't contradict an 80% marginal coverage guarantee. Four requests are a noisy sample, so the observed fraction can move around the target.
Two sources of variation exist: this calibration sample selected one particular radius, and the four test outcomes are another small sample. Even a very long test run with that fixed radius needn't achieve the nominal coverage. Standard split conformal isn't a guarantee conditional on one realized calibration set.[1]
Classification nonconformity scores and adaptive prediction sets
Conformal prediction isn't limited to regression intervals. In multiclass classification, the output is a set of candidate classes guaranteed to contain the true label with probability at least .
Different nonconformity scores produce different set behaviors:
- Naive softmax score: Let denote the model's estimated probability for class . The score is . After computing threshold on calibration data, the prediction set includes all classes whose predicted probability is at least :
When a query is ambiguous and the model distributes probability thinly across many classes (for example, four classes near 0.25), no single class may exceed . That produces an empty prediction set .
- Adaptive Prediction Sets (APS): To avoid empty sets and adapt set sizes to instance difficulty, sort the predicted class probabilities in descending order . Accumulate probabilities until reaching the true class label :[1]
At test time, the prediction set accumulates sorted classes until their cumulative probability mass reaches or exceeds the calibrated threshold :
On easy examples with high confidence (say, top class probability 0.98), contains a single label. On difficult, ambiguous queries, automatically expands to contain multiple classes, preserving marginal coverage while signaling uncertainty through set size.
Why does Adaptive Prediction Sets (APS) prefer accumulating sorted probabilities over thresholding individual softmax outputs?
Answer
Thresholding individual softmax probabilities can produce empty sets on difficult, ambiguous inputs where probability mass is dispersed. APS accumulates sorted probabilities until reaching the calibrated threshold, ensuring non-empty prediction sets whose size dynamically reflects input ambiguity.
Find the traffic the average hides
The four-row result measures overall empirical coverage. Breaking that count down by request type asks a different question: who receives the misses?
That guarantee isn't conditional coverage for every subgroup or individual feature value. Consider a constructed evaluation report with 750 short and 250 long requests. Short requests have 96% coverage; long requests have only 54%. The weighted average still exceeds 80%:
These are hypothetical counts, not results from the four-request test above. They expose what an aggregate metric can hide. If the mix shifted to half short and half long while those subgroup rates stayed the same, overall coverage would fall to 0.5 * 0.96 + 0.5 * 0.54 = 75%. The old population guarantee wouldn't transfer automatically to that new mix.[2]
An 80% conformal target passes across all traffic, but long requests are covered only 54% of the time. Does the population-wide guarantee refute the long-request measurement?
Answer
No. Standard split conformal guarantees marginal coverage across the exchangeable population, not equal conditional coverage for every prompt-length subgroup. A common well-covered group can keep the aggregate above target while a smaller long-request group remains unsafe.
Different failures call for different repairs:
| Failure | Symptom | Appropriate response |
|---|---|---|
| Model trained on calibration rows | Residuals look too small | Refit with a genuinely separate calibration split |
| Prompt lengths shift after launch | Recent interval misses cluster on long prompts | Investigate the shift; fresh calibration helps only if its assumptions fit subsequent traffic |
| Tenant-specific error differs | Overall coverage passes while one tenant fails | Measure subgroup coverage; consider separately calibrated, predefined groups |
| Noise grows with request size | Constant intervals are too narrow for large requests | Use normalized nonconformity scores or group-aware methods |
| Calibration sample is tiny | Desired rank exceeds available observations | Gather more data or acknowledge an unbounded set |
Constant width isn't the only option. Suppose a second trained model predicts error scales of 3 ms for a short request and 6 ms for a long one. If calibration of normalized errors yields a threshold of 2, their interval radii become 2 * 3 = 6 ms and 2 * 6 = 12 ms. That threshold needs its own calibration; it isn't the 8 ms threshold from the raw-error example.
In symbols, learn a positive error scale using training data, then calibrate scores . The interval becomes . Both models must be frozen before calibration; this changes how width adapts, not the marginal guarantee into exact conditional coverage.[3]
Conformal risk control for language model triage routing
Modern language model systems often require bounds on general losses rather than simple interval coverage. In conformal risk control, the objective expands from set containment to bounding the expected value of any bounded loss function :[1]
Here represents a tunable parameter (such as an uncertainty cutoff or threshold). In production language model pipelines, two triage workflows build on this mechanism:
- Hallucination filtering and human escalation: An evaluator or verifier scores each generated answer with an uncertainty metric (such as token entropy, consistency variance across sampled rollouts, or an external critic score). By calibrating threshold , the pipeline routes requests with to human review or a fallback search pipeline, guaranteeing that accepted answers maintain an error rate below .
- Selective intent routing: For tool-calling or API agents, classification prediction sets determine autonomy. If the prediction set contains exactly one intent (), the system executes the tool call automatically. If , the query triggers a clarification prompt or routes to human support.
How does conformal triage routing turn set size into an operational decision for an autonomous agent?
Answer
When the calibrated prediction set contains a single candidate action, the agent executes autonomously. When uncertainty expands the set to multiple candidates or an empty set, the agent halts and routes the request to human review or asks the user for clarification.
Check the decision, not just the percentage
A coverage number without interval width is incomplete. An interval spanning the entire outcome space never misses, but it can't guide a 50-millisecond deadline.
Return to the deadline question. [32, 48] sits below 50 ms, but the 80% marginal guarantee doesn't mean this request has an 80% conditional chance of meeting its deadline. Selecting only requests with upper endpoints below 50 also creates a subgroup; ordinary marginal validity alone doesn't certify that selected group's failure rate.
For monitoring, retain the model and calibration versions, each interval, actual outcome, request group, and width. Inspect counts as well as percentages. A scorecard with two long requests can't establish a reliable long-request rate, and no finite log proves that future traffic will stay exchangeable.
Try these changes to the cells before checking the expected result:
| Experiment | Expected result and interpretation |
|---|---|
| Change the eighth sorted calibration error from 8 to 9 | Rank stays eight; radius becomes 9 and the 40 ms interval becomes [31, 49]. Rank and value are different quantities. |
| Change the fourth test outcome from 55 to 52 | All four outcomes are covered because endpoints are included. This is still a four-row observation, not proof of 100% future coverage. |
| Clip the 95% rank to nine | The code would return radius 12, hiding that the ordinary finite-sample rule required infinity. A finite output isn't evidence of a valid 95% guarantee. |
| Compare the short/long report under a 75/25 versus 50/50 mix | Aggregate coverage changes from 85.5% to 75% even with identical subgroup rates. |