Skip to Content
IntelligenceVOI sampling

Value-of-Information Sampling

What goes wrong with a naive approach

A lot of runtime decisions depend on measurements that are themselves expensive — a detailed frame snapshot, a full buffer diff, a conformal calibration pass. The naive approach is a timer: sample every Δt\Delta t milliseconds regardless of whether anything is happening.

The problem is that a timer has no idea whether sampling would change any decision. If the posterior over “is this frame a violation?” is already sharp at p=0.001p = 0.001, sampling makes it sharper at p=0.0009p = 0.0009 — not actionable, not useful, just overhead. If the posterior is p=0.45p = 0.45 and any new observation would flip the decision, sampling is absolutely worth the cost.

The right quantity is the value of information (VOI): the expected reduction in posterior variance from taking one more sample. Sample iff the VOI gain justifies the cost.

Mental model

Maintain a Beta posterior pBeta(α,β)p \sim \text{Beta}(\alpha, \beta) over the quantity of interest (typically “probability this measurement violates the SLO”). Compare the current posterior variance to the expected posterior variance after one more observation.

  • Current variance Var(p)\operatorname{Var}(p).
  • Expected post-sample variance E[Var(pXn+1)]E[\operatorname{Var}(p \mid X_{n+1})] — averaging over what the next observation might be.

VOI is the difference. Sample iff:

VOI×vscalecsample\text{VOI} \times v_{\text{scale}} \ge c_{\text{sample}}

(i.e., the information gain, valued appropriately, exceeds the cost of the measurement) OR the max_interval timer has expired. The timer provides a safety net — VOI alone can starve a stale posterior.

VOI sampling is the principled cousin of adaptive sampling. It doesn’t need a learning-rate schedule, a backoff policy, or a probe frequency knob. The sample-iff inequality decides.

The math

Beta posterior summaries

E[p]=αα+β,Var(p)=αβ(α+β)2(α+β+1)E[p] = \frac{\alpha}{\alpha + \beta}, \qquad \operatorname{Var}(p) = \frac{\alpha \beta}{(\alpha + \beta)^2 (\alpha + \beta + 1)}

Post-sample variance (one observation)

If we observed a success, αα+1\alpha \to \alpha + 1:

Var(p1)=(α+1)β(α+β+1)2(α+β+2)\operatorname{Var}(p \mid 1) = \frac{(\alpha + 1)\beta}{(\alpha + \beta + 1)^2 (\alpha + \beta + 2)}

If we observed a failure, ββ+1\beta \to \beta + 1:

Var(p0)=α(β+1)(α+β+1)2(α+β+2)\operatorname{Var}(p \mid 0) = \frac{\alpha (\beta + 1)}{(\alpha + \beta + 1)^2 (\alpha + \beta + 2)}

Expected post-sample variance

E[Var(pXn+1)]=E[p]Var(p1)+(1E[p])Var(p0)E[\operatorname{Var}(p \mid X_{n+1})] = E[p] \operatorname{Var}(p \mid 1) + (1 - E[p]) \operatorname{Var}(p \mid 0)

VOI and the sample-iff rule

VOI=Var(p)E[Var(pXn+1)]\text{VOI} = \operatorname{Var}(p) - E[\operatorname{Var}(p \mid X_{n+1})]

Sample iff:

(max_interval exceeded)ORVOI×vscalecsample(\text{max\_interval exceeded}) \quad\text{OR}\quad \text{VOI} \times v_{\text{scale}} \ge c_{\text{sample}}

Defaults (TUI-calibrated)

ParameterDefault
α0\alpha_0 (prior successes)1
β0\beta_0 (prior failures)9
μ0\mu_0 (boundary)0.05
λ\lambda (e-process betting)0.5
sample_cost0.08
min_interval_ms100
max_interval_ms1000

The prior Beta(1,9)\text{Beta}(1, 9) encodes “SLO violations are rare” — a reasonable prior for most TUI metrics. The 100–1000 ms clamps the sampling rate to between 1 Hz and 10 Hz.

Worked example — rare-event posterior

Prior Beta(1,9)\text{Beta}(1, 9) (mean 0.1). After 50 successes, 2 failures the posterior is Beta(3,59)\text{Beta}(3, 59):

E[p]=3/620.048,Var(p)0.00073E[p] = 3/62 \approx 0.048, \qquad \operatorname{Var}(p) \approx 0.00073

Expected post-sample variance is about 0.000720.00072. VOI 105\approx 10^{-5}. With vscale=100v_{\text{scale}} = 100 and csample=0.08c_{\text{sample}} = 0.08:

VOI×vscale=103<0.08\text{VOI} \times v_{\text{scale}} = 10^{-3} < 0.08

VOI says no. But if the timer has been ticking for 1000 ms with no sample, the max_interval clause fires — keeping the posterior from going stale.

When the stream shifts and the posterior pulls toward 0.4 with high variance, VOI grows rapidly and sampling becomes economic again.

Rust interface

crates/ftui-runtime/src/voi_sampling.rs
use ftui_runtime::voi_sampling::{VoiSampler, VoiConfig, SampleDecision}; let mut sampler = VoiSampler::new(VoiConfig { prior_alpha: 1.0, prior_beta: 9.0, mu_0: 0.05, lambda: 0.5, sample_cost: 0.08, min_interval_ms: 100, max_interval_ms: 1000, }); // Once per frame: match sampler.decide(now) { SampleDecision::Sample { reason } => { let x = take_measurement(); sampler.observe(x); } SampleDecision::Skip { reason: _ } => {} }

VoiSampler also exposes the e-value ete_t for pairing with an anytime-valid alert layer; see e-processes.

How to debug

Every decision emits a voi_decision line:

{"schema":"voi_decision","should_sample":false, "voi_gain":1.1e-5,"score":0.0011,"cost":0.08, "e_value":0.94,"boundary_score":0.55, "reason":"voi_below_cost","interval_ms":220}
FTUI_EVIDENCE_SINK=/tmp/ftui.jsonl cargo run -p ftui-demo-showcase # Histogram of sampling reasons: jq -c 'select(.schema=="voi_decision") | .reason' \ /tmp/ftui.jsonl | sort | uniq -c

max_interval firing constantly means VOI is saying no every time — either your vscalev_{\text{scale}} is too small or your posterior is already sharp and you’re wasting measurements.

Pitfalls

sample_cost is expressed in the same units as VOI gain. VOI is in variance units (roughly squared probability); sample_cost is a tuning constant that matches. If you change the prior or the observation model, recalibrate sample_cost empirically.

The Beta model assumes Bernoulli observations. If your stream is continuous (e.g., frame-time microseconds), wrap it in a threshold test first — “is x>μ0x > \mu_0?” — and feed the Bernoulli outcome to the sampler. Using a Gaussian stream directly misreports VOI.

Cross-references

  • Hint ranking — a different consumer of the same Beta-posterior machinery.
  • /runtime/overview — where the sampler is wired into the frame loop.
  • E-processes — the anytime-valid alert layer that pairs with VOI sampling.

Where next