1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
// SPDX-License-Identifier: MIT OR Apache-2.0
//! 2026-09-25: MTP acceptance statistics per batch width, and the per-request Done-line figures.
//!
//! [`AcceptBuckets`] counts verify outcomes per batch-width bucket. Every
//! `PERIOD` verifies a bucket flushes: a fresh flush goes to the run's
//! `AdaptiveRung::observe`, and when `METRALE_MTP_ACCEPT_DEBUG` is set a line
//! logs `p1` (share of verifies whose first draft matched), `mean_na` (mean
//! accepted drafts) and `tok_step = 1 + mean_na`. [`RequestAccept`] holds the
//! same figures for one request.
//!
//! Owner: scheduler.
//! Invariants:
//! - The counters are relaxed atomics; nothing here copies from the device or
//! synchronises a stream.
use std::sync::atomic::{AtomicU64, Ordering};
/// 2026-09-25: Number of width buckets. Widths `0..MAX_N - 1` each have their own
/// bucket; wider ones share the last.
///
/// It must exceed the MTP dispatch cap
/// ([`metrale_model_layers::speculative::mtp_max_seqs`], at most `MAX_MTP_MAX_SEQS`), or
/// distinct widths share a bucket. A shared
/// bucket mixes their statistics, and its flush carries the width of whichever
/// caller tripped [`PERIOD`]; `AdaptiveRung::observe` ignores widths outside
/// `9..=16`, so a flush tripped by a wider caller is lost to the rung.
///
/// `METRALE_MTP_ACCEPT_FOLD_AT_16` (set, whatever its value) folds every width
/// of 16 or more into bucket 16 instead.
const MAX_N: usize = metrale_model_layers::speculative::MAX_MTP_MAX_SEQS + 1;
/// 2026-09-25: The bucket index for batch width `n`, used by
/// [`AcceptBuckets::record`] and the aliasing test.
fn bucket_idx(n: usize, fold_at_16: bool) -> usize {
if fold_at_16 {
n.min(16)
} else {
n.min(MAX_N - 1)
}
}
/// 2026-09-25: Verifies per flush. A fresh flush is one `AdaptiveRung::observe`
/// call, so this sets how often the rung is updated; at n=16 it is 8 verify
/// steps.
const PERIOD: u64 = 128;
/// 2026-09-25: A flush whose `PERIOD` verifies took longer than this to accumulate
/// is not passed to the rung: it describes older traffic. The clock is read
/// when a bucket's window opens and at its flush, not per verify.
const MAX_SAMPLE_SPAN_MS: u64 = 5_000;
struct Bucket {
steps: AtomicU64,
d1: AtomicU64,
na: AtomicU64,
k: AtomicU64,
/// 2026-09-25: Millis since [`AcceptBuckets::epoch`] when this bucket's
/// current window opened (stamped when `steps` goes 0 -> 1), so the flush's
/// span covers exactly this window, the first one included.
window_start_ms: AtomicU64,
}
const fn new_bucket() -> Bucket {
Bucket {
steps: AtomicU64::new(0),
d1: AtomicU64::new(0),
na: AtomicU64::new(0),
k: AtomicU64::new(0),
window_start_ms: AtomicU64::new(0),
}
}
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Bucket = new_bucket();
/// 2026-09-25: The per-width accept table of one scheduler run, built by
/// `SchedCtx::new`.
pub struct AcceptBuckets {
fold_at_16: bool,
/// 2026-09-25: Creation time: the monotonic reference for `window_start_ms`.
epoch: std::time::Instant,
buckets: [Bucket; MAX_N],
}
impl AcceptBuckets {
pub fn new(fold_at_16: bool) -> Self {
Self {
fold_at_16,
epoch: std::time::Instant::now(),
buckets: [INIT; MAX_N],
}
}
}
/// 2026-09-25: Record one sequence's verify outcome at batch width `n`.
///
/// `d1_match` is whether the first draft matched the target
/// (`drafts[0] == verified[0]`), so `p1` is not conditional on the accept
/// chain.
///
/// `k_drafts` is this sequence's retained draft depth, which D-Cut can make
/// differ within one batch; a flush reports the largest depth recorded in its
/// window.
///
/// Counting does not depend on `METRALE_MTP_ACCEPT_DEBUG`, which gates only
/// the log line: the rung is fed from these counters, so its behaviour does
/// not depend on whether the log is on.
impl AcceptBuckets {
/// 2026-09-25: `rung` is the run's rung controller, which receives every fresh
/// flush; `debug` is the `METRALE_MTP_ACCEPT_DEBUG` lever, which gates
/// only the log line.
pub(super) fn record(
&self,
rung: &metrale_speculative::adaptive_rung::AdaptiveRung,
debug: bool,
n: usize,
k_drafts: usize,
d1_match: bool,
num_accepted: usize,
) {
let b = &self.buckets[bucket_idx(n, self.fold_at_16)];
b.k.fetch_max(k_drafts as u64, Ordering::Relaxed);
b.na.fetch_add(num_accepted as u64, Ordering::Relaxed);
if d1_match {
b.d1.fetch_add(1, Ordering::Relaxed);
}
let prev_steps = b.steps.fetch_add(1, Ordering::Relaxed);
if prev_steps == 0 {
b.window_start_ms
.store(self.epoch.elapsed().as_millis() as u64, Ordering::Relaxed);
}
if prev_steps + 1 >= PERIOD {
let steps = b.steps.swap(0, Ordering::Relaxed).max(1);
let d1 = b.d1.swap(0, Ordering::Relaxed);
let na = b.na.swap(0, Ordering::Relaxed);
let k = b.k.swap(0, Ordering::Relaxed);
let mean_na = na as f64 / steps as f64;
let p1 = d1 as f64 / steps as f64;
let span_ms = (self.epoch.elapsed().as_millis() as u64)
.saturating_sub(b.window_start_ms.load(Ordering::Relaxed));
let fresh = span_ms <= MAX_SAMPLE_SPAN_MS;
// 2026-09-25: The rung has no counters of its own; it is fed from
// this flush. A stale flush (MAX_SAMPLE_SPAN_MS) is not passed on.
if fresh {
rung.observe(n, k as usize, p1, mean_na);
}
if debug {
tracing::info!(
"MTP accept n={n} k_drafts={k} verifies={steps} p1={p1:.3} \
mean_na={mean_na:.3} tok_step={:.3} token_ratio={:.4} \
span_ms={span_ms} fresh={fresh}",
1.0 + mean_na,
metrale_speculative::adaptive_rung::token_ratio(
p1,
metrale_speculative::adaptive_rung::p2_cond_from(p1, mean_na)
.unwrap_or(0.0),
),
);
}
}
}
}
/// 2026-09-25: One request's `p1`, `mean_na` and `tok_step = 1 + mean_na`, plus its
/// serial and MTP step shares and depth-regime re-probe count. `log_done`
/// prints them on the request's Done line; `accepted_total` feeds the usage
/// block.
#[derive(Debug, Default, Clone, Copy)]
pub struct RequestAccept {
serial_steps: u64,
mtp_steps: u64,
d1: u64,
na: u64,
pub regime_reprobes: u64,
}
impl RequestAccept {
pub fn record_serial(&mut self) {
self.serial_steps = self.serial_steps.saturating_add(1);
}
/// 2026-09-25: `emitted` is tokens committed this verify (1 + accepted drafts).
/// The first draft counts as matched when `emitted > 1`.
pub fn record_verify_emitted(&mut self, emitted: usize) {
self.mtp_steps = self.mtp_steps.saturating_add(1);
let accepted = emitted.saturating_sub(1) as u64;
self.na = self.na.saturating_add(accepted);
if accepted > 0 {
self.d1 = self.d1.saturating_add(1);
}
}
/// 2026-09-25: Total draft tokens accepted for this request, reported as
/// `usage.completion_tokens_details.accepted_prediction_tokens`. A count,
/// not a rate.
pub fn accepted_total(&self) -> u64 {
self.na
}
pub fn note_regime_reprobe(&mut self) {
self.regime_reprobes = self.regime_reprobes.saturating_add(1);
}
fn total_steps(&self) -> u64 {
self.serial_steps.saturating_add(self.mtp_steps)
}
pub fn serial_frac(&self) -> f64 {
let n = self.total_steps();
if n == 0 {
0.0
} else {
self.serial_steps as f64 / n as f64
}
}
pub fn mtp_frac(&self) -> f64 {
let n = self.total_steps();
if n == 0 {
0.0
} else {
self.mtp_steps as f64 / n as f64
}
}
pub fn p1(&self) -> f64 {
if self.mtp_steps == 0 {
0.0
} else {
self.d1 as f64 / self.mtp_steps as f64
}
}
pub fn mean_na(&self) -> f64 {
if self.mtp_steps == 0 {
0.0
} else {
self.na as f64 / self.mtp_steps as f64
}
}
pub fn tok_step(&self) -> f64 {
1.0 + self.mean_na()
}
pub fn done_suffix(&self) -> String {
format!(
"serial={:.2} mtp={:.2} p1={:.3} mean_na={:.3} tok_step={:.3} regime_reprobes={}",
self.serial_frac(),
self.mtp_frac(),
self.p1(),
self.mean_na(),
self.tok_step(),
self.regime_reprobes
)
}
pub fn log_done(n: usize, reason: &str, tps: f64, ttft_ms: f64, acct: &Self) {
tracing::info!(
"Done: {n} tokens ({reason}) {tps:.1} tok/s, TTFT={ttft_ms:.1}ms, {}",
acct.done_suffix()
);
}
}
#[cfg(test)]
mod tests {
// 2026-09-25: The bucket table must exceed the MTP dispatch cap (see the
// MAX_N doc).
#[test]
fn bucket_table_covers_the_mtp_dispatch_cap() {
assert_eq!(AcceptBuckets::new(false).buckets.len(), MAX_N);
// 2026-09-29: Every cap a serve accepts has its own bucket.
const { assert!(MAX_N > metrale_model_layers::speculative::MAX_MTP_MAX_SEQS) };
assert!(
MAX_N > metrale_model_layers::speculative::mtp_max_seqs(),
"MAX_N {MAX_N} does not cover dispatch cap {}",
metrale_model_layers::speculative::mtp_max_seqs()
);
}
// 2026-09-25: No two widths up to the dispatch cap share a bucket, checked
// on `bucket_idx`. Skipped when either override is set.
#[test]
fn widths_up_to_the_cap_do_not_alias() {
if std::env::var_os("METRALE_MTP_ACCEPT_FOLD_AT_16").is_some() {
return;
}
for n in 0..=metrale_model_layers::speculative::MAX_MTP_MAX_SEQS {
assert_eq!(
bucket_idx(n, false),
n,
"width {n} aliases onto another bucket"
);
}
// 2026-09-25: Beyond the table widths fold onto the last bucket, inside the
// array.
assert_eq!(bucket_idx(1_000, false), MAX_N - 1);
assert!(bucket_idx(usize::MAX, false) < MAX_N);
assert_eq!(
bucket_idx(1_000, true),
16,
"the kill switch restores the fold"
);
}
use super::*;
#[test]
fn empty_suffix_is_zeros() {
let a = RequestAccept::default();
assert_eq!(
a.done_suffix(),
"serial=0.00 mtp=0.00 p1=0.000 mean_na=0.000 tok_step=1.000 regime_reprobes=0"
);
}
#[test]
fn thinking_serial_run_is_all_serial() {
let mut a = RequestAccept::default();
for _ in 0..300 {
a.record_serial();
}
assert!((a.serial_frac() - 1.0).abs() < 1e-9);
assert_eq!(a.mean_na(), 0.0);
assert_eq!(a.tok_step(), 1.0);
assert!(a.done_suffix().contains("serial=1.00"));
assert!(a.done_suffix().contains("mtp=0.00"));
}
#[test]
fn mtp_run_reports_p1_mean_na_tok_step() {
let mut a = RequestAccept::default();
for _ in 0..7 {
a.record_verify_emitted(2);
}
for _ in 0..3 {
a.record_verify_emitted(1);
}
assert!((a.mtp_frac() - 1.0).abs() < 1e-9);
assert!((a.p1() - 0.7).abs() < 1e-9);
assert!((a.mean_na() - 0.7).abs() < 1e-9);
// 2026-09-25: 7 verifies each accepting 1 draft: the per-request
// total the usage field reports is the raw sum, not a rate.
assert_eq!(a.accepted_total(), 7);
assert!((a.tok_step() - 1.7).abs() < 1e-9);
a.note_regime_reprobe();
assert!(a.done_suffix().contains("mean_na=0.700"));
assert!(a.done_suffix().contains("tok_step=1.700"));
assert!(a.done_suffix().contains("regime_reprobes=1"));
}
}