Skip to main content

jugar_probar/perf_gate/
bootstrap.rs

1//! §4.4.4 — bootstrap percentile confidence intervals.
2//!
3//! Percentile method, 10 000 resamples, seed 2026, resampling **whole
4//! requests**. Tokens within a request are not independent, so resampling
5//! tokens (or per-token latencies) would understate the interval by pretending
6//! each token is its own observation.
7//!
8//! BCa is deliberately not implemented: §4.4.4 rejects it as an undocumented
9//! degree of freedom at this dispersion.
10//!
11//! The PRNG is a `SplitMix64` written out in full rather than taken from a
12//! crate. §4.4.4 requires the interval to be *reproducible from the retained
13//! samples*, and a dependency's internal stream is free to change across a
14//! semver-compatible bump — which would silently move every published interval.
15//! The stream is pinned by [`tests::splitmix64_stream_is_pinned`].
16
17use serde::{Deserialize, Serialize};
18
19use super::join::{Ratio, RatioMethod};
20use super::metrics::{percentile, RequestSample};
21use super::protocol::{BOOTSTRAP_RESAMPLES, BOOTSTRAP_SEED};
22
23/// `SplitMix64`, exactly as published by Steele et al. Deterministic across
24/// platforms: `u64` wrapping arithmetic only, no floats in the state.
25#[derive(Debug, Clone)]
26pub struct SplitMix64 {
27    state: u64,
28}
29
30impl SplitMix64 {
31    /// Seed the generator.
32    #[must_use]
33    pub fn new(seed: u64) -> Self {
34        Self { state: seed }
35    }
36
37    /// Next 64 bits.
38    pub fn next_u64(&mut self) -> u64 {
39        self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
40        let mut z = self.state;
41        z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
42        z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
43        z ^ (z >> 31)
44    }
45
46    /// Uniform index in `[0, n)` by Lemire's multiply-shift. Unbiased enough for
47    /// resampling and, unlike modulo, free of the low-bit bias that would skew
48    /// which requests get picked.
49    pub fn index_below(&mut self, n: usize) -> usize {
50        debug_assert!(n > 0, "index_below(0) is undefined");
51        ((u128::from(self.next_u64()) * n as u128) >> 64) as usize
52    }
53}
54
55/// A bootstrap percentile interval, with everything needed to re-derive it.
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57#[serde(deny_unknown_fields)]
58pub struct BootstrapCi {
59    /// The statistic on the observed sample.
60    pub point: f64,
61    /// Lower percentile bound.
62    pub lower: f64,
63    /// Upper percentile bound.
64    pub upper: f64,
65    /// Nominal coverage, e.g. 0.95.
66    pub confidence: f64,
67    /// Resamples drawn.
68    pub resamples: usize,
69    /// The seed. In the receipt, per §4.4.4.
70    pub seed: u64,
71    /// Whole requests, always. Recorded so the unit of resampling is on the page.
72    pub resampling_unit: &'static str,
73    /// Observations resampled.
74    pub n: usize,
75}
76
77/// A statistic of a set of whole requests, e.g. [`super::metrics::agg_tok_s`].
78///
79/// A function pointer rather than a generic `F: Fn(..)`: a fn item passed to a
80/// generic higher-ranked bound makes rustc infer a fresh lifetime and reject the
81/// call, so every call site would need a wrapping closure — which clippy then
82/// correctly flags as redundant. The pointer type takes the fn item directly.
83pub type Statistic = fn(&[RequestSample]) -> f64;
84
85/// §4.4.4 — bootstrap percentile CI for any statistic of a set of whole requests.
86///
87/// `statistic` is applied to the observed samples for the point estimate and to
88/// each resample for the distribution. Passing
89/// [`super::metrics::agg_tok_s`] gives the aggregate's interval; passing a
90/// median gives the median's.
91///
92/// Returns `None` for fewer than two observations, where a bootstrap interval is
93/// not defined. Returning a degenerate `[x, x]` there would read as a
94/// measurement of impossible precision.
95pub fn bootstrap_ci(
96    samples: &[RequestSample],
97    confidence: f64,
98    statistic: Statistic,
99) -> Option<BootstrapCi> {
100    bootstrap_ci_with(
101        samples,
102        confidence,
103        BOOTSTRAP_RESAMPLES,
104        BOOTSTRAP_SEED,
105        statistic,
106    )
107}
108
109/// [`bootstrap_ci`] with the resample count and seed spelled out. The public
110/// entry point pins both to the §4.4.4 values; this exists for the tests that
111/// prove the pinning matters.
112pub fn bootstrap_ci_with(
113    samples: &[RequestSample],
114    confidence: f64,
115    resamples: usize,
116    seed: u64,
117    statistic: Statistic,
118) -> Option<BootstrapCi> {
119    let n = samples.len();
120    if n < 2 || resamples == 0 || !(0.0..1.0).contains(&confidence) {
121        return None;
122    }
123
124    let mut rng = SplitMix64::new(seed);
125    let mut draws = Vec::with_capacity(resamples);
126    // One reusable buffer: resampling WHOLE requests means cloning records, and
127    // 10 000 fresh allocations of n records is the difference between a CI that
128    // is cheap enough to always compute and one people turn off.
129    let mut resample: Vec<RequestSample> = Vec::with_capacity(n);
130    for _ in 0..resamples {
131        resample.clear();
132        for _ in 0..n {
133            resample.push(samples[rng.index_below(n)].clone());
134        }
135        draws.push(statistic(&resample));
136    }
137    draws.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
138
139    let alpha = 1.0 - confidence;
140    let lower = percentile(&draws, alpha / 2.0)?;
141    let upper = percentile(&draws, 1.0 - alpha / 2.0)?;
142
143    Some(BootstrapCi {
144        point: statistic(samples),
145        lower,
146        upper,
147        confidence,
148        resamples,
149        seed,
150        resampling_unit: "whole_request",
151        n,
152    })
153}
154
155/// [`bootstrap_ci`] specialised to §4.4.3's `agg_tok_s`.
156///
157/// A named wrapper rather than a bare function reference at each call site:
158/// passing `agg_tok_s` directly makes rustc infer a fresh lifetime instead of
159/// the higher-ranked one the bound wants, and the resulting error is opaque.
160///
161/// Returns `None` under the same conditions as [`bootstrap_ci`].
162#[must_use]
163pub fn bootstrap_agg_tok_s_ci(samples: &[RequestSample], confidence: f64) -> Option<BootstrapCi> {
164    bootstrap_ci(samples, confidence, super::metrics::agg_tok_s)
165}
166
167/// PP-LLAMA-001 v3.0 §4.3 — the **request-unit** estimator: a paired percentile
168/// bootstrap of the ratio of a per-request statistic across the two lanes.
169///
170/// # What "paired" means here, said explicitly
171///
172/// The two lanes issue their own requests; there is no per-request pairing key,
173/// and there cannot be one — request 7 of the subject and request 7 of the
174/// comparator are not the same event. "Paired" is therefore *pairing of the
175/// resample index*: draw `k` runs one resample of the subject lane and one of
176/// the comparator lane from **one** `SplitMix64(2026)` stream, and the ratio of
177/// the two statistics is draw `k` of the ratio distribution. The lanes are
178/// independent within a draw; what is shared is the seed and the draw index, so
179/// the whole distribution is reproducible from the retained samples of both
180/// lanes and nothing else (§4.4.4's requirement, applied to two lanes).
181///
182/// # The verdict statistic
183///
184/// The **5th percentile** of the ratio draws — a one-sided 95% lower bound.
185/// Not the 2.5th: P-5 asks "is the lower bound at or above `1 − δ`", which is a
186/// one-sided question, and taking `alpha/2` there would report a looser bound
187/// as if it were the same guarantee.
188///
189/// Returns `None` when either lane has fewer than two retained requests, when
190/// `confidence` is not in `(0, 1)`, or when the comparator's statistic is not
191/// positive — a ratio with a zero denominator is not a large ratio.
192#[must_use]
193pub fn paired_ratio_lcb(
194    subject: &[RequestSample],
195    comparator: &[RequestSample],
196    statistic: Statistic,
197    confidence: f64,
198) -> Option<Ratio> {
199    let draws = paired_ratio_draws(
200        subject,
201        comparator,
202        statistic,
203        BOOTSTRAP_RESAMPLES,
204        BOOTSTRAP_SEED,
205        confidence,
206    )?;
207    let denominator = statistic(comparator);
208    Some(Ratio {
209        point: statistic(subject) / denominator,
210        lcb95: percentile(&draws, 1.0 - confidence),
211        method: RatioMethod::PairedPercentileBootstrap,
212        n: subject.len() + comparator.len(),
213    })
214}
215
216/// The sorted ratio draws behind [`paired_ratio_lcb`].
217///
218/// Public so a test can assert *which* percentile the bound is, rather than
219/// re-implementing the draw loop and proving only that two copies of the same
220/// code agree.
221///
222/// Returns `None` under the same conditions as [`paired_ratio_lcb`].
223#[must_use]
224pub fn paired_ratio_draws(
225    subject: &[RequestSample],
226    comparator: &[RequestSample],
227    statistic: Statistic,
228    resamples: usize,
229    seed: u64,
230    confidence: f64,
231) -> Option<Vec<f64>> {
232    let (n_s, n_c) = (subject.len(), comparator.len());
233    if n_s < 2 || n_c < 2 || resamples == 0 || !(0.0..1.0).contains(&confidence) {
234        return None;
235    }
236    if statistic(comparator) <= 0.0 {
237        return None;
238    }
239    let mut rng = SplitMix64::new(seed);
240    let mut draws = Vec::with_capacity(resamples);
241    let mut lane_s: Vec<RequestSample> = Vec::with_capacity(n_s);
242    let mut lane_c: Vec<RequestSample> = Vec::with_capacity(n_c);
243    for _ in 0..resamples {
244        lane_s.clear();
245        for _ in 0..n_s {
246            lane_s.push(subject[rng.index_below(n_s)].clone());
247        }
248        lane_c.clear();
249        for _ in 0..n_c {
250            lane_c.push(comparator[rng.index_below(n_c)].clone());
251        }
252        let denominator = statistic(&lane_c);
253        if denominator > 0.0 {
254            draws.push(statistic(&lane_s) / denominator);
255        }
256    }
257    if draws.is_empty() {
258        return None;
259    }
260    draws.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
261    Some(draws)
262}
263
264/// §3 `dec` — the band's per-request decode rate: the **median** over retained
265/// requests of `(completion_tokens − 1) / (e2e − ttft)`.
266///
267/// `0.0` for a set with no streamed request, where the quantity is undefined;
268/// [`paired_ratio_lcb`] then refuses the ratio rather than dividing by it.
269#[must_use]
270pub fn median_decode_tok_s(samples: &[RequestSample]) -> f64 {
271    let mut rates: Vec<f64> = samples
272        .iter()
273        .filter(|s| s.counts_toward_aggregate())
274        .filter_map(RequestSample::decode_tok_s)
275        .collect();
276    rates.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
277    percentile(&rates, 0.50).unwrap_or(0.0)
278}
279
280/// §3 `ttft` — p50 time-to-first-token, in milliseconds.
281#[must_use]
282pub fn ttft_p50_ms(samples: &[RequestSample]) -> f64 {
283    ttft_percentile_ms(samples, 0.50)
284}
285
286/// §3 `itl_p95` — the 95th percentile of the **pooled** inter-token intervals.
287///
288/// Pooled across requests, not a percentile of per-request percentiles: the
289/// tail this metric exists to expose is a few very late tokens, and averaging
290/// each request's own p95 first hides exactly those.
291#[must_use]
292pub fn itl_p95_ms(samples: &[RequestSample]) -> f64 {
293    let mut gaps: Vec<f64> = samples
294        .iter()
295        .filter(|s| s.counts_toward_aggregate())
296        .flat_map(RequestSample::itl_gaps_ms)
297        .collect();
298    gaps.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
299    percentile(&gaps, 0.95).unwrap_or(0.0)
300}
301
302fn ttft_percentile_ms(samples: &[RequestSample], p: f64) -> f64 {
303    let mut v: Vec<f64> = samples
304        .iter()
305        .filter(|s| s.counts_toward_aggregate())
306        .filter_map(RequestSample::ttft_ms)
307        .collect();
308    v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
309    percentile(&v, p).unwrap_or(0.0)
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315    use crate::perf_gate::metrics::agg_tok_s;
316    use crate::perf_gate::protocol::Outcome;
317
318    fn sample(index: usize, start_s: f64, end_s: f64, tokens: u32) -> RequestSample {
319        RequestSample {
320            index,
321            worker: index % 4,
322            start_s,
323            end_s,
324            token_times_s: vec![start_s + 0.01, end_s],
325            generated_tokens: tokens,
326            prompt_tokens: 512,
327            outcome: Outcome::Completed,
328            in_flight_at_start: 4,
329            drained: false,
330        }
331    }
332
333    fn deck(n: usize) -> Vec<RequestSample> {
334        (0..n)
335            .map(|i| {
336                let start = i as f64 * 0.25;
337                let jitter = f64::from((i % 7) as u32) * 0.05;
338                sample(i, start, start + 1.0 + jitter, 100 + (i % 5) as u32)
339            })
340            .collect()
341    }
342
343    /// The stream is pinned. If this reds, every previously published interval
344    /// moved, and that must be a deliberate, versioned decision.
345    #[test]
346    fn splitmix64_stream_is_pinned() {
347        let mut r = SplitMix64::new(2026);
348        let got: Vec<u64> = (0..4).map(|_| r.next_u64()).collect();
349        assert_eq!(
350            got,
351            vec![
352                15_824_617_304_438_902_051,
353                8_699_989_649_721_214_301,
354                12_310_341_597_754_734_734,
355                7_097_835_237_234_771_186,
356            ],
357            "SplitMix64(2026) stream changed"
358        );
359    }
360
361    #[test]
362    fn index_below_stays_in_range_and_covers_it() {
363        let mut r = SplitMix64::new(BOOTSTRAP_SEED);
364        let mut seen = [false; 5];
365        for _ in 0..500 {
366            let i = r.index_below(5);
367            assert!(i < 5, "index {i} out of range");
368            seen[i] = true;
369        }
370        assert!(seen.iter().all(|&s| s), "every index must be reachable");
371    }
372
373    /// §4.4.4's whole point: same samples + seed 2026 => the identical interval,
374    /// bit for bit, twice.
375    #[test]
376    fn same_samples_and_seed_give_the_identical_interval_twice() {
377        let s = deck(40);
378        let a = bootstrap_agg_tok_s_ci(&s, 0.95).expect("n >= 2");
379        let b = bootstrap_agg_tok_s_ci(&s, 0.95).expect("n >= 2");
380        assert_eq!(
381            a, b,
382            "the CI must be reproducible from the retained samples"
383        );
384        assert_eq!(a.seed, 2026);
385        assert_eq!(a.resamples, 10_000);
386        assert_eq!(a.resampling_unit, "whole_request");
387        assert_eq!(a.n, 40);
388    }
389
390    /// And the seed is load-bearing, not decoration: a different seed must move
391    /// the interval, or "seed 2026" would be an unfalsifiable claim.
392    #[test]
393    fn a_different_seed_gives_a_different_interval() {
394        let s = deck(40);
395        let a = bootstrap_ci_with(&s, 0.95, 10_000, 2026, agg_tok_s).expect("n >= 2");
396        let b = bootstrap_ci_with(&s, 0.95, 10_000, 2027, agg_tok_s).expect("n >= 2");
397        assert_eq!(
398            a.point, b.point,
399            "the point estimate does not depend on the seed"
400        );
401        assert!(
402            (a.lower - b.lower).abs() > f64::EPSILON || (a.upper - b.upper).abs() > f64::EPSILON,
403            "seed had no effect: {a:?} vs {b:?}"
404        );
405    }
406
407    #[test]
408    fn the_interval_brackets_the_point_estimate() {
409        let s = deck(60);
410        let ci = bootstrap_agg_tok_s_ci(&s, 0.95).expect("n >= 2");
411        assert!(ci.lower <= ci.point, "{ci:?}");
412        assert!(ci.point <= ci.upper, "{ci:?}");
413        assert!(
414            ci.lower < ci.upper,
415            "a non-degenerate sample needs width: {ci:?}"
416        );
417    }
418
419    /// Whole requests, not tokens. Resampling n whole records from n records
420    /// must be able to draw the same record twice — that is what makes it a
421    /// bootstrap. A permutation would give zero width.
422    #[test]
423    fn resampling_is_with_replacement_over_whole_requests() {
424        let mut rng = SplitMix64::new(BOOTSTRAP_SEED);
425        let n = 8;
426        let mut counts = vec![0_usize; n];
427        for _ in 0..n {
428            counts[rng.index_below(n)] += 1;
429        }
430        assert!(
431            counts.iter().any(|&c| c >= 2),
432            "a with-replacement draw of n from n must duplicate: {counts:?}"
433        );
434    }
435
436    /// A wider spread of per-request behaviour must widen the interval. An
437    /// undersized or noisy `n` should FAIL a gate by widening, never pass
438    /// silently (§4.4.2).
439    #[test]
440    fn more_dispersion_widens_the_interval() {
441        let tight: Vec<RequestSample> = (0..40)
442            .map(|i| sample(i, i as f64 * 0.25, i as f64 * 0.25 + 1.0, 100))
443            .collect();
444        let loose: Vec<RequestSample> = (0..40)
445            .map(|i| {
446                let start = i as f64 * 0.25;
447                let dur = if i % 2 == 0 { 0.2 } else { 4.0 };
448                sample(i, start, start + dur, 100)
449            })
450            .collect();
451        let a = bootstrap_agg_tok_s_ci(&tight, 0.95).expect("n >= 2");
452        let b = bootstrap_agg_tok_s_ci(&loose, 0.95).expect("n >= 2");
453        assert!(
454            (b.upper - b.lower) > (a.upper - a.lower),
455            "dispersed: {:?} must be wider than tight: {:?}",
456            b,
457            a
458        );
459    }
460
461    #[test]
462    fn fewer_than_two_observations_has_no_interval() {
463        assert!(bootstrap_agg_tok_s_ci(&[], 0.95).is_none());
464        assert!(bootstrap_agg_tok_s_ci(&deck(1), 0.95).is_none());
465        assert!(bootstrap_agg_tok_s_ci(&deck(2), 0.95).is_some());
466    }
467
468    #[test]
469    fn a_nonsense_confidence_has_no_interval() {
470        let s = deck(10);
471        assert!(bootstrap_ci(&s, 1.0, agg_tok_s).is_none());
472        assert!(bootstrap_ci(&s, -0.1, agg_tok_s).is_none());
473    }
474
475    /// A lane whose per-request rate is `rate` tok/s, `n` requests.
476    fn lane(n: usize, rate: f64, jitter: f64) -> Vec<RequestSample> {
477        (0..n)
478            .map(|i| {
479                let start = i as f64 * 0.25;
480                // Distinct per-request rates: a median over a handful of
481                // repeated values is a step function, and its bootstrap
482                // percentile would be identical under every seed — which would
483                // make the seed test pass for the wrong reason.
484                let r = rate + jitter * (((i * 7 + 3) % 23) as f64 / 23.0 - 0.5);
485                // 128 tokens; 127 gaps at 1/r seconds each.
486                let step = 1.0 / r;
487                let times: Vec<f64> = (0..128)
488                    .map(|k| start + 0.05 + f64::from(k) * step)
489                    .collect();
490                let end = times[127] + 0.01;
491                RequestSample {
492                    index: i,
493                    worker: i % 4,
494                    start_s: start,
495                    end_s: end,
496                    token_times_s: times,
497                    generated_tokens: 128,
498                    prompt_tokens: 512,
499                    outcome: Outcome::Completed,
500                    in_flight_at_start: 1,
501                    drained: false,
502                }
503            })
504            .collect()
505    }
506
507    /// §4.4.4 applied to two lanes: the same two sample sets and seed 2026 give
508    /// the identical bound, bit for bit, twice.
509    #[test]
510    fn paired_ratio_lcb_is_reproducible_bit_for_bit_at_seed_2026() {
511        let subject = lane(30, 100.0, 3.0);
512        let comparator = lane(30, 90.0, 3.0);
513        let a = paired_ratio_lcb(&subject, &comparator, median_decode_tok_s, 0.95)
514            .expect("both lanes have n >= 2");
515        let b = paired_ratio_lcb(&subject, &comparator, median_decode_tok_s, 0.95)
516            .expect("both lanes have n >= 2");
517        assert_eq!(a, b, "the bound must be reproducible from the samples");
518        assert_eq!(BOOTSTRAP_SEED, 2026);
519        assert_eq!(BOOTSTRAP_RESAMPLES, 10_000);
520        assert_eq!(a.method, RatioMethod::PairedPercentileBootstrap);
521        assert_eq!(a.n, 60, "both lanes' retained requests");
522
523        // And the seed is load-bearing: a different stream moves the bound.
524        let other = paired_ratio_draws(
525            &subject,
526            &comparator,
527            median_decode_tok_s,
528            10_000,
529            2027,
530            0.95,
531        )
532        .expect("draws");
533        let mine = paired_ratio_draws(
534            &subject,
535            &comparator,
536            median_decode_tok_s,
537            10_000,
538            2026,
539            0.95,
540        )
541        .expect("draws");
542        assert_ne!(
543            percentile(&other, 0.05),
544            percentile(&mine, 0.05),
545            "seed 2026 must not be decoration"
546        );
547    }
548
549    /// P-5 is a ONE-SIDED question. Taking `alpha/2` would report a looser
550    /// bound under the same name.
551    #[test]
552    fn lcb95_is_the_fifth_percentile_not_the_2_5th() {
553        let subject = lane(30, 100.0, 8.0);
554        let comparator = lane(30, 90.0, 8.0);
555        let draws = paired_ratio_draws(
556            &subject,
557            &comparator,
558            median_decode_tok_s,
559            BOOTSTRAP_RESAMPLES,
560            BOOTSTRAP_SEED,
561            0.95,
562        )
563        .expect("draws");
564        let bound = paired_ratio_lcb(&subject, &comparator, median_decode_tok_s, 0.95)
565            .expect("bound")
566            .lcb95
567            .expect("lcb95");
568        let p05 = percentile(&draws, 0.05).expect("p05");
569        let p025 = percentile(&draws, 0.025).expect("p025");
570        assert_eq!(bound, p05, "the bound is the 5th percentile");
571        assert_ne!(
572            p05, p025,
573            "the two percentiles must differ, or this test proves nothing"
574        );
575        assert!(p025 < p05, "the 2.5th percentile is the looser bound");
576    }
577
578    /// A lane divided by itself is 1.0 exactly, and its bound brackets it from
579    /// below. If the point estimate drifted off 1.0 the statistic would be
580    /// resample-dependent, which it must not be.
581    #[test]
582    fn identical_lanes_give_point_one() {
583        let l = lane(30, 100.0, 4.0);
584        for statistic in [
585            median_decode_tok_s as Statistic,
586            ttft_p50_ms as Statistic,
587            itl_p95_ms as Statistic,
588        ] {
589            let r = paired_ratio_lcb(&l, &l, statistic, 0.95).expect("n >= 2");
590            assert_eq!(r.point, 1.0, "a lane against itself is parity");
591            let lcb = r.lcb95.expect("bounded");
592            assert!(lcb <= r.point, "{r:?}");
593            assert!(lcb > 0.0, "{r:?}");
594        }
595    }
596
597    /// The point estimate tracks the lanes: a faster subject raises it, and the
598    /// direction is subject-over-comparator, never the reverse.
599    #[test]
600    fn the_ratio_is_subject_over_comparator() {
601        let fast = lane(30, 120.0, 2.0);
602        let slow = lane(30, 60.0, 2.0);
603        let up = paired_ratio_lcb(&fast, &slow, median_decode_tok_s, 0.95).expect("n >= 2");
604        let down = paired_ratio_lcb(&slow, &fast, median_decode_tok_s, 0.95).expect("n >= 2");
605        assert!(up.point > 1.5, "{up:?}");
606        assert!(down.point < 0.7, "{down:?}");
607        assert!(
608            (up.point * down.point - 1.0).abs() < 1e-9,
609            "{up:?} {down:?}"
610        );
611    }
612
613    /// A lane with one retained request supports no bootstrap, and a lane whose
614    /// statistic is zero is not a denominator.
615    #[test]
616    fn a_degenerate_lane_has_no_paired_bound() {
617        let ok = lane(30, 100.0, 2.0);
618        assert!(paired_ratio_lcb(&ok[..1], &ok, median_decode_tok_s, 0.95).is_none());
619        assert!(paired_ratio_lcb(&ok, &ok[..1], median_decode_tok_s, 0.95).is_none());
620        assert!(paired_ratio_lcb(&ok, &ok, median_decode_tok_s, 1.0).is_none());
621
622        let unstreamed: Vec<RequestSample> = ok
623            .iter()
624            .map(|s| RequestSample {
625                token_times_s: Vec::new(),
626                ..s.clone()
627            })
628            .collect();
629        assert!(
630            paired_ratio_lcb(&ok, &unstreamed, median_decode_tok_s, 0.95).is_none(),
631            "a zero denominator is not a large ratio"
632        );
633    }
634
635    /// The three request-unit statistics are the §3 definitions, computed over
636    /// completed requests only.
637    #[test]
638    fn the_request_unit_statistics_are_the_section_3_definitions() {
639        let l = lane(4, 100.0, 0.0);
640        // 128 tokens at 100 tok/s: 127 gaps of 10 ms, decode = 127/1.27 = 100.
641        assert!(
642            (median_decode_tok_s(&l) - 100.0).abs() < 1e-6,
643            "{}",
644            median_decode_tok_s(&l)
645        );
646        assert!((ttft_p50_ms(&l) - 50.0).abs() < 1e-6, "{}", ttft_p50_ms(&l));
647        assert!((itl_p95_ms(&l) - 10.0).abs() < 1e-6, "{}", itl_p95_ms(&l));
648        assert_eq!(median_decode_tok_s(&[]), 0.0);
649        assert_eq!(ttft_p50_ms(&[]), 0.0);
650        assert_eq!(itl_p95_ms(&[]), 0.0);
651    }
652}