metrale_speculative/mtp_gate/
driver.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: The `MtpGate` methods the scheduler calls: construction, the
4//! snapshot accessor, the depth hooks, and the per-step record and decision
5//! calls. The window arithmetic and arbitration are in `mtp_gate.rs`.
6//!
7//! Owner: speculative.
8//! Invariants: none beyond the types.
9
10use super::*;
11
12impl MtpGate {
13    /// 2026-09-25: For the scheduler snapshot: the mode (`Probing` during a
14    /// probe) and the delivered-throughput estimate of the current mode (0.0
15    /// until measured).
16    pub fn observe(&self) -> (crate::snapshot::MtpModeSnap, f32) {
17        use crate::snapshot::MtpModeSnap;
18        let mode = if self.probing {
19            MtpModeSnap::Probing
20        } else {
21            match self.mode {
22                Mode::Mtp => MtpModeSnap::Mtp,
23                Mode::Serial => MtpModeSnap::Serial,
24            }
25        };
26        let stats = match self.mode {
27            Mode::Mtp => &self.mtp,
28            Mode::Serial => &self.serial,
29        };
30        (mode, stats.tps.unwrap_or(0.0) as f32)
31    }
32
33    /// 2026-09-25: Starts in Mtp mode. `num_drafts` is only logged;
34    /// arbitration does not model the draft count.
35    pub fn new(num_drafts: usize) -> Self {
36        // 2026-09-25: Read once here: `event_interval` reads them on every
37        // recorded step.
38        let reprobe = reprobe_tokens();
39        let refresh = serial_refresh_tokens();
40        tracing::info!(
41            "MTP gate: throughput-arbitrated (K={num_drafts}); window={WINDOW_STEPS} steps, \
42             dwell={SWITCH_DWELL_WINDOWS}, reprobe={reprobe} tok, refresh={refresh} tok",
43        );
44        Self {
45            reprobe,
46            refresh,
47            mode: Mode::Mtp,
48            probing: false,
49            probe_windows_left: 0,
50            mtp: ModeStats::default(),
51            serial: ModeStats::default(),
52            win_tokens: 0.0,
53            win_wall: 0.0,
54            win_steps: 0,
55            losing_windows: 0,
56            loss_other_windows: 0,
57            tokens_since_event: 0,
58            observed_depth: 0,
59            measured_at_depth: 0,
60            width_regime: 0,
61            fresh: None,
62            settle_steps: 0,
63            regime_reprobes: 0,
64        }
65    }
66
67    pub fn note_depth(&mut self, depth: usize) {
68        self.observed_depth = depth;
69    }
70
71    /// 2026-09-25: When the context depth has moved by
72    /// `REMEASURE_DEPTH_FACTOR` since the last measurement (both floored at
73    /// `REMEASURE_DEPTH_FLOOR`), marks both estimates stale, keeps them as
74    /// they are, and brings the next probe (one `WINDOW_STEPS` window)
75    /// forward. Returns whether it fired.
76    pub fn maybe_remeasure(&mut self, current_depth: usize) -> bool {
77        let measured = self.measured_at_depth.max(REMEASURE_DEPTH_FLOOR);
78        let live = current_depth.max(REMEASURE_DEPTH_FLOOR);
79        if live >= measured * REMEASURE_DEPTH_FACTOR || measured >= live * REMEASURE_DEPTH_FACTOR {
80            tracing::info!(
81                "MTP gate: depth regime changed ({} -> {} tokens); baselines stale, \
82                 will re-probe on cadence",
83                self.measured_at_depth,
84                current_depth,
85            );
86            self.mtp.stale = true;
87            self.serial.stale = true;
88            self.measured_at_depth = current_depth;
89            // 2026-09-25: Make the next probe due now.
90            self.tokens_since_event = self.tokens_since_event.max(self.event_interval());
91            self.regime_reprobes = self.regime_reprobes.saturating_add(1);
92            true
93        } else {
94            false
95        }
96    }
97
98    /// 2026-09-25: The last mode switch, returned once.
99    pub fn take_fresh_decision(&mut self) -> Option<GateDecision> {
100        self.fresh.take()
101    }
102
103    /// 2026-09-25: Which step the scheduler should run next.
104    pub fn next_step(&self) -> GateStep {
105        let effective = if self.probing {
106            Self::other(self.mode)
107        } else {
108            self.mode
109        };
110        match effective {
111            Mode::Mtp => GateStep::MeasureVerify,
112            Mode::Serial => GateStep::MeasureDecode,
113        }
114    }
115
116    /// 2026-09-25: Records one plain decode step over `width` sequences. Each
117    /// emits one token, so the step is charged `width` tokens (at least 1).
118    pub fn record_decode(&mut self, wall: Duration, width: usize) {
119        self.note_width(width.max(1));
120        self.record_step(wall, width.max(1));
121    }
122
123    /// 2026-09-25: Records one MTP step over `width` sequences: `emitted` is
124    /// the tokens all of them committed (at least 1 is charged). Bootstrap and
125    /// propose time count against Mtp mode.
126    pub fn record_verify_step(&mut self, wall: Duration, emitted: usize, width: usize) {
127        self.note_width(width.max(1));
128        self.record_step(wall, emitted.max(1));
129    }
130}