Skip to main content

eredu_core/
speculative.rs

1//! High-level contracts and orchestration for speculative execution backends.
2
3use crate::{
4    backend::{
5        BoundedCompletion, BoundedCompletionOutcome, BoundedCompletionWait, Completion,
6        CompletionCancellationMode, ModelRuntime, SpeculativeTokenFilterController, Submission,
7        TextGenerationBackend, TextGenerationConfig,
8    },
9    generation::{
10        FinishReason, GenerationCancellationToken, GenerationError, GenerationSequence,
11        SemanticEvent, SpeculativeCancellationDisposition, SpeculativeConfig, SpeculativeRequestId,
12        SpeculativeRequestLifecycle, SpeculativeRequestStatus, SpeculativeRound,
13        SpeculativeSchedulerOptions, TokenTerminalSignals,
14    },
15};
16use serde::{Deserialize, Serialize};
17use std::{
18    sync::Arc,
19    time::{Duration, Instant},
20};
21
22/// Draft-model source selected for one speculative-generation request.
23#[non_exhaustive]
24pub enum SpeculativeDraft<'a, D> {
25    /// Separately prepared assistant owned by the selected backend.
26    External(&'a mut D),
27    /// Draft heads embedded in the selected target model.
28    Embedded,
29}
30
31/// One backend-independent speculative-generation result.
32pub struct SpeculativeGenerationOutput {
33    /// Canonical emitted token ids, including terminal EOS when emitted.
34    token_ids: Vec<u32>,
35    /// Portable terminal reason selected by the generation lifecycle.
36    finish_reason: FinishReason,
37    /// Portable speculative execution telemetry.
38    stats: SpeculativeStats,
39}
40
41impl SpeculativeGenerationOutput {
42    /// Creates one completed portable result.
43    pub fn new(token_ids: Vec<u32>, finish_reason: FinishReason, stats: SpeculativeStats) -> Self {
44        Self {
45            token_ids,
46            finish_reason,
47            stats,
48        }
49    }
50
51    /// Canonical emitted token ids.
52    pub fn token_ids(&self) -> &[u32] {
53        &self.token_ids
54    }
55    /// Terminal generation reason.
56    pub const fn finish_reason(&self) -> FinishReason {
57        self.finish_reason
58    }
59    /// Portable speculative telemetry.
60    pub const fn stats(&self) -> &SpeculativeStats {
61        &self.stats
62    }
63}
64
65/// Completed speculative requests plus aggregate fair-scheduler telemetry.
66pub struct SpeculativeGenerationBatchOutput {
67    /// Per-request results in submission order.
68    requests: Vec<SpeculativeGenerationOutput>,
69    /// Aggregate scheduler telemetry.
70    scheduler: SpeculativeSchedulerStats,
71}
72
73impl SpeculativeGenerationBatchOutput {
74    /// Creates a completed batch in stable submission order.
75    pub fn new(
76        requests: Vec<SpeculativeGenerationOutput>,
77        scheduler: SpeculativeSchedulerStats,
78    ) -> Self {
79        Self {
80            requests,
81            scheduler,
82        }
83    }
84    /// Per-request results in submission order.
85    pub fn requests(&self) -> &[SpeculativeGenerationOutput] {
86        &self.requests
87    }
88    /// Consumes the batch and returns its request results.
89    pub fn into_requests(self) -> Vec<SpeculativeGenerationOutput> {
90        self.requests
91    }
92    /// Aggregate scheduler telemetry.
93    pub const fn scheduler(&self) -> &SpeculativeSchedulerStats {
94        &self.scheduler
95    }
96    /// Appends a result while adapting another backend-neutral execution path.
97    pub fn push_request(&mut self, request: SpeculativeGenerationOutput) {
98        self.requests.push(request);
99    }
100    /// Clears adapted request results while retaining scheduler telemetry.
101    pub fn clear_requests(&mut self) {
102        self.requests.clear();
103    }
104}
105
106/// One independently executable lane in a speculative batch.
107pub struct SpeculativeGenerationLane<'a, B, C>
108where
109    B: TextGenerationBackend,
110    C: SpeculativeTokenFilterController,
111{
112    /// Backend-owned prompt prepared by the selected session backend.
113    prompt: Option<B::Prompt>,
114    /// Fully resolved portable sampling configuration and random seed.
115    generation: Option<TextGenerationConfig>,
116    /// Resolved token budget, proposal width, temperature, and EOS ids.
117    config: Option<SpeculativeConfig>,
118    /// Portable canonical grammar state.
119    constraint: Option<C>,
120    /// Transactional decoded semantic parser state.
121    semantic: Option<Box<dyn SpeculativeSemanticState>>,
122    /// Cooperative cancellation owned by this lane.
123    cancellation: Option<GenerationCancellationToken>,
124    /// Called synchronously for canonical events from this lane.
125    on_event: Option<Box<dyn FnMut(SemanticEvent) + 'a>>,
126}
127
128impl<'a, B, C> SpeculativeGenerationLane<'a, B, C>
129where
130    B: TextGenerationBackend,
131    C: SpeculativeTokenFilterController,
132{
133    /// Creates one independently executable speculative lane.
134    #[allow(clippy::too_many_arguments)]
135    pub fn new(
136        prompt: B::Prompt,
137        generation: TextGenerationConfig,
138        config: SpeculativeConfig,
139        constraint: C,
140        semantic: Box<dyn SpeculativeSemanticState>,
141        cancellation: GenerationCancellationToken,
142        on_event: Box<dyn FnMut(SemanticEvent) + 'a>,
143    ) -> Self {
144        Self {
145            prompt: Some(prompt),
146            generation: Some(generation),
147            config: Some(config),
148            constraint: Some(constraint),
149            semantic: Some(semantic),
150            cancellation: Some(cancellation),
151            on_event: Some(on_event),
152        }
153    }
154    /// Takes the backend-owned prompt exactly once.
155    pub fn take_prompt(&mut self) -> B::Prompt {
156        self.prompt.take().expect("lane prompt already taken")
157    }
158    /// Borrows the backend-owned prompt before preparation consumes it.
159    pub fn prompt(&self) -> &B::Prompt {
160        self.prompt.as_ref().expect("lane prompt already taken")
161    }
162    /// Takes the resolved generation controls exactly once.
163    pub fn take_generation(&mut self) -> TextGenerationConfig {
164        self.generation
165            .take()
166            .expect("lane generation already taken")
167    }
168    /// Borrows resolved generation controls.
169    pub fn generation(&self) -> &TextGenerationConfig {
170        self.generation
171            .as_ref()
172            .expect("lane generation already taken")
173    }
174    /// Takes the speculative controls exactly once.
175    pub fn take_config(&mut self) -> SpeculativeConfig {
176        self.config.take().expect("lane config already taken")
177    }
178    /// Borrows speculative controls.
179    pub fn config(&self) -> &SpeculativeConfig {
180        self.config.as_ref().expect("lane config already taken")
181    }
182    /// Takes the grammar controller exactly once.
183    pub fn take_constraint(&mut self) -> C {
184        self.constraint
185            .take()
186            .expect("lane constraint already taken")
187    }
188    /// Takes semantic state exactly once.
189    pub fn take_semantic(&mut self) -> Box<dyn SpeculativeSemanticState> {
190        self.semantic
191            .take()
192            .expect("lane semantic state already taken")
193    }
194    /// Takes cancellation state exactly once.
195    pub fn take_cancellation(&mut self) -> GenerationCancellationToken {
196        self.cancellation
197            .take()
198            .expect("lane cancellation already taken")
199    }
200    /// Takes the event callback exactly once.
201    pub fn take_on_event(&mut self) -> Box<dyn FnMut(SemanticEvent) + 'a> {
202        self.on_event
203            .take()
204            .expect("lane event callback already taken")
205    }
206}
207
208/// Backend-preparation input for one or more speculative lanes.
209pub struct SpeculativeGenerationBatchRequest<'a, B, D, C>
210where
211    B: TextGenerationBackend,
212    C: SpeculativeTokenFilterController,
213{
214    /// Embedded or separately prepared draft-model selection.
215    drafting: Option<SpeculativeDraft<'a, D>>,
216    /// Independently prepared speculative lanes.
217    lanes: Option<Vec<SpeculativeGenerationLane<'a, B, C>>>,
218    /// Target tokenizer vocabulary identity used for drafter compatibility.
219    tokenizer_fingerprint: [u8; 32],
220}
221
222impl<'a, B, D, C> SpeculativeGenerationBatchRequest<'a, B, D, C>
223where
224    B: TextGenerationBackend,
225    C: SpeculativeTokenFilterController,
226{
227    /// Creates one validated backend-preparation request.
228    pub fn new(
229        drafting: SpeculativeDraft<'a, D>,
230        lanes: Vec<SpeculativeGenerationLane<'a, B, C>>,
231        tokenizer_fingerprint: [u8; 32],
232    ) -> Self {
233        Self {
234            drafting: Some(drafting),
235            lanes: Some(lanes),
236            tokenizer_fingerprint,
237        }
238    }
239    /// Target tokenizer vocabulary identity.
240    pub const fn tokenizer_fingerprint(&self) -> [u8; 32] {
241        self.tokenizer_fingerprint
242    }
243    /// Takes draft selection exactly once.
244    pub fn take_drafting(&mut self) -> SpeculativeDraft<'a, D> {
245        self.drafting.take().expect("draft selection already taken")
246    }
247    /// Takes prepared lanes exactly once.
248    pub fn take_lanes(&mut self) -> Vec<SpeculativeGenerationLane<'a, B, C>> {
249        self.lanes.take().expect("speculative lanes already taken")
250    }
251}
252
253/// Optional speculative model-session capability.
254///
255/// Implementations prepare native executors, caches, sampling state, and
256/// execution placement, then expose them to the caller-provided neutral
257/// visitor. The backend must not drive request lifecycles or fair scheduling.
258/// A backend is selected for the complete model session; requests cannot mix
259/// runtime implementations.
260pub trait SpeculativeGenerationBackend: TextGenerationBackend {
261    /// Backend-owned separately prepared draft model.
262    type Drafter;
263
264    /// Reports fail-closed speculative support for the selected model session.
265    fn speculative_capability(runtime: &ModelRuntime<Self>) -> SpeculativeCapability;
266
267    /// Prepares native execution resources and lends them to neutral orchestration.
268    fn with_speculative_execution<C, V>(
269        runtime: &mut ModelRuntime<Self>,
270        request: SpeculativeGenerationBatchRequest<'_, Self, Self::Drafter, C>,
271        visitor: V,
272    ) -> Result<SpeculativeGenerationBatchOutput, Self::Error>
273    where
274        C: SpeculativeTokenFilterController,
275        V: SpeculativeGenerationVisitor;
276}
277
278/// Relationship between target and assistant execution placements.
279#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
280#[serde(rename_all = "snake_case")]
281#[non_exhaustive]
282pub enum SpeculativeExecutionTopology {
283    /// Target and assistant operations share one ordered execution queue.
284    #[default]
285    Single,
286    /// Distinct queues share one device and can use ordered handoffs.
287    SameDeviceSplit,
288    /// Target and assistant use different devices and require transfers.
289    CrossDeviceSplit,
290}
291
292impl std::fmt::Display for SpeculativeExecutionTopology {
293    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294        formatter.write_str(match self {
295            Self::Single => "single",
296            Self::SameDeviceSplit => "same-device-split",
297            Self::CrossDeviceSplit => "cross-device-split",
298        })
299    }
300}
301
302/// How a model exposes speculative draft-token weights.
303#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
304#[serde(rename_all = "snake_case")]
305#[non_exhaustive]
306pub enum SpeculativeDraftSource {
307    /// Drafting weights live in a separately prepared model.
308    Separate,
309    /// Drafting weights are embedded in the selected target model.
310    Embedded,
311}
312
313/// Fail-closed speculative-decoding capability of a prepared model session.
314#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
315#[serde(rename_all = "snake_case")]
316#[non_exhaustive]
317pub enum SpeculativeCapability {
318    /// The model does not advertise executable draft weights.
319    Unavailable,
320    /// The target declares an interface for the stated draft source.
321    ///
322    /// Further preparation is still required before execution is ready. For
323    /// an external assistant this includes tokenizer and architecture
324    /// compatibility proof, assistant construction, and target/assistant
325    /// pairing.
326    Declared {
327        /// Location of the drafting weights.
328        draft_source: SpeculativeDraftSource,
329    },
330    /// Speculative execution is already available with the stated draft source.
331    Ready {
332        /// Location of the drafting weights.
333        draft_source: SpeculativeDraftSource,
334    },
335    /// Draft weights exist, but this backend cannot execute them.
336    Unsupported {
337        /// Location of the drafting weights.
338        draft_source: SpeculativeDraftSource,
339        /// Stable architecture identity reported by the backend.
340        architecture: String,
341    },
342}
343
344impl SpeculativeCapability {
345    /// Returns the declared draft source, including a source that is not ready yet.
346    pub const fn draft_source(&self) -> Option<SpeculativeDraftSource> {
347        match self {
348            Self::Declared { draft_source }
349            | Self::Ready { draft_source }
350            | Self::Unsupported { draft_source, .. } => Some(*draft_source),
351            Self::Unavailable => None,
352        }
353    }
354
355    /// Returns whether planning may attempt to realize the requested source.
356    ///
357    /// This accepts both a declaration awaiting preparation and an already
358    /// ready realization. It rejects a source that is absent or unsupported.
359    pub fn admits_source(&self, requested: SpeculativeDraftSource) -> bool {
360        matches!(
361            self,
362            Self::Declared { draft_source } | Self::Ready { draft_source }
363                if *draft_source == requested
364        )
365    }
366
367    /// Returns whether the requested source is executable in this session now.
368    pub fn is_ready_for(&self, requested: SpeculativeDraftSource) -> bool {
369        matches!(
370            self,
371            Self::Ready { draft_source } if *draft_source == requested
372        )
373    }
374}
375
376/// Statistics collected from one speculative sequence.
377#[derive(Debug, Clone, Default)]
378pub struct SpeculativeStats {
379    /// Relationship between the request's target and draft execution placements.
380    execution_topology: SpeculativeExecutionTopology,
381    /// Target tokens evaluated during prefill and verification.
382    target_tokens: usize,
383    /// Assistant tokens proposed.
384    draft_tokens: usize,
385    /// Assistant tokens accepted by target verification.
386    accepted_tokens: usize,
387    /// Number of target verification rounds.
388    rounds: usize,
389    /// Accepted proposal count for each round.
390    accept_lens: Vec<usize>,
391    /// Tokens emitted, including a terminal EOS token when one is produced.
392    emitted_tokens: usize,
393    /// Tokens drafted on an optimistic continuation.
394    optimistic_draft_tokens: usize,
395    /// Optimistic continuation blocks drafted.
396    optimistic_draft_blocks: usize,
397    /// Optimistically drafted tokens promoted after full acceptance.
398    reused_optimistic_tokens: usize,
399    /// Optimistic continuation blocks promoted after full acceptance.
400    reused_optimistic_blocks: usize,
401    /// First optimistic tokens consumed by matching target bonuses.
402    consumed_optimistic_tokens: usize,
403    /// Optimistically drafted tokens discarded.
404    discarded_optimistic_tokens: usize,
405    /// Optimistic continuation blocks discarded.
406    discarded_optimistic_blocks: usize,
407    /// Target bonus tokens emitted while an optimistic branch existed.
408    optimistic_target_bonus_tokens: usize,
409    /// Non-terminal target bonuses matching the first optimistic token.
410    optimistic_bonus_matches: usize,
411    /// Non-terminal target bonuses differing from the first optimistic token.
412    optimistic_bonus_mismatches: usize,
413    /// Whether deterministic cost accounting disabled further optimistic branches.
414    adaptive_lookahead_disabled: bool,
415    /// Host wall time spent producing optional same-request branches.
416    optimistic_draft_time: Duration,
417    /// Host wall time retained target verification remained in flight.
418    verification_in_flight_time: Duration,
419    /// Whether architecture component timings were collected.
420    component_timings_collected: bool,
421    /// Device execution time spent encoding committed target context.
422    draft_context_time: Duration,
423    /// Device execution time spent executing assistant proposal blocks.
424    draft_assistant_time: Duration,
425    /// Device execution time spent projecting proposal states to logits.
426    draft_head_time: Duration,
427    /// Device execution time spent executing target verification passes.
428    target_verification_time: Duration,
429    /// Scheduler operations performed for this request.
430    scheduler_turns: usize,
431    /// Draft turns performed while another request had target work in flight.
432    cross_request_draft_opportunities: usize,
433    /// Wall-clock generation duration.
434    elapsed: Duration,
435}
436
437impl SpeculativeStats {
438    /// Selected target/draft placement relationship.
439    pub const fn execution_topology(&self) -> SpeculativeExecutionTopology {
440        self.execution_topology
441    }
442    /// Target tokens evaluated.
443    pub const fn target_tokens(&self) -> usize {
444        self.target_tokens
445    }
446    /// Assistant tokens proposed.
447    pub const fn draft_tokens(&self) -> usize {
448        self.draft_tokens
449    }
450    /// Assistant tokens accepted.
451    pub const fn accepted_tokens(&self) -> usize {
452        self.accepted_tokens
453    }
454    /// Target verification rounds.
455    pub const fn rounds(&self) -> usize {
456        self.rounds
457    }
458    /// Accepted proposal count per round.
459    pub fn accept_lens(&self) -> &[usize] {
460        &self.accept_lens
461    }
462    /// Emitted token count.
463    pub const fn emitted_tokens(&self) -> usize {
464        self.emitted_tokens
465    }
466    /// Optimistically drafted token count.
467    pub const fn optimistic_draft_tokens(&self) -> usize {
468        self.optimistic_draft_tokens
469    }
470    /// Optimistic block count.
471    pub const fn optimistic_draft_blocks(&self) -> usize {
472        self.optimistic_draft_blocks
473    }
474    /// Reused optimistic token count.
475    pub const fn reused_optimistic_tokens(&self) -> usize {
476        self.reused_optimistic_tokens
477    }
478    /// Reused optimistic block count.
479    pub const fn reused_optimistic_blocks(&self) -> usize {
480        self.reused_optimistic_blocks
481    }
482    /// Optimistic tokens consumed by target bonuses.
483    pub const fn consumed_optimistic_tokens(&self) -> usize {
484        self.consumed_optimistic_tokens
485    }
486    /// Discarded optimistic token count.
487    pub const fn discarded_optimistic_tokens(&self) -> usize {
488        self.discarded_optimistic_tokens
489    }
490    /// Discarded optimistic block count.
491    pub const fn discarded_optimistic_blocks(&self) -> usize {
492        self.discarded_optimistic_blocks
493    }
494    /// Target bonuses emitted while an optimistic branch existed.
495    pub const fn optimistic_target_bonus_tokens(&self) -> usize {
496        self.optimistic_target_bonus_tokens
497    }
498    /// Matching optimistic bonus count.
499    pub const fn optimistic_bonus_matches(&self) -> usize {
500        self.optimistic_bonus_matches
501    }
502    /// Mismatching optimistic bonus count.
503    pub const fn optimistic_bonus_mismatches(&self) -> usize {
504        self.optimistic_bonus_mismatches
505    }
506    /// Whether adaptive lookahead is disabled.
507    pub const fn adaptive_lookahead_disabled(&self) -> bool {
508        self.adaptive_lookahead_disabled
509    }
510    /// Time spent drafting optimistic branches.
511    pub const fn optimistic_draft_time(&self) -> Duration {
512        self.optimistic_draft_time
513    }
514    /// Time retained verification remained in flight.
515    pub const fn verification_in_flight_time(&self) -> Duration {
516        self.verification_in_flight_time
517    }
518    /// Whether component timings were collected.
519    pub const fn component_timings_collected(&self) -> bool {
520        self.component_timings_collected
521    }
522    /// Draft-context device time.
523    pub const fn draft_context_time(&self) -> Duration {
524        self.draft_context_time
525    }
526    /// Draft-assistant device time.
527    pub const fn draft_assistant_time(&self) -> Duration {
528        self.draft_assistant_time
529    }
530    /// Draft-head device time.
531    pub const fn draft_head_time(&self) -> Duration {
532        self.draft_head_time
533    }
534    /// Target-verification device time.
535    pub const fn target_verification_time(&self) -> Duration {
536        self.target_verification_time
537    }
538    /// Scheduler turns for this request.
539    pub const fn scheduler_turns(&self) -> usize {
540        self.scheduler_turns
541    }
542    /// Draft turns performed beside other in-flight target work.
543    pub const fn cross_request_draft_opportunities(&self) -> usize {
544        self.cross_request_draft_opportunities
545    }
546    /// Wall-clock generation duration.
547    pub const fn elapsed(&self) -> Duration {
548        self.elapsed
549    }
550
551    /// Adds backend-measured component timings without exposing mutable fields.
552    pub fn add_component_timings(
553        &mut self,
554        draft_context: Duration,
555        draft_assistant: Duration,
556        draft_head: Duration,
557        target_verification: Duration,
558    ) {
559        self.draft_context_time += draft_context;
560        self.draft_assistant_time += draft_assistant;
561        self.draft_head_time += draft_head;
562        self.target_verification_time += target_verification;
563        self.component_timings_collected = true;
564    }
565
566    /// Adds completed scheduler rounds to portable telemetry.
567    pub fn add_scheduler_rounds(&mut self, rounds: usize) {
568        self.rounds += rounds;
569    }
570
571    /// Records aggregate optimistic work used by adaptive-lookahead policy.
572    pub fn record_optimistic_accounting(
573        &mut self,
574        drafted_blocks: usize,
575        reused_tokens: usize,
576        discarded_tokens: usize,
577    ) {
578        self.optimistic_draft_blocks += drafted_blocks;
579        self.reused_optimistic_tokens += reused_tokens;
580        self.discarded_optimistic_tokens += discarded_tokens;
581    }
582
583    /// Clears the cached adaptive-lookahead decision before policy re-evaluation.
584    pub fn reset_adaptive_lookahead_decision(&mut self) {
585        self.adaptive_lookahead_disabled = false;
586    }
587
588    /// Fraction of proposed tokens accepted by the target.
589    pub fn accept_rate(&self) -> f64 {
590        if self.draft_tokens == 0 {
591            0.0
592        } else {
593            self.accepted_tokens as f64 / self.draft_tokens as f64
594        }
595    }
596
597    /// Re-evaluates whether optional lookahead remains profitable.
598    pub fn update_adaptive_lookahead(&mut self, options: SpeculativeSchedulerOptions) {
599        if !options.adaptive_lookahead
600            || self.adaptive_lookahead_disabled
601            || self.optimistic_draft_blocks < options.adaptive_lookahead_min_blocks
602        {
603            return;
604        }
605        self.adaptive_lookahead_disabled = self.reused_optimistic_tokens == 0
606            || self.reused_optimistic_tokens < self.discarded_optimistic_tokens;
607    }
608}
609
610/// Aggregate bounded-scheduler telemetry.
611#[derive(Debug, Clone, Default)]
612pub struct SpeculativeSchedulerStats {
613    /// Relationship between scheduler target and draft placements.
614    execution_topology: SpeculativeExecutionTopology,
615    /// Total scheduler operations.
616    turns: usize,
617    /// Draft turns performed while another request was being verified.
618    cross_request_draft_opportunities: usize,
619    /// Maximum simultaneously retained target verification transactions.
620    peak_in_flight_verifications: usize,
621    /// Maximum simultaneously retained optimistic draft branches.
622    peak_optimistic_branches: usize,
623}
624
625impl SpeculativeSchedulerStats {
626    /// Selected target/draft placement relationship.
627    pub const fn execution_topology(&self) -> SpeculativeExecutionTopology {
628        self.execution_topology
629    }
630    /// Scheduler turn count.
631    pub const fn turns(&self) -> usize {
632        self.turns
633    }
634    /// Draft turns performed beside other in-flight target work.
635    pub const fn cross_request_draft_opportunities(&self) -> usize {
636        self.cross_request_draft_opportunities
637    }
638    /// Peak retained target verifications.
639    pub const fn peak_in_flight_verifications(&self) -> usize {
640        self.peak_in_flight_verifications
641    }
642    /// Peak retained optimistic branches.
643    pub const fn peak_optimistic_branches(&self) -> usize {
644        self.peak_optimistic_branches
645    }
646}
647
648/// Backend telemetry that can contribute to portable speculative statistics.
649///
650/// Implementations translate backend-specific measurements into the stable
651/// semantic counters and durations owned by [`SpeculativeStats`].
652pub trait SpeculativeTelemetry: Default {
653    /// Records one completed backend observation.
654    fn record(self, stats: &mut SpeculativeStats);
655}
656
657impl SpeculativeTelemetry for () {
658    fn record(self, _stats: &mut SpeculativeStats) {}
659}
660
661/// Backend-owned first-token output and assistant seed state.
662#[derive(Debug)]
663pub struct SpeculativePrefill<State, Logits> {
664    /// Opaque logits used by the selected backend sampler.
665    logits: Logits,
666    /// Backend state from which the first proposal round begins.
667    state: State,
668    /// Number of prompt tokens evaluated by the target.
669    evaluated_tokens: usize,
670}
671
672impl<State, Logits> SpeculativePrefill<State, Logits> {
673    /// Creates a backend-owned prefill result.
674    pub const fn new(logits: Logits, state: State, evaluated_tokens: usize) -> Self {
675        Self {
676            logits,
677            state,
678            evaluated_tokens,
679        }
680    }
681
682    /// Decomposes the prefill into logits, proposal seed state, and evaluated-token count.
683    pub fn into_parts(self) -> (Logits, State, usize) {
684        (self.logits, self.state, self.evaluated_tokens)
685    }
686}
687
688/// Result of committing one exact target verification transaction.
689#[derive(Debug)]
690pub struct SpeculativeCommit<State> {
691    /// Assistant seed state matching the committed target cache.
692    state: State,
693    /// Target tokens replayed while restoring the exact retained prefix.
694    replayed_tokens: usize,
695}
696
697impl<State> SpeculativeCommit<State> {
698    /// Creates an exact target-commit result.
699    pub const fn new(state: State, replayed_tokens: usize) -> Self {
700        Self {
701            state,
702            replayed_tokens,
703        }
704    }
705
706    /// Decomposes the commit into its exact proposal seed and replay count.
707    pub fn into_parts(self) -> (State, usize) {
708        (self.state, self.replayed_tokens)
709    }
710}
711
712/// Whole-session speculative execution contract.
713///
714/// Tensor values, execution queues, caches, model state, logits, native
715/// completions, and errors remain opaque associated types. The contract models
716/// only high-level prefill, proposal, verification, and exact commit actions;
717/// it deliberately does not define primitive tensor operations.
718pub trait SpeculativeExecutor {
719    /// Backend-owned model input accepted by prefill submission.
720    type Input;
721    /// Complete backend-owned target cache.
722    type Cache;
723    /// Target state used to seed one proposal round.
724    type TargetState;
725    /// Private, discardable assistant state.
726    type DraftState: Clone;
727    /// Exact target-cache checkpoint marker.
728    type CacheCheckpoint;
729    /// Retained target verification output.
730    type Verification;
731    /// Opaque logits consumed by the backend's sampling adapter.
732    type Logits;
733    /// Backend execution assignment for one operation.
734    type Context<'a>: Copy;
735    /// Exact completion for submitted verification work.
736    type Completion: BoundedCompletion<Error = Self::Error>;
737    /// Optional backend-specific component telemetry.
738    type Telemetry: SpeculativeTelemetry;
739    /// Structured backend error.
740    type Error: std::error::Error + Send + Sync + 'static;
741
742    /// Maximum proposals supported in one verification transaction.
743    fn max_proposals(&self) -> usize {
744        usize::MAX
745    }
746
747    /// Enables optional component telemetry.
748    fn set_telemetry_enabled(&mut self, _enabled: bool) {}
749
750    /// Whether optional component telemetry is available.
751    fn supports_telemetry(&self) -> bool {
752        false
753    }
754
755    /// Resolves and drains assistant telemetry since the previous call.
756    fn take_telemetry(&mut self) -> Result<Self::Telemetry, Self::Error> {
757        Ok(Self::Telemetry::default())
758    }
759
760    /// Resolves telemetry retained by one target verification output.
761    fn take_verification_telemetry(
762        &mut self,
763        _output: &mut Self::Verification,
764    ) -> Result<Self::Telemetry, Self::Error> {
765        Ok(Self::Telemetry::default())
766    }
767
768    /// Whether cloned assistant state can be promoted after an exact bonus match.
769    fn supports_exact_optimistic_promotion(&self) -> bool {
770        false
771    }
772
773    /// Prefills the target and returns first-token logits plus assistant seed state.
774    fn prefill<'context>(
775        &mut self,
776        input: Self::Input,
777        cache: &mut Self::Cache,
778        context: Self::Context<'context>,
779    ) -> Result<SpeculativePrefill<Self::TargetState, Self::Logits>, Self::Error>;
780
781    /// Starts one private proposal round sized to the available output budget.
782    fn begin_proposal<'a>(
783        &mut self,
784        state: &Self::TargetState,
785        last_token: u32,
786        proposal_capacity: usize,
787        context: Self::Context<'a>,
788    ) -> Result<Self::DraftState, Self::Error>;
789
790    /// Produces opaque next-token logits and advances private assistant state.
791    fn proposal_logits<'a>(
792        &mut self,
793        state: &mut Self::DraftState,
794        last_token: u32,
795        context: Self::Context<'a>,
796    ) -> Result<Self::Logits, Self::Error>;
797
798    /// Captures the exact cache boundary before target verification.
799    fn checkpoint(&self, cache: &Self::Cache) -> Result<Self::CacheCheckpoint, Self::Error>;
800
801    /// Restores the exact cache boundary after an aborted verification transaction.
802    fn restore_checkpoint<'a>(
803        &mut self,
804        cache: &mut Self::Cache,
805        checkpoint: &Self::CacheCheckpoint,
806        context: Self::Context<'a>,
807    ) -> Result<(), Self::Error>;
808
809    /// Submits verification of the last committed token and proposal block.
810    ///
811    /// Implementations materialize token tensors internally and return an exact
812    /// completion retaining every resource required by the submission.
813    fn submit_verification<'a>(
814        &mut self,
815        input_tokens: &[u32],
816        cache: &mut Self::Cache,
817        context: Self::Context<'a>,
818    ) -> Result<Submission<Self::Verification, Self::Completion>, Self::Error>;
819
820    /// Selects one prediction row from retained verification output.
821    fn verification_logits<'a>(
822        &self,
823        output: &Self::Verification,
824        index: usize,
825        context: Self::Context<'a>,
826    ) -> Result<Self::Logits, Self::Error>;
827
828    /// Commits exactly the requested verified inputs and restores matching seed state.
829    fn commit_verification<'a>(
830        &mut self,
831        output: Self::Verification,
832        draft_state: Self::DraftState,
833        cache: &mut Self::Cache,
834        checkpoint: &Self::CacheCheckpoint,
835        verified_inputs: usize,
836        context: Self::Context<'a>,
837    ) -> Result<SpeculativeCommit<Self::TargetState>, Self::Error>;
838}
839
840/// Target decision for one assistant proposal.
841#[derive(Debug, Clone, Copy, Eq, PartialEq)]
842#[non_exhaustive]
843pub enum ProposalDecision {
844    /// Retain the assistant proposal.
845    Accept,
846    /// Reject it and commit this target replacement.
847    Reject(u32),
848}
849
850/// Logical model side on which an opaque sampling operation executes.
851#[derive(Debug, Clone, Copy, Eq, PartialEq)]
852#[non_exhaustive]
853pub enum SamplingPlacement {
854    /// Canonical target-model execution.
855    Target,
856    /// Tentative assistant-model execution.
857    Draft,
858}
859
860/// Absolute committed-output coordinate for position-stable draft randomness.
861#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
862pub struct SpeculativeDraftRandomPosition(usize);
863
864impl SpeculativeDraftRandomPosition {
865    /// Creates an absolute draft-randomness coordinate.
866    pub const fn new(position: usize) -> Self {
867        Self(position)
868    }
869
870    /// Returns the zero-based absolute output position.
871    pub const fn get(self) -> usize {
872        self.0
873    }
874}
875
876/// Backend-owned random streams for canonical and position-stable sampling.
877#[derive(Debug, Clone)]
878pub struct SpeculativeRandomness<R, D> {
879    /// Sequential target randomness.
880    target: Option<R>,
881    /// Position-addressable assistant randomness.
882    draft: Option<D>,
883}
884
885impl<R, D> SpeculativeRandomness<R, D> {
886    /// Creates independent target and draft random streams.
887    pub const fn new(target: Option<R>, draft: Option<D>) -> Self {
888        Self { target, draft }
889    }
890}
891
892/// One backend-prepared lane lent to neutral speculative orchestration.
893///
894/// The lane contains opaque execution values but no scheduler or lifecycle
895/// policy. Its cache borrow remains valid only for the visitor invocation.
896pub struct PreparedSpeculativeLane<'a, E, S, C, P>
897where
898    E: SpeculativeExecutor,
899    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
900    C: SpeculativeConstraint,
901    P: SpeculativePublisher<C>,
902{
903    /// Backend-owned request cache.
904    cache: Option<&'a mut E::Cache>,
905    /// Backend-owned prepared model input.
906    input: Option<E::Input>,
907    /// Validated speculative generation controls.
908    config: Option<SpeculativeConfig>,
909    /// Canonical sampling, constraint, publication, and cancellation state.
910    runtime: Option<SpeculativeOutputRuntime<S, C, P>>,
911    /// Independent target and draft random streams.
912    randomness: Option<SpeculativeRandomness<S::RandomState, S::DraftRandomness>>,
913}
914
915impl<'a, E, S, C, P> PreparedSpeculativeLane<'a, E, S, C, P>
916where
917    E: SpeculativeExecutor,
918    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
919    C: SpeculativeConstraint,
920    P: SpeculativePublisher<C>,
921{
922    /// Creates one backend-prepared lane for neutral orchestration.
923    pub fn new(
924        cache: &'a mut E::Cache,
925        input: E::Input,
926        config: SpeculativeConfig,
927        runtime: SpeculativeOutputRuntime<S, C, P>,
928        randomness: SpeculativeRandomness<S::RandomState, S::DraftRandomness>,
929    ) -> Self {
930        Self {
931            cache: Some(cache),
932            input: Some(input),
933            config: Some(config),
934            runtime: Some(runtime),
935            randomness: Some(randomness),
936        }
937    }
938    /// Takes the backend cache borrow exactly once.
939    pub fn take_cache(&mut self) -> &'a mut E::Cache {
940        self.cache.take().expect("prepared cache already taken")
941    }
942    /// Takes model input exactly once.
943    pub fn take_input(&mut self) -> E::Input {
944        self.input.take().expect("prepared input already taken")
945    }
946    /// Takes speculative controls exactly once.
947    pub fn take_config(&mut self) -> SpeculativeConfig {
948        self.config.take().expect("prepared config already taken")
949    }
950    /// Takes portable output state exactly once.
951    pub fn take_runtime(&mut self) -> SpeculativeOutputRuntime<S, C, P> {
952        self.runtime.take().expect("prepared runtime already taken")
953    }
954    /// Takes target/draft randomness exactly once.
955    pub fn take_randomness(&mut self) -> SpeculativeRandomness<S::RandomState, S::DraftRandomness> {
956        self.randomness
957            .take()
958            .expect("prepared randomness already taken")
959    }
960}
961
962/// Facade/runtime-owned driver for backend-prepared speculative execution.
963///
964/// The generic method lets a backend lend any concrete executor realization
965/// without erasing native tensor, cache, completion, or sampling types. The
966/// visitor owns request registration, fair action selection, completion
967/// driving, terminal validation, and public output construction.
968pub trait SpeculativeGenerationVisitor {
969    /// Drives one prepared set of lanes through the neutral lifecycle.
970    #[allow(clippy::too_many_arguments)]
971    fn run<'a, E, S, C, P>(
972        self,
973        executor: &'a mut E,
974        lanes: Vec<PreparedSpeculativeLane<'a, E, S, C, P>>,
975        topology: SpeculativeExecutionTopology,
976        optimistic_execution_available: bool,
977        component_timings_collected: bool,
978        context: E::Context<'a>,
979    ) -> Result<SpeculativeGenerationBatchOutput, SpeculativeDriverError<E::Error>>
980    where
981        E: SpeculativeExecutor + 'a,
982        S: SpeculativeSampling<Logits = E::Logits, Error = E::Error, Context<'a> = E::Context<'a>>
983            + 'a,
984        C: SpeculativeConstraint,
985        P: SpeculativePublisher<C>;
986}
987
988/// Backend mechanisms used by core-owned speculative sampling policy.
989///
990/// Implementations keep logits, distributions, and random state opaque while
991/// exposing only the probability and RNG operations needed by the portable
992/// accept-or-replace algorithm. Core owns when those mechanisms run, which
993/// token is selected, and when tentative sampler state is promoted.
994pub trait SpeculativeSampling: Clone {
995    /// Raw model logits.
996    type Logits;
997    /// Processed distribution retained for verification.
998    type Distribution;
999    /// Caller-provided randomness seed.
1000    type Seed;
1001    /// Sequential random state.
1002    type RandomState: Clone;
1003    /// Position-addressable assistant random state.
1004    type DraftRandomness: Clone;
1005    /// Backend-native root from which core allocates canonical substreams.
1006    type RandomnessRoot;
1007    /// Backend execution assignment.
1008    type Context<'a>: Copy
1009    where
1010        Self: 'a;
1011    /// Structured backend error.
1012    type Error: std::error::Error + Send + Sync + 'static;
1013
1014    /// Whether cloned sampler state is safe for optimistic promotion.
1015    fn supports_exact_optimistic_promotion(&self) -> bool {
1016        false
1017    }
1018
1019    /// Whether the canonical grammar accepts its current prefix.
1020    fn grammar_is_complete(&mut self) -> Result<bool, Self::Error> {
1021        Ok(false)
1022    }
1023
1024    /// Whether a tentative token history completes the grammar.
1025    fn prefix_is_complete(&self, _history: &[u32]) -> Result<bool, Self::Error> {
1026        Ok(false)
1027    }
1028
1029    /// Creates one opaque native root from the caller-provided seed.
1030    fn randomness_root<'a>(
1031        seed: Option<Self::Seed>,
1032        context: Self::Context<'a>,
1033    ) -> Result<Self::RandomnessRoot, Self::Error>
1034    where
1035        Self: 'a;
1036
1037    /// Splits the next canonical target substream from a native root.
1038    fn target_randomness_from_root<'a>(
1039        root: &mut Self::RandomnessRoot,
1040        context: Self::Context<'a>,
1041    ) -> Result<Self::RandomState, Self::Error>
1042    where
1043        Self: 'a;
1044
1045    /// Splits the next position-addressable draft root from a native root.
1046    fn draft_randomness_from_root<'a>(
1047        root: &mut Self::RandomnessRoot,
1048        context: Self::Context<'a>,
1049    ) -> Result<Self::DraftRandomness, Self::Error>
1050    where
1051        Self: 'a;
1052
1053    /// Allocates target then draft randomness in the canonical neutral order.
1054    fn initialize_randomness<'a>(
1055        seed: Option<Self::Seed>,
1056        temperature: f32,
1057        context: Self::Context<'a>,
1058    ) -> Result<SpeculativeRandomness<Self::RandomState, Self::DraftRandomness>, Self::Error>
1059    where
1060        Self: 'a,
1061    {
1062        if temperature == 0.0 {
1063            return Ok(SpeculativeRandomness::new(None, None));
1064        }
1065        let mut root = Self::randomness_root(seed, context)?;
1066        let target = Self::target_randomness_from_root(&mut root, context)?;
1067        let draft = Self::draft_randomness_from_root(&mut root, context)?;
1068        Ok(SpeculativeRandomness::new(Some(target), Some(draft)))
1069    }
1070
1071    /// Derives assistant randomness for one absolute output position.
1072    fn draft_randomness_at<'a>(
1073        root: &Self::DraftRandomness,
1074        position: SpeculativeDraftRandomPosition,
1075        context: Self::Context<'a>,
1076    ) -> Result<Self::RandomState, Self::Error>
1077    where
1078        Self: 'a;
1079
1080    /// Processes raw logits against one logical history.
1081    fn process_logits<'a>(
1082        &mut self,
1083        logits: &Self::Logits,
1084        temperature: f32,
1085        history: &[u32],
1086        placement: SamplingPlacement,
1087        context: Self::Context<'a>,
1088    ) -> Result<Self::Distribution, Self::Error>
1089    where
1090        Self: 'a;
1091
1092    /// Samples one token from a processed distribution.
1093    fn sample<'a>(
1094        &self,
1095        distribution: &Self::Distribution,
1096        temperature: f32,
1097        randomness: Option<&mut Self::RandomState>,
1098        placement: SamplingPlacement,
1099        context: Self::Context<'a>,
1100    ) -> Result<u32, Self::Error>
1101    where
1102        Self: 'a;
1103
1104    /// Returns the normalized probability assigned to one token.
1105    fn probability_at<'a>(
1106        &self,
1107        distribution: &Self::Distribution,
1108        token: u32,
1109        placement: SamplingPlacement,
1110        context: Self::Context<'a>,
1111    ) -> Result<f32, Self::Error>
1112    where
1113        Self: 'a;
1114
1115    /// Draws one value from the half-open unit interval.
1116    fn sample_unit_interval<'a>(
1117        &self,
1118        randomness: Option<&mut Self::RandomState>,
1119        context: Self::Context<'a>,
1120    ) -> Result<f32, Self::Error>
1121    where
1122        Self: 'a;
1123
1124    /// Computes the normalized positive probability difference `left-right`.
1125    ///
1126    /// `None` means that the positive difference has no usable mass. Core
1127    /// owns the fallback to the target distribution in that case.
1128    fn positive_probability_difference<'a>(
1129        &self,
1130        left: &Self::Distribution,
1131        right: &Self::Distribution,
1132        placement: SamplingPlacement,
1133        context: Self::Context<'a>,
1134    ) -> Result<Option<Self::Distribution>, Self::Error>
1135    where
1136        Self: 'a;
1137
1138    /// Applies a mechanism-specific sampler update for a core-selected token.
1139    fn update_sampler_state<'a>(
1140        &mut self,
1141        distribution: &Self::Distribution,
1142        token: u32,
1143        placement: SamplingPlacement,
1144        context: Self::Context<'a>,
1145    ) -> Result<(), Self::Error>
1146    where
1147        Self: 'a;
1148
1149    /// Makes retained assistant distributions available to target resolution.
1150    fn prepare_verification<'a>(
1151        &self,
1152        _distributions: &mut [&mut Self::Distribution],
1153        _temperature: f32,
1154        _context: Self::Context<'a>,
1155    ) -> Result<(), Self::Error>
1156    where
1157        Self: 'a,
1158    {
1159        Ok(())
1160    }
1161}
1162
1163/// Computes the portable speculative acceptance probability.
1164///
1165/// A proposal with no draft probability is accepted because the assistant
1166/// could not have sampled it from the represented distribution. Otherwise
1167/// acceptance is capped at one exactly as required by speculative decoding.
1168pub fn speculative_acceptance_probability(target_probability: f32, draft_probability: f32) -> f32 {
1169    if draft_probability <= 0.0 {
1170        1.0
1171    } else {
1172        (target_probability / draft_probability).min(1.0)
1173    }
1174}
1175
1176/// Applies the core-owned accept-or-replace policy to one proposal.
1177pub fn decide_speculative_proposal<'a, S>(
1178    sampler: &S,
1179    target: &S::Distribution,
1180    draft: &S::Distribution,
1181    proposed: u32,
1182    temperature: f32,
1183    randomness: Option<&mut S::RandomState>,
1184    context: S::Context<'a>,
1185) -> Result<ProposalDecision, S::Error>
1186where
1187    S: SpeculativeSampling + 'a,
1188{
1189    let mut randomness = randomness;
1190    if temperature == 0.0 {
1191        let chosen = sampler.sample(
1192            target,
1193            temperature,
1194            None,
1195            SamplingPlacement::Target,
1196            context,
1197        )?;
1198        return Ok(if chosen == proposed {
1199            ProposalDecision::Accept
1200        } else {
1201            ProposalDecision::Reject(chosen)
1202        });
1203    }
1204
1205    let target_probability =
1206        sampler.probability_at(target, proposed, SamplingPlacement::Target, context)?;
1207    let draft_probability =
1208        sampler.probability_at(draft, proposed, SamplingPlacement::Target, context)?;
1209    let acceptance = speculative_acceptance_probability(target_probability, draft_probability);
1210    if sampler.sample_unit_interval(randomness.as_deref_mut(), context)? <= acceptance {
1211        return Ok(ProposalDecision::Accept);
1212    }
1213
1214    let residual = sampler.positive_probability_difference(
1215        target,
1216        draft,
1217        SamplingPlacement::Target,
1218        context,
1219    )?;
1220    let replacement = sampler.sample(
1221        residual.as_ref().unwrap_or(target),
1222        temperature,
1223        randomness,
1224        SamplingPlacement::Target,
1225        context,
1226    )?;
1227    Ok(ProposalDecision::Reject(replacement))
1228}
1229
1230/// One sampled assistant proposal and its retained distribution.
1231#[derive(Debug)]
1232pub struct SpeculativeProposal<D> {
1233    /// Proposed token id.
1234    token: u32,
1235    /// Backend-owned processed assistant distribution.
1236    distribution: D,
1237}
1238
1239impl<D> SpeculativeProposal<D> {
1240    /// Creates one retained assistant proposal.
1241    pub const fn new(token: u32, distribution: D) -> Self {
1242        Self {
1243            token,
1244            distribution,
1245        }
1246    }
1247    /// Proposed token id.
1248    pub const fn token(&self) -> u32 {
1249        self.token
1250    }
1251    /// Retained assistant distribution.
1252    pub const fn distribution(&self) -> &D {
1253        &self.distribution
1254    }
1255}
1256
1257/// Backend-owned assistant state paired with a portable proposal sequence.
1258pub struct SpeculativeDraftBlock<S, D> {
1259    /// Assistant state after producing every proposal.
1260    state: S,
1261    /// Ordered proposed tokens and opaque distributions.
1262    proposals: Vec<SpeculativeProposal<D>>,
1263}
1264
1265impl<S, D> SpeculativeDraftBlock<S, D> {
1266    /// Creates one ordered assistant proposal block.
1267    pub fn new(state: S, proposals: Vec<SpeculativeProposal<D>>) -> Self {
1268        Self { state, proposals }
1269    }
1270    /// Assistant state after every proposal.
1271    pub const fn state(&self) -> &S {
1272        &self.state
1273    }
1274    /// Ordered proposals retained by this block.
1275    pub fn proposals(&self) -> &[SpeculativeProposal<D>] {
1276        &self.proposals
1277    }
1278}
1279
1280/// Tentative continuation drafted against an assumed canonical prefix.
1281pub struct SpeculativeOptimisticBranch<S, D> {
1282    /// Backend-owned tentative draft block.
1283    block: SpeculativeDraftBlock<S, D>,
1284    /// Prefix against which the block was produced.
1285    assumed_prefix: Vec<u32>,
1286}
1287
1288impl<S, D> SpeculativeOptimisticBranch<S, D> {
1289    /// Creates one tentative continuation tied to an assumed prefix.
1290    pub fn new(block: SpeculativeDraftBlock<S, D>, assumed_prefix: Vec<u32>) -> Self {
1291        Self {
1292            block,
1293            assumed_prefix,
1294        }
1295    }
1296}
1297
1298/// Optimistic state retained after a committed target transaction.
1299#[non_exhaustive]
1300pub enum SpeculativeContinuation<S, D> {
1301    /// No reusable proposal block remains.
1302    None,
1303    /// A matching branch may seed the next canonical round.
1304    Promoted(SpeculativeDraftBlock<S, D>),
1305}
1306
1307impl<S, D> SpeculativeContinuation<S, D> {
1308    /// Returns the promoted block, when one exists.
1309    pub fn into_block(self) -> Option<SpeculativeDraftBlock<S, D>> {
1310        match self {
1311            Self::None => None,
1312            Self::Promoted(block) => Some(block),
1313        }
1314    }
1315}
1316
1317/// Exact target verification resources retained through resolution.
1318///
1319/// Completion is declared first so it is dropped before the output and every
1320/// resource reachable from it. Its destructor must preserve exact-completion
1321/// safety when the scheduler itself is abandoned.
1322pub struct PendingSpeculativeVerification<E, D>
1323where
1324    E: SpeculativeExecutor,
1325{
1326    completion: E::Completion,
1327    verification: E::Verification,
1328    checkpoint: E::CacheCheckpoint,
1329    block: SpeculativeDraftBlock<E::DraftState, D>,
1330    optimistic: Option<SpeculativeOptimisticBranch<E::DraftState, D>>,
1331    submitted: Instant,
1332    submitted_tokens: usize,
1333}
1334
1335impl<E, D> PendingSpeculativeVerification<E, D>
1336where
1337    E: SpeculativeExecutor,
1338{
1339    /// Observes exact completion without consuming or waiting on retained resources.
1340    pub fn is_complete(&self) -> Result<bool, E::Error> {
1341        self.completion.is_complete()
1342    }
1343
1344    /// Canonical block being verified.
1345    pub const fn block(&self) -> &SpeculativeDraftBlock<E::DraftState, D> {
1346        &self.block
1347    }
1348
1349    /// Whether one optimistic continuation is retained.
1350    pub const fn has_optimistic_branch(&self) -> bool {
1351        self.optimistic.is_some()
1352    }
1353
1354    /// Installs exactly one tentative optimistic branch.
1355    pub fn set_optimistic_branch(
1356        &mut self,
1357        branch: SpeculativeOptimisticBranch<E::DraftState, D>,
1358    ) -> Result<(), GenerationError> {
1359        if self.optimistic.is_some() {
1360            return Err(GenerationError::OptimisticBranchAlreadyPresent);
1361        }
1362        self.optimistic = Some(branch);
1363        Ok(())
1364    }
1365
1366    /// Number of target tokens submitted for verification.
1367    pub const fn submitted_tokens(&self) -> usize {
1368        self.submitted_tokens
1369    }
1370
1371    /// Time elapsed since target submission.
1372    pub fn elapsed(&self) -> Duration {
1373        self.submitted.elapsed()
1374    }
1375}
1376
1377/// Structured failure in backend-independent speculative output handling.
1378#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
1379#[non_exhaustive]
1380pub enum SpeculativeOutputError {
1381    /// Transactional semantic parsing, decoding, or stop matching failed.
1382    #[error("speculative semantic state failed during {operation}: {message}")]
1383    Semantic {
1384        /// Logical semantic operation.
1385        operation: String,
1386        /// Portable diagnostic detail.
1387        message: String,
1388    },
1389    /// A committed-token callback rejected publication.
1390    #[error("speculative output publication failed: {message}")]
1391    Publication {
1392        /// Portable diagnostic detail.
1393        message: String,
1394    },
1395}
1396
1397/// Coarse production lifecycle boundaries for speculative work.
1398///
1399/// These boundaries complement typed activation observation. They make the
1400/// ordering of admission, native work, completion, cache persistence, and
1401/// publication observable without exposing backend tensors or queues.
1402#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
1403#[non_exhaustive]
1404pub enum SpeculativeLifecycleStage {
1405    /// Neutral request admission, before compatibility selection.
1406    Admission,
1407    /// Compatibility selection succeeded, before native construction.
1408    Compatibility,
1409    /// Prepared input is about to be consumed.
1410    Input,
1411    /// Backend execution or construction is about to begin.
1412    Execution,
1413    /// A cross-device transfer is about to begin.
1414    Transfer,
1415    /// An exact completion is about to be inspected or waited.
1416    Completion,
1417    /// Completed model output is about to enter portable resolution.
1418    Observation,
1419    /// Backend cache state is about to be made canonical.
1420    CachePersistence,
1421    /// Committed output is about to be published.
1422    Publication,
1423    /// Cancellation state is about to be published.
1424    Cancellation,
1425}
1426
1427/// Explicit, production-carried observer for coarse speculative lifecycle work.
1428///
1429/// Returning an error prevents the associated boundary from starting. The
1430/// observer is shared because one prepared realization can serve several
1431/// scheduler lanes concurrently.
1432pub trait SpeculativeLifecycleObserver: Send + Sync {
1433    /// Observes one lifecycle boundary before its associated work.
1434    fn observe(&self, stage: SpeculativeLifecycleStage) -> Result<(), SpeculativeOutputError>;
1435}
1436
1437impl<F> SpeculativeLifecycleObserver for F
1438where
1439    F: Fn(SpeculativeLifecycleStage) -> Result<(), SpeculativeOutputError> + Send + Sync,
1440{
1441    fn observe(&self, stage: SpeculativeLifecycleStage) -> Result<(), SpeculativeOutputError> {
1442        self(stage)
1443    }
1444}
1445
1446impl SpeculativeOutputError {
1447    /// Creates a semantic-state failure with operation context.
1448    pub fn semantic(operation: impl Into<String>, message: impl Into<String>) -> Self {
1449        Self::Semantic {
1450            operation: operation.into(),
1451            message: message.into(),
1452        }
1453    }
1454
1455    /// Creates a committed-output publication failure.
1456    pub fn publication(message: impl Into<String>) -> Self {
1457        Self::Publication {
1458            message: message.into(),
1459        }
1460    }
1461}
1462
1463/// Transactional semantic state paired with committed token sequencing.
1464pub trait SpeculativeConstraint: Sized {
1465    /// Forks state for tentative verification.
1466    fn fork(&self) -> Result<Self, SpeculativeOutputError>;
1467    /// Stages one token and reports a matched stop condition.
1468    fn push_token(&mut self, token: u32) -> Result<bool, SpeculativeOutputError>;
1469    /// Stages terminal output.
1470    fn finish(&mut self, reason: FinishReason) -> Result<(), SpeculativeOutputError>;
1471}
1472
1473/// Backend adapter that publishes committed output and terminal cancellation.
1474///
1475/// The adapter may own callbacks and decoded semantic-event buffers, but core
1476/// decides when publication is legal relative to exact cache commit.
1477pub trait SpeculativePublisher<C> {
1478    /// Publishes tokens and staged semantic output after cache commit.
1479    ///
1480    /// Returns `true` when cancellation won this boundary. A successful call
1481    /// is one atomic publication boundary; implementations must stage any
1482    /// already-pending cancellation before exposing tokens or semantic events.
1483    fn publish_committed(
1484        &mut self,
1485        constraint: &mut C,
1486        tokens: &[u32],
1487        cancellation: &GenerationCancellationToken,
1488        sequence_finished: bool,
1489    ) -> Result<bool, SpeculativeOutputError>;
1490
1491    /// Publishes the cancellation terminal state.
1492    fn publish_cancelled(&mut self, constraint: &mut C) -> Result<(), SpeculativeOutputError>;
1493}
1494
1495/// Object-safe forkable semantic state used by speculative transactions.
1496///
1497/// This interface owns decoded semantic events and never exposes a backend
1498/// tensor, stream, completion, or error type.
1499pub trait SpeculativeSemanticState {
1500    /// Forks the exact committed prefix for tentative verification.
1501    fn fork_box(&self) -> Result<Box<dyn SpeculativeSemanticState>, SpeculativeOutputError>;
1502    /// Stages one token and reports whether a stop sequence matched.
1503    fn push_token(&mut self, token: u32) -> Result<bool, SpeculativeOutputError>;
1504    /// Stages normal terminal output.
1505    fn finish(&mut self, reason: FinishReason) -> Result<(), SpeculativeOutputError>;
1506    /// Stages cancellation output.
1507    fn cancel(&mut self) -> Result<(), SpeculativeOutputError>;
1508    /// Drains events authorized by the next exact commit boundary.
1509    fn take_events(&mut self) -> Vec<crate::generation::SemanticEvent>;
1510}
1511
1512/// Optional transactional semantic state shared by plain and structured speculative decoding.
1513pub struct SpeculativeSemanticConstraint {
1514    state: Option<Box<dyn SpeculativeSemanticState>>,
1515}
1516
1517impl SpeculativeSemanticConstraint {
1518    /// Creates an unconstrained output state for token-only generation.
1519    pub const fn plain() -> Self {
1520        Self { state: None }
1521    }
1522
1523    /// Creates a transactional structured-output state.
1524    pub fn semantic(state: Box<dyn SpeculativeSemanticState>) -> Self {
1525        Self { state: Some(state) }
1526    }
1527}
1528
1529impl SpeculativeConstraint for SpeculativeSemanticConstraint {
1530    fn fork(&self) -> Result<Self, SpeculativeOutputError> {
1531        Ok(Self {
1532            state: self
1533                .state
1534                .as_ref()
1535                .map(|state| state.fork_box())
1536                .transpose()?,
1537        })
1538    }
1539
1540    fn push_token(&mut self, token: u32) -> Result<bool, SpeculativeOutputError> {
1541        self.state
1542            .as_mut()
1543            .map(|state| state.push_token(token))
1544            .transpose()
1545            .map(|matched| matched.unwrap_or(false))
1546    }
1547
1548    fn finish(&mut self, reason: FinishReason) -> Result<(), SpeculativeOutputError> {
1549        if let Some(state) = &mut self.state {
1550            state.finish(reason)?;
1551        }
1552        Ok(())
1553    }
1554}
1555
1556/// Fallible atomic committed-token batch callback.
1557type SpeculativeTokenCallback<'a> = dyn FnMut(&[u32]) -> Result<(), SpeculativeOutputError> + 'a;
1558
1559/// Core-owned committed-token and semantic-event publication adapter.
1560pub struct SpeculativeCallbackPublisher<'a> {
1561    on_tokens: Box<SpeculativeTokenCallback<'a>>,
1562    on_event: Option<Box<dyn FnMut(crate::generation::SemanticEvent) + 'a>>,
1563}
1564
1565impl<'a> SpeculativeCallbackPublisher<'a> {
1566    /// Publishes committed token ids without decoded semantic events.
1567    /// The callback is one atomic publication attempt: returning an error must
1568    /// leave every token in the supplied batch unpublished.
1569    pub fn tokens(
1570        on_tokens: impl FnMut(&[u32]) -> Result<(), SpeculativeOutputError> + 'a,
1571    ) -> Self {
1572        Self {
1573            on_tokens: Box::new(on_tokens),
1574            on_event: None,
1575        }
1576    }
1577
1578    /// Publishes transactional semantic events and ignores raw token callbacks.
1579    pub fn semantic(on_event: impl FnMut(crate::generation::SemanticEvent) + 'a) -> Self {
1580        Self {
1581            on_tokens: Box::new(|_| Ok(())),
1582            on_event: Some(Box::new(on_event)),
1583        }
1584    }
1585}
1586
1587impl SpeculativePublisher<SpeculativeSemanticConstraint> for SpeculativeCallbackPublisher<'_> {
1588    fn publish_committed(
1589        &mut self,
1590        constraint: &mut SpeculativeSemanticConstraint,
1591        tokens: &[u32],
1592        cancellation: &GenerationCancellationToken,
1593        sequence_finished: bool,
1594    ) -> Result<bool, SpeculativeOutputError> {
1595        let cancellation_won = cancellation.is_cancelled() && !sequence_finished;
1596        if cancellation_won {
1597            if let Some(state) = &mut constraint.state {
1598                state.cancel()?;
1599            }
1600        }
1601        (self.on_tokens)(tokens)?;
1602        if let (Some(state), Some(on_event)) = (&mut constraint.state, &mut self.on_event) {
1603            for event in state.take_events() {
1604                on_event(event);
1605            }
1606        }
1607        let cancellation_after_callbacks =
1608            !cancellation_won && cancellation.is_cancelled() && !sequence_finished;
1609        if cancellation_after_callbacks {
1610            if let (Some(state), Some(on_event)) = (&mut constraint.state, &mut self.on_event) {
1611                state.cancel()?;
1612                for event in state.take_events() {
1613                    on_event(event);
1614                }
1615            }
1616        }
1617        Ok(cancellation_won || cancellation_after_callbacks)
1618    }
1619
1620    fn publish_cancelled(
1621        &mut self,
1622        constraint: &mut SpeculativeSemanticConstraint,
1623    ) -> Result<(), SpeculativeOutputError> {
1624        if let (Some(state), Some(on_event)) = (&mut constraint.state, &mut self.on_event) {
1625            state.cancel()?;
1626            for event in state.take_events() {
1627                on_event(event);
1628            }
1629        }
1630        Ok(())
1631    }
1632}
1633
1634/// Canonical speculative sampler, sequence, constraint, and output sink.
1635pub struct SpeculativeOutputRuntime<S, C, P> {
1636    sampler: S,
1637    sequence: GenerationSequence,
1638    constraint: C,
1639    publisher: P,
1640    cancellation: GenerationCancellationToken,
1641    lifecycle_observer: Option<Arc<dyn SpeculativeLifecycleObserver>>,
1642}
1643
1644impl<S, C, P> SpeculativeOutputRuntime<S, C, P>
1645where
1646    S: SpeculativeSampling,
1647    C: SpeculativeConstraint,
1648    P: SpeculativePublisher<C>,
1649{
1650    /// Creates one canonical output runtime.
1651    pub fn new(
1652        sampler: S,
1653        sequence: GenerationSequence,
1654        constraint: C,
1655        publisher: P,
1656        cancellation: GenerationCancellationToken,
1657    ) -> Self {
1658        Self {
1659            sampler,
1660            sequence,
1661            constraint,
1662            publisher,
1663            cancellation,
1664            lifecycle_observer: None,
1665        }
1666    }
1667
1668    /// Installs explicit production lifecycle observation for this lane.
1669    pub fn with_lifecycle_observer(
1670        mut self,
1671        observer: Arc<dyn SpeculativeLifecycleObserver>,
1672    ) -> Self {
1673        self.lifecycle_observer = Some(observer);
1674        self
1675    }
1676
1677    /// Observes one lifecycle boundary before its associated work.
1678    pub fn observe_lifecycle(
1679        &self,
1680        stage: SpeculativeLifecycleStage,
1681    ) -> Result<(), SpeculativeOutputError> {
1682        self.lifecycle_observer
1683            .as_ref()
1684            .map_or(Ok(()), |observer| observer.observe(stage))
1685    }
1686
1687    /// Canonical sampling state.
1688    pub const fn sampler(&self) -> &S {
1689        &self.sampler
1690    }
1691
1692    /// Mutable canonical sampling state.
1693    pub const fn sampler_mut(&mut self) -> &mut S {
1694        &mut self.sampler
1695    }
1696
1697    /// Canonical committed sequence.
1698    pub const fn sequence(&self) -> &GenerationSequence {
1699        &self.sequence
1700    }
1701
1702    /// Mutable canonical committed sequence.
1703    pub const fn sequence_mut(&mut self) -> &mut GenerationSequence {
1704        &mut self.sequence
1705    }
1706
1707    /// Transactional semantic constraint.
1708    pub const fn constraint(&self) -> &C {
1709        &self.constraint
1710    }
1711
1712    /// Mutable transactional semantic constraint.
1713    pub const fn constraint_mut(&mut self) -> &mut C {
1714        &mut self.constraint
1715    }
1716
1717    /// Cooperative cancellation token.
1718    pub const fn cancellation(&self) -> &GenerationCancellationToken {
1719        &self.cancellation
1720    }
1721
1722    /// Applies cancellation and publishes its terminal semantic state.
1723    pub fn cancel(&mut self) -> Result<(), SpeculativeOutputError> {
1724        if self.sequence.is_finished() {
1725            return Ok(());
1726        }
1727        let mut constraint = self.constraint.fork()?;
1728        let mut sequence = self.sequence.clone();
1729        self.cancel_candidate(&mut constraint, &mut sequence)?;
1730        self.constraint = constraint;
1731        self.sequence = sequence;
1732        Ok(())
1733    }
1734
1735    /// Installs logical state only after its matching backend boundary committed.
1736    pub fn install_committed_state(
1737        &mut self,
1738        sampler: S,
1739        constraint: C,
1740        sequence: GenerationSequence,
1741    ) {
1742        self.sampler = sampler;
1743        self.constraint = constraint;
1744        self.sequence = sequence;
1745    }
1746
1747    /// Publishes tokens only after their backend cache transaction committed.
1748    pub fn publish_committed(&mut self, tokens: &[u32]) -> Result<bool, SpeculativeOutputError> {
1749        self.observe_lifecycle(SpeculativeLifecycleStage::Publication)?;
1750        let cancellation_won = self.publisher.publish_committed(
1751            &mut self.constraint,
1752            tokens,
1753            &self.cancellation,
1754            self.sequence.is_finished(),
1755        )? || (self.cancellation.is_cancelled()
1756            && !self.sequence.is_finished());
1757        if cancellation_won {
1758            self.sequence.cancel();
1759        }
1760        Ok(cancellation_won)
1761    }
1762
1763    fn publish_candidate(
1764        &mut self,
1765        constraint: &mut C,
1766        sequence: &mut GenerationSequence,
1767        tokens: &[u32],
1768    ) -> Result<bool, SpeculativeOutputError> {
1769        self.observe_lifecycle(SpeculativeLifecycleStage::Publication)?;
1770        let cancellation_won = self.publisher.publish_committed(
1771            constraint,
1772            tokens,
1773            &self.cancellation,
1774            sequence.is_finished(),
1775        )? || (self.cancellation.is_cancelled() && !sequence.is_finished());
1776        if cancellation_won {
1777            sequence.cancel();
1778        }
1779        Ok(cancellation_won)
1780    }
1781
1782    fn cancel_candidate(
1783        &mut self,
1784        constraint: &mut C,
1785        sequence: &mut GenerationSequence,
1786    ) -> Result<(), SpeculativeOutputError> {
1787        if !sequence.is_finished() {
1788            self.observe_lifecycle(SpeculativeLifecycleStage::Cancellation)?;
1789        }
1790        if sequence.cancel() {
1791            self.publisher.publish_cancelled(constraint)?;
1792        }
1793        Ok(())
1794    }
1795
1796    /// Consumes the runtime into its backend-owned parts.
1797    pub(crate) fn into_parts(self) -> (S, GenerationSequence, C, P) {
1798        (self.sampler, self.sequence, self.constraint, self.publisher)
1799    }
1800}
1801
1802/// Error returned by portable proposal and verification drivers.
1803#[derive(Debug, thiserror::Error)]
1804#[non_exhaustive]
1805pub enum SpeculativeDriverError<E: std::error::Error + 'static> {
1806    /// Backend execution or sampling failed.
1807    #[error(transparent)]
1808    Backend(#[from] E),
1809    /// Transactional semantic output or committed publication failed.
1810    #[error(transparent)]
1811    Output(SpeculativeOutputError),
1812    /// Portable lifecycle validation failed.
1813    #[error(transparent)]
1814    Generation(GenerationError),
1815    /// Exact verification exceeded its deadline after entering a safe disposition.
1816    #[error("speculative verification completion deadline exceeded ({cancellation:?})")]
1817    CompletionDeadline {
1818        /// Cancellation or retained-orphan mechanism actually applied.
1819        cancellation: CompletionCancellationMode,
1820    },
1821    /// The backend completion cannot honor the selected timeout disposition.
1822    #[error("speculative completion does not support {cancellation:?}")]
1823    UnsupportedCompletionCancellation {
1824        /// Disposition rejected before any speculative submission.
1825        cancellation: CompletionCancellationMode,
1826    },
1827}
1828
1829/// Resolved speculative transaction ready for backend cache commit.
1830pub struct ResolvedSpeculativeRound<S, C, R> {
1831    /// Tentatively advanced sampler state.
1832    sampler: S,
1833    /// Tentatively advanced semantic state.
1834    constraint: C,
1835    /// Tentatively advanced canonical sequence.
1836    sequence: GenerationSequence,
1837    /// Tentatively advanced target randomness.
1838    target_randomness: Option<R>,
1839    /// Number of accepted proposals.
1840    accepted_proposals: usize,
1841    /// Tokens visible after cache commit.
1842    committed_tokens: Vec<u32>,
1843    /// Exact verification inputs retained by cache commit.
1844    verified_inputs: usize,
1845    /// Target bonus token, when full acceptance produced one.
1846    bonus_token: Option<u32>,
1847    /// Terminal reason after this round.
1848    finish_reason: Option<FinishReason>,
1849}
1850
1851/// Generates one assistant proposal block through opaque backend operations.
1852#[allow(clippy::too_many_arguments)]
1853pub fn propose_block<'a, E, S>(
1854    executor: &mut E,
1855    sampler: &S,
1856    state: &mut E::DraftState,
1857    first_previous: u32,
1858    count: usize,
1859    base_history: &[u32],
1860    temperature: f32,
1861    eos_token_ids: &[u32],
1862    draft_randomness: Option<&S::DraftRandomness>,
1863    context: E::Context<'a>,
1864) -> Result<Vec<SpeculativeProposal<S::Distribution>>, SpeculativeDriverError<E::Error>>
1865where
1866    E: SpeculativeExecutor + 'a,
1867    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error, Context<'a> = E::Context<'a>> + 'a,
1868{
1869    let mut branch_sampler = sampler.clone();
1870    let mut history = Vec::with_capacity(base_history.len() + count);
1871    history.extend_from_slice(base_history);
1872    let mut proposals: Vec<SpeculativeProposal<S::Distribution>> = Vec::with_capacity(count);
1873    for offset in 0..count {
1874        let previous = proposals
1875            .last()
1876            .map_or(first_previous, |proposal| proposal.token);
1877        let raw = executor.proposal_logits(state, previous, context)?;
1878        let distribution = branch_sampler.process_logits(
1879            &raw,
1880            temperature,
1881            &history,
1882            SamplingPlacement::Draft,
1883            context,
1884        )?;
1885        let mut position_state = draft_randomness
1886            .map(|root| {
1887                S::draft_randomness_at(
1888                    root,
1889                    SpeculativeDraftRandomPosition::new(base_history.len() + offset),
1890                    context,
1891                )
1892            })
1893            .transpose()?;
1894        let token = branch_sampler.sample(
1895            &distribution,
1896            temperature,
1897            position_state.as_mut(),
1898            SamplingPlacement::Draft,
1899            context,
1900        )?;
1901        proposals.push(SpeculativeProposal {
1902            token,
1903            distribution,
1904        });
1905        history.push(token);
1906        if eos_token_ids.contains(&token) || branch_sampler.prefix_is_complete(&history)? {
1907            break;
1908        }
1909    }
1910    Ok(proposals)
1911}
1912
1913/// Resolves one target verification transaction without backend-specific math.
1914#[allow(clippy::too_many_arguments)]
1915pub fn resolve_round<'a, E, S, C>(
1916    executor: &E,
1917    verification: &E::Verification,
1918    mut proposals: Vec<SpeculativeProposal<S::Distribution>>,
1919    sampler: &S,
1920    sequence: &GenerationSequence,
1921    constraint: &C,
1922    target_randomness: Option<&S::RandomState>,
1923    temperature: f32,
1924    context: E::Context<'a>,
1925) -> Result<ResolvedSpeculativeRound<S, C, S::RandomState>, SpeculativeDriverError<E::Error>>
1926where
1927    E: SpeculativeExecutor + 'a,
1928    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error, Context<'a> = E::Context<'a>> + 'a,
1929    C: SpeculativeConstraint,
1930{
1931    let mut draft_distributions = proposals
1932        .iter_mut()
1933        .map(|proposal| &mut proposal.distribution)
1934        .collect::<Vec<_>>();
1935    sampler.prepare_verification(&mut draft_distributions, temperature, context)?;
1936    let proposal_count = proposals.len();
1937    let mut sampler = sampler.clone();
1938    let mut sequence = sequence.clone();
1939    let mut constraint = constraint.fork().map_err(SpeculativeDriverError::Output)?;
1940    let mut target_randomness = target_randomness.cloned();
1941    let mut history = sequence.tokens().to_vec();
1942    let mut round =
1943        SpeculativeRound::new(proposal_count).map_err(SpeculativeDriverError::Generation)?;
1944    let mut finish_reason = None;
1945
1946    for (index, proposal) in proposals.iter().enumerate() {
1947        let raw = executor.verification_logits(verification, index, context)?;
1948        let target = sampler.process_logits(
1949            &raw,
1950            temperature,
1951            &history,
1952            SamplingPlacement::Target,
1953            context,
1954        )?;
1955        match decide_speculative_proposal(
1956            &sampler,
1957            &target,
1958            &proposal.distribution,
1959            proposal.token,
1960            temperature,
1961            target_randomness.as_mut(),
1962            context,
1963        )? {
1964            ProposalDecision::Accept => {
1965                sampler.update_sampler_state(
1966                    &target,
1967                    proposal.token,
1968                    SamplingPlacement::Target,
1969                    context,
1970                )?;
1971                history.push(proposal.token);
1972                finish_reason = commit_terminal_token(
1973                    &mut sequence,
1974                    &mut sampler,
1975                    &mut constraint,
1976                    proposal.token,
1977                )?;
1978                round
1979                    .accept(proposal.token, finish_reason.is_some())
1980                    .map_err(SpeculativeDriverError::Generation)?;
1981                if finish_reason.is_some() {
1982                    break;
1983                }
1984            }
1985            ProposalDecision::Reject(replacement) => {
1986                sampler.update_sampler_state(
1987                    &target,
1988                    replacement,
1989                    SamplingPlacement::Target,
1990                    context,
1991                )?;
1992                finish_reason = commit_terminal_token(
1993                    &mut sequence,
1994                    &mut sampler,
1995                    &mut constraint,
1996                    replacement,
1997                )?;
1998                round
1999                    .reject_with(replacement, finish_reason.is_some())
2000                    .map_err(SpeculativeDriverError::Generation)?;
2001                break;
2002            }
2003        }
2004    }
2005
2006    let mut bonus_token = None;
2007    if round.is_full_acceptance() && !sequence.is_finished() {
2008        let raw = executor.verification_logits(verification, proposal_count, context)?;
2009        let target = sampler.process_logits(
2010            &raw,
2011            temperature,
2012            &history,
2013            SamplingPlacement::Target,
2014            context,
2015        )?;
2016        let chosen = sampler.sample(
2017            &target,
2018            temperature,
2019            target_randomness.as_mut(),
2020            SamplingPlacement::Target,
2021            context,
2022        )?;
2023        sampler.update_sampler_state(&target, chosen, SamplingPlacement::Target, context)?;
2024        finish_reason =
2025            commit_terminal_token(&mut sequence, &mut sampler, &mut constraint, chosen)?;
2026        round
2027            .bonus(chosen, finish_reason.is_some())
2028            .map_err(SpeculativeDriverError::Generation)?;
2029        bonus_token = Some(chosen);
2030    }
2031    let plan = round
2032        .commit_plan()
2033        .map_err(SpeculativeDriverError::Generation)?;
2034    Ok(ResolvedSpeculativeRound {
2035        sampler,
2036        constraint,
2037        sequence,
2038        target_randomness,
2039        accepted_proposals: plan.accepted_proposals,
2040        committed_tokens: plan.committed_tokens.to_vec(),
2041        verified_inputs: plan.verified_inputs,
2042        bonus_token,
2043        finish_reason,
2044    })
2045}
2046
2047/// Submits one exact target verification and takes ownership of its resources.
2048pub fn submit_verification_transaction<'a, E, D>(
2049    executor: &mut E,
2050    cache: &mut E::Cache,
2051    last_committed_token: u32,
2052    block: SpeculativeDraftBlock<E::DraftState, D>,
2053    context: E::Context<'a>,
2054) -> Result<PendingSpeculativeVerification<E, D>, SpeculativeDriverError<E::Error>>
2055where
2056    E: SpeculativeExecutor + 'a,
2057{
2058    if block.proposals.is_empty() {
2059        return Err(SpeculativeDriverError::Generation(
2060            GenerationError::EmptyProposalBlock,
2061        ));
2062    }
2063    let mut input_tokens = Vec::with_capacity(block.proposals.len() + 1);
2064    input_tokens.push(last_committed_token);
2065    input_tokens.extend(block.proposals.iter().map(|proposal| proposal.token));
2066    let checkpoint = executor.checkpoint(cache)?;
2067    let submission = match executor.submit_verification(&input_tokens, cache, context) {
2068        Ok(submission) => submission,
2069        Err(error) => {
2070            executor.restore_checkpoint(cache, &checkpoint, context)?;
2071            return Err(error.into());
2072        }
2073    };
2074    Ok(PendingSpeculativeVerification {
2075        completion: submission.completion,
2076        verification: submission.output,
2077        checkpoint,
2078        block,
2079        optimistic: None,
2080        submitted: Instant::now(),
2081        submitted_tokens: input_tokens.len(),
2082    })
2083}
2084
2085/// Request state selected after committed output publication.
2086#[non_exhaustive]
2087pub enum SpeculativePublicationStatus<S, D> {
2088    /// Continue from canonical target state and an optional promoted block.
2089    Continue(SpeculativeContinuation<S, D>),
2090    /// Generation reached a normal terminal condition.
2091    Completed,
2092    /// Cancellation won at or after the exact commit boundary.
2093    Cancelled,
2094}
2095
2096/// Backend and portable state after exact commit and legal publication.
2097pub struct PublishedSpeculativeVerification<TargetState, DraftState, Distribution, RandomState, T> {
2098    /// Target state matching the committed backend cache.
2099    target_state: TargetState,
2100    /// Canonical target randomness after resolution.
2101    target_randomness: Option<RandomState>,
2102    /// Updated portable request telemetry.
2103    stats: SpeculativeStats,
2104    /// Backend component telemetry observed at exact completion.
2105    telemetry: T,
2106    /// Request continuation selected after publication.
2107    status: SpeculativePublicationStatus<DraftState, Distribution>,
2108}
2109
2110/// Publication result produced after a speculative verification commits.
2111pub type PublishedSpeculativeResult<E, S> = Result<
2112    PublishedSpeculativeVerification<
2113        <E as SpeculativeExecutor>::TargetState,
2114        <E as SpeculativeExecutor>::DraftState,
2115        <S as SpeculativeSampling>::Distribution,
2116        <S as SpeculativeSampling>::RandomState,
2117        <E as SpeculativeExecutor>::Telemetry,
2118    >,
2119    SpeculativeDriverError<<E as SpeculativeExecutor>::Error>,
2120>;
2121
2122/// Waits, resolves, commits, and only then publishes one verification.
2123///
2124/// Portable sampler, sequence, constraint, telemetry, and optimistic state are
2125/// advanced transactionally. A backend cache-commit failure leaves the
2126/// canonical output runtime unchanged and publishes nothing.
2127#[allow(clippy::too_many_arguments)]
2128pub fn resolve_commit_and_publish<'a, E, S, C, P>(
2129    executor: &mut E,
2130    cache: &mut E::Cache,
2131    pending: PendingSpeculativeVerification<E, S::Distribution>,
2132    runtime: &mut SpeculativeOutputRuntime<S, C, P>,
2133    target_randomness: Option<&S::RandomState>,
2134    temperature: f32,
2135    mut stats: SpeculativeStats,
2136    options: SpeculativeSchedulerOptions,
2137    context: E::Context<'a>,
2138) -> PublishedSpeculativeResult<E, S>
2139where
2140    E: SpeculativeExecutor + 'a,
2141    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error, Context<'a> = E::Context<'a>> + 'a,
2142    C: SpeculativeConstraint,
2143    P: SpeculativePublisher<C>,
2144{
2145    let PendingSpeculativeVerification {
2146        completion,
2147        mut verification,
2148        checkpoint,
2149        block,
2150        optimistic,
2151        submitted,
2152        submitted_tokens: _,
2153    } = pending;
2154    let completion_wait = match options.completion_wait() {
2155        Ok(wait) => wait,
2156        Err(error) => {
2157            let emergency_wait = BoundedCompletionWait::new(
2158                Duration::from_nanos(1),
2159                CompletionCancellationMode::QuarantineUntilComplete,
2160            )
2161            .expect("emergency completion disposition is positive");
2162            let disposition = completion.wait_bounded(emergency_wait);
2163            executor.restore_checkpoint(cache, &checkpoint, context)?;
2164            disposition?;
2165            return Err(SpeculativeDriverError::Generation(error));
2166        }
2167    };
2168    if let Err(error) = runtime.observe_lifecycle(SpeculativeLifecycleStage::Completion) {
2169        let disposition = completion.wait_bounded(completion_wait);
2170        executor.restore_checkpoint(cache, &checkpoint, context)?;
2171        disposition?;
2172        return Err(SpeculativeDriverError::Output(error));
2173    }
2174    match completion.is_complete() {
2175        Ok(true) => {
2176            if let Err(error) = completion.wait() {
2177                drop(completion);
2178                executor.restore_checkpoint(cache, &checkpoint, context)?;
2179                return Err(error.into());
2180            }
2181        }
2182        Ok(false) => match completion.wait_bounded(completion_wait) {
2183            Ok(BoundedCompletionOutcome::Completed) => {}
2184            Ok(BoundedCompletionOutcome::DeadlineExceeded { cancellation }) => {
2185                executor.restore_checkpoint(cache, &checkpoint, context)?;
2186                return Err(SpeculativeDriverError::CompletionDeadline { cancellation });
2187            }
2188            Err(error) => {
2189                executor.restore_checkpoint(cache, &checkpoint, context)?;
2190                return Err(error.into());
2191            }
2192        },
2193        Err(error) => {
2194            drop(completion);
2195            executor.restore_checkpoint(cache, &checkpoint, context)?;
2196            return Err(error.into());
2197        }
2198    }
2199    let telemetry = match executor.take_verification_telemetry(&mut verification) {
2200        Ok(telemetry) => telemetry,
2201        Err(error) => {
2202            executor.restore_checkpoint(cache, &checkpoint, context)?;
2203            return Err(error.into());
2204        }
2205    };
2206    stats.verification_in_flight_time += submitted.elapsed();
2207    if let Err(error) = runtime.observe_lifecycle(SpeculativeLifecycleStage::Observation) {
2208        executor.restore_checkpoint(cache, &checkpoint, context)?;
2209        return Err(SpeculativeDriverError::Output(error));
2210    }
2211    let mut canonical_proposal_prefix = runtime.sequence().tokens().to_vec();
2212    canonical_proposal_prefix.extend(block.proposals.iter().map(|proposal| proposal.token));
2213    let mut resolved = match resolve_round::<E, S, C>(
2214        executor,
2215        &verification,
2216        block.proposals,
2217        runtime.sampler(),
2218        runtime.sequence(),
2219        runtime.constraint(),
2220        target_randomness,
2221        temperature,
2222        context,
2223    ) {
2224        Ok(resolved) => resolved,
2225        Err(error) => {
2226            executor.restore_checkpoint(cache, &checkpoint, context)?;
2227            return Err(error);
2228        }
2229    };
2230    let accepted = resolved.accepted_proposals;
2231    let committed_tokens = resolved.committed_tokens;
2232    let terminal = resolved.finish_reason;
2233    let mut continuation = match resolve_optimistic_branch(
2234        optimistic,
2235        &canonical_proposal_prefix,
2236        resolved.bonus_token,
2237        terminal.is_some(),
2238        &mut stats,
2239    ) {
2240        Ok(continuation) => continuation,
2241        Err(error) => {
2242            executor.restore_checkpoint(cache, &checkpoint, context)?;
2243            return Err(SpeculativeDriverError::Generation(error));
2244        }
2245    };
2246    stats.accepted_tokens += accepted;
2247    stats.accept_lens.push(accepted);
2248    stats.rounds += 1;
2249    if let Err(error) = runtime.observe_lifecycle(SpeculativeLifecycleStage::CachePersistence) {
2250        executor.restore_checkpoint(cache, &checkpoint, context)?;
2251        return Err(SpeculativeDriverError::Output(error));
2252    }
2253    let commit = match executor.commit_verification(
2254        verification,
2255        block.state,
2256        cache,
2257        &checkpoint,
2258        resolved.verified_inputs,
2259        context,
2260    ) {
2261        Ok(commit) => commit,
2262        Err(error) => {
2263            executor.restore_checkpoint(cache, &checkpoint, context)?;
2264            return Err(error.into());
2265        }
2266    };
2267    stats.target_tokens += commit.replayed_tokens;
2268    stats.emitted_tokens += committed_tokens.len();
2269    let target_randomness = resolved.target_randomness;
2270    let cancelled = match runtime.publish_candidate(
2271        &mut resolved.constraint,
2272        &mut resolved.sequence,
2273        &committed_tokens,
2274    ) {
2275        Ok(cancelled) => cancelled,
2276        Err(error) => {
2277            executor.restore_checkpoint(cache, &checkpoint, context)?;
2278            return Err(SpeculativeDriverError::Output(error));
2279        }
2280    };
2281    runtime.install_committed_state(resolved.sampler, resolved.constraint, resolved.sequence);
2282    let status = if cancelled {
2283        discard_continuation(&mut stats, continuation);
2284        SpeculativePublicationStatus::Cancelled
2285    } else if terminal.is_some() {
2286        discard_continuation(&mut stats, continuation);
2287        SpeculativePublicationStatus::Completed
2288    } else {
2289        stats.update_adaptive_lookahead(options);
2290        SpeculativePublicationStatus::Continue(std::mem::replace(
2291            &mut continuation,
2292            SpeculativeContinuation::None,
2293        ))
2294    };
2295    Ok(PublishedSpeculativeVerification {
2296        target_state: commit.state,
2297        target_randomness,
2298        stats,
2299        telemetry,
2300        status,
2301    })
2302}
2303
2304/// Resolves an exact retained verification solely to reach a safe cancellation boundary.
2305#[allow(clippy::too_many_arguments)]
2306pub fn cancel_pending_verification<'a, E, S, C, P>(
2307    executor: &mut E,
2308    cache: &mut E::Cache,
2309    pending: PendingSpeculativeVerification<E, S::Distribution>,
2310    runtime: &mut SpeculativeOutputRuntime<S, C, P>,
2311    mut stats: SpeculativeStats,
2312    completion_wait: BoundedCompletionWait,
2313    context: E::Context<'a>,
2314) -> Result<(SpeculativeStats, E::Telemetry), SpeculativeDriverError<E::Error>>
2315where
2316    E: SpeculativeExecutor + 'a,
2317    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error, Context<'a> = E::Context<'a>> + 'a,
2318    C: SpeculativeConstraint,
2319    P: SpeculativePublisher<C>,
2320{
2321    let PendingSpeculativeVerification {
2322        completion,
2323        mut verification,
2324        checkpoint,
2325        block,
2326        optimistic,
2327        submitted,
2328        submitted_tokens: _,
2329    } = pending;
2330    if let Err(error) = runtime.observe_lifecycle(SpeculativeLifecycleStage::Completion) {
2331        let disposition = completion.wait_bounded(completion_wait);
2332        executor.restore_checkpoint(cache, &checkpoint, context)?;
2333        disposition?;
2334        return Err(SpeculativeDriverError::Output(error));
2335    }
2336    match completion.is_complete() {
2337        Ok(true) => {
2338            if let Err(error) = completion.wait() {
2339                drop(completion);
2340                executor.restore_checkpoint(cache, &checkpoint, context)?;
2341                return Err(error.into());
2342            }
2343        }
2344        Ok(false) => match completion.wait_bounded(completion_wait) {
2345            Ok(BoundedCompletionOutcome::Completed) => {}
2346            Ok(BoundedCompletionOutcome::DeadlineExceeded { cancellation }) => {
2347                executor.restore_checkpoint(cache, &checkpoint, context)?;
2348                return Err(SpeculativeDriverError::CompletionDeadline { cancellation });
2349            }
2350            Err(error) => {
2351                executor.restore_checkpoint(cache, &checkpoint, context)?;
2352                return Err(error.into());
2353            }
2354        },
2355        Err(error) => {
2356            drop(completion);
2357            executor.restore_checkpoint(cache, &checkpoint, context)?;
2358            return Err(error.into());
2359        }
2360    }
2361    let telemetry = match executor.take_verification_telemetry(&mut verification) {
2362        Ok(telemetry) => telemetry,
2363        Err(error) => {
2364            executor.restore_checkpoint(cache, &checkpoint, context)?;
2365            return Err(error.into());
2366        }
2367    };
2368    stats.verification_in_flight_time += submitted.elapsed();
2369    if let Err(error) = runtime.observe_lifecycle(SpeculativeLifecycleStage::Observation) {
2370        executor.restore_checkpoint(cache, &checkpoint, context)?;
2371        return Err(SpeculativeDriverError::Output(error));
2372    }
2373    discard_branch(&mut stats, optimistic);
2374    if let Err(error) = runtime.observe_lifecycle(SpeculativeLifecycleStage::CachePersistence) {
2375        executor.restore_checkpoint(cache, &checkpoint, context)?;
2376        return Err(SpeculativeDriverError::Output(error));
2377    }
2378    let mut constraint = match runtime.constraint().fork() {
2379        Ok(constraint) => constraint,
2380        Err(error) => {
2381            executor.restore_checkpoint(cache, &checkpoint, context)?;
2382            return Err(SpeculativeDriverError::Output(error));
2383        }
2384    };
2385    let mut sequence = runtime.sequence().clone();
2386    let commit = match executor.commit_verification(
2387        verification,
2388        block.state,
2389        cache,
2390        &checkpoint,
2391        1,
2392        context,
2393    ) {
2394        Ok(commit) => commit,
2395        Err(error) => {
2396            executor.restore_checkpoint(cache, &checkpoint, context)?;
2397            return Err(error.into());
2398        }
2399    };
2400    stats.target_tokens += commit.replayed_tokens;
2401    if let Err(error) = runtime.cancel_candidate(&mut constraint, &mut sequence) {
2402        executor.restore_checkpoint(cache, &checkpoint, context)?;
2403        return Err(SpeculativeDriverError::Output(error));
2404    }
2405    runtime.install_committed_state(runtime.sampler().clone(), constraint, sequence);
2406    Ok((stats, telemetry))
2407}
2408
2409/// Resolves, promotes, or discards one optimistic branch and updates telemetry.
2410pub fn resolve_optimistic_branch<S, D>(
2411    branch: Option<SpeculativeOptimisticBranch<S, D>>,
2412    canonical_prefix: &[u32],
2413    bonus: Option<u32>,
2414    terminal: bool,
2415    stats: &mut SpeculativeStats,
2416) -> Result<SpeculativeContinuation<S, D>, GenerationError> {
2417    let Some(branch) = branch else {
2418        return Ok(SpeculativeContinuation::None);
2419    };
2420    let Some(bonus) = bonus else {
2421        discard_branch(stats, Some(branch));
2422        return Ok(SpeculativeContinuation::None);
2423    };
2424    let optimistic_tokens = branch
2425        .block
2426        .proposals
2427        .iter()
2428        .map(|proposal| proposal.token)
2429        .collect::<Vec<_>>();
2430    let decision = crate::generation::resolve_optimistic_reuse(
2431        &branch.assumed_prefix,
2432        canonical_prefix,
2433        &optimistic_tokens,
2434        bonus,
2435        terminal,
2436    )?;
2437    stats.optimistic_target_bonus_tokens += 1;
2438    if decision == crate::generation::OptimisticReuseDecision::DiscardTerminal {
2439        discard_branch(stats, Some(branch));
2440        return Ok(SpeculativeContinuation::None);
2441    }
2442    let drafted = branch.block.proposals.len();
2443    let SpeculativeDraftBlock { state, proposals } = branch.block;
2444    let mut proposals = proposals.into_iter();
2445    let _matched_or_discarded = proposals
2446        .next()
2447        .expect("validated optimistic branch is non-empty");
2448    Ok(match decision {
2449        crate::generation::OptimisticReuseDecision::DiscardMismatch => {
2450            stats.optimistic_bonus_mismatches += 1;
2451            stats.discarded_optimistic_tokens += drafted;
2452            stats.discarded_optimistic_blocks += 1;
2453            SpeculativeContinuation::None
2454        }
2455        crate::generation::OptimisticReuseDecision::MatchedConsumed => {
2456            stats.optimistic_bonus_matches += 1;
2457            stats.consumed_optimistic_tokens += 1;
2458            SpeculativeContinuation::None
2459        }
2460        crate::generation::OptimisticReuseDecision::MatchedRetained => {
2461            stats.optimistic_bonus_matches += 1;
2462            stats.consumed_optimistic_tokens += 1;
2463            let proposals = proposals.collect::<Vec<_>>();
2464            stats.draft_tokens += proposals.len();
2465            stats.reused_optimistic_tokens += proposals.len();
2466            stats.reused_optimistic_blocks += 1;
2467            SpeculativeContinuation::Promoted(SpeculativeDraftBlock { state, proposals })
2468        }
2469        crate::generation::OptimisticReuseDecision::DiscardTerminal => {
2470            unreachable!("terminal decision handled before branch destruction")
2471        }
2472    })
2473}
2474
2475fn discard_branch<S, D>(
2476    stats: &mut SpeculativeStats,
2477    branch: Option<SpeculativeOptimisticBranch<S, D>>,
2478) {
2479    if let Some(branch) = branch {
2480        stats.discarded_optimistic_tokens += branch.block.proposals.len();
2481        stats.discarded_optimistic_blocks += 1;
2482    }
2483}
2484
2485fn discard_continuation<S, D>(
2486    stats: &mut SpeculativeStats,
2487    continuation: SpeculativeContinuation<S, D>,
2488) {
2489    if let SpeculativeContinuation::Promoted(block) = continuation {
2490        stats.discarded_optimistic_tokens += block.proposals.len();
2491        stats.discarded_optimistic_blocks += 1;
2492        stats.draft_tokens = stats.draft_tokens.saturating_sub(block.proposals.len());
2493        stats.reused_optimistic_tokens = stats
2494            .reused_optimistic_tokens
2495            .saturating_sub(block.proposals.len());
2496        stats.reused_optimistic_blocks = stats.reused_optimistic_blocks.saturating_sub(1);
2497    }
2498}
2499
2500fn commit_terminal_token<S, C>(
2501    sequence: &mut GenerationSequence,
2502    sampler: &mut S,
2503    constraint: &mut C,
2504    token: u32,
2505) -> Result<Option<FinishReason>, SpeculativeDriverError<S::Error>>
2506where
2507    S: SpeculativeSampling,
2508    C: SpeculativeConstraint,
2509{
2510    let stop_matched = constraint
2511        .push_token(token)
2512        .map_err(SpeculativeDriverError::Output)?;
2513    let grammar_complete = if stop_matched {
2514        false
2515    } else {
2516        sampler.grammar_is_complete()?
2517    };
2518    let reason = sequence
2519        .commit(
2520            token,
2521            TokenTerminalSignals {
2522                stop_sequence: stop_matched,
2523                grammar_complete,
2524            },
2525        )
2526        .map_err(SpeculativeDriverError::Generation)?
2527        .finish_reason;
2528    if let Some(reason) = reason {
2529        constraint
2530            .finish(reason)
2531            .map_err(SpeculativeDriverError::Output)?;
2532    }
2533    Ok(reason)
2534}
2535
2536/// One backend-neutral speculative request with opaque execution resources.
2537///
2538/// The request owns every resource slot whose presence is constrained by the
2539/// lifecycle: target state, canonical draft block, exact in-flight
2540/// verification, randomness, output state, and cache access. Backends choose
2541/// the concrete associated types but cannot maintain a parallel request state.
2542pub struct SpeculativeRequest<'cache, E, S, C, P>
2543where
2544    E: SpeculativeExecutor,
2545    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
2546    C: SpeculativeConstraint,
2547    P: SpeculativePublisher<C>,
2548{
2549    id: SpeculativeRequestId,
2550    cache: &'cache mut E::Cache,
2551    config: SpeculativeConfig,
2552    runtime: SpeculativeOutputRuntime<S, C, P>,
2553    target_randomness: Option<S::RandomState>,
2554    draft_randomness: Option<S::DraftRandomness>,
2555    stats: SpeculativeStats,
2556    started: Instant,
2557    target_state: Option<E::TargetState>,
2558    block: Option<SpeculativeDraftBlock<E::DraftState, S::Distribution>>,
2559    pending: Option<PendingSpeculativeVerification<E, S::Distribution>>,
2560    lifecycle: SpeculativeRequestLifecycle,
2561}
2562
2563impl<'cache, E, S, C, P> SpeculativeRequest<'cache, E, S, C, P>
2564where
2565    E: SpeculativeExecutor,
2566    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
2567    C: SpeculativeConstraint,
2568    P: SpeculativePublisher<C>,
2569{
2570    /// Stable insertion-order identity.
2571    pub const fn id(&self) -> SpeculativeRequestId {
2572        self.id
2573    }
2574
2575    /// Current validated lifecycle status.
2576    pub const fn status(&self) -> SpeculativeRequestStatus {
2577        self.lifecycle.status()
2578    }
2579
2580    /// Portable request statistics.
2581    pub const fn stats(&self) -> &SpeculativeStats {
2582        &self.stats
2583    }
2584
2585    /// Canonical committed token sequence.
2586    pub const fn sequence(&self) -> &GenerationSequence {
2587        self.runtime.sequence()
2588    }
2589
2590    /// Canonical sampler state.
2591    pub const fn sampler(&self) -> &S {
2592        self.runtime.sampler()
2593    }
2594
2595    /// Canonical proposal block awaiting submission, when present.
2596    pub const fn block(&self) -> Option<&SpeculativeDraftBlock<E::DraftState, S::Distribution>> {
2597        self.block.as_ref()
2598    }
2599
2600    /// Whether an exact target verification remains retained.
2601    pub const fn has_pending_verification(&self) -> bool {
2602        self.pending.is_some()
2603    }
2604
2605    fn transition(
2606        &mut self,
2607        next: SpeculativeRequestStatus,
2608    ) -> Result<(), SpeculativeDriverError<E::Error>> {
2609        self.lifecycle
2610            .transition(next)
2611            .map_err(SpeculativeDriverError::Generation)
2612    }
2613
2614    fn request_cancellation(&mut self) -> Result<(), SpeculativeDriverError<E::Error>> {
2615        let lifecycle = self.lifecycle.clone();
2616        match self
2617            .lifecycle
2618            .request_cancellation(self.pending.is_some())
2619            .map_err(SpeculativeDriverError::Generation)?
2620        {
2621            SpeculativeCancellationDisposition::AlreadyTerminal
2622            | SpeculativeCancellationDisposition::Deferred => {}
2623            SpeculativeCancellationDisposition::CancelNow => {
2624                if let Err(error) = self.runtime.cancel() {
2625                    self.lifecycle = lifecycle;
2626                    return Err(SpeculativeDriverError::Output(error));
2627                }
2628                self.block = None;
2629                self.stats.elapsed = self.started.elapsed();
2630            }
2631        }
2632        Ok(())
2633    }
2634
2635    fn candidate<'context>(
2636        &self,
2637        executor: &E,
2638        optimistic_execution_available: bool,
2639        completion_wait: BoundedCompletionWait,
2640    ) -> Result<SpeculativeCandidate, SpeculativeDriverError<E::Error>>
2641    where
2642        E: 'context,
2643        S: SpeculativeSampling<
2644                Logits = E::Logits,
2645                Error = E::Error,
2646                Context<'context> = E::Context<'context>,
2647            > + 'context,
2648    {
2649        let (verification_complete, verification_deadline_expired) =
2650            if let Some(pending) = self.pending.as_ref() {
2651                (
2652                    pending.is_complete()?,
2653                    pending.submitted.elapsed() >= completion_wait.timeout(),
2654                )
2655            } else {
2656                (false, false)
2657            };
2658        let optimistic_eligible = if self.lifecycle.status()
2659            != SpeculativeRequestStatus::TargetVerificationInFlight
2660            || !optimistic_execution_available
2661        {
2662            false
2663        } else {
2664            let pending = self
2665                .pending
2666                .as_ref()
2667                .expect("in-flight request retains its verification transaction");
2668            let block = pending.block();
2669            let assumed_len = self.runtime.sequence().tokens().len() + block.proposals.len();
2670            let mut assumed_prefix = Vec::with_capacity(assumed_len);
2671            assumed_prefix.extend_from_slice(self.runtime.sequence().tokens());
2672            assumed_prefix.extend(block.proposals.iter().map(|proposal| proposal.token));
2673            executor.supports_exact_optimistic_promotion()
2674                && self.runtime.sampler().supports_exact_optimistic_promotion()
2675                && !self.stats.adaptive_lookahead_disabled
2676                && !block.proposals.is_empty()
2677                && !self.runtime.sampler().prefix_is_complete(&assumed_prefix)?
2678                && !block
2679                    .proposals
2680                    .last()
2681                    .is_some_and(|proposal| self.config.eos_token_ids.contains(&proposal.token))
2682                && self.config.max_tokens.saturating_sub(assumed_len) > 1
2683        };
2684        Ok(SpeculativeCandidate {
2685            status: self.lifecycle.status(),
2686            optimistic_eligible,
2687            verification_complete,
2688            verification_deadline_expired,
2689        })
2690    }
2691
2692    fn draft_committed<'context>(
2693        &mut self,
2694        executor: &mut E,
2695        context: E::Context<'context>,
2696    ) -> Result<bool, SpeculativeDriverError<E::Error>>
2697    where
2698        E: 'context,
2699        S: SpeculativeSampling<
2700                Logits = E::Logits,
2701                Error = E::Error,
2702                Context<'context> = E::Context<'context>,
2703            > + 'context,
2704    {
2705        self.runtime
2706            .observe_lifecycle(SpeculativeLifecycleStage::Execution)
2707            .map_err(SpeculativeDriverError::Output)?;
2708        let target_count = self
2709            .config
2710            .max_draft_tokens
2711            .min(executor.max_proposals())
2712            .min(
2713                self.config
2714                    .max_tokens
2715                    .saturating_sub(self.runtime.sequence().tokens().len()),
2716            );
2717        if target_count == 0 {
2718            self.transition(SpeculativeRequestStatus::Completed)?;
2719            self.stats.elapsed = self.started.elapsed();
2720            return Ok(false);
2721        }
2722
2723        let mut block = if let Some(block) = self.block.take() {
2724            block
2725        } else {
2726            let last = *self
2727                .runtime
2728                .sequence()
2729                .tokens()
2730                .last()
2731                .expect("prefill emitted a token");
2732            let target_state = self
2733                .target_state
2734                .as_ref()
2735                .expect("ready request has target state");
2736            SpeculativeDraftBlock {
2737                state: executor.begin_proposal(target_state, last, target_count, context)?,
2738                proposals: Vec::new(),
2739            }
2740        };
2741        if block.proposals.len() > target_count {
2742            return Err(SpeculativeDriverError::Generation(
2743                GenerationError::ProposalCapacityExceeded {
2744                    proposed: block.proposals.len(),
2745                    capacity: target_count,
2746                },
2747            ));
2748        }
2749        let additional = if block
2750            .proposals
2751            .last()
2752            .is_some_and(|proposal| self.config.eos_token_ids.contains(&proposal.token))
2753        {
2754            0
2755        } else {
2756            target_count - block.proposals.len()
2757        };
2758        if additional > 0 {
2759            let mut history =
2760                Vec::with_capacity(self.runtime.sequence().tokens().len() + block.proposals.len());
2761            history.extend_from_slice(self.runtime.sequence().tokens());
2762            history.extend(block.proposals.iter().map(|proposal| proposal.token));
2763            let previous = block.proposals.last().map_or_else(
2764                || {
2765                    *self
2766                        .runtime
2767                        .sequence()
2768                        .tokens()
2769                        .last()
2770                        .expect("prefill emitted a token")
2771                },
2772                |proposal| proposal.token,
2773            );
2774            let proposals = propose_block(
2775                executor,
2776                self.runtime.sampler(),
2777                &mut block.state,
2778                previous,
2779                additional,
2780                &history,
2781                self.config.temperature,
2782                &self.config.eos_token_ids,
2783                self.draft_randomness.as_ref(),
2784                context,
2785            )?;
2786            self.stats.draft_tokens += proposals.len();
2787            block.proposals.extend(proposals);
2788        }
2789        executor.take_telemetry()?.record(&mut self.stats);
2790        self.block = Some(block);
2791        self.transition(SpeculativeRequestStatus::ReadyToSubmitVerification)?;
2792        Ok(additional > 0)
2793    }
2794
2795    fn submit_verification<'context>(
2796        &mut self,
2797        executor: &mut E,
2798        context: E::Context<'context>,
2799    ) -> Result<(), SpeculativeDriverError<E::Error>>
2800    where
2801        E: 'context,
2802        S: SpeculativeSampling<
2803                Logits = E::Logits,
2804                Error = E::Error,
2805                Context<'context> = E::Context<'context>,
2806            > + 'context,
2807    {
2808        self.runtime
2809            .observe_lifecycle(SpeculativeLifecycleStage::Execution)
2810            .map_err(SpeculativeDriverError::Output)?;
2811        let block = self
2812            .block
2813            .take()
2814            .expect("verification-ready request has a draft block");
2815        let last = *self
2816            .runtime
2817            .sequence()
2818            .tokens()
2819            .last()
2820            .expect("prefill emitted a token");
2821        let pending = submit_verification_transaction(executor, self.cache, last, block, context)?;
2822        self.stats.target_tokens += pending.submitted_tokens();
2823        self.pending = Some(pending);
2824        self.transition(SpeculativeRequestStatus::TargetVerificationInFlight)
2825    }
2826
2827    fn draft_optimistic<'context>(
2828        &mut self,
2829        executor: &mut E,
2830        context: E::Context<'context>,
2831    ) -> Result<(), SpeculativeDriverError<E::Error>>
2832    where
2833        E: 'context,
2834        S: SpeculativeSampling<
2835                Logits = E::Logits,
2836                Error = E::Error,
2837                Context<'context> = E::Context<'context>,
2838            > + 'context,
2839    {
2840        self.runtime
2841            .observe_lifecycle(SpeculativeLifecycleStage::Execution)
2842            .map_err(SpeculativeDriverError::Output)?;
2843        let started = Instant::now();
2844        self.transition(SpeculativeRequestStatus::OptimisticDraftRunning)?;
2845        let pending = self
2846            .pending
2847            .as_mut()
2848            .expect("optimistic request has an in-flight verification");
2849        let block = pending.block();
2850        let assumed_len = self.runtime.sequence().tokens().len() + block.proposals.len();
2851        let count = self
2852            .config
2853            .max_draft_tokens
2854            .min(executor.max_proposals())
2855            .min(self.config.max_tokens.saturating_sub(assumed_len));
2856        let mut state = block.state.clone();
2857        let last = block
2858            .proposals
2859            .last()
2860            .expect("optimistic block has an assumed token")
2861            .token;
2862        let mut history = Vec::with_capacity(assumed_len);
2863        history.extend_from_slice(self.runtime.sequence().tokens());
2864        history.extend(block.proposals.iter().map(|proposal| proposal.token));
2865        let proposals = propose_block(
2866            executor,
2867            self.runtime.sampler(),
2868            &mut state,
2869            last,
2870            count,
2871            &history,
2872            self.config.temperature,
2873            &self.config.eos_token_ids,
2874            self.draft_randomness.as_ref(),
2875            context,
2876        )?;
2877        self.stats.optimistic_draft_tokens += proposals.len();
2878        self.stats.optimistic_draft_blocks += 1;
2879        self.stats.optimistic_draft_time += started.elapsed();
2880        pending
2881            .set_optimistic_branch(SpeculativeOptimisticBranch {
2882                block: SpeculativeDraftBlock { state, proposals },
2883                assumed_prefix: history,
2884            })
2885            .map_err(SpeculativeDriverError::Generation)?;
2886        self.transition(SpeculativeRequestStatus::OptimisticDraftReady)
2887    }
2888
2889    fn resolve_verification<'context>(
2890        &mut self,
2891        executor: &mut E,
2892        options: SpeculativeSchedulerOptions,
2893        context: E::Context<'context>,
2894    ) -> Result<(), SpeculativeDriverError<E::Error>>
2895    where
2896        E: 'context,
2897        S: SpeculativeSampling<
2898                Logits = E::Logits,
2899                Error = E::Error,
2900                Context<'context> = E::Context<'context>,
2901            > + 'context,
2902    {
2903        self.transition(SpeculativeRequestStatus::VerificationResolution)?;
2904        let pending = self
2905            .pending
2906            .take()
2907            .expect("resolving request has an in-flight verification");
2908        if self.lifecycle.cancellation_pending() || self.runtime.cancellation().is_cancelled() {
2909            let (mut stats, telemetry) = cancel_pending_verification(
2910                executor,
2911                self.cache,
2912                pending,
2913                &mut self.runtime,
2914                self.stats.clone(),
2915                options
2916                    .completion_wait()
2917                    .map_err(SpeculativeDriverError::Generation)?,
2918                context,
2919            )?;
2920            telemetry.record(&mut stats);
2921            self.stats = stats;
2922            self.transition(SpeculativeRequestStatus::Cancelled)?;
2923            self.stats.elapsed = self.started.elapsed();
2924            return Ok(());
2925        }
2926        let mut published = resolve_commit_and_publish(
2927            executor,
2928            self.cache,
2929            pending,
2930            &mut self.runtime,
2931            self.target_randomness.as_ref(),
2932            self.config.temperature,
2933            self.stats.clone(),
2934            options,
2935            context,
2936        )?;
2937        published.telemetry.record(&mut published.stats);
2938        self.target_state = Some(published.target_state);
2939        self.target_randomness = published.target_randomness;
2940        self.stats = published.stats;
2941        match published.status {
2942            SpeculativePublicationStatus::Continue(continuation) => {
2943                self.block = continuation.into_block();
2944                self.transition(SpeculativeRequestStatus::ReadyToDraft)?;
2945            }
2946            SpeculativePublicationStatus::Completed => {
2947                self.transition(SpeculativeRequestStatus::Completed)?;
2948                self.stats.elapsed = self.started.elapsed();
2949            }
2950            SpeculativePublicationStatus::Cancelled => {
2951                self.transition(SpeculativeRequestStatus::Cancelled)?;
2952                self.stats.elapsed = self.started.elapsed();
2953            }
2954        }
2955        Ok(())
2956    }
2957}
2958
2959/// One completed request returned in stable submission order.
2960pub struct CompletedSpeculativeRequest<S> {
2961    /// Stable request identity.
2962    id: SpeculativeRequestId,
2963    /// Canonical generated token sequence.
2964    token_ids: Vec<u32>,
2965    /// Portable request telemetry.
2966    stats: SpeculativeStats,
2967    /// Final backend sampling state.
2968    sampler: S,
2969    /// Terminal reason selected by the canonical sequence.
2970    finish_reason: Option<FinishReason>,
2971    /// Terminal lifecycle status.
2972    status: SpeculativeRequestStatus,
2973}
2974
2975impl<S> CompletedSpeculativeRequest<S> {
2976    /// Stable request identity.
2977    pub const fn id(&self) -> SpeculativeRequestId {
2978        self.id
2979    }
2980    /// Canonical emitted token ids.
2981    pub fn token_ids(&self) -> &[u32] {
2982        &self.token_ids
2983    }
2984    /// Portable request telemetry.
2985    pub const fn stats(&self) -> &SpeculativeStats {
2986        &self.stats
2987    }
2988    /// Final sampling state.
2989    pub const fn sampler(&self) -> &S {
2990        &self.sampler
2991    }
2992    /// Terminal reason, when completed normally.
2993    pub const fn finish_reason(&self) -> Option<FinishReason> {
2994        self.finish_reason
2995    }
2996    /// Terminal request status.
2997    pub const fn status(&self) -> SpeculativeRequestStatus {
2998        self.status
2999    }
3000    /// Consumes the request into a named handoff artifact.
3001    pub fn into_artifact(self) -> CompletedSpeculativeRequestArtifact<S> {
3002        CompletedSpeculativeRequestArtifact {
3003            id: self.id,
3004            token_ids: self.token_ids,
3005            stats: self.stats,
3006            sampler: self.sampler,
3007            finish_reason: self.finish_reason,
3008            status: self.status,
3009        }
3010    }
3011}
3012
3013/// Named consuming artifact for adapting one completed speculative request.
3014pub struct CompletedSpeculativeRequestArtifact<S> {
3015    id: SpeculativeRequestId,
3016    token_ids: Vec<u32>,
3017    stats: SpeculativeStats,
3018    sampler: S,
3019    finish_reason: Option<FinishReason>,
3020    status: SpeculativeRequestStatus,
3021}
3022
3023impl<S> CompletedSpeculativeRequestArtifact<S> {
3024    /// Stable request identity.
3025    pub const fn id(&self) -> SpeculativeRequestId {
3026        self.id
3027    }
3028    /// Takes canonical token ids.
3029    pub fn take_token_ids(&mut self) -> Vec<u32> {
3030        std::mem::take(&mut self.token_ids)
3031    }
3032    /// Takes request telemetry.
3033    pub fn take_stats(&mut self) -> SpeculativeStats {
3034        std::mem::take(&mut self.stats)
3035    }
3036    /// Consumes the artifact into its final sampler.
3037    pub fn into_sampler(self) -> S {
3038        self.sampler
3039    }
3040    /// Terminal finish reason.
3041    pub const fn finish_reason(&self) -> Option<FinishReason> {
3042        self.finish_reason
3043    }
3044    /// Terminal lifecycle status.
3045    pub const fn status(&self) -> SpeculativeRequestStatus {
3046        self.status
3047    }
3048}
3049
3050/// Completed request table and aggregate fair-scheduler telemetry.
3051pub struct CompletedSpeculativeSchedule<S> {
3052    /// Requests in stable submission order.
3053    requests: Vec<CompletedSpeculativeRequest<S>>,
3054    /// Aggregate scheduler telemetry.
3055    scheduler: SpeculativeSchedulerStats,
3056}
3057
3058impl<S> CompletedSpeculativeSchedule<S> {
3059    /// Consumes the schedule into request results.
3060    pub fn into_requests(self) -> Vec<CompletedSpeculativeRequest<S>> {
3061        self.requests
3062    }
3063    /// Takes completed requests while retaining access to scheduler telemetry.
3064    pub fn take_requests(&mut self) -> Vec<CompletedSpeculativeRequest<S>> {
3065        std::mem::take(&mut self.requests)
3066    }
3067    /// Takes aggregate scheduler telemetry.
3068    pub fn take_scheduler(&mut self) -> SpeculativeSchedulerStats {
3069        std::mem::take(&mut self.scheduler)
3070    }
3071    /// Aggregate scheduler telemetry.
3072    pub const fn scheduler(&self) -> &SpeculativeSchedulerStats {
3073        &self.scheduler
3074    }
3075}
3076
3077/// Canonical table and action coordinator for speculative requests.
3078pub struct SpeculativeRequestTable<'cache, E, S, C, P>
3079where
3080    E: SpeculativeExecutor,
3081    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
3082    C: SpeculativeConstraint,
3083    P: SpeculativePublisher<C>,
3084{
3085    schedule: SpeculativeSchedule,
3086    requests: Vec<SpeculativeRequest<'cache, E, S, C, P>>,
3087    stats: SpeculativeSchedulerStats,
3088}
3089
3090impl<'cache, E, S, C, P> SpeculativeRequestTable<'cache, E, S, C, P>
3091where
3092    E: SpeculativeExecutor,
3093    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
3094    C: SpeculativeConstraint,
3095    P: SpeculativePublisher<C>,
3096{
3097    /// Creates an empty validated request table.
3098    pub fn new(
3099        options: SpeculativeSchedulerOptions,
3100        topology: SpeculativeExecutionTopology,
3101    ) -> Result<Self, GenerationError> {
3102        Ok(Self {
3103            schedule: SpeculativeSchedule::new(options)?,
3104            requests: Vec::new(),
3105            stats: SpeculativeSchedulerStats {
3106                execution_topology: topology,
3107                ..SpeculativeSchedulerStats::default()
3108            },
3109        })
3110    }
3111
3112    /// Returns one request by stable identity.
3113    pub fn request(
3114        &self,
3115        id: SpeculativeRequestId,
3116    ) -> Option<&SpeculativeRequest<'cache, E, S, C, P>> {
3117        self.requests.get(id.index())
3118    }
3119
3120    /// Returns one request's current status.
3121    pub fn status(&self, id: SpeculativeRequestId) -> Option<SpeculativeRequestStatus> {
3122        self.request(id).map(SpeculativeRequest::status)
3123    }
3124
3125    /// Whether every request is terminal.
3126    pub fn is_finished(&self) -> bool {
3127        self.requests
3128            .iter()
3129            .all(|request| request.lifecycle.is_terminal())
3130    }
3131
3132    /// Validated scheduler options.
3133    pub const fn options(&self) -> SpeculativeSchedulerOptions {
3134        self.schedule.options()
3135    }
3136
3137    /// Prefills and inserts one request, or records its pre-existing terminal state.
3138    #[allow(clippy::too_many_arguments)]
3139    pub fn submit<'context>(
3140        &mut self,
3141        executor: &mut E,
3142        cache: &'cache mut E::Cache,
3143        input: E::Input,
3144        config: SpeculativeConfig,
3145        mut runtime: SpeculativeOutputRuntime<S, C, P>,
3146        randomness: SpeculativeRandomness<S::RandomState, S::DraftRandomness>,
3147        component_timings_collected: bool,
3148        context: E::Context<'context>,
3149    ) -> Result<SpeculativeRequestId, SpeculativeDriverError<E::Error>>
3150    where
3151        E: 'context,
3152        S: SpeculativeSampling<
3153                Logits = E::Logits,
3154                Error = E::Error,
3155                Context<'context> = E::Context<'context>,
3156            > + 'context,
3157    {
3158        config
3159            .validate()
3160            .map_err(SpeculativeDriverError::Generation)?;
3161        if executor.max_proposals() == 0 {
3162            return Err(SpeculativeDriverError::Generation(
3163                GenerationError::NoBackendDraftCapacity,
3164            ));
3165        }
3166        let id = SpeculativeRequestId::new(self.requests.len());
3167        let started = Instant::now();
3168        let mut stats = SpeculativeStats {
3169            execution_topology: self.stats.execution_topology,
3170            component_timings_collected,
3171            ..SpeculativeStats::default()
3172        };
3173        let (target_randomness, draft_randomness) = (randomness.target, randomness.draft);
3174        let (target_state, lifecycle) = if runtime.cancellation().is_cancelled() {
3175            runtime.cancel().map_err(SpeculativeDriverError::Output)?;
3176            stats.elapsed = started.elapsed();
3177            (None, SpeculativeRequestLifecycle::cancelled())
3178        } else if runtime.sequence().is_finished() {
3179            stats.elapsed = started.elapsed();
3180            (None, SpeculativeRequestLifecycle::completed())
3181        } else {
3182            runtime
3183                .observe_lifecycle(SpeculativeLifecycleStage::Input)
3184                .map_err(SpeculativeDriverError::Output)?;
3185            runtime
3186                .observe_lifecycle(SpeculativeLifecycleStage::Execution)
3187                .map_err(SpeculativeDriverError::Output)?;
3188            let checkpoint = executor.checkpoint(cache)?;
3189            let attempt = (|| {
3190                let prefill = executor.prefill(input, cache, context)?;
3191                let mut sampler = runtime.sampler().clone();
3192                let mut constraint = runtime
3193                    .constraint()
3194                    .fork()
3195                    .map_err(SpeculativeDriverError::Output)?;
3196                let mut sequence = runtime.sequence().clone();
3197                let mut target_randomness = target_randomness.clone();
3198                let first_logits = sampler.process_logits(
3199                    &prefill.logits,
3200                    config.temperature,
3201                    &[],
3202                    SamplingPlacement::Target,
3203                    context,
3204                )?;
3205                let first = sampler.sample(
3206                    &first_logits,
3207                    config.temperature,
3208                    target_randomness.as_mut(),
3209                    SamplingPlacement::Target,
3210                    context,
3211                )?;
3212                sampler.update_sampler_state(
3213                    &first_logits,
3214                    first,
3215                    SamplingPlacement::Target,
3216                    context,
3217                )?;
3218                let reason =
3219                    commit_terminal_token(&mut sequence, &mut sampler, &mut constraint, first)?;
3220                let cancelled = runtime
3221                    .publish_candidate(&mut constraint, &mut sequence, &[first])
3222                    .map_err(SpeculativeDriverError::Output)?;
3223                runtime.install_committed_state(sampler, constraint, sequence);
3224                Ok::<_, SpeculativeDriverError<E::Error>>((
3225                    prefill.evaluated_tokens,
3226                    prefill.state,
3227                    target_randomness,
3228                    reason,
3229                    cancelled,
3230                ))
3231            })();
3232            let (evaluated_tokens, target_state, target_randomness, reason, cancelled) =
3233                match attempt {
3234                    Ok(result) => result,
3235                    Err(error) => {
3236                        executor.restore_checkpoint(cache, &checkpoint, context)?;
3237                        return Err(error);
3238                    }
3239                };
3240            stats.target_tokens = evaluated_tokens;
3241            stats.scheduler_turns = 1;
3242            stats.emitted_tokens = 1;
3243            let lifecycle = if cancelled {
3244                stats.elapsed = started.elapsed();
3245                SpeculativeRequestLifecycle::cancelled()
3246            } else if reason.is_some() {
3247                stats.elapsed = started.elapsed();
3248                SpeculativeRequestLifecycle::completed()
3249            } else {
3250                let mut lifecycle = SpeculativeRequestLifecycle::new();
3251                lifecycle
3252                    .transition(SpeculativeRequestStatus::ReadyToDraft)
3253                    .map_err(SpeculativeDriverError::Generation)?;
3254                lifecycle
3255            };
3256            self.stats.turns += 1;
3257            self.requests.push(SpeculativeRequest {
3258                id,
3259                cache,
3260                config,
3261                runtime,
3262                target_randomness,
3263                draft_randomness,
3264                stats,
3265                started,
3266                target_state: Some(target_state),
3267                block: None,
3268                pending: None,
3269                lifecycle,
3270            });
3271            return Ok(id);
3272        };
3273        self.requests.push(SpeculativeRequest {
3274            id,
3275            cache,
3276            config,
3277            runtime,
3278            target_randomness,
3279            draft_randomness,
3280            stats,
3281            started,
3282            target_state,
3283            block: None,
3284            pending: None,
3285            lifecycle,
3286        });
3287        Ok(id)
3288    }
3289
3290    /// Requests cancellation without releasing an exact in-flight transaction.
3291    pub fn cancel(
3292        &mut self,
3293        id: SpeculativeRequestId,
3294    ) -> Result<(), SpeculativeDriverError<E::Error>> {
3295        let request = self.requests.get_mut(id.index()).ok_or_else(|| {
3296            SpeculativeDriverError::Generation(GenerationError::UnknownSpeculativeRequest {
3297                index: id.index(),
3298            })
3299        })?;
3300        request.request_cancellation()
3301    }
3302
3303    /// Applies one fairly selected request action.
3304    pub fn step<'context>(
3305        &mut self,
3306        executor: &mut E,
3307        optimistic_execution_available: bool,
3308        context: E::Context<'context>,
3309    ) -> Result<bool, SpeculativeDriverError<E::Error>>
3310    where
3311        E: 'context,
3312        S: SpeculativeSampling<
3313                Logits = E::Logits,
3314                Error = E::Error,
3315                Context<'context> = E::Context<'context>,
3316            > + 'context,
3317    {
3318        let cancelled = self
3319            .requests
3320            .iter()
3321            .filter(|request| {
3322                request.runtime.cancellation().is_cancelled() && !request.lifecycle.is_terminal()
3323            })
3324            .map(|request| request.id)
3325            .collect::<Vec<_>>();
3326        for id in cancelled {
3327            self.cancel(id)?;
3328        }
3329        if self.is_finished() {
3330            return Ok(false);
3331        }
3332
3333        let candidates = self
3334            .requests
3335            .iter()
3336            .map(|request| {
3337                request.candidate(
3338                    executor,
3339                    optimistic_execution_available,
3340                    self.schedule
3341                        .options()
3342                        .completion_wait()
3343                        .expect("speculative schedule retains validated completion options"),
3344                )
3345            })
3346            .collect::<Result<Vec<_>, _>>()?;
3347        let Some(action) = self
3348            .schedule
3349            .next_action(&candidates)
3350            .map_err(SpeculativeDriverError::Generation)?
3351        else {
3352            return Ok(false);
3353        };
3354        let index = match action {
3355            SpeculativeAction::SubmitVerification(index)
3356            | SpeculativeAction::DraftOptimistic(index)
3357            | SpeculativeAction::PollVerification(index)
3358            | SpeculativeAction::ResolveVerification(index)
3359            | SpeculativeAction::DraftCommitted { index, .. } => index,
3360        };
3361        self.stats.turns += 1;
3362        self.requests[index].stats.scheduler_turns += 1;
3363        match action {
3364            SpeculativeAction::SubmitVerification(index) => {
3365                self.requests[index].submit_verification(executor, context)?;
3366                let in_flight = self
3367                    .requests
3368                    .iter()
3369                    .filter(|request| request.pending.is_some())
3370                    .count();
3371                self.stats.peak_in_flight_verifications =
3372                    self.stats.peak_in_flight_verifications.max(in_flight);
3373            }
3374            SpeculativeAction::DraftCommitted {
3375                index,
3376                cross_request,
3377            } => {
3378                let drafted = self.requests[index].draft_committed(executor, context)?;
3379                if cross_request && drafted {
3380                    self.requests[index].stats.cross_request_draft_opportunities += 1;
3381                    self.stats.cross_request_draft_opportunities += 1;
3382                }
3383            }
3384            SpeculativeAction::DraftOptimistic(index) => {
3385                self.requests[index].draft_optimistic(executor, context)?;
3386                let optimistic = self
3387                    .requests
3388                    .iter()
3389                    .filter(|request| {
3390                        request
3391                            .pending
3392                            .as_ref()
3393                            .is_some_and(PendingSpeculativeVerification::has_optimistic_branch)
3394                    })
3395                    .count();
3396                self.stats.peak_optimistic_branches =
3397                    self.stats.peak_optimistic_branches.max(optimistic);
3398            }
3399            SpeculativeAction::PollVerification(_) => {}
3400            SpeculativeAction::ResolveVerification(index) => {
3401                self.requests[index].resolve_verification(
3402                    executor,
3403                    self.schedule.options(),
3404                    context,
3405                )?;
3406            }
3407        }
3408        Ok(true)
3409    }
3410
3411    /// Drives every request to a terminal state.
3412    pub fn run<'context>(
3413        &mut self,
3414        executor: &mut E,
3415        optimistic_execution_available: bool,
3416        context: E::Context<'context>,
3417    ) -> Result<(), SpeculativeDriverError<E::Error>>
3418    where
3419        E: 'context,
3420        S: SpeculativeSampling<
3421                Logits = E::Logits,
3422                Error = E::Error,
3423                Context<'context> = E::Context<'context>,
3424            > + 'context,
3425    {
3426        while self.step(executor, optimistic_execution_available, context)? {}
3427        Ok(())
3428    }
3429
3430    /// Consumes a terminal table and returns outputs in stable submission order.
3431    pub fn finish(
3432        self,
3433    ) -> Result<CompletedSpeculativeSchedule<S>, SpeculativeDriverError<E::Error>> {
3434        if !self.is_finished() {
3435            return Err(SpeculativeDriverError::Generation(
3436                GenerationError::ActiveSpeculativeRequests,
3437            ));
3438        }
3439        Ok(CompletedSpeculativeSchedule {
3440            requests: self
3441                .requests
3442                .into_iter()
3443                .map(|request| {
3444                    let (sampler, sequence, _, _) = request.runtime.into_parts();
3445                    CompletedSpeculativeRequest {
3446                        id: request.id,
3447                        finish_reason: sequence.finish_reason(),
3448                        token_ids: sequence.into_tokens(),
3449                        stats: request.stats,
3450                        sampler,
3451                        status: request.lifecycle.status(),
3452                    }
3453                })
3454                .collect(),
3455            scheduler: self.stats,
3456        })
3457    }
3458}
3459
3460/// Portable candidate snapshot used by fair speculative action selection.
3461#[derive(Debug, Clone, Copy, Eq, PartialEq)]
3462pub struct SpeculativeCandidate {
3463    /// Current validated request status.
3464    status: SpeculativeRequestStatus,
3465    /// Whether this request may start exact optimistic work now.
3466    optimistic_eligible: bool,
3467    /// Whether retained verification reached exact completion without blocking.
3468    verification_complete: bool,
3469    /// Whether an incomplete verification reached its selected bounded deadline.
3470    verification_deadline_expired: bool,
3471}
3472
3473/// One backend action selected by the portable fair scheduler.
3474#[derive(Debug, Clone, Copy, Eq, PartialEq)]
3475#[non_exhaustive]
3476pub enum SpeculativeAction {
3477    /// Submit a prepared proposal block.
3478    SubmitVerification(usize),
3479    /// Draft canonical proposals; the flag records cross-request overlap.
3480    DraftCommitted {
3481        /// Selected request index.
3482        index: usize,
3483        /// Whether target work from another request is in flight.
3484        cross_request: bool,
3485    },
3486    /// Draft against an unresolved optimistic prefix.
3487    DraftOptimistic(usize),
3488    /// Nonblocking observation retained an incomplete verification.
3489    PollVerification(usize),
3490    /// Resolve one exact verification completion.
3491    ResolveVerification(usize),
3492}
3493
3494/// Backend-neutral fair action selector for speculative requests.
3495pub struct SpeculativeSchedule {
3496    options: SpeculativeSchedulerOptions,
3497    cursor: usize,
3498}
3499
3500impl SpeculativeSchedule {
3501    /// Creates a validated schedule.
3502    pub fn new(options: SpeculativeSchedulerOptions) -> Result<Self, GenerationError> {
3503        Ok(Self {
3504            options: options.validate()?,
3505            cursor: 0,
3506        })
3507    }
3508
3509    /// Validated scheduler options.
3510    pub const fn options(&self) -> SpeculativeSchedulerOptions {
3511        self.options
3512    }
3513
3514    /// Selects the next fair action, or `None` when every request is terminal.
3515    pub fn next_action(
3516        &mut self,
3517        candidates: &[SpeculativeCandidate],
3518    ) -> Result<Option<SpeculativeAction>, GenerationError> {
3519        if candidates.iter().all(|candidate| {
3520            matches!(
3521                candidate.status,
3522                SpeculativeRequestStatus::Completed | SpeculativeRequestStatus::Cancelled
3523            )
3524        }) {
3525            return Ok(None);
3526        }
3527        let in_flight = candidates
3528            .iter()
3529            .filter(|candidate| {
3530                matches!(
3531                    candidate.status,
3532                    SpeculativeRequestStatus::TargetVerificationInFlight
3533                        | SpeculativeRequestStatus::OptimisticDraftRunning
3534                        | SpeculativeRequestStatus::OptimisticDraftReady
3535                        | SpeculativeRequestStatus::VerificationResolution
3536                )
3537            })
3538            .count();
3539        let optimistic = candidates
3540            .iter()
3541            .filter(|candidate| candidate.status == SpeculativeRequestStatus::OptimisticDraftReady)
3542            .count();
3543
3544        if in_flight < self.options.max_in_flight_verifications {
3545            if let Some(index) = self.select(candidates, |candidate| {
3546                candidate.status == SpeculativeRequestStatus::ReadyToSubmitVerification
3547            }) {
3548                return Ok(Some(SpeculativeAction::SubmitVerification(index)));
3549            }
3550        }
3551        if in_flight > 0 {
3552            if optimistic < self.options.max_optimistic_branches
3553                && self.options.lookahead_blocks > 0
3554            {
3555                if let Some(index) = self.select(candidates, |candidate| {
3556                    candidate.status == SpeculativeRequestStatus::TargetVerificationInFlight
3557                        && candidate.optimistic_eligible
3558                }) {
3559                    return Ok(Some(SpeculativeAction::DraftOptimistic(index)));
3560                }
3561            }
3562            if let Some(index) = self.select(candidates, |candidate| {
3563                candidate.status == SpeculativeRequestStatus::ReadyToDraft
3564            }) {
3565                return Ok(Some(SpeculativeAction::DraftCommitted {
3566                    index,
3567                    cross_request: true,
3568                }));
3569            }
3570            if let Some(index) = self.select(candidates, |candidate| {
3571                matches!(
3572                    candidate.status,
3573                    SpeculativeRequestStatus::TargetVerificationInFlight
3574                        | SpeculativeRequestStatus::OptimisticDraftReady
3575                ) && (candidate.verification_complete || candidate.verification_deadline_expired)
3576            }) {
3577                return Ok(Some(SpeculativeAction::ResolveVerification(index)));
3578            }
3579            if let Some(index) = self.select(candidates, |candidate| {
3580                matches!(
3581                    candidate.status,
3582                    SpeculativeRequestStatus::TargetVerificationInFlight
3583                        | SpeculativeRequestStatus::OptimisticDraftReady
3584                ) && !candidate.verification_complete
3585                    && !candidate.verification_deadline_expired
3586            }) {
3587                return Ok(Some(SpeculativeAction::PollVerification(index)));
3588            }
3589        } else if let Some(index) = self.select(candidates, |candidate| {
3590            candidate.status == SpeculativeRequestStatus::ReadyToDraft
3591        }) {
3592            return Ok(Some(SpeculativeAction::DraftCommitted {
3593                index,
3594                cross_request: false,
3595            }));
3596        }
3597        Err(GenerationError::StalledSpeculativeSchedule)
3598    }
3599
3600    fn select(
3601        &mut self,
3602        candidates: &[SpeculativeCandidate],
3603        predicate: impl Fn(&SpeculativeCandidate) -> bool,
3604    ) -> Option<usize> {
3605        for offset in 0..candidates.len() {
3606            let index = (self.cursor + offset) % candidates.len();
3607            if predicate(&candidates[index]) {
3608                self.cursor = (index + 1) % candidates.len();
3609                return Some(index);
3610            }
3611        }
3612        None
3613    }
3614}
3615
3616#[cfg(test)]
3617mod tests {
3618    use super::*;
3619    use std::{
3620        cell::{Cell, RefCell},
3621        convert::Infallible,
3622        fmt,
3623        rc::Rc,
3624        sync::{
3625            atomic::{AtomicUsize, Ordering},
3626            Arc, Mutex,
3627        },
3628    };
3629
3630    type TransactionTrace = Rc<RefCell<Vec<&'static str>>>;
3631
3632    #[test]
3633    fn speculative_telemetry_preserves_exact_statistics_and_durations() {
3634        let stats = SpeculativeStats {
3635            execution_topology: SpeculativeExecutionTopology::CrossDeviceSplit,
3636            target_tokens: 31,
3637            draft_tokens: 8,
3638            accepted_tokens: 5,
3639            rounds: 2,
3640            accept_lens: vec![2, 3],
3641            emitted_tokens: 7,
3642            optimistic_draft_tokens: 9,
3643            reused_optimistic_tokens: 4,
3644            discarded_optimistic_tokens: 5,
3645            adaptive_lookahead_disabled: true,
3646            optimistic_draft_time: Duration::from_millis(125),
3647            verification_in_flight_time: Duration::from_millis(375),
3648            ..SpeculativeStats::default()
3649        };
3650        assert_eq!(
3651            crate::speculative_decoding_telemetry(&stats),
3652            crate::SpeculativeDecodingTelemetry {
3653                execution_topology: "cross-device-split".into(),
3654                target_tokens: 31,
3655                draft_tokens: 8,
3656                accepted_tokens: 5,
3657                accept_rate: 0.625,
3658                rounds: 2,
3659                accept_lens: vec![2, 3],
3660                emitted_tokens: 7,
3661                optimistic_draft_tokens: 9,
3662                reused_optimistic_tokens: 4,
3663                discarded_optimistic_tokens: 5,
3664                adaptive_lookahead_disabled: true,
3665                optimistic_draft_seconds: 0.125,
3666                verification_in_flight_seconds: 0.375,
3667            }
3668        );
3669        assert_eq!(
3670            crate::speculative_decoding_telemetry(&SpeculativeStats::default()).accept_rate,
3671            0.0
3672        );
3673    }
3674
3675    #[test]
3676    fn speculative_capability_schema_round_trips_without_backend_identity() {
3677        let capability = SpeculativeCapability::Unsupported {
3678            draft_source: SpeculativeDraftSource::Embedded,
3679            architecture: "future_decoder".into(),
3680        };
3681        let json = serde_json::to_string(&capability).unwrap();
3682        assert_eq!(
3683            serde_json::from_str::<SpeculativeCapability>(&json).unwrap(),
3684            capability
3685        );
3686        assert!(!json.contains("mlx"));
3687    }
3688
3689    #[test]
3690    fn declared_capability_admits_preparation_without_claiming_execution_readiness() {
3691        let capability = SpeculativeCapability::Declared {
3692            draft_source: SpeculativeDraftSource::Separate,
3693        };
3694
3695        assert_eq!(
3696            capability.draft_source(),
3697            Some(SpeculativeDraftSource::Separate)
3698        );
3699        assert!(capability.admits_source(SpeculativeDraftSource::Separate));
3700        assert!(!capability.admits_source(SpeculativeDraftSource::Embedded));
3701        assert!(!capability.is_ready_for(SpeculativeDraftSource::Separate));
3702        assert_eq!(
3703            serde_json::from_str::<SpeculativeCapability>(
3704                &serde_json::to_string(&capability).unwrap()
3705            )
3706            .unwrap(),
3707            capability
3708        );
3709    }
3710
3711    #[test]
3712    fn only_ready_capability_claims_immediate_execution() {
3713        let ready = SpeculativeCapability::Ready {
3714            draft_source: SpeculativeDraftSource::Embedded,
3715        };
3716        let unsupported = SpeculativeCapability::Unsupported {
3717            draft_source: SpeculativeDraftSource::Embedded,
3718            architecture: "example".into(),
3719        };
3720
3721        assert!(ready.admits_source(SpeculativeDraftSource::Embedded));
3722        assert!(ready.is_ready_for(SpeculativeDraftSource::Embedded));
3723        assert!(!unsupported.admits_source(SpeculativeDraftSource::Embedded));
3724        assert!(!unsupported.is_ready_for(SpeculativeDraftSource::Embedded));
3725        assert!(!SpeculativeCapability::Unavailable.admits_source(SpeculativeDraftSource::Embedded));
3726    }
3727
3728    #[derive(Debug, Clone, Default)]
3729    struct Done {
3730        trace: Option<TransactionTrace>,
3731    }
3732
3733    impl Completion for Done {
3734        type Error = Infallible;
3735
3736        fn is_complete(&self) -> Result<bool, Self::Error> {
3737            Ok(true)
3738        }
3739
3740        fn wait(&self) -> Result<(), Self::Error> {
3741            if let Some(trace) = &self.trace {
3742                trace.borrow_mut().push("wait");
3743            }
3744            Ok(())
3745        }
3746    }
3747
3748    impl BoundedCompletion for Done {
3749        fn wait_bounded(
3750            self,
3751            _policy: BoundedCompletionWait,
3752        ) -> Result<BoundedCompletionOutcome, Self::Error> {
3753            self.wait()?;
3754            Ok(BoundedCompletionOutcome::Completed)
3755        }
3756    }
3757
3758    #[derive(Clone, Default)]
3759    struct PortableSemanticState {
3760        events: Vec<crate::generation::SemanticEvent>,
3761    }
3762
3763    impl SpeculativeSemanticState for PortableSemanticState {
3764        fn fork_box(&self) -> Result<Box<dyn SpeculativeSemanticState>, SpeculativeOutputError> {
3765            let mut fork = self.clone();
3766            fork.events.clear();
3767            Ok(Box::new(fork))
3768        }
3769
3770        fn push_token(&mut self, token: u32) -> Result<bool, SpeculativeOutputError> {
3771            self.events
3772                .push(crate::generation::SemanticEvent::TextDelta(
3773                    token.to_string(),
3774                ));
3775            Ok(false)
3776        }
3777
3778        fn finish(&mut self, reason: FinishReason) -> Result<(), SpeculativeOutputError> {
3779            self.events
3780                .push(crate::generation::SemanticEvent::Finished { reason });
3781            Ok(())
3782        }
3783
3784        fn cancel(&mut self) -> Result<(), SpeculativeOutputError> {
3785            self.finish(FinishReason::Cancelled)
3786        }
3787
3788        fn take_events(&mut self) -> Vec<crate::generation::SemanticEvent> {
3789            std::mem::take(&mut self.events)
3790        }
3791    }
3792
3793    #[test]
3794    fn core_semantic_publisher_commits_and_cancels_without_backend_errors() {
3795        let published = Rc::new(RefCell::new(Vec::new()));
3796        let mut constraint =
3797            SpeculativeSemanticConstraint::semantic(Box::new(PortableSemanticState::default()));
3798        constraint.push_token(7).unwrap();
3799        constraint.finish(FinishReason::MaxTokens).unwrap();
3800        {
3801            let published = Rc::clone(&published);
3802            let mut publisher = SpeculativeCallbackPublisher::semantic(move |event| {
3803                published.borrow_mut().push(event)
3804            });
3805            assert!(!publisher
3806                .publish_committed(
3807                    &mut constraint,
3808                    &[7],
3809                    &GenerationCancellationToken::new(),
3810                    true,
3811                )
3812                .unwrap());
3813        }
3814        assert_eq!(
3815            *published.borrow(),
3816            vec![
3817                crate::generation::SemanticEvent::TextDelta("7".into()),
3818                crate::generation::SemanticEvent::Finished {
3819                    reason: FinishReason::MaxTokens,
3820                },
3821            ]
3822        );
3823
3824        let cancelled = Rc::new(RefCell::new(Vec::new()));
3825        let mut constraint =
3826            SpeculativeSemanticConstraint::semantic(Box::new(PortableSemanticState::default()));
3827        {
3828            let cancelled = Rc::clone(&cancelled);
3829            let mut publisher = SpeculativeCallbackPublisher::semantic(move |event| {
3830                cancelled.borrow_mut().push(event)
3831            });
3832            publisher.publish_cancelled(&mut constraint).unwrap();
3833        }
3834        assert_eq!(
3835            *cancelled.borrow(),
3836            vec![crate::generation::SemanticEvent::Finished {
3837                reason: FinishReason::Cancelled,
3838            }]
3839        );
3840
3841        let mut constraint = SpeculativeSemanticConstraint::plain();
3842        let mut publisher = SpeculativeCallbackPublisher::tokens(|_| {
3843            Err(SpeculativeOutputError::publication("consumer closed"))
3844        });
3845        assert_eq!(
3846            publisher
3847                .publish_committed(
3848                    &mut constraint,
3849                    &[11],
3850                    &GenerationCancellationToken::new(),
3851                    false,
3852                )
3853                .unwrap_err(),
3854            SpeculativeOutputError::publication("consumer closed")
3855        );
3856    }
3857
3858    #[derive(Default)]
3859    struct MockExecutor {
3860        trace: Option<TransactionTrace>,
3861        full_acceptance: bool,
3862    }
3863
3864    struct MockVerification {
3865        tokens: Vec<u32>,
3866        logits: Vec<Vec<f32>>,
3867    }
3868
3869    impl SpeculativeExecutor for MockExecutor {
3870        type Input = Vec<u32>;
3871        type Cache = Vec<u32>;
3872        type TargetState = usize;
3873        type DraftState = Vec<u32>;
3874        type CacheCheckpoint = usize;
3875        type Verification = MockVerification;
3876        type Logits = Vec<f32>;
3877        type Context<'a> = ();
3878        type Completion = Done;
3879        type Telemetry = ();
3880        type Error = Infallible;
3881
3882        fn supports_exact_optimistic_promotion(&self) -> bool {
3883            true
3884        }
3885
3886        fn prefill<'context>(
3887            &mut self,
3888            input: Self::Input,
3889            cache: &mut Self::Cache,
3890            _: Self::Context<'context>,
3891        ) -> Result<SpeculativePrefill<Self::TargetState, Self::Logits>, Self::Error> {
3892            cache.extend_from_slice(&input);
3893            Ok(SpeculativePrefill {
3894                logits: vec![0.0, 1.0],
3895                state: cache.len(),
3896                evaluated_tokens: input.len(),
3897            })
3898        }
3899
3900        fn begin_proposal<'a>(
3901            &mut self,
3902            _: &Self::TargetState,
3903            last_token: u32,
3904            _: usize,
3905            _: Self::Context<'a>,
3906        ) -> Result<Self::DraftState, Self::Error> {
3907            Ok(vec![last_token])
3908        }
3909
3910        fn proposal_logits<'a>(
3911            &mut self,
3912            state: &mut Self::DraftState,
3913            last_token: u32,
3914            _: Self::Context<'a>,
3915        ) -> Result<Self::Logits, Self::Error> {
3916            state.push(last_token + 1);
3917            Ok(vec![0.0, 1.0])
3918        }
3919
3920        fn checkpoint(&self, cache: &Self::Cache) -> Result<Self::CacheCheckpoint, Self::Error> {
3921            Ok(cache.len())
3922        }
3923
3924        fn restore_checkpoint<'a>(
3925            &mut self,
3926            cache: &mut Self::Cache,
3927            checkpoint: &Self::CacheCheckpoint,
3928            _: Self::Context<'a>,
3929        ) -> Result<(), Self::Error> {
3930            cache.truncate(*checkpoint);
3931            Ok(())
3932        }
3933
3934        fn submit_verification<'a>(
3935            &mut self,
3936            input_tokens: &[u32],
3937            cache: &mut Self::Cache,
3938            _: Self::Context<'a>,
3939        ) -> Result<Submission<Self::Verification, Self::Completion>, Self::Error> {
3940            cache.extend_from_slice(input_tokens);
3941            let logits = if self.full_acceptance {
3942                vec![vec![0.0, 1.0], vec![0.0, 1.0], vec![0.0, 1.0]]
3943            } else {
3944                vec![vec![0.0, 1.0], vec![1.0, 0.0], vec![0.0, 1.0]]
3945            };
3946            Ok(Submission {
3947                output: MockVerification {
3948                    tokens: input_tokens.to_vec(),
3949                    logits,
3950                },
3951                completion: Done {
3952                    trace: self.trace.clone(),
3953                },
3954            })
3955        }
3956
3957        fn verification_logits<'a>(
3958            &self,
3959            output: &Self::Verification,
3960            index: usize,
3961            _: Self::Context<'a>,
3962        ) -> Result<Self::Logits, Self::Error> {
3963            Ok(output.logits[index].clone())
3964        }
3965
3966        fn commit_verification<'a>(
3967            &mut self,
3968            output: Self::Verification,
3969            draft_state: Self::DraftState,
3970            cache: &mut Self::Cache,
3971            checkpoint: &Self::CacheCheckpoint,
3972            verified_inputs: usize,
3973            _: Self::Context<'a>,
3974        ) -> Result<SpeculativeCommit<Self::TargetState>, Self::Error> {
3975            assert!(!output.tokens.is_empty());
3976            if let Some(trace) = &self.trace {
3977                trace.borrow_mut().push("commit");
3978            }
3979            cache.truncate(*checkpoint + verified_inputs);
3980            Ok(SpeculativeCommit {
3981                state: draft_state.len(),
3982                replayed_tokens: 0,
3983            })
3984        }
3985    }
3986
3987    #[test]
3988    fn mock_executor_prefill_propose_verify_and_commit_without_a_tensor_runtime() {
3989        let mut executor = MockExecutor::default();
3990        let mut cache = Vec::new();
3991        let prefill = executor.prefill(vec![4, 5], &mut cache, ()).unwrap();
3992        let mut draft = executor.begin_proposal(&prefill.state, 5, 2, ()).unwrap();
3993        assert_eq!(
3994            executor.proposal_logits(&mut draft, 5, ()).unwrap(),
3995            [0.0, 1.0]
3996        );
3997        let checkpoint = executor.checkpoint(&cache).unwrap();
3998        let submission = executor
3999            .submit_verification(&[5, 6], &mut cache, ())
4000            .unwrap();
4001        submission.completion.wait().unwrap();
4002        let commit = executor
4003            .commit_verification(submission.output, draft, &mut cache, &checkpoint, 1, ())
4004            .unwrap();
4005        assert_eq!(cache, [4, 5, 5]);
4006        assert_eq!(commit.replayed_tokens, 0);
4007    }
4008
4009    #[test]
4010    fn execution_topology_is_a_portable_schema() {
4011        let topology = SpeculativeExecutionTopology::CrossDeviceSplit;
4012        let encoded = serde_json::to_string(&topology).unwrap();
4013        assert_eq!(encoded, "\"cross_device_split\"");
4014        assert_eq!(
4015            serde_json::from_str::<SpeculativeExecutionTopology>(&encoded).unwrap(),
4016            topology
4017        );
4018    }
4019
4020    #[derive(Clone)]
4021    struct MockSampling {
4022        committed: Vec<u32>,
4023        trace: Option<TransactionTrace>,
4024        unit_draw: f32,
4025        draft_prefix_limit: Option<usize>,
4026    }
4027
4028    impl Default for MockSampling {
4029        fn default() -> Self {
4030            Self {
4031                committed: Vec::new(),
4032                trace: None,
4033                unit_draw: 0.5,
4034                draft_prefix_limit: None,
4035            }
4036        }
4037    }
4038
4039    impl MockSampling {
4040        fn record(&self, operation: &'static str) {
4041            if let Some(trace) = &self.trace {
4042                trace.borrow_mut().push(operation);
4043            }
4044        }
4045    }
4046
4047    impl SpeculativeSampling for MockSampling {
4048        type Logits = Vec<f32>;
4049        type Distribution = Vec<f32>;
4050        type Seed = ();
4051        type RandomState = usize;
4052        type DraftRandomness = usize;
4053        type RandomnessRoot = usize;
4054        type Context<'a> = ();
4055        type Error = Infallible;
4056
4057        fn supports_exact_optimistic_promotion(&self) -> bool {
4058            true
4059        }
4060
4061        fn prefix_is_complete(&self, history: &[u32]) -> Result<bool, Self::Error> {
4062            Ok(self
4063                .draft_prefix_limit
4064                .is_some_and(|limit| history.len() >= limit))
4065        }
4066
4067        fn randomness_root<'a>(
4068            _: Option<Self::Seed>,
4069            _: Self::Context<'a>,
4070        ) -> Result<Self::RandomnessRoot, Self::Error>
4071        where
4072            Self: 'a,
4073        {
4074            Ok(0)
4075        }
4076
4077        fn target_randomness_from_root<'a>(
4078            root: &mut Self::RandomnessRoot,
4079            _: Self::Context<'a>,
4080        ) -> Result<Self::RandomState, Self::Error>
4081        where
4082            Self: 'a,
4083        {
4084            let target = *root;
4085            *root += 1;
4086            Ok(target)
4087        }
4088
4089        fn draft_randomness_from_root<'a>(
4090            root: &mut Self::RandomnessRoot,
4091            _: Self::Context<'a>,
4092        ) -> Result<Self::DraftRandomness, Self::Error>
4093        where
4094            Self: 'a,
4095        {
4096            let draft = *root;
4097            *root += 1;
4098            Ok(draft)
4099        }
4100
4101        fn draft_randomness_at<'a>(
4102            root: &Self::DraftRandomness,
4103            position: SpeculativeDraftRandomPosition,
4104            _: Self::Context<'a>,
4105        ) -> Result<Self::RandomState, Self::Error>
4106        where
4107            Self: 'a,
4108        {
4109            Ok(root + position.get())
4110        }
4111
4112        fn process_logits<'a>(
4113            &mut self,
4114            logits: &Self::Logits,
4115            _: f32,
4116            _: &[u32],
4117            _: SamplingPlacement,
4118            _: Self::Context<'a>,
4119        ) -> Result<Self::Distribution, Self::Error>
4120        where
4121            Self: 'a,
4122        {
4123            Ok(logits.clone())
4124        }
4125
4126        fn sample<'a>(
4127            &self,
4128            distribution: &Self::Distribution,
4129            _: f32,
4130            randomness: Option<&mut Self::RandomState>,
4131            _: SamplingPlacement,
4132            _: Self::Context<'a>,
4133        ) -> Result<u32, Self::Error>
4134        where
4135            Self: 'a,
4136        {
4137            self.record("sample");
4138            if let Some(randomness) = randomness {
4139                *randomness += 1;
4140            }
4141            Ok(argmax(distribution))
4142        }
4143
4144        fn probability_at<'a>(
4145            &self,
4146            distribution: &Self::Distribution,
4147            token: u32,
4148            _: SamplingPlacement,
4149            _: Self::Context<'a>,
4150        ) -> Result<f32, Self::Error>
4151        where
4152            Self: 'a,
4153        {
4154            self.record("probability");
4155            let maximum = distribution
4156                .iter()
4157                .copied()
4158                .max_by(f32::total_cmp)
4159                .unwrap_or(0.0);
4160            let normalizer = distribution
4161                .iter()
4162                .map(|value| (value - maximum).exp())
4163                .sum::<f32>();
4164            Ok((distribution[token as usize] - maximum).exp() / normalizer)
4165        }
4166
4167        fn sample_unit_interval<'a>(
4168            &self,
4169            randomness: Option<&mut Self::RandomState>,
4170            _: Self::Context<'a>,
4171        ) -> Result<f32, Self::Error>
4172        where
4173            Self: 'a,
4174        {
4175            self.record("uniform");
4176            if let Some(randomness) = randomness {
4177                *randomness += 1;
4178            }
4179            Ok(self.unit_draw)
4180        }
4181
4182        fn positive_probability_difference<'a>(
4183            &self,
4184            left: &Self::Distribution,
4185            right: &Self::Distribution,
4186            _: SamplingPlacement,
4187            _: Self::Context<'a>,
4188        ) -> Result<Option<Self::Distribution>, Self::Error>
4189        where
4190            Self: 'a,
4191        {
4192            self.record("difference");
4193            let probabilities = |distribution: &[f32]| {
4194                let maximum = distribution
4195                    .iter()
4196                    .copied()
4197                    .max_by(f32::total_cmp)
4198                    .unwrap_or(0.0);
4199                let values = distribution
4200                    .iter()
4201                    .map(|value| (value - maximum).exp())
4202                    .collect::<Vec<_>>();
4203                let normalizer = values.iter().sum::<f32>();
4204                values
4205                    .into_iter()
4206                    .map(|value| value / normalizer)
4207                    .collect::<Vec<_>>()
4208            };
4209            let left = probabilities(left);
4210            let right = probabilities(right);
4211            let difference = left
4212                .iter()
4213                .zip(right)
4214                .map(|(left, right)| (left - right).max(0.0))
4215                .collect::<Vec<_>>();
4216            Ok(difference
4217                .iter()
4218                .any(|value| *value > f32::EPSILON)
4219                .then_some(difference))
4220        }
4221
4222        fn update_sampler_state<'a>(
4223            &mut self,
4224            _: &Self::Distribution,
4225            token: u32,
4226            _: SamplingPlacement,
4227            _: Self::Context<'a>,
4228        ) -> Result<(), Self::Error>
4229        where
4230            Self: 'a,
4231        {
4232            self.record("update");
4233            self.committed.push(token);
4234            Ok(())
4235        }
4236    }
4237
4238    fn argmax(values: &[f32]) -> u32 {
4239        values
4240            .iter()
4241            .enumerate()
4242            .max_by(|(_, left), (_, right)| left.total_cmp(right))
4243            .map(|(index, _)| index as u32)
4244            .unwrap()
4245    }
4246
4247    #[derive(Default)]
4248    struct MockConstraint {
4249        tokens: Vec<u32>,
4250        finished: Option<FinishReason>,
4251    }
4252
4253    impl SpeculativeConstraint for MockConstraint {
4254        fn fork(&self) -> Result<Self, SpeculativeOutputError> {
4255            Ok(Self {
4256                tokens: self.tokens.clone(),
4257                finished: self.finished,
4258            })
4259        }
4260
4261        fn push_token(&mut self, token: u32) -> Result<bool, SpeculativeOutputError> {
4262            self.tokens.push(token);
4263            Ok(false)
4264        }
4265
4266        fn finish(&mut self, reason: FinishReason) -> Result<(), SpeculativeOutputError> {
4267            self.finished = Some(reason);
4268            Ok(())
4269        }
4270    }
4271
4272    #[derive(Default)]
4273    struct MockPublisher {
4274        tokens: Vec<u32>,
4275        cancelled: bool,
4276        trace: Option<TransactionTrace>,
4277    }
4278
4279    impl SpeculativePublisher<MockConstraint> for MockPublisher {
4280        fn publish_committed(
4281            &mut self,
4282            _: &mut MockConstraint,
4283            tokens: &[u32],
4284            _: &GenerationCancellationToken,
4285            _: bool,
4286        ) -> Result<bool, SpeculativeOutputError> {
4287            if let Some(trace) = &self.trace {
4288                trace.borrow_mut().push("publish");
4289            }
4290            self.tokens.extend_from_slice(tokens);
4291            Ok(false)
4292        }
4293
4294        fn publish_cancelled(
4295            &mut self,
4296            _: &mut MockConstraint,
4297        ) -> Result<(), SpeculativeOutputError> {
4298            if let Some(trace) = &self.trace {
4299                trace.borrow_mut().push("cancel");
4300            }
4301            self.cancelled = true;
4302            Ok(())
4303        }
4304    }
4305
4306    fn mock_output_runtime(
4307        cancellation: GenerationCancellationToken,
4308        trace: Option<TransactionTrace>,
4309    ) -> SpeculativeOutputRuntime<MockSampling, MockConstraint, MockPublisher> {
4310        let mut sequence = GenerationSequence::new(8, []);
4311        sequence.commit(5, TokenTerminalSignals::default()).unwrap();
4312        SpeculativeOutputRuntime::new(
4313            MockSampling::default(),
4314            sequence,
4315            MockConstraint::default(),
4316            MockPublisher {
4317                trace,
4318                ..MockPublisher::default()
4319            },
4320            cancellation,
4321        )
4322    }
4323
4324    fn empty_mock_runtime(
4325        max_tokens: usize,
4326        cancellation: GenerationCancellationToken,
4327    ) -> SpeculativeOutputRuntime<MockSampling, MockConstraint, MockPublisher> {
4328        SpeculativeOutputRuntime::new(
4329            MockSampling::default(),
4330            GenerationSequence::new(max_tokens, []),
4331            MockConstraint::default(),
4332            MockPublisher::default(),
4333            cancellation,
4334        )
4335    }
4336
4337    #[derive(Default)]
4338    struct LifecycleTrace {
4339        stages: Mutex<Vec<SpeculativeLifecycleStage>>,
4340        fail: Option<SpeculativeLifecycleStage>,
4341    }
4342
4343    impl LifecycleTrace {
4344        fn failing(stage: SpeculativeLifecycleStage) -> Self {
4345            Self {
4346                stages: Mutex::default(),
4347                fail: Some(stage),
4348            }
4349        }
4350
4351        fn stages(&self) -> Vec<SpeculativeLifecycleStage> {
4352            self.stages.lock().unwrap().clone()
4353        }
4354    }
4355
4356    impl SpeculativeLifecycleObserver for LifecycleTrace {
4357        fn observe(&self, stage: SpeculativeLifecycleStage) -> Result<(), SpeculativeOutputError> {
4358            self.stages.lock().unwrap().push(stage);
4359            if self.fail == Some(stage) {
4360                Err(SpeculativeOutputError::semantic(
4361                    "lifecycle observation",
4362                    format!("injected {stage:?} failure"),
4363                ))
4364            } else {
4365                Ok(())
4366            }
4367        }
4368    }
4369
4370    #[test]
4371    fn request_table_consumes_explicit_production_lifecycle_observation() {
4372        let observer = Arc::new(LifecycleTrace::default());
4373        let mut executor = MockExecutor::default();
4374        let mut cache = Vec::new();
4375        let config = SpeculativeConfig {
4376            max_tokens: 3,
4377            max_draft_tokens: 2,
4378            temperature: 0.7,
4379            eos_token_ids: Vec::new(),
4380        };
4381        let runtime = empty_mock_runtime(config.max_tokens, GenerationCancellationToken::new())
4382            .with_lifecycle_observer(observer.clone());
4383        let mut table = SpeculativeRequestTable::new(
4384            SpeculativeSchedulerOptions::default().with_lookahead(false),
4385            SpeculativeExecutionTopology::Single,
4386        )
4387        .unwrap();
4388        table
4389            .submit(
4390                &mut executor,
4391                &mut cache,
4392                vec![4],
4393                config,
4394                runtime,
4395                SpeculativeRandomness {
4396                    target: Some(0),
4397                    draft: Some(0),
4398                },
4399                false,
4400                (),
4401            )
4402            .unwrap();
4403        table.run(&mut executor, false, ()).unwrap();
4404        table.finish().unwrap();
4405
4406        let stages = observer.stages();
4407        assert_eq!(stages[0], SpeculativeLifecycleStage::Input);
4408        assert_eq!(stages[1], SpeculativeLifecycleStage::Execution);
4409        assert_eq!(stages[2], SpeculativeLifecycleStage::Publication);
4410        assert_eq!(
4411            stages
4412                .iter()
4413                .filter(|stage| **stage == SpeculativeLifecycleStage::Observation)
4414                .count(),
4415            1
4416        );
4417        let observation = stages
4418            .iter()
4419            .position(|stage| *stage == SpeculativeLifecycleStage::Observation)
4420            .unwrap();
4421        let persistence = stages
4422            .iter()
4423            .position(|stage| *stage == SpeculativeLifecycleStage::CachePersistence)
4424            .unwrap();
4425        let final_publication = stages
4426            .iter()
4427            .rposition(|stage| *stage == SpeculativeLifecycleStage::Publication)
4428            .unwrap();
4429        assert!(observation < persistence);
4430        assert!(persistence < final_publication);
4431    }
4432
4433    #[test]
4434    fn input_and_execution_observer_failures_prevent_prefill_and_publication() {
4435        for failure in [
4436            SpeculativeLifecycleStage::Input,
4437            SpeculativeLifecycleStage::Execution,
4438        ] {
4439            let observer = Arc::new(LifecycleTrace::failing(failure));
4440            let publication = TransactionTrace::default();
4441            let runtime = SpeculativeOutputRuntime::new(
4442                MockSampling::default(),
4443                GenerationSequence::new(3, []),
4444                MockConstraint::default(),
4445                MockPublisher {
4446                    trace: Some(publication.clone()),
4447                    ..MockPublisher::default()
4448                },
4449                GenerationCancellationToken::new(),
4450            )
4451            .with_lifecycle_observer(observer.clone());
4452            let mut executor = MockExecutor::default();
4453            let mut cache = Vec::new();
4454            let mut table = SpeculativeRequestTable::new(
4455                SpeculativeSchedulerOptions::default().with_lookahead(false),
4456                SpeculativeExecutionTopology::Single,
4457            )
4458            .unwrap();
4459            let error = table
4460                .submit(
4461                    &mut executor,
4462                    &mut cache,
4463                    vec![4],
4464                    SpeculativeConfig {
4465                        max_tokens: 3,
4466                        max_draft_tokens: 2,
4467                        temperature: 0.7,
4468                        eos_token_ids: Vec::new(),
4469                    },
4470                    runtime,
4471                    SpeculativeRandomness {
4472                        target: Some(0),
4473                        draft: Some(0),
4474                    },
4475                    false,
4476                    (),
4477                )
4478                .unwrap_err();
4479            assert!(matches!(error, SpeculativeDriverError::Output(_)));
4480            assert!(cache.is_empty());
4481            assert!(publication.borrow().is_empty());
4482            assert_eq!(observer.stages().last(), Some(&failure));
4483        }
4484    }
4485
4486    #[test]
4487    fn deterministic_proposal_policy_uses_only_target_selection() {
4488        let trace = TransactionTrace::default();
4489        let sampler = MockSampling {
4490            trace: Some(trace.clone()),
4491            ..MockSampling::default()
4492        };
4493        let mut randomness = 7;
4494        let decision = decide_speculative_proposal(
4495            &sampler,
4496            &vec![2.0, 0.0],
4497            &vec![0.0, 2.0],
4498            1,
4499            0.0,
4500            Some(&mut randomness),
4501            (),
4502        )
4503        .unwrap();
4504
4505        assert_eq!(decision, ProposalDecision::Reject(0));
4506        assert_eq!(randomness, 7);
4507        assert_eq!(*trace.borrow(), ["sample"]);
4508    }
4509
4510    #[test]
4511    fn neutral_randomness_assigns_target_then_position_stable_draft() {
4512        let randomness = MockSampling::initialize_randomness(Some(()), 0.7, ()).unwrap();
4513        assert_eq!(randomness.target, Some(0));
4514        assert_eq!(randomness.draft, Some(1));
4515        assert_eq!(
4516            MockSampling::draft_randomness_at(
4517                randomness.draft.as_ref().unwrap(),
4518                SpeculativeDraftRandomPosition::new(4),
4519                (),
4520            )
4521            .unwrap(),
4522            5
4523        );
4524
4525        let deterministic = MockSampling::initialize_randomness(None, 0.0, ()).unwrap();
4526        assert_eq!(deterministic.target, None);
4527        assert_eq!(deterministic.draft, None);
4528    }
4529
4530    #[test]
4531    fn stochastic_proposal_policy_causally_selects_acceptance_or_residual() {
4532        assert_eq!(speculative_acceptance_probability(0.25, 0.5), 0.5);
4533        assert_eq!(speculative_acceptance_probability(0.25, 0.0), 1.0);
4534
4535        let accepted_trace = TransactionTrace::default();
4536        let accepted_sampler = MockSampling {
4537            trace: Some(accepted_trace.clone()),
4538            unit_draw: 0.9,
4539            ..MockSampling::default()
4540        };
4541        let mut accepted_randomness = 0;
4542        let accepted = decide_speculative_proposal(
4543            &accepted_sampler,
4544            &vec![0.0, 1.0],
4545            &vec![0.0, 1.0],
4546            1,
4547            0.7,
4548            Some(&mut accepted_randomness),
4549            (),
4550        )
4551        .unwrap();
4552        assert_eq!(accepted, ProposalDecision::Accept);
4553        assert_eq!(accepted_randomness, 1);
4554        assert_eq!(
4555            *accepted_trace.borrow(),
4556            ["probability", "probability", "uniform"]
4557        );
4558
4559        let rejected_trace = TransactionTrace::default();
4560        let rejected_sampler = MockSampling {
4561            trace: Some(rejected_trace.clone()),
4562            unit_draw: 0.5,
4563            ..MockSampling::default()
4564        };
4565        let mut rejected_randomness = 0;
4566        let rejected = decide_speculative_proposal(
4567            &rejected_sampler,
4568            &vec![0.0, 2.0],
4569            &vec![2.0, 0.0],
4570            0,
4571            0.7,
4572            Some(&mut rejected_randomness),
4573            (),
4574        )
4575        .unwrap();
4576        assert_eq!(rejected, ProposalDecision::Reject(1));
4577        assert_eq!(rejected_randomness, 2);
4578        assert_eq!(
4579            *rejected_trace.borrow(),
4580            [
4581                "probability",
4582                "probability",
4583                "uniform",
4584                "difference",
4585                "sample"
4586            ]
4587        );
4588    }
4589
4590    #[test]
4591    fn portable_driver_proposes_and_resolves_acceptance_and_replacement() {
4592        let mut executor = MockExecutor::default();
4593        let sampler = MockSampling::default();
4594        let mut draft = executor.begin_proposal(&2, 5, 2, ()).unwrap();
4595        let proposals = propose_block(
4596            &mut executor,
4597            &sampler,
4598            &mut draft,
4599            5,
4600            2,
4601            &[5],
4602            0.7,
4603            &[],
4604            Some(&0),
4605            (),
4606        )
4607        .unwrap();
4608        assert_eq!(
4609            proposals
4610                .iter()
4611                .map(|proposal| proposal.token)
4612                .collect::<Vec<_>>(),
4613            [1, 1]
4614        );
4615
4616        let mut cache = vec![4, 5];
4617        let verification = executor
4618            .submit_verification(&[5, 1, 1], &mut cache, ())
4619            .unwrap()
4620            .output;
4621        let mut sequence = GenerationSequence::new(8, []);
4622        sequence.commit(5, TokenTerminalSignals::default()).unwrap();
4623        let canonical_randomness = 0;
4624        let resolved = resolve_round::<MockExecutor, MockSampling, MockConstraint>(
4625            &executor,
4626            &verification,
4627            proposals,
4628            &sampler,
4629            &sequence,
4630            &MockConstraint::default(),
4631            Some(&canonical_randomness),
4632            0.7,
4633            (),
4634        )
4635        .unwrap();
4636        assert_eq!(resolved.accepted_proposals, 1);
4637        assert_eq!(resolved.committed_tokens, [1, 0]);
4638        assert_eq!(resolved.verified_inputs, 2);
4639        assert_eq!(resolved.sampler.committed, [1, 0]);
4640        assert_eq!(resolved.sequence.tokens(), [5, 1, 0]);
4641        assert_eq!(resolved.constraint.tokens, [1, 0]);
4642        assert_eq!(resolved.target_randomness, Some(3));
4643        assert_eq!(resolved.finish_reason, None);
4644        assert!(sampler.committed.is_empty());
4645        assert_eq!(canonical_randomness, 0);
4646    }
4647
4648    #[test]
4649    fn portable_schedule_is_fair_and_respects_retained_capacity() {
4650        let mut schedule =
4651            SpeculativeSchedule::new(SpeculativeSchedulerOptions::default()).unwrap();
4652        let ready = SpeculativeCandidate {
4653            status: SpeculativeRequestStatus::ReadyToSubmitVerification,
4654            optimistic_eligible: false,
4655            verification_complete: false,
4656            verification_deadline_expired: false,
4657        };
4658        assert_eq!(
4659            schedule.next_action(&[ready, ready]).unwrap(),
4660            Some(SpeculativeAction::SubmitVerification(0))
4661        );
4662        assert_eq!(
4663            schedule.next_action(&[ready, ready]).unwrap(),
4664            Some(SpeculativeAction::SubmitVerification(1))
4665        );
4666
4667        let in_flight = SpeculativeCandidate {
4668            status: SpeculativeRequestStatus::TargetVerificationInFlight,
4669            optimistic_eligible: false,
4670            verification_complete: true,
4671            verification_deadline_expired: false,
4672        };
4673        let draft = SpeculativeCandidate {
4674            status: SpeculativeRequestStatus::ReadyToDraft,
4675            optimistic_eligible: false,
4676            verification_complete: false,
4677            verification_deadline_expired: false,
4678        };
4679        assert_eq!(
4680            schedule.next_action(&[in_flight, ready, draft]).unwrap(),
4681            Some(SpeculativeAction::DraftCommitted {
4682                index: 2,
4683                cross_request: true,
4684            })
4685        );
4686    }
4687
4688    #[test]
4689    fn request_table_owns_actions_resources_fairness_and_deferred_cancellation() {
4690        let mut executor = MockExecutor::default();
4691        let mut first_cache = Vec::new();
4692        let mut second_cache = Vec::new();
4693        let options = SpeculativeSchedulerOptions::default().with_lookahead(false);
4694        let mut table =
4695            SpeculativeRequestTable::new(options, SpeculativeExecutionTopology::Single).unwrap();
4696        let config = SpeculativeConfig {
4697            max_tokens: 3,
4698            max_draft_tokens: 2,
4699            temperature: 0.7,
4700            eos_token_ids: Vec::new(),
4701        };
4702        let first_cancellation = GenerationCancellationToken::new();
4703        let first = table
4704            .submit(
4705                &mut executor,
4706                &mut first_cache,
4707                vec![4],
4708                config.clone(),
4709                empty_mock_runtime(config.max_tokens, first_cancellation.clone()),
4710                SpeculativeRandomness {
4711                    target: Some(0),
4712                    draft: Some(0),
4713                },
4714                false,
4715                (),
4716            )
4717            .unwrap();
4718        let second = table
4719            .submit(
4720                &mut executor,
4721                &mut second_cache,
4722                vec![8],
4723                config.clone(),
4724                empty_mock_runtime(config.max_tokens, GenerationCancellationToken::new()),
4725                SpeculativeRandomness {
4726                    target: Some(0),
4727                    draft: Some(10),
4728                },
4729                false,
4730                (),
4731            )
4732            .unwrap();
4733
4734        assert_eq!(
4735            table.status(first),
4736            Some(SpeculativeRequestStatus::ReadyToDraft)
4737        );
4738        assert_eq!(
4739            table.status(second),
4740            Some(SpeculativeRequestStatus::ReadyToDraft)
4741        );
4742        table.step(&mut executor, false, ()).unwrap();
4743        table.step(&mut executor, false, ()).unwrap();
4744        assert!(table.request(first).unwrap().has_pending_verification());
4745        first_cancellation.cancel();
4746        table.run(&mut executor, false, ()).unwrap();
4747
4748        let output = table.finish().unwrap();
4749        assert_eq!(output.requests.len(), 2);
4750        assert_eq!(output.requests[0].id, first);
4751        assert_eq!(
4752            output.requests[0].status,
4753            SpeculativeRequestStatus::Cancelled
4754        );
4755        assert_eq!(output.requests[0].token_ids, [1]);
4756        assert_eq!(output.requests[1].id, second);
4757        assert_eq!(
4758            output.requests[1].status,
4759            SpeculativeRequestStatus::Completed
4760        );
4761        assert_eq!(output.requests[1].token_ids, [1, 1, 0]);
4762        assert!(output.scheduler.cross_request_draft_opportunities > 0);
4763        assert_eq!(first_cache, [4, 1]);
4764        assert_eq!(second_cache, [8, 1, 1]);
4765    }
4766
4767    #[test]
4768    fn request_table_applies_optimistic_actions_without_backend_scheduler_state() {
4769        let mut executor = MockExecutor::default();
4770        let mut cache = Vec::new();
4771        let config = SpeculativeConfig {
4772            max_tokens: 5,
4773            max_draft_tokens: 2,
4774            temperature: 0.7,
4775            eos_token_ids: Vec::new(),
4776        };
4777        let mut table = SpeculativeRequestTable::new(
4778            SpeculativeSchedulerOptions::default(),
4779            SpeculativeExecutionTopology::SameDeviceSplit,
4780        )
4781        .unwrap();
4782        let id = table
4783            .submit(
4784                &mut executor,
4785                &mut cache,
4786                vec![4],
4787                config.clone(),
4788                empty_mock_runtime(config.max_tokens, GenerationCancellationToken::new()),
4789                SpeculativeRandomness {
4790                    target: Some(0),
4791                    draft: Some(0),
4792                },
4793                false,
4794                (),
4795            )
4796            .unwrap();
4797
4798        table.step(&mut executor, true, ()).unwrap();
4799        table.step(&mut executor, true, ()).unwrap();
4800        table.step(&mut executor, true, ()).unwrap();
4801        assert_eq!(
4802            table.status(id),
4803            Some(SpeculativeRequestStatus::OptimisticDraftReady)
4804        );
4805        table.run(&mut executor, true, ()).unwrap();
4806        let output = table.finish().unwrap();
4807        assert_eq!(
4808            output.requests[0].status,
4809            SpeculativeRequestStatus::Completed
4810        );
4811        assert!(output.requests[0].stats.optimistic_draft_blocks > 0);
4812        assert!(output.requests[0].stats.discarded_optimistic_blocks > 0);
4813        assert_eq!(output.scheduler.peak_optimistic_branches, 1);
4814    }
4815
4816    #[test]
4817    fn request_table_promotes_only_the_exact_matching_optimistic_suffix() {
4818        let mut executor = MockExecutor {
4819            full_acceptance: true,
4820            ..MockExecutor::default()
4821        };
4822        let mut cache = Vec::new();
4823        let config = SpeculativeConfig {
4824            max_tokens: 8,
4825            max_draft_tokens: 2,
4826            temperature: 0.7,
4827            eos_token_ids: Vec::new(),
4828        };
4829        let mut table = SpeculativeRequestTable::new(
4830            SpeculativeSchedulerOptions::default(),
4831            SpeculativeExecutionTopology::SameDeviceSplit,
4832        )
4833        .unwrap();
4834        let id = table
4835            .submit(
4836                &mut executor,
4837                &mut cache,
4838                vec![4],
4839                config.clone(),
4840                empty_mock_runtime(config.max_tokens, GenerationCancellationToken::new()),
4841                SpeculativeRandomness {
4842                    target: Some(0),
4843                    draft: Some(0),
4844                },
4845                false,
4846                (),
4847            )
4848            .unwrap();
4849
4850        table.step(&mut executor, true, ()).unwrap();
4851        table.step(&mut executor, true, ()).unwrap();
4852        table.step(&mut executor, true, ()).unwrap();
4853        assert_eq!(
4854            table.status(id),
4855            Some(SpeculativeRequestStatus::OptimisticDraftReady)
4856        );
4857        table.step(&mut executor, true, ()).unwrap();
4858
4859        let request = table.request(id).unwrap();
4860        assert_eq!(request.status(), SpeculativeRequestStatus::ReadyToDraft);
4861        assert_eq!(
4862            request
4863                .block()
4864                .unwrap()
4865                .proposals()
4866                .iter()
4867                .map(|proposal| proposal.token())
4868                .collect::<Vec<_>>(),
4869            [1]
4870        );
4871        assert_eq!(request.stats().optimistic_bonus_matches, 1);
4872        assert_eq!(request.stats().consumed_optimistic_tokens, 1);
4873        assert_eq!(request.stats().reused_optimistic_tokens, 1);
4874        assert_eq!(request.stats().reused_optimistic_blocks, 1);
4875        assert_eq!(request.stats().discarded_optimistic_tokens, 0);
4876    }
4877
4878    #[test]
4879    fn request_table_draft_generation_stops_at_the_sampler_grammar_boundary() {
4880        let mut executor = MockExecutor::default();
4881        let mut cache = Vec::new();
4882        let config = SpeculativeConfig {
4883            max_tokens: 8,
4884            max_draft_tokens: 4,
4885            temperature: 0.7,
4886            eos_token_ids: Vec::new(),
4887        };
4888        let runtime = SpeculativeOutputRuntime::new(
4889            MockSampling {
4890                draft_prefix_limit: Some(2),
4891                ..MockSampling::default()
4892            },
4893            GenerationSequence::new(config.max_tokens, []),
4894            MockConstraint::default(),
4895            MockPublisher::default(),
4896            GenerationCancellationToken::new(),
4897        );
4898        let mut table = SpeculativeRequestTable::new(
4899            SpeculativeSchedulerOptions::default().with_lookahead(false),
4900            SpeculativeExecutionTopology::Single,
4901        )
4902        .unwrap();
4903        let id = table
4904            .submit(
4905                &mut executor,
4906                &mut cache,
4907                vec![4],
4908                config,
4909                runtime,
4910                SpeculativeRandomness {
4911                    target: Some(0),
4912                    draft: Some(0),
4913                },
4914                false,
4915                (),
4916            )
4917            .unwrap();
4918
4919        table.step(&mut executor, false, ()).unwrap();
4920
4921        let request = table.request(id).unwrap();
4922        assert_eq!(
4923            request.status(),
4924            SpeculativeRequestStatus::ReadyToSubmitVerification
4925        );
4926        assert_eq!(request.block().unwrap().proposals().len(), 1);
4927        assert_eq!(request.stats().draft_tokens, 1);
4928    }
4929
4930    #[test]
4931    fn coordinator_commits_before_publication_and_discards_mismatched_lookahead() {
4932        let trace = TransactionTrace::default();
4933        let mut executor = MockExecutor {
4934            trace: Some(trace.clone()),
4935            ..MockExecutor::default()
4936        };
4937        let mut cache = vec![4, 5];
4938        let block = SpeculativeDraftBlock {
4939            state: vec![5, 1, 1],
4940            proposals: vec![
4941                SpeculativeProposal {
4942                    token: 1,
4943                    distribution: vec![0.0, 1.0],
4944                },
4945                SpeculativeProposal {
4946                    token: 1,
4947                    distribution: vec![0.0, 1.0],
4948                },
4949            ],
4950        };
4951        let mut pending =
4952            submit_verification_transaction(&mut executor, &mut cache, 5, block, ()).unwrap();
4953        pending
4954            .set_optimistic_branch(SpeculativeOptimisticBranch {
4955                block: SpeculativeDraftBlock {
4956                    state: vec![5, 1, 1, 2],
4957                    proposals: vec![SpeculativeProposal {
4958                        token: 2,
4959                        distribution: vec![0.0, 0.0, 1.0],
4960                    }],
4961                },
4962                assumed_prefix: vec![5, 1, 1],
4963            })
4964            .unwrap();
4965        let mut runtime =
4966            mock_output_runtime(GenerationCancellationToken::new(), Some(trace.clone()));
4967        let published = resolve_commit_and_publish(
4968            &mut executor,
4969            &mut cache,
4970            pending,
4971            &mut runtime,
4972            Some(&0),
4973            0.7,
4974            SpeculativeStats::default(),
4975            SpeculativeSchedulerOptions::default(),
4976            (),
4977        )
4978        .unwrap();
4979
4980        assert!(matches!(
4981            published.status,
4982            SpeculativePublicationStatus::Continue(SpeculativeContinuation::None)
4983        ));
4984        assert_eq!(published.stats.accepted_tokens, 1);
4985        assert_eq!(published.stats.discarded_optimistic_tokens, 1);
4986        assert_eq!(cache, [4, 5, 5, 1]);
4987        let (_, sequence, constraint, publisher) = runtime.into_parts();
4988        assert_eq!(sequence.tokens(), [5, 1, 0]);
4989        assert_eq!(constraint.tokens, [1, 0]);
4990        assert_eq!(publisher.tokens, [1, 0]);
4991        assert!(!publisher.cancelled);
4992        assert_eq!(*trace.borrow(), ["wait", "commit", "publish"]);
4993    }
4994
4995    #[test]
4996    fn coordinator_cancels_only_after_retained_verification_is_safe() {
4997        let trace = TransactionTrace::default();
4998        let mut executor = MockExecutor {
4999            trace: Some(trace.clone()),
5000            ..MockExecutor::default()
5001        };
5002        let mut cache = vec![4, 5];
5003        let block = SpeculativeDraftBlock {
5004            state: vec![5, 1],
5005            proposals: vec![SpeculativeProposal {
5006                token: 1,
5007                distribution: vec![0.0, 1.0],
5008            }],
5009        };
5010        let mut pending =
5011            submit_verification_transaction(&mut executor, &mut cache, 5, block, ()).unwrap();
5012        pending
5013            .set_optimistic_branch(SpeculativeOptimisticBranch {
5014                block: SpeculativeDraftBlock {
5015                    state: vec![5, 1, 2],
5016                    proposals: vec![SpeculativeProposal {
5017                        token: 2,
5018                        distribution: vec![0.0, 0.0, 1.0],
5019                    }],
5020                },
5021                assumed_prefix: vec![5, 1],
5022            })
5023            .unwrap();
5024        let cancellation = GenerationCancellationToken::new();
5025        cancellation.cancel();
5026        let mut runtime = mock_output_runtime(cancellation, Some(trace.clone()));
5027        let (stats, ()) = cancel_pending_verification(
5028            &mut executor,
5029            &mut cache,
5030            pending,
5031            &mut runtime,
5032            SpeculativeStats::default(),
5033            SpeculativeSchedulerOptions::default()
5034                .completion_wait()
5035                .unwrap(),
5036            (),
5037        )
5038        .unwrap();
5039
5040        assert_eq!(stats.discarded_optimistic_tokens, 1);
5041        assert_eq!(cache, [4, 5, 5]);
5042        let (_, sequence, _, publisher) = runtime.into_parts();
5043        assert_eq!(sequence.finish_reason(), Some(FinishReason::Cancelled));
5044        assert!(publisher.tokens.is_empty());
5045        assert!(publisher.cancelled);
5046        assert_eq!(*trace.borrow(), ["wait", "commit", "cancel"]);
5047    }
5048
5049    type FailureTrace = Rc<RefCell<Vec<&'static str>>>;
5050
5051    #[derive(Debug, Clone, Copy, Eq, PartialEq)]
5052    enum TransactionFailure {
5053        Completion,
5054        Commit,
5055        Restore,
5056    }
5057
5058    impl fmt::Display for TransactionFailure {
5059        fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
5060            formatter.write_str(match self {
5061                Self::Completion => "completion failed",
5062                Self::Commit => "commit failed",
5063                Self::Restore => "restore failed",
5064            })
5065        }
5066    }
5067
5068    impl std::error::Error for TransactionFailure {}
5069
5070    struct DropProbe {
5071        event: &'static str,
5072        drops: Rc<Cell<usize>>,
5073        trace: FailureTrace,
5074    }
5075
5076    impl Drop for DropProbe {
5077        fn drop(&mut self) {
5078            self.drops.set(self.drops.get() + 1);
5079            self.trace.borrow_mut().push(self.event);
5080        }
5081    }
5082
5083    struct DelayedCompletion {
5084        ready: Rc<Cell<bool>>,
5085        ready_after_polls: Option<usize>,
5086        polls: Rc<Cell<usize>>,
5087        fail: bool,
5088        publication_attempts: Rc<Cell<usize>>,
5089        publications_at_submission: usize,
5090        trace: FailureTrace,
5091        _probe: DropProbe,
5092    }
5093
5094    impl Completion for DelayedCompletion {
5095        type Error = TransactionFailure;
5096
5097        fn is_complete(&self) -> Result<bool, Self::Error> {
5098            let polls = self.polls.get() + 1;
5099            self.polls.set(polls);
5100            if self
5101                .ready_after_polls
5102                .is_some_and(|ready_after| polls >= ready_after)
5103            {
5104                self.ready.set(true);
5105            }
5106            Ok(self.ready.get())
5107        }
5108
5109        fn wait(&self) -> Result<(), Self::Error> {
5110            assert_eq!(
5111                self.publication_attempts.get(),
5112                self.publications_at_submission,
5113                "verification completion must precede any later publication"
5114            );
5115            self.trace.borrow_mut().push("wait");
5116            self.ready.set(true);
5117            if self.fail {
5118                Err(TransactionFailure::Completion)
5119            } else {
5120                Ok(())
5121            }
5122        }
5123    }
5124
5125    impl BoundedCompletion for DelayedCompletion {
5126        fn wait_bounded(
5127            self,
5128            _policy: BoundedCompletionWait,
5129        ) -> Result<BoundedCompletionOutcome, Self::Error> {
5130            self.wait()?;
5131            Ok(BoundedCompletionOutcome::Completed)
5132        }
5133    }
5134
5135    struct TransactionVerification {
5136        logits: Vec<Vec<f32>>,
5137        ready: Rc<Cell<bool>>,
5138        _probe: DropProbe,
5139    }
5140
5141    struct TransactionDraftState {
5142        values: Vec<u32>,
5143        _probe: Option<DropProbe>,
5144    }
5145
5146    impl Clone for TransactionDraftState {
5147        fn clone(&self) -> Self {
5148            Self {
5149                values: self.values.clone(),
5150                _probe: None,
5151            }
5152        }
5153    }
5154
5155    #[derive(Clone)]
5156    struct TransactionCheckpoint {
5157        target: Vec<u32>,
5158        draft: Vec<u32>,
5159    }
5160
5161    struct TransactionCache {
5162        target: Vec<u32>,
5163        draft: Vec<u32>,
5164        fail_restore: bool,
5165        trace: FailureTrace,
5166    }
5167
5168    struct TransactionExecutor {
5169        fail_completion: bool,
5170        fail_commit: bool,
5171        replayed_tokens: usize,
5172        ready_after_polls: Option<usize>,
5173        ready: Rc<Cell<bool>>,
5174        completion_polls: Rc<Cell<usize>>,
5175        publication_attempts: Rc<Cell<usize>>,
5176        completion_drops: Rc<Cell<usize>>,
5177        verification_drops: Rc<Cell<usize>>,
5178        committed_draft: Rc<RefCell<Vec<u32>>>,
5179        trace: FailureTrace,
5180    }
5181
5182    impl TransactionExecutor {
5183        fn new(trace: FailureTrace, publication_attempts: Rc<Cell<usize>>) -> Self {
5184            Self {
5185                fail_completion: false,
5186                fail_commit: false,
5187                replayed_tokens: 0,
5188                ready_after_polls: Some(1),
5189                ready: Rc::new(Cell::new(false)),
5190                completion_polls: Rc::new(Cell::new(0)),
5191                publication_attempts,
5192                completion_drops: Rc::new(Cell::new(0)),
5193                verification_drops: Rc::new(Cell::new(0)),
5194                committed_draft: Rc::new(RefCell::new(Vec::new())),
5195                trace,
5196            }
5197        }
5198    }
5199
5200    impl SpeculativeExecutor for TransactionExecutor {
5201        type Input = Vec<u32>;
5202        type Cache = TransactionCache;
5203        type TargetState = (Vec<u32>, Vec<u32>);
5204        type DraftState = TransactionDraftState;
5205        type CacheCheckpoint = TransactionCheckpoint;
5206        type Verification = TransactionVerification;
5207        type Logits = Vec<f32>;
5208        type Context<'a> = ();
5209        type Completion = DelayedCompletion;
5210        type Telemetry = ();
5211        type Error = TransactionFailure;
5212
5213        fn prefill<'a>(
5214            &mut self,
5215            input: Self::Input,
5216            cache: &mut Self::Cache,
5217            _: Self::Context<'a>,
5218        ) -> Result<SpeculativePrefill<Self::TargetState, Self::Logits>, Self::Error> {
5219            cache.target.extend(input);
5220            Ok(SpeculativePrefill::new(
5221                vec![0.0, 1.0],
5222                (cache.target.clone(), cache.draft.clone()),
5223                1,
5224            ))
5225        }
5226
5227        fn begin_proposal<'a>(
5228            &mut self,
5229            _: &Self::TargetState,
5230            last_token: u32,
5231            _: usize,
5232            _: Self::Context<'a>,
5233        ) -> Result<Self::DraftState, Self::Error> {
5234            Ok(TransactionDraftState {
5235                values: vec![last_token],
5236                _probe: None,
5237            })
5238        }
5239
5240        fn proposal_logits<'a>(
5241            &mut self,
5242            state: &mut Self::DraftState,
5243            last_token: u32,
5244            _: Self::Context<'a>,
5245        ) -> Result<Self::Logits, Self::Error> {
5246            state.values.push(last_token);
5247            Ok(vec![0.0, 1.0])
5248        }
5249
5250        fn checkpoint(&self, cache: &Self::Cache) -> Result<Self::CacheCheckpoint, Self::Error> {
5251            Ok(TransactionCheckpoint {
5252                target: cache.target.clone(),
5253                draft: cache.draft.clone(),
5254            })
5255        }
5256
5257        fn restore_checkpoint<'a>(
5258            &mut self,
5259            cache: &mut Self::Cache,
5260            checkpoint: &Self::CacheCheckpoint,
5261            _: Self::Context<'a>,
5262        ) -> Result<(), Self::Error> {
5263            cache.trace.borrow_mut().push("restore");
5264            if cache.fail_restore {
5265                return Err(TransactionFailure::Restore);
5266            }
5267            cache.target.clone_from(&checkpoint.target);
5268            cache.draft.clone_from(&checkpoint.draft);
5269            Ok(())
5270        }
5271
5272        fn submit_verification<'a>(
5273            &mut self,
5274            input_tokens: &[u32],
5275            cache: &mut Self::Cache,
5276            _: Self::Context<'a>,
5277        ) -> Result<Submission<Self::Verification, Self::Completion>, Self::Error> {
5278            self.ready.set(false);
5279            self.completion_polls.set(0);
5280            cache.target.extend_from_slice(input_tokens);
5281            self.trace.borrow_mut().push("submit");
5282            Ok(Submission {
5283                output: TransactionVerification {
5284                    logits: vec![vec![0.0, 1.0], vec![1.0, 0.0], vec![0.0, 1.0]],
5285                    ready: self.ready.clone(),
5286                    _probe: DropProbe {
5287                        event: "drop_verification",
5288                        drops: self.verification_drops.clone(),
5289                        trace: self.trace.clone(),
5290                    },
5291                },
5292                completion: DelayedCompletion {
5293                    ready: self.ready.clone(),
5294                    ready_after_polls: self.ready_after_polls,
5295                    polls: self.completion_polls.clone(),
5296                    fail: self.fail_completion,
5297                    publications_at_submission: self.publication_attempts.get(),
5298                    publication_attempts: self.publication_attempts.clone(),
5299                    trace: self.trace.clone(),
5300                    _probe: DropProbe {
5301                        event: "drop_completion",
5302                        drops: self.completion_drops.clone(),
5303                        trace: self.trace.clone(),
5304                    },
5305                },
5306            })
5307        }
5308
5309        fn verification_logits<'a>(
5310            &self,
5311            output: &Self::Verification,
5312            index: usize,
5313            _: Self::Context<'a>,
5314        ) -> Result<Self::Logits, Self::Error> {
5315            assert!(
5316                output.ready.get(),
5317                "verification read before completion wait"
5318            );
5319            Ok(output.logits[index].clone())
5320        }
5321
5322        fn commit_verification<'a>(
5323            &mut self,
5324            output: Self::Verification,
5325            state: Self::DraftState,
5326            cache: &mut Self::Cache,
5327            checkpoint: &Self::CacheCheckpoint,
5328            verified_inputs: usize,
5329            _: Self::Context<'a>,
5330        ) -> Result<SpeculativeCommit<Self::TargetState>, Self::Error> {
5331            assert!(output.ready.get(), "commit before completion wait");
5332            self.trace.borrow_mut().push("commit");
5333            self.committed_draft.borrow_mut().clone_from(&state.values);
5334            if self.fail_commit {
5335                return Err(TransactionFailure::Commit);
5336            }
5337            cache
5338                .target
5339                .truncate(checkpoint.target.len() + verified_inputs);
5340            cache.draft.clone_from(&state.values);
5341            Ok(SpeculativeCommit::new(
5342                (cache.target.clone(), cache.draft.clone()),
5343                self.replayed_tokens,
5344            ))
5345        }
5346    }
5347
5348    #[derive(Clone, Default)]
5349    struct TransactionSampling {
5350        committed: Vec<u32>,
5351    }
5352
5353    impl SpeculativeSampling for TransactionSampling {
5354        type Logits = Vec<f32>;
5355        type Distribution = Vec<f32>;
5356        type Seed = ();
5357        type RandomState = usize;
5358        type DraftRandomness = usize;
5359        type RandomnessRoot = usize;
5360        type Context<'a> = ();
5361        type Error = TransactionFailure;
5362
5363        fn randomness_root<'a>(_: Option<Self::Seed>, _: ()) -> Result<usize, Self::Error>
5364        where
5365            Self: 'a,
5366        {
5367            Ok(0)
5368        }
5369
5370        fn target_randomness_from_root<'a>(root: &mut usize, _: ()) -> Result<usize, Self::Error>
5371        where
5372            Self: 'a,
5373        {
5374            Ok(*root)
5375        }
5376
5377        fn draft_randomness_from_root<'a>(root: &mut usize, _: ()) -> Result<usize, Self::Error>
5378        where
5379            Self: 'a,
5380        {
5381            Ok(*root)
5382        }
5383
5384        fn draft_randomness_at<'a>(
5385            root: &usize,
5386            position: SpeculativeDraftRandomPosition,
5387            _: (),
5388        ) -> Result<usize, Self::Error>
5389        where
5390            Self: 'a,
5391        {
5392            Ok(*root + position.get())
5393        }
5394
5395        fn process_logits<'a>(
5396            &mut self,
5397            logits: &Vec<f32>,
5398            _: f32,
5399            _: &[u32],
5400            _: SamplingPlacement,
5401            _: (),
5402        ) -> Result<Vec<f32>, Self::Error>
5403        where
5404            Self: 'a,
5405        {
5406            Ok(logits.clone())
5407        }
5408
5409        fn sample<'a>(
5410            &self,
5411            distribution: &Vec<f32>,
5412            _: f32,
5413            _: Option<&mut usize>,
5414            _: SamplingPlacement,
5415            _: (),
5416        ) -> Result<u32, Self::Error>
5417        where
5418            Self: 'a,
5419        {
5420            Ok(argmax(distribution))
5421        }
5422
5423        fn probability_at<'a>(
5424            &self,
5425            distribution: &Vec<f32>,
5426            token: u32,
5427            _: SamplingPlacement,
5428            _: (),
5429        ) -> Result<f32, Self::Error>
5430        where
5431            Self: 'a,
5432        {
5433            Ok(if argmax(distribution) == token {
5434                1.0
5435            } else {
5436                0.0
5437            })
5438        }
5439
5440        fn sample_unit_interval<'a>(&self, _: Option<&mut usize>, _: ()) -> Result<f32, Self::Error>
5441        where
5442            Self: 'a,
5443        {
5444            Ok(0.5)
5445        }
5446
5447        fn positive_probability_difference<'a>(
5448            &self,
5449            left: &Vec<f32>,
5450            _: &Vec<f32>,
5451            _: SamplingPlacement,
5452            _: (),
5453        ) -> Result<Option<Vec<f32>>, Self::Error>
5454        where
5455            Self: 'a,
5456        {
5457            Ok(Some(left.clone()))
5458        }
5459
5460        fn update_sampler_state<'a>(
5461            &mut self,
5462            _: &Vec<f32>,
5463            token: u32,
5464            _: SamplingPlacement,
5465            _: (),
5466        ) -> Result<(), Self::Error>
5467        where
5468            Self: 'a,
5469        {
5470            self.committed.push(token);
5471            Ok(())
5472        }
5473    }
5474
5475    struct ObservedPublisher {
5476        tokens: Vec<u32>,
5477        cancelled: bool,
5478        fail_committed: bool,
5479        fail_cancelled: bool,
5480        attempts: Rc<Cell<usize>>,
5481        trace: FailureTrace,
5482    }
5483
5484    impl SpeculativePublisher<MockConstraint> for ObservedPublisher {
5485        fn publish_committed(
5486            &mut self,
5487            _: &mut MockConstraint,
5488            tokens: &[u32],
5489            _: &GenerationCancellationToken,
5490            _: bool,
5491        ) -> Result<bool, SpeculativeOutputError> {
5492            self.attempts.set(self.attempts.get() + 1);
5493            self.trace.borrow_mut().push("publish");
5494            if self.fail_committed {
5495                return Err(SpeculativeOutputError::publication("injected failure"));
5496            }
5497            self.tokens.extend_from_slice(tokens);
5498            Ok(false)
5499        }
5500
5501        fn publish_cancelled(
5502            &mut self,
5503            _: &mut MockConstraint,
5504        ) -> Result<(), SpeculativeOutputError> {
5505            self.attempts.set(self.attempts.get() + 1);
5506            self.trace.borrow_mut().push("cancel");
5507            if self.fail_cancelled {
5508                return Err(SpeculativeOutputError::publication(
5509                    "injected cancellation failure",
5510                ));
5511            }
5512            self.cancelled = true;
5513            Ok(())
5514        }
5515    }
5516
5517    fn transaction_cache(trace: FailureTrace) -> TransactionCache {
5518        TransactionCache {
5519            target: vec![4, 5],
5520            draft: vec![4, 5],
5521            fail_restore: false,
5522            trace,
5523        }
5524    }
5525
5526    fn transaction_block(
5527        trace: FailureTrace,
5528        draft_drops: Rc<Cell<usize>>,
5529    ) -> SpeculativeDraftBlock<TransactionDraftState, Vec<f32>> {
5530        SpeculativeDraftBlock::new(
5531            TransactionDraftState {
5532                values: vec![5, 1, 1],
5533                _probe: Some(DropProbe {
5534                    event: "drop_draft",
5535                    drops: draft_drops,
5536                    trace,
5537                }),
5538            },
5539            vec![
5540                SpeculativeProposal::new(1, vec![0.0, 1.0]),
5541                SpeculativeProposal::new(1, vec![0.0, 1.0]),
5542            ],
5543        )
5544    }
5545
5546    fn transaction_runtime(
5547        trace: FailureTrace,
5548        attempts: Rc<Cell<usize>>,
5549        fail_committed: bool,
5550        cancellation: GenerationCancellationToken,
5551    ) -> SpeculativeOutputRuntime<TransactionSampling, MockConstraint, ObservedPublisher> {
5552        let mut sequence = GenerationSequence::new(8, []);
5553        sequence.commit(5, TokenTerminalSignals::default()).unwrap();
5554        SpeculativeOutputRuntime::new(
5555            TransactionSampling::default(),
5556            sequence,
5557            MockConstraint::default(),
5558            ObservedPublisher {
5559                tokens: Vec::new(),
5560                cancelled: false,
5561                fail_committed,
5562                fail_cancelled: false,
5563                attempts,
5564                trace,
5565            },
5566            cancellation,
5567        )
5568    }
5569
5570    fn empty_transaction_runtime(
5571        max_tokens: usize,
5572        trace: FailureTrace,
5573        attempts: Rc<Cell<usize>>,
5574    ) -> SpeculativeOutputRuntime<TransactionSampling, MockConstraint, ObservedPublisher> {
5575        SpeculativeOutputRuntime::new(
5576            TransactionSampling::default(),
5577            GenerationSequence::new(max_tokens, []),
5578            MockConstraint::default(),
5579            ObservedPublisher {
5580                tokens: Vec::new(),
5581                cancelled: false,
5582                fail_committed: false,
5583                fail_cancelled: false,
5584                attempts,
5585                trace,
5586            },
5587            GenerationCancellationToken::new(),
5588        )
5589    }
5590
5591    #[test]
5592    fn observation_and_cache_persistence_failures_restore_before_publication() {
5593        for failure in [
5594            SpeculativeLifecycleStage::Completion,
5595            SpeculativeLifecycleStage::Observation,
5596            SpeculativeLifecycleStage::CachePersistence,
5597        ] {
5598            let observer = Arc::new(LifecycleTrace::failing(failure));
5599            let trace = FailureTrace::default();
5600            let attempts = Rc::new(Cell::new(0));
5601            let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
5602            let mut cache = transaction_cache(trace.clone());
5603            let config = SpeculativeConfig {
5604                max_tokens: 3,
5605                max_draft_tokens: 2,
5606                temperature: 0.7,
5607                eos_token_ids: Vec::new(),
5608            };
5609            let runtime =
5610                empty_transaction_runtime(config.max_tokens, trace.clone(), attempts.clone())
5611                    .with_lifecycle_observer(observer.clone());
5612            let mut table = SpeculativeRequestTable::new(
5613                SpeculativeSchedulerOptions::default().with_lookahead(false),
5614                SpeculativeExecutionTopology::Single,
5615            )
5616            .unwrap();
5617            let id = table
5618                .submit(
5619                    &mut executor,
5620                    &mut cache,
5621                    vec![4],
5622                    config,
5623                    runtime,
5624                    SpeculativeRandomness {
5625                        target: Some(0),
5626                        draft: Some(0),
5627                    },
5628                    false,
5629                    (),
5630                )
5631                .unwrap();
5632            table.step(&mut executor, false, ()).unwrap();
5633            table.step(&mut executor, false, ()).unwrap();
5634            assert!(table.request(id).unwrap().has_pending_verification());
5635
5636            let error = table.step(&mut executor, false, ()).unwrap_err();
5637            assert!(matches!(error, SpeculativeDriverError::Output(_)));
5638            assert_eq!(attempts.get(), 1, "failed boundary published output");
5639            assert_eq!(cache.target, [4, 5, 4]);
5640            assert_eq!(cache.draft, [4, 5]);
5641            assert!(!trace.borrow().contains(&"commit"));
5642            assert!(trace.borrow().contains(&"restore"));
5643            if failure == SpeculativeLifecycleStage::Completion {
5644                let trace = trace.borrow();
5645                let completion_drop = trace
5646                    .iter()
5647                    .position(|event| *event == "drop_completion")
5648                    .expect("completion failure must dispose retained work");
5649                let restore = trace
5650                    .iter()
5651                    .position(|event| *event == "restore")
5652                    .expect("completion failure must restore the checkpoint");
5653                assert!(completion_drop < restore);
5654            }
5655            assert_eq!(
5656                observer
5657                    .stages()
5658                    .iter()
5659                    .filter(|stage| **stage == failure)
5660                    .count(),
5661                1
5662            );
5663        }
5664    }
5665
5666    #[test]
5667    fn cancellation_observer_failure_prevents_terminal_mutation_and_publication() {
5668        let observer = Arc::new(LifecycleTrace::failing(
5669            SpeculativeLifecycleStage::Cancellation,
5670        ));
5671        let trace = FailureTrace::default();
5672        let attempts = Rc::new(Cell::new(0));
5673        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
5674        let mut cache = transaction_cache(trace.clone());
5675        let cancellation = GenerationCancellationToken::new();
5676        cancellation.cancel();
5677        let runtime = transaction_runtime(trace, attempts.clone(), false, cancellation)
5678            .with_lifecycle_observer(observer.clone());
5679        let mut table = SpeculativeRequestTable::new(
5680            SpeculativeSchedulerOptions::default().with_lookahead(false),
5681            SpeculativeExecutionTopology::Single,
5682        )
5683        .unwrap();
5684        let error = table
5685            .submit(
5686                &mut executor,
5687                &mut cache,
5688                vec![4],
5689                SpeculativeConfig {
5690                    max_tokens: 3,
5691                    max_draft_tokens: 2,
5692                    temperature: 0.7,
5693                    eos_token_ids: Vec::new(),
5694                },
5695                runtime,
5696                SpeculativeRandomness {
5697                    target: Some(0),
5698                    draft: Some(0),
5699                },
5700                false,
5701                (),
5702            )
5703            .unwrap_err();
5704        assert!(matches!(error, SpeculativeDriverError::Output(_)));
5705        assert_eq!(attempts.get(), 0);
5706        assert_eq!(cache.target, [4, 5]);
5707        assert_eq!(cache.draft, [4, 5]);
5708        assert_eq!(observer.stages(), [SpeculativeLifecycleStage::Cancellation]);
5709    }
5710
5711    #[test]
5712    fn prefill_publication_failure_restores_cache_and_logical_state() {
5713        let trace = FailureTrace::default();
5714        let attempts = Rc::new(Cell::new(0));
5715        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
5716        let mut cache = transaction_cache(trace.clone());
5717        let mut runtime = empty_transaction_runtime(3, trace.clone(), attempts.clone());
5718        runtime.publisher.fail_committed = true;
5719        let mut table = SpeculativeRequestTable::new(
5720            SpeculativeSchedulerOptions::default().with_lookahead(false),
5721            SpeculativeExecutionTopology::Single,
5722        )
5723        .unwrap();
5724        let error = table
5725            .submit(
5726                &mut executor,
5727                &mut cache,
5728                vec![4],
5729                SpeculativeConfig {
5730                    max_tokens: 3,
5731                    max_draft_tokens: 2,
5732                    temperature: 0.7,
5733                    eos_token_ids: Vec::new(),
5734                },
5735                runtime,
5736                SpeculativeRandomness {
5737                    target: Some(0),
5738                    draft: Some(0),
5739                },
5740                false,
5741                (),
5742            )
5743            .unwrap_err();
5744        assert!(matches!(error, SpeculativeDriverError::Output(_)));
5745        assert_eq!(table.requests.len(), 0);
5746        drop(table);
5747        assert_eq!(cache.target, [4, 5]);
5748        assert_eq!(cache.draft, [4, 5]);
5749        assert!(trace.borrow().contains(&"restore"));
5750    }
5751
5752    #[test]
5753    fn pending_cancellation_publication_failure_restores_cache_without_cancelling_sequence() {
5754        let trace = FailureTrace::default();
5755        let attempts = Rc::new(Cell::new(0));
5756        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
5757        let mut cache = transaction_cache(trace.clone());
5758        let mut table = SpeculativeRequestTable::new(
5759            SpeculativeSchedulerOptions::default().with_lookahead(false),
5760            SpeculativeExecutionTopology::Single,
5761        )
5762        .unwrap();
5763        let id = table
5764            .submit(
5765                &mut executor,
5766                &mut cache,
5767                vec![4],
5768                SpeculativeConfig {
5769                    max_tokens: 3,
5770                    max_draft_tokens: 2,
5771                    temperature: 0.7,
5772                    eos_token_ids: Vec::new(),
5773                },
5774                empty_transaction_runtime(3, trace.clone(), attempts),
5775                SpeculativeRandomness {
5776                    target: Some(0),
5777                    draft: Some(0),
5778                },
5779                false,
5780                (),
5781            )
5782            .unwrap();
5783        table.step(&mut executor, false, ()).unwrap();
5784        table.step(&mut executor, false, ()).unwrap();
5785        table.requests[id.index()].runtime.publisher.fail_cancelled = true;
5786        table.cancel(id).unwrap();
5787        let error = table.step(&mut executor, false, ()).unwrap_err();
5788        assert!(matches!(error, SpeculativeDriverError::Output(_)));
5789        assert!(!table.requests[id.index()].runtime.sequence().is_finished());
5790        drop(table);
5791        assert_eq!(cache.target, [4, 5, 4]);
5792        assert!(trace.borrow().contains(&"restore"));
5793    }
5794
5795    #[test]
5796    fn direct_cancellation_publication_failure_restores_lifecycle_and_sequence() {
5797        let trace = FailureTrace::default();
5798        let attempts = Rc::new(Cell::new(0));
5799        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
5800        let mut cache = transaction_cache(trace.clone());
5801        let mut table = SpeculativeRequestTable::new(
5802            SpeculativeSchedulerOptions::default().with_lookahead(false),
5803            SpeculativeExecutionTopology::Single,
5804        )
5805        .unwrap();
5806        let id = table
5807            .submit(
5808                &mut executor,
5809                &mut cache,
5810                vec![4],
5811                SpeculativeConfig {
5812                    max_tokens: 3,
5813                    max_draft_tokens: 2,
5814                    temperature: 0.7,
5815                    eos_token_ids: Vec::new(),
5816                },
5817                empty_transaction_runtime(3, trace, attempts),
5818                SpeculativeRandomness {
5819                    target: Some(0),
5820                    draft: Some(0),
5821                },
5822                false,
5823                (),
5824            )
5825            .unwrap();
5826        table.step(&mut executor, false, ()).unwrap();
5827        assert!(table.requests[id.index()].block().is_some());
5828        assert!(!table.requests[id.index()].has_pending_verification());
5829        let status = table.requests[id.index()].status();
5830        let tokens = table.requests[id.index()].sequence().tokens().to_vec();
5831        let finish_reason = table.requests[id.index()].sequence().finish_reason();
5832        table.requests[id.index()].runtime.publisher.fail_cancelled = true;
5833
5834        let error = table.cancel(id).unwrap_err();
5835
5836        assert!(matches!(error, SpeculativeDriverError::Output(_)));
5837        assert_eq!(table.requests[id.index()].status(), status);
5838        assert_eq!(table.requests[id.index()].sequence().tokens(), tokens);
5839        assert_eq!(
5840            table.requests[id.index()].sequence().finish_reason(),
5841            finish_reason
5842        );
5843        assert!(table.requests[id.index()].block().is_some());
5844        assert!(!table.requests[id.index()].runtime.publisher.cancelled);
5845    }
5846
5847    #[test]
5848    fn publication_observer_failure_occurs_after_commit_but_before_publisher() {
5849        let publication_boundaries = Arc::new(AtomicUsize::new(0));
5850        let observer: Arc<dyn SpeculativeLifecycleObserver> = Arc::new({
5851            let publication_boundaries = Arc::clone(&publication_boundaries);
5852            move |stage| {
5853                if stage == SpeculativeLifecycleStage::Publication
5854                    && publication_boundaries.fetch_add(1, Ordering::SeqCst) == 1
5855                {
5856                    Err(SpeculativeOutputError::publication(
5857                        "injected lifecycle publication failure",
5858                    ))
5859                } else {
5860                    Ok(())
5861                }
5862            }
5863        });
5864        let trace = FailureTrace::default();
5865        let attempts = Rc::new(Cell::new(0));
5866        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
5867        let mut cache = transaction_cache(trace.clone());
5868        let config = SpeculativeConfig {
5869            max_tokens: 3,
5870            max_draft_tokens: 2,
5871            temperature: 0.7,
5872            eos_token_ids: Vec::new(),
5873        };
5874        let runtime = empty_transaction_runtime(config.max_tokens, trace.clone(), attempts.clone())
5875            .with_lifecycle_observer(observer);
5876        let mut table = SpeculativeRequestTable::new(
5877            SpeculativeSchedulerOptions::default().with_lookahead(false),
5878            SpeculativeExecutionTopology::Single,
5879        )
5880        .unwrap();
5881        table
5882            .submit(
5883                &mut executor,
5884                &mut cache,
5885                vec![4],
5886                config,
5887                runtime,
5888                SpeculativeRandomness {
5889                    target: Some(0),
5890                    draft: Some(0),
5891                },
5892                false,
5893                (),
5894            )
5895            .unwrap();
5896        table.step(&mut executor, false, ()).unwrap();
5897        table.step(&mut executor, false, ()).unwrap();
5898        let error = table.step(&mut executor, false, ()).unwrap_err();
5899        assert!(matches!(error, SpeculativeDriverError::Output(_)));
5900        assert_eq!(publication_boundaries.load(Ordering::SeqCst), 2);
5901        assert_eq!(attempts.get(), 1, "verification output reached publisher");
5902        assert!(trace.borrow().contains(&"commit"));
5903        assert_eq!(
5904            trace
5905                .borrow()
5906                .iter()
5907                .filter(|event| **event == "publish")
5908                .count(),
5909            1
5910        );
5911    }
5912
5913    #[test]
5914    fn request_table_accounts_nonzero_cache_replay_after_delayed_exact_completion() {
5915        let trace = FailureTrace::default();
5916        let attempts = Rc::new(Cell::new(0));
5917        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
5918        executor.replayed_tokens = 4;
5919        let mut cache = transaction_cache(trace.clone());
5920        let config = SpeculativeConfig {
5921            max_tokens: 3,
5922            max_draft_tokens: 2,
5923            temperature: 0.7,
5924            eos_token_ids: Vec::new(),
5925        };
5926        let mut table = SpeculativeRequestTable::new(
5927            SpeculativeSchedulerOptions::default().with_lookahead(false),
5928            SpeculativeExecutionTopology::Single,
5929        )
5930        .unwrap();
5931        let id = table
5932            .submit(
5933                &mut executor,
5934                &mut cache,
5935                vec![4],
5936                config.clone(),
5937                empty_transaction_runtime(config.max_tokens, trace.clone(), attempts.clone()),
5938                SpeculativeRandomness {
5939                    target: Some(0),
5940                    draft: Some(0),
5941                },
5942                false,
5943                (),
5944            )
5945            .unwrap();
5946        assert_eq!(attempts.get(), 1);
5947
5948        table.run(&mut executor, false, ()).unwrap();
5949        let output = table.finish().unwrap();
5950        assert_eq!(output.requests[0].id(), id);
5951        assert_eq!(output.requests[0].stats().target_tokens, 8);
5952        assert_eq!(output.requests[0].stats().emitted_tokens, 3);
5953        assert_eq!(attempts.get(), 2);
5954        assert_eq!(cache.target, [4, 5, 4, 1, 1]);
5955        assert_eq!(cache.draft, [1, 1, 1]);
5956        let trace = trace.borrow();
5957        assert!(
5958            trace.iter().position(|event| *event == "wait").unwrap()
5959                < trace.iter().position(|event| *event == "commit").unwrap()
5960        );
5961        assert_eq!(trace.iter().filter(|event| **event == "publish").count(), 2);
5962    }
5963
5964    #[test]
5965    fn request_table_completion_failure_restores_cache_before_any_new_publication() {
5966        let trace = FailureTrace::default();
5967        let attempts = Rc::new(Cell::new(0));
5968        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
5969        executor.fail_completion = true;
5970        let mut cache = transaction_cache(trace.clone());
5971        let config = SpeculativeConfig {
5972            max_tokens: 3,
5973            max_draft_tokens: 2,
5974            temperature: 0.7,
5975            eos_token_ids: Vec::new(),
5976        };
5977        let mut table = SpeculativeRequestTable::new(
5978            SpeculativeSchedulerOptions::default().with_lookahead(false),
5979            SpeculativeExecutionTopology::Single,
5980        )
5981        .unwrap();
5982        let id = table
5983            .submit(
5984                &mut executor,
5985                &mut cache,
5986                vec![4],
5987                config.clone(),
5988                empty_transaction_runtime(config.max_tokens, trace.clone(), attempts.clone()),
5989                SpeculativeRandomness {
5990                    target: Some(0),
5991                    draft: Some(0),
5992                },
5993                false,
5994                (),
5995            )
5996            .unwrap();
5997        assert_eq!(attempts.get(), 1, "only prefill is published");
5998        table.step(&mut executor, false, ()).unwrap();
5999        table.step(&mut executor, false, ()).unwrap();
6000        assert!(table.request(id).unwrap().has_pending_verification());
6001
6002        let error = table.step(&mut executor, false, ()).unwrap_err();
6003        assert_eq!(error.to_string(), "completion failed");
6004        assert_eq!(attempts.get(), 1, "failed verification publishes nothing");
6005        drop(table);
6006        assert_eq!(cache.target, [4, 5, 4]);
6007        assert_eq!(cache.draft, [4, 5]);
6008        let trace = trace.borrow();
6009        assert_eq!(trace.iter().filter(|event| **event == "publish").count(), 1);
6010        assert!(trace.contains(&"restore"));
6011    }
6012
6013    #[test]
6014    fn request_table_never_resolving_completion_retains_capacity_without_wait_or_publication() {
6015        let trace = FailureTrace::default();
6016        let attempts = Rc::new(Cell::new(0));
6017        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
6018        executor.ready_after_polls = None;
6019        let mut first_cache = transaction_cache(trace.clone());
6020        let mut second_cache = transaction_cache(trace.clone());
6021        let config = SpeculativeConfig {
6022            max_tokens: 4,
6023            max_draft_tokens: 2,
6024            temperature: 0.7,
6025            eos_token_ids: Vec::new(),
6026        };
6027        let mut table = SpeculativeRequestTable::new(
6028            SpeculativeSchedulerOptions::default().with_lookahead(false),
6029            SpeculativeExecutionTopology::Single,
6030        )
6031        .unwrap();
6032        let first = table
6033            .submit(
6034                &mut executor,
6035                &mut first_cache,
6036                vec![4],
6037                config.clone(),
6038                empty_transaction_runtime(config.max_tokens, trace.clone(), attempts.clone()),
6039                SpeculativeRandomness {
6040                    target: Some(0),
6041                    draft: Some(0),
6042                },
6043                false,
6044                (),
6045            )
6046            .unwrap();
6047        table.step(&mut executor, false, ()).unwrap();
6048        table.step(&mut executor, false, ()).unwrap();
6049        assert!(table.request(first).unwrap().has_pending_verification());
6050        table.cancel(first).unwrap();
6051
6052        let second = table
6053            .submit(
6054                &mut executor,
6055                &mut second_cache,
6056                vec![8],
6057                config.clone(),
6058                empty_transaction_runtime(config.max_tokens, trace.clone(), attempts.clone()),
6059                SpeculativeRandomness {
6060                    target: Some(10),
6061                    draft: Some(10),
6062                },
6063                false,
6064                (),
6065            )
6066            .unwrap();
6067        for _ in 0..4 {
6068            assert!(table.step(&mut executor, false, ()).unwrap());
6069        }
6070
6071        assert_eq!(
6072            table.status(first),
6073            Some(SpeculativeRequestStatus::TargetVerificationInFlight)
6074        );
6075        assert!(table.request(first).unwrap().has_pending_verification());
6076        assert_eq!(
6077            table.status(second),
6078            Some(SpeculativeRequestStatus::ReadyToSubmitVerification)
6079        );
6080        assert!(!table.request(second).unwrap().has_pending_verification());
6081        assert!(executor.completion_polls.get() >= 4);
6082        assert_eq!(executor.completion_drops.get(), 0);
6083        assert_eq!(executor.verification_drops.get(), 0);
6084        assert_eq!(attempts.get(), 2, "only the two prefills are published");
6085        {
6086            let trace = trace.borrow();
6087            assert!(!trace.contains(&"wait"));
6088            assert!(!trace.contains(&"commit"));
6089            assert!(!trace.contains(&"restore"));
6090            assert!(!trace.contains(&"cancel"));
6091            assert_eq!(trace.iter().filter(|event| **event == "publish").count(), 2);
6092        }
6093
6094        drop(table);
6095        assert_eq!(first_cache.target, [4, 5, 4, 1, 1, 1]);
6096        assert_eq!(first_cache.draft, [4, 5]);
6097        assert_eq!(second_cache.target, [4, 5, 8]);
6098        assert_eq!(second_cache.draft, [4, 5]);
6099    }
6100
6101    #[test]
6102    fn delayed_completion_retains_every_resource_and_waits_before_commit_or_publication() {
6103        let trace = FailureTrace::default();
6104        let attempts = Rc::new(Cell::new(0));
6105        let draft_drops = Rc::new(Cell::new(0));
6106        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
6107        let mut cache = transaction_cache(trace.clone());
6108        let pending = submit_verification_transaction(
6109            &mut executor,
6110            &mut cache,
6111            5,
6112            transaction_block(trace.clone(), draft_drops.clone()),
6113            (),
6114        )
6115        .unwrap();
6116
6117        assert!(!executor.ready.get());
6118        assert_eq!(executor.completion_drops.get(), 0);
6119        assert_eq!(executor.verification_drops.get(), 0);
6120        assert_eq!(draft_drops.get(), 0);
6121        assert_eq!(attempts.get(), 0);
6122        assert_eq!(cache.target, [4, 5, 5, 1, 1]);
6123
6124        let mut runtime = transaction_runtime(
6125            trace.clone(),
6126            attempts.clone(),
6127            false,
6128            GenerationCancellationToken::new(),
6129        );
6130        resolve_commit_and_publish(
6131            &mut executor,
6132            &mut cache,
6133            pending,
6134            &mut runtime,
6135            Some(&0),
6136            0.7,
6137            SpeculativeStats::default(),
6138            SpeculativeSchedulerOptions::default(),
6139            (),
6140        )
6141        .unwrap();
6142
6143        assert!(executor.ready.get());
6144        assert_eq!(executor.completion_drops.get(), 1);
6145        assert_eq!(executor.verification_drops.get(), 1);
6146        assert_eq!(draft_drops.get(), 1);
6147        assert_eq!(executor.committed_draft.borrow().as_slice(), [5, 1, 1]);
6148        assert_eq!(cache.target, [4, 5, 5, 1]);
6149        assert_eq!(cache.draft, [5, 1, 1]);
6150        assert_eq!(
6151            trace.borrow().as_slice(),
6152            [
6153                "submit",
6154                "wait",
6155                "commit",
6156                "drop_draft",
6157                "drop_verification",
6158                "publish",
6159                "drop_completion"
6160            ]
6161        );
6162    }
6163
6164    #[test]
6165    fn completion_failure_drops_resources_and_restores_every_checkpoint_without_publication() {
6166        let trace = FailureTrace::default();
6167        let attempts = Rc::new(Cell::new(0));
6168        let draft_drops = Rc::new(Cell::new(0));
6169        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
6170        executor.fail_completion = true;
6171        let mut cache = transaction_cache(trace.clone());
6172        let pending = submit_verification_transaction(
6173            &mut executor,
6174            &mut cache,
6175            5,
6176            transaction_block(trace.clone(), draft_drops.clone()),
6177            (),
6178        )
6179        .unwrap();
6180        let mut runtime = transaction_runtime(
6181            trace.clone(),
6182            attempts.clone(),
6183            false,
6184            GenerationCancellationToken::new(),
6185        );
6186
6187        let error = resolve_commit_and_publish(
6188            &mut executor,
6189            &mut cache,
6190            pending,
6191            &mut runtime,
6192            Some(&0),
6193            0.7,
6194            SpeculativeStats::default(),
6195            SpeculativeSchedulerOptions::default(),
6196            (),
6197        )
6198        .err()
6199        .unwrap();
6200
6201        assert_eq!(error.to_string(), "completion failed");
6202        assert_eq!(cache.target, [4, 5]);
6203        assert_eq!(cache.draft, [4, 5]);
6204        assert_eq!(attempts.get(), 0);
6205        assert_eq!(executor.completion_drops.get(), 1);
6206        assert_eq!(executor.verification_drops.get(), 1);
6207        assert_eq!(draft_drops.get(), 1);
6208        assert!(executor.committed_draft.borrow().is_empty());
6209        let (sampler, sequence, constraint, publisher) = runtime.into_parts();
6210        assert!(sampler.committed.is_empty());
6211        assert_eq!(sequence.tokens(), [5]);
6212        assert!(constraint.tokens.is_empty());
6213        assert!(publisher.tokens.is_empty());
6214        assert_eq!(
6215            trace.borrow().as_slice(),
6216            [
6217                "submit",
6218                "wait",
6219                "drop_completion",
6220                "restore",
6221                "drop_draft",
6222                "drop_verification"
6223            ]
6224        );
6225    }
6226
6227    #[test]
6228    fn invalid_direct_completion_policy_disposes_work_before_restore() {
6229        let trace = FailureTrace::default();
6230        let attempts = Rc::new(Cell::new(0));
6231        let draft_drops = Rc::new(Cell::new(0));
6232        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
6233        executor.ready_after_polls = None;
6234        let mut cache = transaction_cache(trace.clone());
6235        let pending = submit_verification_transaction(
6236            &mut executor,
6237            &mut cache,
6238            5,
6239            transaction_block(trace.clone(), draft_drops),
6240            (),
6241        )
6242        .unwrap();
6243        let mut runtime = transaction_runtime(
6244            trace.clone(),
6245            attempts,
6246            false,
6247            GenerationCancellationToken::new(),
6248        );
6249        let options = SpeculativeSchedulerOptions {
6250            completion_timeout_milliseconds: 0,
6251            ..SpeculativeSchedulerOptions::default()
6252        };
6253
6254        let error = resolve_commit_and_publish(
6255            &mut executor,
6256            &mut cache,
6257            pending,
6258            &mut runtime,
6259            Some(&0),
6260            0.7,
6261            SpeculativeStats::default(),
6262            options,
6263            (),
6264        )
6265        .err()
6266        .expect("invalid completion policy must fail");
6267
6268        assert!(matches!(
6269            error,
6270            SpeculativeDriverError::Generation(GenerationError::ZeroSpeculativeCompletionTimeout)
6271        ));
6272        let trace = trace.borrow();
6273        let drop = trace
6274            .iter()
6275            .position(|event| *event == "drop_completion")
6276            .unwrap();
6277        let restore = trace.iter().position(|event| *event == "restore").unwrap();
6278        assert!(drop < restore);
6279    }
6280
6281    #[test]
6282    fn publisher_failure_restores_backend_and_keeps_logical_state_uncommitted() {
6283        let trace = FailureTrace::default();
6284        let attempts = Rc::new(Cell::new(0));
6285        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
6286        let mut cache = transaction_cache(trace.clone());
6287        let pending = submit_verification_transaction(
6288            &mut executor,
6289            &mut cache,
6290            5,
6291            transaction_block(trace.clone(), Rc::new(Cell::new(0))),
6292            (),
6293        )
6294        .unwrap();
6295        let mut runtime = transaction_runtime(
6296            trace.clone(),
6297            attempts.clone(),
6298            true,
6299            GenerationCancellationToken::new(),
6300        );
6301
6302        let error = resolve_commit_and_publish(
6303            &mut executor,
6304            &mut cache,
6305            pending,
6306            &mut runtime,
6307            Some(&0),
6308            0.7,
6309            SpeculativeStats::default(),
6310            SpeculativeSchedulerOptions::default(),
6311            (),
6312        )
6313        .err()
6314        .unwrap();
6315
6316        assert!(matches!(error, SpeculativeDriverError::Output(_)));
6317        assert_eq!(cache.target, [4, 5]);
6318        assert_eq!(cache.draft, [4, 5]);
6319        assert_eq!(executor.committed_draft.borrow().as_slice(), [5, 1, 1]);
6320        let (sampler, sequence, constraint, publisher) = runtime.into_parts();
6321        assert!(sampler.committed.is_empty());
6322        assert_eq!(sequence.tokens(), [5]);
6323        assert!(constraint.tokens.is_empty());
6324        assert!(publisher.tokens.is_empty());
6325        assert_eq!(attempts.get(), 1);
6326        assert!(trace.borrow().contains(&"restore"));
6327    }
6328
6329    #[test]
6330    fn commit_failure_restores_target_and_draft_checkpoints_without_promotion() {
6331        let trace = FailureTrace::default();
6332        let attempts = Rc::new(Cell::new(0));
6333        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
6334        executor.fail_commit = true;
6335        let mut cache = transaction_cache(trace.clone());
6336        let pending = submit_verification_transaction(
6337            &mut executor,
6338            &mut cache,
6339            5,
6340            transaction_block(trace.clone(), Rc::new(Cell::new(0))),
6341            (),
6342        )
6343        .unwrap();
6344        let mut runtime = transaction_runtime(
6345            trace.clone(),
6346            attempts.clone(),
6347            false,
6348            GenerationCancellationToken::new(),
6349        );
6350
6351        let error = resolve_commit_and_publish(
6352            &mut executor,
6353            &mut cache,
6354            pending,
6355            &mut runtime,
6356            Some(&0),
6357            0.7,
6358            SpeculativeStats::default(),
6359            SpeculativeSchedulerOptions::default(),
6360            (),
6361        )
6362        .err()
6363        .unwrap();
6364
6365        assert_eq!(error.to_string(), "commit failed");
6366        assert_eq!(cache.target, [4, 5]);
6367        assert_eq!(cache.draft, [4, 5]);
6368        assert_eq!(executor.committed_draft.borrow().as_slice(), [5, 1, 1]);
6369        assert_eq!(attempts.get(), 0);
6370        let (sampler, sequence, constraint, publisher) = runtime.into_parts();
6371        assert!(sampler.committed.is_empty());
6372        assert_eq!(sequence.tokens(), [5]);
6373        assert!(constraint.tokens.is_empty());
6374        assert!(publisher.tokens.is_empty());
6375        let trace = trace.borrow();
6376        assert!(
6377            trace.iter().position(|event| *event == "commit").unwrap()
6378                < trace.iter().position(|event| *event == "restore").unwrap()
6379        );
6380    }
6381
6382    #[test]
6383    fn pending_cancellation_waits_commits_safe_prefix_and_discards_draft_state() {
6384        let trace = FailureTrace::default();
6385        let attempts = Rc::new(Cell::new(0));
6386        let draft_drops = Rc::new(Cell::new(0));
6387        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
6388        let mut cache = transaction_cache(trace.clone());
6389        let pending = submit_verification_transaction(
6390            &mut executor,
6391            &mut cache,
6392            5,
6393            transaction_block(trace.clone(), draft_drops.clone()),
6394            (),
6395        )
6396        .unwrap();
6397        let cancellation = GenerationCancellationToken::new();
6398        cancellation.cancel();
6399        let mut runtime = transaction_runtime(trace.clone(), attempts.clone(), false, cancellation);
6400
6401        cancel_pending_verification(
6402            &mut executor,
6403            &mut cache,
6404            pending,
6405            &mut runtime,
6406            SpeculativeStats::default(),
6407            SpeculativeSchedulerOptions::default()
6408                .completion_wait()
6409                .unwrap(),
6410            (),
6411        )
6412        .unwrap();
6413
6414        assert_eq!(cache.target, [4, 5, 5]);
6415        assert_eq!(cache.draft, [5, 1, 1]);
6416        assert_eq!(executor.committed_draft.borrow().as_slice(), [5, 1, 1]);
6417        assert_eq!(draft_drops.get(), 1);
6418        assert_eq!(executor.completion_drops.get(), 1);
6419        assert_eq!(executor.verification_drops.get(), 1);
6420        let (sampler, sequence, constraint, publisher) = runtime.into_parts();
6421        assert!(sampler.committed.is_empty());
6422        assert_eq!(sequence.tokens(), [5]);
6423        assert_eq!(sequence.finish_reason(), Some(FinishReason::Cancelled));
6424        assert!(constraint.tokens.is_empty());
6425        assert!(publisher.tokens.is_empty());
6426        assert!(publisher.cancelled);
6427        assert_eq!(attempts.get(), 1);
6428        let trace = trace.borrow();
6429        assert!(
6430            trace.iter().position(|event| *event == "wait").unwrap()
6431                < trace.iter().position(|event| *event == "commit").unwrap()
6432        );
6433        assert!(
6434            trace.iter().position(|event| *event == "commit").unwrap()
6435                < trace.iter().position(|event| *event == "cancel").unwrap()
6436        );
6437    }
6438
6439    #[test]
6440    fn restore_failure_is_an_explicit_indeterminate_backend_error_without_publication() {
6441        let trace = FailureTrace::default();
6442        let attempts = Rc::new(Cell::new(0));
6443        let mut executor = TransactionExecutor::new(trace.clone(), attempts.clone());
6444        executor.fail_completion = true;
6445        let mut cache = transaction_cache(trace.clone());
6446        cache.fail_restore = true;
6447        let pending = submit_verification_transaction(
6448            &mut executor,
6449            &mut cache,
6450            5,
6451            transaction_block(trace.clone(), Rc::new(Cell::new(0))),
6452            (),
6453        )
6454        .unwrap();
6455        let mut runtime = transaction_runtime(
6456            trace.clone(),
6457            attempts.clone(),
6458            false,
6459            GenerationCancellationToken::new(),
6460        );
6461
6462        let error = resolve_commit_and_publish(
6463            &mut executor,
6464            &mut cache,
6465            pending,
6466            &mut runtime,
6467            Some(&0),
6468            0.7,
6469            SpeculativeStats::default(),
6470            SpeculativeSchedulerOptions::default(),
6471            (),
6472        )
6473        .err()
6474        .unwrap();
6475
6476        assert_eq!(error.to_string(), "restore failed");
6477        assert_eq!(cache.target, [4, 5, 5, 1, 1]);
6478        assert_eq!(cache.draft, [4, 5]);
6479        assert_eq!(attempts.get(), 0);
6480        let (sampler, sequence, constraint, publisher) = runtime.into_parts();
6481        assert!(sampler.committed.is_empty());
6482        assert_eq!(sequence.tokens(), [5]);
6483        assert!(constraint.tokens.is_empty());
6484        assert!(publisher.tokens.is_empty());
6485        assert_eq!(
6486            trace.borrow().as_slice()[..4],
6487            ["submit", "wait", "drop_completion", "restore"]
6488        );
6489    }
6490}