Skip to main content

eredu_core/
speculative.rs

1//! High-level contracts and orchestration for speculative execution backends.
2
3use crate::{
4    backend::{
5        Completion, ModelRuntime, SpeculativeTokenFilterController, Submission,
6        TextGenerationBackend, TextGenerationConfig,
7    },
8    generation::{
9        FinishReason, GenerationCancellationToken, GenerationError, GenerationSequence,
10        SemanticEvent, SpeculativeCancellationDisposition, SpeculativeConfig, SpeculativeRequestId,
11        SpeculativeRequestLifecycle, SpeculativeRequestStatus, SpeculativeRound,
12        SpeculativeSchedulerOptions, TokenTerminalSignals,
13    },
14};
15use serde::{Deserialize, Serialize};
16use std::time::{Duration, Instant};
17
18/// Draft-model source selected for one speculative-generation request.
19#[non_exhaustive]
20pub enum SpeculativeDraft<'a, D> {
21    /// Separately prepared assistant owned by the selected backend.
22    External(&'a mut D),
23    /// Draft heads embedded in the selected target model.
24    Embedded,
25}
26
27/// One backend-independent speculative-generation result.
28pub struct SpeculativeGenerationOutput {
29    /// Canonical emitted token ids, including terminal EOS when emitted.
30    token_ids: Vec<u32>,
31    /// Portable terminal reason selected by the generation lifecycle.
32    finish_reason: FinishReason,
33    /// Portable speculative execution telemetry.
34    stats: SpeculativeStats,
35}
36
37impl SpeculativeGenerationOutput {
38    /// Creates one completed portable result.
39    pub fn new(token_ids: Vec<u32>, finish_reason: FinishReason, stats: SpeculativeStats) -> Self {
40        Self {
41            token_ids,
42            finish_reason,
43            stats,
44        }
45    }
46
47    /// Canonical emitted token ids.
48    pub fn token_ids(&self) -> &[u32] {
49        &self.token_ids
50    }
51    /// Terminal generation reason.
52    pub const fn finish_reason(&self) -> FinishReason {
53        self.finish_reason
54    }
55    /// Portable speculative telemetry.
56    pub const fn stats(&self) -> &SpeculativeStats {
57        &self.stats
58    }
59}
60
61/// Completed speculative requests plus aggregate fair-scheduler telemetry.
62pub struct SpeculativeGenerationBatchOutput {
63    /// Per-request results in submission order.
64    requests: Vec<SpeculativeGenerationOutput>,
65    /// Aggregate scheduler telemetry.
66    scheduler: SpeculativeSchedulerStats,
67}
68
69impl SpeculativeGenerationBatchOutput {
70    /// Creates a completed batch in stable submission order.
71    pub fn new(
72        requests: Vec<SpeculativeGenerationOutput>,
73        scheduler: SpeculativeSchedulerStats,
74    ) -> Self {
75        Self {
76            requests,
77            scheduler,
78        }
79    }
80    /// Per-request results in submission order.
81    pub fn requests(&self) -> &[SpeculativeGenerationOutput] {
82        &self.requests
83    }
84    /// Consumes the batch and returns its request results.
85    pub fn into_requests(self) -> Vec<SpeculativeGenerationOutput> {
86        self.requests
87    }
88    /// Aggregate scheduler telemetry.
89    pub const fn scheduler(&self) -> &SpeculativeSchedulerStats {
90        &self.scheduler
91    }
92    /// Appends a result while adapting another backend-neutral execution path.
93    pub fn push_request(&mut self, request: SpeculativeGenerationOutput) {
94        self.requests.push(request);
95    }
96    /// Clears adapted request results while retaining scheduler telemetry.
97    pub fn clear_requests(&mut self) {
98        self.requests.clear();
99    }
100}
101
102/// One independently executable lane in a speculative batch.
103pub struct SpeculativeGenerationLane<'a, B, C>
104where
105    B: TextGenerationBackend,
106    C: SpeculativeTokenFilterController,
107{
108    /// Backend-owned prompt prepared by the selected session backend.
109    prompt: Option<B::Prompt>,
110    /// Fully resolved portable sampling configuration and random seed.
111    generation: Option<TextGenerationConfig>,
112    /// Resolved token budget, proposal width, temperature, and EOS ids.
113    config: Option<SpeculativeConfig>,
114    /// Portable canonical grammar state.
115    constraint: Option<C>,
116    /// Transactional decoded semantic parser state.
117    semantic: Option<Box<dyn SpeculativeSemanticState>>,
118    /// Cooperative cancellation owned by this lane.
119    cancellation: Option<GenerationCancellationToken>,
120    /// Called synchronously for canonical events from this lane.
121    on_event: Option<Box<dyn FnMut(SemanticEvent) + 'a>>,
122}
123
124impl<'a, B, C> SpeculativeGenerationLane<'a, B, C>
125where
126    B: TextGenerationBackend,
127    C: SpeculativeTokenFilterController,
128{
129    /// Creates one independently executable speculative lane.
130    #[allow(clippy::too_many_arguments)]
131    pub fn new(
132        prompt: B::Prompt,
133        generation: TextGenerationConfig,
134        config: SpeculativeConfig,
135        constraint: C,
136        semantic: Box<dyn SpeculativeSemanticState>,
137        cancellation: GenerationCancellationToken,
138        on_event: Box<dyn FnMut(SemanticEvent) + 'a>,
139    ) -> Self {
140        Self {
141            prompt: Some(prompt),
142            generation: Some(generation),
143            config: Some(config),
144            constraint: Some(constraint),
145            semantic: Some(semantic),
146            cancellation: Some(cancellation),
147            on_event: Some(on_event),
148        }
149    }
150    /// Takes the backend-owned prompt exactly once.
151    pub fn take_prompt(&mut self) -> B::Prompt {
152        self.prompt.take().expect("lane prompt already taken")
153    }
154    /// Borrows the backend-owned prompt before preparation consumes it.
155    pub fn prompt(&self) -> &B::Prompt {
156        self.prompt.as_ref().expect("lane prompt already taken")
157    }
158    /// Takes the resolved generation controls exactly once.
159    pub fn take_generation(&mut self) -> TextGenerationConfig {
160        self.generation
161            .take()
162            .expect("lane generation already taken")
163    }
164    /// Borrows resolved generation controls.
165    pub fn generation(&self) -> &TextGenerationConfig {
166        self.generation
167            .as_ref()
168            .expect("lane generation already taken")
169    }
170    /// Takes the speculative controls exactly once.
171    pub fn take_config(&mut self) -> SpeculativeConfig {
172        self.config.take().expect("lane config already taken")
173    }
174    /// Borrows speculative controls.
175    pub fn config(&self) -> &SpeculativeConfig {
176        self.config.as_ref().expect("lane config already taken")
177    }
178    /// Takes the grammar controller exactly once.
179    pub fn take_constraint(&mut self) -> C {
180        self.constraint
181            .take()
182            .expect("lane constraint already taken")
183    }
184    /// Takes semantic state exactly once.
185    pub fn take_semantic(&mut self) -> Box<dyn SpeculativeSemanticState> {
186        self.semantic
187            .take()
188            .expect("lane semantic state already taken")
189    }
190    /// Takes cancellation state exactly once.
191    pub fn take_cancellation(&mut self) -> GenerationCancellationToken {
192        self.cancellation
193            .take()
194            .expect("lane cancellation already taken")
195    }
196    /// Takes the event callback exactly once.
197    pub fn take_on_event(&mut self) -> Box<dyn FnMut(SemanticEvent) + 'a> {
198        self.on_event
199            .take()
200            .expect("lane event callback already taken")
201    }
202}
203
204/// Backend-preparation input for one or more speculative lanes.
205pub struct SpeculativeGenerationBatchRequest<'a, B, D, C>
206where
207    B: TextGenerationBackend,
208    C: SpeculativeTokenFilterController,
209{
210    /// Embedded or separately prepared draft-model selection.
211    drafting: Option<SpeculativeDraft<'a, D>>,
212    /// Independently prepared speculative lanes.
213    lanes: Option<Vec<SpeculativeGenerationLane<'a, B, C>>>,
214    /// Target tokenizer vocabulary identity used for drafter compatibility.
215    tokenizer_fingerprint: [u8; 32],
216}
217
218impl<'a, B, D, C> SpeculativeGenerationBatchRequest<'a, B, D, C>
219where
220    B: TextGenerationBackend,
221    C: SpeculativeTokenFilterController,
222{
223    /// Creates one validated backend-preparation request.
224    pub fn new(
225        drafting: SpeculativeDraft<'a, D>,
226        lanes: Vec<SpeculativeGenerationLane<'a, B, C>>,
227        tokenizer_fingerprint: [u8; 32],
228    ) -> Self {
229        Self {
230            drafting: Some(drafting),
231            lanes: Some(lanes),
232            tokenizer_fingerprint,
233        }
234    }
235    /// Target tokenizer vocabulary identity.
236    pub const fn tokenizer_fingerprint(&self) -> [u8; 32] {
237        self.tokenizer_fingerprint
238    }
239    /// Takes draft selection exactly once.
240    pub fn take_drafting(&mut self) -> SpeculativeDraft<'a, D> {
241        self.drafting.take().expect("draft selection already taken")
242    }
243    /// Takes prepared lanes exactly once.
244    pub fn take_lanes(&mut self) -> Vec<SpeculativeGenerationLane<'a, B, C>> {
245        self.lanes.take().expect("speculative lanes already taken")
246    }
247}
248
249/// Optional speculative model-session capability.
250///
251/// Implementations prepare native executors, caches, sampling state, and
252/// execution placement, then expose them to the caller-provided neutral
253/// visitor. The backend must not drive request lifecycles or fair scheduling.
254/// A backend is selected for the complete model session; requests cannot mix
255/// runtime implementations.
256pub trait SpeculativeGenerationBackend: TextGenerationBackend {
257    /// Backend-owned separately prepared draft model.
258    type Drafter;
259
260    /// Reports fail-closed speculative support for the selected model session.
261    fn speculative_capability(runtime: &ModelRuntime<Self>) -> SpeculativeCapability;
262
263    /// Prepares native execution resources and lends them to neutral orchestration.
264    fn with_speculative_execution<C, V>(
265        runtime: &mut ModelRuntime<Self>,
266        request: SpeculativeGenerationBatchRequest<'_, Self, Self::Drafter, C>,
267        visitor: V,
268    ) -> Result<SpeculativeGenerationBatchOutput, Self::Error>
269    where
270        C: SpeculativeTokenFilterController,
271        V: SpeculativeGenerationVisitor;
272}
273
274/// Relationship between target and assistant execution placements.
275#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
276#[serde(rename_all = "snake_case")]
277#[non_exhaustive]
278pub enum SpeculativeExecutionTopology {
279    /// Target and assistant operations share one ordered execution queue.
280    #[default]
281    Single,
282    /// Distinct queues share one device and can use ordered handoffs.
283    SameDeviceSplit,
284    /// Target and assistant use different devices and require transfers.
285    CrossDeviceSplit,
286}
287
288impl std::fmt::Display for SpeculativeExecutionTopology {
289    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        formatter.write_str(match self {
291            Self::Single => "single",
292            Self::SameDeviceSplit => "same-device-split",
293            Self::CrossDeviceSplit => "cross-device-split",
294        })
295    }
296}
297
298/// How a model exposes speculative draft-token weights.
299#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
300#[serde(rename_all = "snake_case")]
301#[non_exhaustive]
302pub enum SpeculativeDraftSource {
303    /// Drafting weights live in a separately prepared model.
304    Separate,
305    /// Drafting weights are embedded in the selected target model.
306    Embedded,
307}
308
309/// Fail-closed speculative-decoding capability of a prepared model session.
310#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
311#[serde(rename_all = "snake_case")]
312#[non_exhaustive]
313pub enum SpeculativeCapability {
314    /// The model does not advertise executable draft weights.
315    Unavailable,
316    /// Speculative execution is available with the stated draft source.
317    Ready {
318        /// Location of the drafting weights.
319        draft_source: SpeculativeDraftSource,
320    },
321    /// Draft weights exist, but this backend cannot execute them.
322    Unsupported {
323        /// Location of the drafting weights.
324        draft_source: SpeculativeDraftSource,
325        /// Stable architecture identity reported by the backend.
326        architecture: String,
327    },
328}
329
330/// Statistics collected from one speculative sequence.
331#[derive(Debug, Clone, Default)]
332pub struct SpeculativeStats {
333    /// Relationship between the request's target and draft execution placements.
334    execution_topology: SpeculativeExecutionTopology,
335    /// Target tokens evaluated during prefill and verification.
336    target_tokens: usize,
337    /// Assistant tokens proposed.
338    draft_tokens: usize,
339    /// Assistant tokens accepted by target verification.
340    accepted_tokens: usize,
341    /// Number of target verification rounds.
342    rounds: usize,
343    /// Accepted proposal count for each round.
344    accept_lens: Vec<usize>,
345    /// Tokens emitted, including a terminal EOS token when one is produced.
346    emitted_tokens: usize,
347    /// Tokens drafted on an optimistic continuation.
348    optimistic_draft_tokens: usize,
349    /// Optimistic continuation blocks drafted.
350    optimistic_draft_blocks: usize,
351    /// Optimistically drafted tokens promoted after full acceptance.
352    reused_optimistic_tokens: usize,
353    /// Optimistic continuation blocks promoted after full acceptance.
354    reused_optimistic_blocks: usize,
355    /// First optimistic tokens consumed by matching target bonuses.
356    consumed_optimistic_tokens: usize,
357    /// Optimistically drafted tokens discarded.
358    discarded_optimistic_tokens: usize,
359    /// Optimistic continuation blocks discarded.
360    discarded_optimistic_blocks: usize,
361    /// Target bonus tokens emitted while an optimistic branch existed.
362    optimistic_target_bonus_tokens: usize,
363    /// Non-terminal target bonuses matching the first optimistic token.
364    optimistic_bonus_matches: usize,
365    /// Non-terminal target bonuses differing from the first optimistic token.
366    optimistic_bonus_mismatches: usize,
367    /// Whether deterministic cost accounting disabled further optimistic branches.
368    adaptive_lookahead_disabled: bool,
369    /// Host wall time spent producing optional same-request branches.
370    optimistic_draft_time: Duration,
371    /// Host wall time retained target verification remained in flight.
372    verification_in_flight_time: Duration,
373    /// Whether architecture component timings were collected.
374    component_timings_collected: bool,
375    /// Device execution time spent encoding committed target context.
376    draft_context_time: Duration,
377    /// Device execution time spent executing assistant proposal blocks.
378    draft_assistant_time: Duration,
379    /// Device execution time spent projecting proposal states to logits.
380    draft_head_time: Duration,
381    /// Device execution time spent executing target verification passes.
382    target_verification_time: Duration,
383    /// Scheduler operations performed for this request.
384    scheduler_turns: usize,
385    /// Draft turns performed while another request had target work in flight.
386    cross_request_draft_opportunities: usize,
387    /// Wall-clock generation duration.
388    elapsed: Duration,
389}
390
391impl SpeculativeStats {
392    /// Selected target/draft placement relationship.
393    pub const fn execution_topology(&self) -> SpeculativeExecutionTopology {
394        self.execution_topology
395    }
396    /// Target tokens evaluated.
397    pub const fn target_tokens(&self) -> usize {
398        self.target_tokens
399    }
400    /// Assistant tokens proposed.
401    pub const fn draft_tokens(&self) -> usize {
402        self.draft_tokens
403    }
404    /// Assistant tokens accepted.
405    pub const fn accepted_tokens(&self) -> usize {
406        self.accepted_tokens
407    }
408    /// Target verification rounds.
409    pub const fn rounds(&self) -> usize {
410        self.rounds
411    }
412    /// Accepted proposal count per round.
413    pub fn accept_lens(&self) -> &[usize] {
414        &self.accept_lens
415    }
416    /// Emitted token count.
417    pub const fn emitted_tokens(&self) -> usize {
418        self.emitted_tokens
419    }
420    /// Optimistically drafted token count.
421    pub const fn optimistic_draft_tokens(&self) -> usize {
422        self.optimistic_draft_tokens
423    }
424    /// Optimistic block count.
425    pub const fn optimistic_draft_blocks(&self) -> usize {
426        self.optimistic_draft_blocks
427    }
428    /// Reused optimistic token count.
429    pub const fn reused_optimistic_tokens(&self) -> usize {
430        self.reused_optimistic_tokens
431    }
432    /// Reused optimistic block count.
433    pub const fn reused_optimistic_blocks(&self) -> usize {
434        self.reused_optimistic_blocks
435    }
436    /// Optimistic tokens consumed by target bonuses.
437    pub const fn consumed_optimistic_tokens(&self) -> usize {
438        self.consumed_optimistic_tokens
439    }
440    /// Discarded optimistic token count.
441    pub const fn discarded_optimistic_tokens(&self) -> usize {
442        self.discarded_optimistic_tokens
443    }
444    /// Discarded optimistic block count.
445    pub const fn discarded_optimistic_blocks(&self) -> usize {
446        self.discarded_optimistic_blocks
447    }
448    /// Target bonuses emitted while an optimistic branch existed.
449    pub const fn optimistic_target_bonus_tokens(&self) -> usize {
450        self.optimistic_target_bonus_tokens
451    }
452    /// Matching optimistic bonus count.
453    pub const fn optimistic_bonus_matches(&self) -> usize {
454        self.optimistic_bonus_matches
455    }
456    /// Mismatching optimistic bonus count.
457    pub const fn optimistic_bonus_mismatches(&self) -> usize {
458        self.optimistic_bonus_mismatches
459    }
460    /// Whether adaptive lookahead is disabled.
461    pub const fn adaptive_lookahead_disabled(&self) -> bool {
462        self.adaptive_lookahead_disabled
463    }
464    /// Time spent drafting optimistic branches.
465    pub const fn optimistic_draft_time(&self) -> Duration {
466        self.optimistic_draft_time
467    }
468    /// Time retained verification remained in flight.
469    pub const fn verification_in_flight_time(&self) -> Duration {
470        self.verification_in_flight_time
471    }
472    /// Whether component timings were collected.
473    pub const fn component_timings_collected(&self) -> bool {
474        self.component_timings_collected
475    }
476    /// Draft-context device time.
477    pub const fn draft_context_time(&self) -> Duration {
478        self.draft_context_time
479    }
480    /// Draft-assistant device time.
481    pub const fn draft_assistant_time(&self) -> Duration {
482        self.draft_assistant_time
483    }
484    /// Draft-head device time.
485    pub const fn draft_head_time(&self) -> Duration {
486        self.draft_head_time
487    }
488    /// Target-verification device time.
489    pub const fn target_verification_time(&self) -> Duration {
490        self.target_verification_time
491    }
492    /// Scheduler turns for this request.
493    pub const fn scheduler_turns(&self) -> usize {
494        self.scheduler_turns
495    }
496    /// Draft turns performed beside other in-flight target work.
497    pub const fn cross_request_draft_opportunities(&self) -> usize {
498        self.cross_request_draft_opportunities
499    }
500    /// Wall-clock generation duration.
501    pub const fn elapsed(&self) -> Duration {
502        self.elapsed
503    }
504
505    /// Adds backend-measured component timings without exposing mutable fields.
506    pub fn add_component_timings(
507        &mut self,
508        draft_context: Duration,
509        draft_assistant: Duration,
510        draft_head: Duration,
511        target_verification: Duration,
512    ) {
513        self.draft_context_time += draft_context;
514        self.draft_assistant_time += draft_assistant;
515        self.draft_head_time += draft_head;
516        self.target_verification_time += target_verification;
517        self.component_timings_collected = true;
518    }
519
520    /// Adds completed scheduler rounds to portable telemetry.
521    pub fn add_scheduler_rounds(&mut self, rounds: usize) {
522        self.rounds += rounds;
523    }
524
525    /// Records aggregate optimistic work used by adaptive-lookahead policy.
526    pub fn record_optimistic_accounting(
527        &mut self,
528        drafted_blocks: usize,
529        reused_tokens: usize,
530        discarded_tokens: usize,
531    ) {
532        self.optimistic_draft_blocks += drafted_blocks;
533        self.reused_optimistic_tokens += reused_tokens;
534        self.discarded_optimistic_tokens += discarded_tokens;
535    }
536
537    /// Clears the cached adaptive-lookahead decision before policy re-evaluation.
538    pub fn reset_adaptive_lookahead_decision(&mut self) {
539        self.adaptive_lookahead_disabled = false;
540    }
541
542    /// Fraction of proposed tokens accepted by the target.
543    pub fn accept_rate(&self) -> f64 {
544        if self.draft_tokens == 0 {
545            0.0
546        } else {
547            self.accepted_tokens as f64 / self.draft_tokens as f64
548        }
549    }
550
551    /// Re-evaluates whether optional lookahead remains profitable.
552    pub fn update_adaptive_lookahead(&mut self, options: SpeculativeSchedulerOptions) {
553        if !options.adaptive_lookahead
554            || self.adaptive_lookahead_disabled
555            || self.optimistic_draft_blocks < options.adaptive_lookahead_min_blocks
556        {
557            return;
558        }
559        self.adaptive_lookahead_disabled = self.reused_optimistic_tokens == 0
560            || self.reused_optimistic_tokens < self.discarded_optimistic_tokens;
561    }
562}
563
564/// Aggregate bounded-scheduler telemetry.
565#[derive(Debug, Clone, Default)]
566pub struct SpeculativeSchedulerStats {
567    /// Relationship between scheduler target and draft placements.
568    execution_topology: SpeculativeExecutionTopology,
569    /// Total scheduler operations.
570    turns: usize,
571    /// Draft turns performed while another request was being verified.
572    cross_request_draft_opportunities: usize,
573    /// Maximum simultaneously retained target verification transactions.
574    peak_in_flight_verifications: usize,
575    /// Maximum simultaneously retained optimistic draft branches.
576    peak_optimistic_branches: usize,
577}
578
579impl SpeculativeSchedulerStats {
580    /// Selected target/draft placement relationship.
581    pub const fn execution_topology(&self) -> SpeculativeExecutionTopology {
582        self.execution_topology
583    }
584    /// Scheduler turn count.
585    pub const fn turns(&self) -> usize {
586        self.turns
587    }
588    /// Draft turns performed beside other in-flight target work.
589    pub const fn cross_request_draft_opportunities(&self) -> usize {
590        self.cross_request_draft_opportunities
591    }
592    /// Peak retained target verifications.
593    pub const fn peak_in_flight_verifications(&self) -> usize {
594        self.peak_in_flight_verifications
595    }
596    /// Peak retained optimistic branches.
597    pub const fn peak_optimistic_branches(&self) -> usize {
598        self.peak_optimistic_branches
599    }
600}
601
602/// Backend telemetry that can contribute to portable speculative statistics.
603///
604/// Implementations translate backend-specific measurements into the stable
605/// semantic counters and durations owned by [`SpeculativeStats`].
606pub trait SpeculativeTelemetry: Default {
607    /// Records one completed backend observation.
608    fn record(self, stats: &mut SpeculativeStats);
609}
610
611impl SpeculativeTelemetry for () {
612    fn record(self, _stats: &mut SpeculativeStats) {}
613}
614
615/// Backend-owned first-token output and assistant seed state.
616#[derive(Debug)]
617pub struct SpeculativePrefill<State, Logits> {
618    /// Opaque logits used by the selected backend sampler.
619    logits: Logits,
620    /// Backend state from which the first proposal round begins.
621    state: State,
622    /// Number of prompt tokens evaluated by the target.
623    evaluated_tokens: usize,
624}
625
626impl<State, Logits> SpeculativePrefill<State, Logits> {
627    /// Creates a backend-owned prefill result.
628    pub const fn new(logits: Logits, state: State, evaluated_tokens: usize) -> Self {
629        Self {
630            logits,
631            state,
632            evaluated_tokens,
633        }
634    }
635}
636
637/// Result of committing one exact target verification transaction.
638#[derive(Debug)]
639pub struct SpeculativeCommit<State> {
640    /// Assistant seed state matching the committed target cache.
641    state: State,
642    /// Target tokens replayed while restoring the exact retained prefix.
643    replayed_tokens: usize,
644}
645
646impl<State> SpeculativeCommit<State> {
647    /// Creates an exact target-commit result.
648    pub const fn new(state: State, replayed_tokens: usize) -> Self {
649        Self {
650            state,
651            replayed_tokens,
652        }
653    }
654}
655
656/// Whole-session speculative execution contract.
657///
658/// Tensor values, execution queues, caches, model state, logits, native
659/// completions, and errors remain opaque associated types. The contract models
660/// only high-level prefill, proposal, verification, and exact commit actions;
661/// it deliberately does not define primitive tensor operations.
662pub trait SpeculativeExecutor {
663    /// Backend-owned model input accepted by prefill submission.
664    type Input;
665    /// Complete backend-owned target cache.
666    type Cache;
667    /// Target state used to seed one proposal round.
668    type TargetState;
669    /// Private, discardable assistant state.
670    type DraftState: Clone;
671    /// Exact target-cache checkpoint marker.
672    type CacheCheckpoint;
673    /// Retained target verification output.
674    type Verification;
675    /// Opaque logits consumed by the backend's sampling adapter.
676    type Logits;
677    /// Backend execution assignment for one operation.
678    type Context<'a>: Copy
679    where
680        Self: 'a;
681    /// Exact completion for submitted verification work.
682    type Completion: Completion<Error = Self::Error>;
683    /// Optional backend-specific component telemetry.
684    type Telemetry: SpeculativeTelemetry;
685    /// Structured backend error.
686    type Error: std::error::Error + Send + Sync + 'static;
687
688    /// Maximum proposals supported in one verification transaction.
689    fn max_proposals(&self) -> usize {
690        usize::MAX
691    }
692
693    /// Enables optional component telemetry.
694    fn set_telemetry_enabled(&mut self, _enabled: bool) {}
695
696    /// Whether optional component telemetry is available.
697    fn supports_telemetry(&self) -> bool {
698        false
699    }
700
701    /// Resolves and drains assistant telemetry since the previous call.
702    fn take_telemetry(&mut self) -> Result<Self::Telemetry, Self::Error> {
703        Ok(Self::Telemetry::default())
704    }
705
706    /// Resolves telemetry retained by one target verification output.
707    fn take_verification_telemetry(
708        &mut self,
709        _output: &mut Self::Verification,
710    ) -> Result<Self::Telemetry, Self::Error> {
711        Ok(Self::Telemetry::default())
712    }
713
714    /// Whether cloned assistant state can be promoted after an exact bonus match.
715    fn supports_exact_optimistic_promotion(&self) -> bool {
716        false
717    }
718
719    /// Prefills the target and returns first-token logits plus assistant seed state.
720    fn prefill<'context>(
721        &mut self,
722        input: Self::Input,
723        cache: &mut Self::Cache,
724        context: Self::Context<'context>,
725    ) -> Result<SpeculativePrefill<Self::TargetState, Self::Logits>, Self::Error>
726    where
727        Self: 'context;
728
729    /// Starts one private proposal round sized to the available output budget.
730    fn begin_proposal<'a>(
731        &mut self,
732        state: &Self::TargetState,
733        last_token: u32,
734        proposal_capacity: usize,
735        context: Self::Context<'a>,
736    ) -> Result<Self::DraftState, Self::Error>;
737
738    /// Produces opaque next-token logits and advances private assistant state.
739    fn proposal_logits<'a>(
740        &mut self,
741        state: &mut Self::DraftState,
742        last_token: u32,
743        context: Self::Context<'a>,
744    ) -> Result<Self::Logits, Self::Error>;
745
746    /// Captures the exact cache boundary before target verification.
747    fn checkpoint(cache: &Self::Cache) -> Self::CacheCheckpoint;
748
749    /// Submits verification of the last committed token and proposal block.
750    ///
751    /// Implementations materialize token tensors internally and return an exact
752    /// completion retaining every resource required by the submission.
753    fn submit_verification<'a>(
754        &mut self,
755        input_tokens: &[u32],
756        cache: &mut Self::Cache,
757        context: Self::Context<'a>,
758    ) -> Result<Submission<Self::Verification, Self::Completion>, Self::Error>;
759
760    /// Selects one prediction row from retained verification output.
761    fn verification_logits<'a>(
762        output: &Self::Verification,
763        index: usize,
764        context: Self::Context<'a>,
765    ) -> Result<Self::Logits, Self::Error>
766    where
767        Self: 'a;
768
769    /// Commits exactly the requested verified inputs and restores matching seed state.
770    fn commit_verification<'a>(
771        &mut self,
772        output: Self::Verification,
773        draft_state: Self::DraftState,
774        cache: &mut Self::Cache,
775        checkpoint: Self::CacheCheckpoint,
776        verified_inputs: usize,
777        context: Self::Context<'a>,
778    ) -> Result<SpeculativeCommit<Self::TargetState>, Self::Error>;
779}
780
781/// Target decision for one assistant proposal.
782#[derive(Debug, Clone, Copy, Eq, PartialEq)]
783#[non_exhaustive]
784pub enum ProposalDecision {
785    /// Retain the assistant proposal.
786    Accept,
787    /// Reject it and commit this target replacement.
788    Reject(u32),
789}
790
791/// Logical model side on which an opaque sampling operation executes.
792#[derive(Debug, Clone, Copy, Eq, PartialEq)]
793#[non_exhaustive]
794pub enum SamplingPlacement {
795    /// Canonical target-model execution.
796    Target,
797    /// Tentative assistant-model execution.
798    Draft,
799}
800
801/// Backend-owned random streams for canonical and position-stable sampling.
802#[derive(Debug, Clone)]
803pub struct SpeculativeRandomness<R, D> {
804    /// Sequential target randomness.
805    target: Option<R>,
806    /// Position-addressable assistant randomness.
807    draft: Option<D>,
808}
809
810impl<R, D> SpeculativeRandomness<R, D> {
811    /// Creates independent target and draft random streams.
812    pub const fn new(target: Option<R>, draft: Option<D>) -> Self {
813        Self { target, draft }
814    }
815}
816
817/// One backend-prepared lane lent to neutral speculative orchestration.
818///
819/// The lane contains opaque execution values but no scheduler or lifecycle
820/// policy. Its cache borrow remains valid only for the visitor invocation.
821pub struct PreparedSpeculativeLane<'a, E, S, C, P>
822where
823    E: SpeculativeExecutor,
824    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
825    C: SpeculativeConstraint,
826    P: SpeculativePublisher<C>,
827{
828    /// Backend-owned request cache.
829    cache: Option<&'a mut E::Cache>,
830    /// Backend-owned prepared model input.
831    input: Option<E::Input>,
832    /// Validated speculative generation controls.
833    config: Option<SpeculativeConfig>,
834    /// Canonical sampling, constraint, publication, and cancellation state.
835    runtime: Option<SpeculativeOutputRuntime<S, C, P>>,
836    /// Independent target and draft random streams.
837    randomness: Option<SpeculativeRandomness<S::RandomState, S::DraftRandomness>>,
838}
839
840impl<'a, E, S, C, P> PreparedSpeculativeLane<'a, E, S, C, P>
841where
842    E: SpeculativeExecutor,
843    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
844    C: SpeculativeConstraint,
845    P: SpeculativePublisher<C>,
846{
847    /// Creates one backend-prepared lane for neutral orchestration.
848    pub fn new(
849        cache: &'a mut E::Cache,
850        input: E::Input,
851        config: SpeculativeConfig,
852        runtime: SpeculativeOutputRuntime<S, C, P>,
853        randomness: SpeculativeRandomness<S::RandomState, S::DraftRandomness>,
854    ) -> Self {
855        Self {
856            cache: Some(cache),
857            input: Some(input),
858            config: Some(config),
859            runtime: Some(runtime),
860            randomness: Some(randomness),
861        }
862    }
863    /// Takes the backend cache borrow exactly once.
864    pub fn take_cache(&mut self) -> &'a mut E::Cache {
865        self.cache.take().expect("prepared cache already taken")
866    }
867    /// Takes model input exactly once.
868    pub fn take_input(&mut self) -> E::Input {
869        self.input.take().expect("prepared input already taken")
870    }
871    /// Takes speculative controls exactly once.
872    pub fn take_config(&mut self) -> SpeculativeConfig {
873        self.config.take().expect("prepared config already taken")
874    }
875    /// Takes portable output state exactly once.
876    pub fn take_runtime(&mut self) -> SpeculativeOutputRuntime<S, C, P> {
877        self.runtime.take().expect("prepared runtime already taken")
878    }
879    /// Takes target/draft randomness exactly once.
880    pub fn take_randomness(&mut self) -> SpeculativeRandomness<S::RandomState, S::DraftRandomness> {
881        self.randomness
882            .take()
883            .expect("prepared randomness already taken")
884    }
885}
886
887/// Facade/runtime-owned driver for backend-prepared speculative execution.
888///
889/// The generic method lets a backend lend any concrete executor realization
890/// without erasing native tensor, cache, completion, or sampling types. The
891/// visitor owns request registration, fair action selection, completion
892/// driving, terminal validation, and public output construction.
893pub trait SpeculativeGenerationVisitor {
894    /// Drives one prepared set of lanes through the neutral lifecycle.
895    #[allow(clippy::too_many_arguments)]
896    fn run<'a, E, S, C, P>(
897        self,
898        executor: &'a mut E,
899        lanes: Vec<PreparedSpeculativeLane<'a, E, S, C, P>>,
900        topology: SpeculativeExecutionTopology,
901        optimistic_execution_available: bool,
902        component_timings_collected: bool,
903        context: E::Context<'a>,
904    ) -> Result<SpeculativeGenerationBatchOutput, SpeculativeDriverError<E::Error>>
905    where
906        E: SpeculativeExecutor + 'a,
907        S: SpeculativeSampling<Logits = E::Logits, Error = E::Error, Context<'a> = E::Context<'a>>
908            + 'a,
909        C: SpeculativeConstraint,
910        P: SpeculativePublisher<C>;
911}
912
913/// High-level sampling contract used by speculative orchestration.
914///
915/// Backends implement complete semantic operations over opaque logits and
916/// distributions. Core never requests softmax, indexing, random kernels, or
917/// another primitive tensor operation.
918pub trait SpeculativeSampling: Clone {
919    /// Raw model logits.
920    type Logits;
921    /// Processed distribution retained for verification.
922    type Distribution;
923    /// Caller-provided randomness seed.
924    type Seed;
925    /// Sequential random state.
926    type RandomState: Clone;
927    /// Position-addressable assistant random state.
928    type DraftRandomness: Clone;
929    /// Backend execution assignment.
930    type Context<'a>: Copy
931    where
932        Self: 'a;
933    /// Structured backend error.
934    type Error: std::error::Error + Send + Sync + 'static;
935
936    /// Whether cloned sampler state is safe for optimistic promotion.
937    fn supports_exact_optimistic_promotion(&self) -> bool {
938        false
939    }
940
941    /// Whether the canonical grammar accepts its current prefix.
942    fn grammar_is_complete(&mut self) -> Result<bool, Self::Error> {
943        Ok(false)
944    }
945
946    /// Whether a tentative token history completes the grammar.
947    fn prefix_is_complete(&self, _history: &[u32]) -> Result<bool, Self::Error> {
948        Ok(false)
949    }
950
951    /// Splits caller randomness into canonical and position-stable streams.
952    fn initialize_randomness<'a>(
953        seed: Option<Self::Seed>,
954        temperature: f32,
955        context: Self::Context<'a>,
956    ) -> Result<SpeculativeRandomness<Self::RandomState, Self::DraftRandomness>, Self::Error>
957    where
958        Self: 'a;
959
960    /// Derives assistant randomness for one absolute output position.
961    fn draft_randomness_at<'a>(
962        root: &Self::DraftRandomness,
963        position: usize,
964        context: Self::Context<'a>,
965    ) -> Result<Self::RandomState, Self::Error>
966    where
967        Self: 'a;
968
969    /// Processes raw logits against one logical history.
970    fn process_logits<'a>(
971        &mut self,
972        logits: &Self::Logits,
973        temperature: f32,
974        history: &[u32],
975        placement: SamplingPlacement,
976        context: Self::Context<'a>,
977    ) -> Result<Self::Distribution, Self::Error>
978    where
979        Self: 'a;
980
981    /// Samples one token from a processed distribution.
982    fn sample<'a>(
983        &self,
984        distribution: &Self::Distribution,
985        temperature: f32,
986        randomness: Option<&mut Self::RandomState>,
987        placement: SamplingPlacement,
988        context: Self::Context<'a>,
989    ) -> Result<u32, Self::Error>
990    where
991        Self: 'a;
992
993    /// Makes the exact accept-or-replacement decision for one proposal.
994    fn decide_proposal<'a>(
995        &self,
996        target: &Self::Distribution,
997        draft: &Self::Distribution,
998        proposed: u32,
999        temperature: f32,
1000        randomness: Option<&mut Self::RandomState>,
1001        context: Self::Context<'a>,
1002    ) -> Result<ProposalDecision, Self::Error>
1003    where
1004        Self: 'a;
1005
1006    /// Commits a token only after target acceptance or replacement.
1007    fn commit_token<'a>(
1008        &mut self,
1009        distribution: &Self::Distribution,
1010        token: u32,
1011        placement: SamplingPlacement,
1012        context: Self::Context<'a>,
1013    ) -> Result<(), Self::Error>
1014    where
1015        Self: 'a;
1016
1017    /// Makes retained assistant distributions available to target resolution.
1018    fn prepare_verification<'a>(
1019        &self,
1020        _distributions: &mut [&mut Self::Distribution],
1021        _temperature: f32,
1022        _context: Self::Context<'a>,
1023    ) -> Result<(), Self::Error>
1024    where
1025        Self: 'a,
1026    {
1027        Ok(())
1028    }
1029}
1030
1031/// One sampled assistant proposal and its retained distribution.
1032#[derive(Debug)]
1033pub struct SpeculativeProposal<D> {
1034    /// Proposed token id.
1035    token: u32,
1036    /// Backend-owned processed assistant distribution.
1037    distribution: D,
1038}
1039
1040impl<D> SpeculativeProposal<D> {
1041    /// Creates one retained assistant proposal.
1042    pub const fn new(token: u32, distribution: D) -> Self {
1043        Self {
1044            token,
1045            distribution,
1046        }
1047    }
1048    /// Proposed token id.
1049    pub const fn token(&self) -> u32 {
1050        self.token
1051    }
1052    /// Retained assistant distribution.
1053    pub const fn distribution(&self) -> &D {
1054        &self.distribution
1055    }
1056}
1057
1058/// Backend-owned assistant state paired with a portable proposal sequence.
1059pub struct SpeculativeDraftBlock<S, D> {
1060    /// Assistant state after producing every proposal.
1061    state: S,
1062    /// Ordered proposed tokens and opaque distributions.
1063    proposals: Vec<SpeculativeProposal<D>>,
1064}
1065
1066impl<S, D> SpeculativeDraftBlock<S, D> {
1067    /// Creates one ordered assistant proposal block.
1068    pub fn new(state: S, proposals: Vec<SpeculativeProposal<D>>) -> Self {
1069        Self { state, proposals }
1070    }
1071    /// Assistant state after every proposal.
1072    pub const fn state(&self) -> &S {
1073        &self.state
1074    }
1075    /// Ordered proposals retained by this block.
1076    pub fn proposals(&self) -> &[SpeculativeProposal<D>] {
1077        &self.proposals
1078    }
1079}
1080
1081/// Tentative continuation drafted against an assumed canonical prefix.
1082pub struct SpeculativeOptimisticBranch<S, D> {
1083    /// Backend-owned tentative draft block.
1084    block: SpeculativeDraftBlock<S, D>,
1085    /// Prefix against which the block was produced.
1086    assumed_prefix: Vec<u32>,
1087}
1088
1089impl<S, D> SpeculativeOptimisticBranch<S, D> {
1090    /// Creates one tentative continuation tied to an assumed prefix.
1091    pub fn new(block: SpeculativeDraftBlock<S, D>, assumed_prefix: Vec<u32>) -> Self {
1092        Self {
1093            block,
1094            assumed_prefix,
1095        }
1096    }
1097}
1098
1099/// Optimistic state retained after a committed target transaction.
1100#[non_exhaustive]
1101pub enum SpeculativeContinuation<S, D> {
1102    /// No reusable proposal block remains.
1103    None,
1104    /// A matching branch may seed the next canonical round.
1105    Promoted(SpeculativeDraftBlock<S, D>),
1106}
1107
1108impl<S, D> SpeculativeContinuation<S, D> {
1109    /// Returns the promoted block, when one exists.
1110    pub fn into_block(self) -> Option<SpeculativeDraftBlock<S, D>> {
1111        match self {
1112            Self::None => None,
1113            Self::Promoted(block) => Some(block),
1114        }
1115    }
1116}
1117
1118/// Exact target verification resources retained through resolution.
1119///
1120/// Completion is declared first so it is dropped before the output and every
1121/// resource reachable from it. Its destructor must preserve exact-completion
1122/// safety when the scheduler itself is abandoned.
1123pub struct PendingSpeculativeVerification<E, D>
1124where
1125    E: SpeculativeExecutor,
1126{
1127    completion: E::Completion,
1128    verification: E::Verification,
1129    checkpoint: E::CacheCheckpoint,
1130    block: SpeculativeDraftBlock<E::DraftState, D>,
1131    optimistic: Option<SpeculativeOptimisticBranch<E::DraftState, D>>,
1132    submitted: Instant,
1133    submitted_tokens: usize,
1134}
1135
1136impl<E, D> PendingSpeculativeVerification<E, D>
1137where
1138    E: SpeculativeExecutor,
1139{
1140    /// Canonical block being verified.
1141    pub const fn block(&self) -> &SpeculativeDraftBlock<E::DraftState, D> {
1142        &self.block
1143    }
1144
1145    /// Whether one optimistic continuation is retained.
1146    pub const fn has_optimistic_branch(&self) -> bool {
1147        self.optimistic.is_some()
1148    }
1149
1150    /// Installs exactly one tentative optimistic branch.
1151    pub fn set_optimistic_branch(
1152        &mut self,
1153        branch: SpeculativeOptimisticBranch<E::DraftState, D>,
1154    ) -> Result<(), GenerationError> {
1155        if self.optimistic.is_some() {
1156            return Err(GenerationError::OptimisticBranchAlreadyPresent);
1157        }
1158        self.optimistic = Some(branch);
1159        Ok(())
1160    }
1161
1162    /// Number of target tokens submitted for verification.
1163    pub const fn submitted_tokens(&self) -> usize {
1164        self.submitted_tokens
1165    }
1166
1167    /// Time elapsed since target submission.
1168    pub fn elapsed(&self) -> Duration {
1169        self.submitted.elapsed()
1170    }
1171}
1172
1173/// Structured failure in backend-independent speculative output handling.
1174#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
1175#[non_exhaustive]
1176pub enum SpeculativeOutputError {
1177    /// Transactional semantic parsing, decoding, or stop matching failed.
1178    #[error("speculative semantic state failed during {operation}: {message}")]
1179    Semantic {
1180        /// Logical semantic operation.
1181        operation: String,
1182        /// Portable diagnostic detail.
1183        message: String,
1184    },
1185    /// A committed-token callback rejected publication.
1186    #[error("speculative output publication failed: {message}")]
1187    Publication {
1188        /// Portable diagnostic detail.
1189        message: String,
1190    },
1191}
1192
1193impl SpeculativeOutputError {
1194    /// Creates a semantic-state failure with operation context.
1195    pub fn semantic(operation: impl Into<String>, message: impl Into<String>) -> Self {
1196        Self::Semantic {
1197            operation: operation.into(),
1198            message: message.into(),
1199        }
1200    }
1201
1202    /// Creates a committed-output publication failure.
1203    pub fn publication(message: impl Into<String>) -> Self {
1204        Self::Publication {
1205            message: message.into(),
1206        }
1207    }
1208}
1209
1210/// Transactional semantic state paired with committed token sequencing.
1211pub trait SpeculativeConstraint: Sized {
1212    /// Forks state for tentative verification.
1213    fn fork(&self) -> Result<Self, SpeculativeOutputError>;
1214    /// Stages one token and reports a matched stop condition.
1215    fn push_token(&mut self, token: u32) -> Result<bool, SpeculativeOutputError>;
1216    /// Stages terminal output.
1217    fn finish(&mut self, reason: FinishReason) -> Result<(), SpeculativeOutputError>;
1218}
1219
1220/// Backend adapter that publishes committed output and terminal cancellation.
1221///
1222/// The adapter may own callbacks and decoded semantic-event buffers, but core
1223/// decides when publication is legal relative to exact cache commit.
1224pub trait SpeculativePublisher<C> {
1225    /// Publishes tokens and staged semantic output after cache commit.
1226    ///
1227    /// Returns `true` when cancellation was observed during publication.
1228    fn publish_committed(
1229        &mut self,
1230        constraint: &mut C,
1231        tokens: &[u32],
1232        cancellation: &GenerationCancellationToken,
1233        sequence_finished: bool,
1234    ) -> Result<bool, SpeculativeOutputError>;
1235
1236    /// Publishes the cancellation terminal state.
1237    fn publish_cancelled(&mut self, constraint: &mut C) -> Result<(), SpeculativeOutputError>;
1238}
1239
1240/// Object-safe forkable semantic state used by speculative transactions.
1241///
1242/// This interface owns decoded semantic events and never exposes a backend
1243/// tensor, stream, completion, or error type.
1244pub trait SpeculativeSemanticState {
1245    /// Forks the exact committed prefix for tentative verification.
1246    fn fork_box(&self) -> Result<Box<dyn SpeculativeSemanticState>, SpeculativeOutputError>;
1247    /// Stages one token and reports whether a stop sequence matched.
1248    fn push_token(&mut self, token: u32) -> Result<bool, SpeculativeOutputError>;
1249    /// Stages normal terminal output.
1250    fn finish(&mut self, reason: FinishReason) -> Result<(), SpeculativeOutputError>;
1251    /// Stages cancellation output.
1252    fn cancel(&mut self) -> Result<(), SpeculativeOutputError>;
1253    /// Drains events authorized by the next exact commit boundary.
1254    fn take_events(&mut self) -> Vec<crate::generation::SemanticEvent>;
1255}
1256
1257/// Optional transactional semantic state shared by plain and structured speculative decoding.
1258pub struct SpeculativeSemanticConstraint {
1259    state: Option<Box<dyn SpeculativeSemanticState>>,
1260}
1261
1262impl SpeculativeSemanticConstraint {
1263    /// Creates an unconstrained output state for token-only generation.
1264    pub const fn plain() -> Self {
1265        Self { state: None }
1266    }
1267
1268    /// Creates a transactional structured-output state.
1269    pub fn semantic(state: Box<dyn SpeculativeSemanticState>) -> Self {
1270        Self { state: Some(state) }
1271    }
1272}
1273
1274impl SpeculativeConstraint for SpeculativeSemanticConstraint {
1275    fn fork(&self) -> Result<Self, SpeculativeOutputError> {
1276        Ok(Self {
1277            state: self
1278                .state
1279                .as_ref()
1280                .map(|state| state.fork_box())
1281                .transpose()?,
1282        })
1283    }
1284
1285    fn push_token(&mut self, token: u32) -> Result<bool, SpeculativeOutputError> {
1286        self.state
1287            .as_mut()
1288            .map(|state| state.push_token(token))
1289            .transpose()
1290            .map(|matched| matched.unwrap_or(false))
1291    }
1292
1293    fn finish(&mut self, reason: FinishReason) -> Result<(), SpeculativeOutputError> {
1294        if let Some(state) = &mut self.state {
1295            state.finish(reason)?;
1296        }
1297        Ok(())
1298    }
1299}
1300
1301/// Core-owned committed-token and semantic-event publication adapter.
1302pub struct SpeculativeCallbackPublisher<'a> {
1303    on_token: Box<dyn FnMut(u32) -> Result<(), SpeculativeOutputError> + 'a>,
1304    on_event: Option<Box<dyn FnMut(crate::generation::SemanticEvent) + 'a>>,
1305}
1306
1307impl<'a> SpeculativeCallbackPublisher<'a> {
1308    /// Publishes committed token ids without decoded semantic events.
1309    pub fn tokens(on_token: impl FnMut(u32) -> Result<(), SpeculativeOutputError> + 'a) -> Self {
1310        Self {
1311            on_token: Box::new(on_token),
1312            on_event: None,
1313        }
1314    }
1315
1316    /// Publishes transactional semantic events and ignores raw token callbacks.
1317    pub fn semantic(on_event: impl FnMut(crate::generation::SemanticEvent) + 'a) -> Self {
1318        Self {
1319            on_token: Box::new(|_| Ok(())),
1320            on_event: Some(Box::new(on_event)),
1321        }
1322    }
1323}
1324
1325impl SpeculativePublisher<SpeculativeSemanticConstraint> for SpeculativeCallbackPublisher<'_> {
1326    fn publish_committed(
1327        &mut self,
1328        constraint: &mut SpeculativeSemanticConstraint,
1329        tokens: &[u32],
1330        cancellation: &GenerationCancellationToken,
1331        sequence_finished: bool,
1332    ) -> Result<bool, SpeculativeOutputError> {
1333        for &token in tokens {
1334            (self.on_token)(token)?;
1335        }
1336        let mut cancellation_won = false;
1337        if let (Some(state), Some(on_event)) = (&mut constraint.state, &mut self.on_event) {
1338            for event in state.take_events() {
1339                on_event(event);
1340                if cancellation.is_cancelled() && !sequence_finished {
1341                    cancellation_won = true;
1342                    break;
1343                }
1344            }
1345        }
1346        Ok(cancellation_won || (cancellation.is_cancelled() && !sequence_finished))
1347    }
1348
1349    fn publish_cancelled(
1350        &mut self,
1351        constraint: &mut SpeculativeSemanticConstraint,
1352    ) -> Result<(), SpeculativeOutputError> {
1353        if let (Some(state), Some(on_event)) = (&mut constraint.state, &mut self.on_event) {
1354            state.cancel()?;
1355            for event in state.take_events() {
1356                on_event(event);
1357            }
1358        }
1359        Ok(())
1360    }
1361}
1362
1363/// Canonical speculative sampler, sequence, constraint, and output sink.
1364pub struct SpeculativeOutputRuntime<S, C, P> {
1365    sampler: S,
1366    sequence: GenerationSequence,
1367    constraint: C,
1368    publisher: P,
1369    cancellation: GenerationCancellationToken,
1370}
1371
1372impl<S, C, P> SpeculativeOutputRuntime<S, C, P>
1373where
1374    S: SpeculativeSampling,
1375    C: SpeculativeConstraint,
1376    P: SpeculativePublisher<C>,
1377{
1378    /// Creates one canonical output runtime.
1379    pub fn new(
1380        sampler: S,
1381        sequence: GenerationSequence,
1382        constraint: C,
1383        publisher: P,
1384        cancellation: GenerationCancellationToken,
1385    ) -> Self {
1386        Self {
1387            sampler,
1388            sequence,
1389            constraint,
1390            publisher,
1391            cancellation,
1392        }
1393    }
1394
1395    /// Canonical sampling state.
1396    pub const fn sampler(&self) -> &S {
1397        &self.sampler
1398    }
1399
1400    /// Mutable canonical sampling state.
1401    pub const fn sampler_mut(&mut self) -> &mut S {
1402        &mut self.sampler
1403    }
1404
1405    /// Canonical committed sequence.
1406    pub const fn sequence(&self) -> &GenerationSequence {
1407        &self.sequence
1408    }
1409
1410    /// Mutable canonical committed sequence.
1411    pub const fn sequence_mut(&mut self) -> &mut GenerationSequence {
1412        &mut self.sequence
1413    }
1414
1415    /// Transactional semantic constraint.
1416    pub const fn constraint(&self) -> &C {
1417        &self.constraint
1418    }
1419
1420    /// Mutable transactional semantic constraint.
1421    pub const fn constraint_mut(&mut self) -> &mut C {
1422        &mut self.constraint
1423    }
1424
1425    /// Cooperative cancellation token.
1426    pub const fn cancellation(&self) -> &GenerationCancellationToken {
1427        &self.cancellation
1428    }
1429
1430    /// Applies cancellation and publishes its terminal semantic state.
1431    pub fn cancel(&mut self) -> Result<(), SpeculativeOutputError> {
1432        if self.sequence.cancel() {
1433            self.publisher.publish_cancelled(&mut self.constraint)?;
1434        }
1435        Ok(())
1436    }
1437
1438    /// Installs logical state only after its matching backend boundary committed.
1439    pub fn install_committed_state(
1440        &mut self,
1441        sampler: S,
1442        constraint: C,
1443        sequence: GenerationSequence,
1444    ) {
1445        self.sampler = sampler;
1446        self.constraint = constraint;
1447        self.sequence = sequence;
1448    }
1449
1450    /// Publishes tokens only after their backend cache transaction committed.
1451    pub fn publish_committed(&mut self, tokens: &[u32]) -> Result<bool, SpeculativeOutputError> {
1452        let cancellation_won = self.publisher.publish_committed(
1453            &mut self.constraint,
1454            tokens,
1455            &self.cancellation,
1456            self.sequence.is_finished(),
1457        )? || (self.cancellation.is_cancelled()
1458            && !self.sequence.is_finished());
1459        if cancellation_won {
1460            self.cancel()?;
1461        }
1462        Ok(cancellation_won)
1463    }
1464
1465    /// Consumes the runtime into its backend-owned parts.
1466    pub(crate) fn into_parts(self) -> (S, GenerationSequence, C, P) {
1467        (self.sampler, self.sequence, self.constraint, self.publisher)
1468    }
1469}
1470
1471/// Error returned by portable proposal and verification drivers.
1472#[derive(Debug, thiserror::Error)]
1473#[non_exhaustive]
1474pub enum SpeculativeDriverError<E: std::error::Error + 'static> {
1475    /// Backend execution or sampling failed.
1476    #[error(transparent)]
1477    Backend(#[from] E),
1478    /// Transactional semantic output or committed publication failed.
1479    #[error(transparent)]
1480    Output(SpeculativeOutputError),
1481    /// Portable lifecycle validation failed.
1482    #[error(transparent)]
1483    Generation(GenerationError),
1484}
1485
1486/// Resolved speculative transaction ready for backend cache commit.
1487pub struct ResolvedSpeculativeRound<S, C, R> {
1488    /// Tentatively advanced sampler state.
1489    sampler: S,
1490    /// Tentatively advanced semantic state.
1491    constraint: C,
1492    /// Tentatively advanced canonical sequence.
1493    sequence: GenerationSequence,
1494    /// Tentatively advanced target randomness.
1495    target_randomness: Option<R>,
1496    /// Number of accepted proposals.
1497    accepted_proposals: usize,
1498    /// Tokens visible after cache commit.
1499    committed_tokens: Vec<u32>,
1500    /// Exact verification inputs retained by cache commit.
1501    verified_inputs: usize,
1502    /// Target bonus token, when full acceptance produced one.
1503    bonus_token: Option<u32>,
1504    /// Terminal reason after this round.
1505    finish_reason: Option<FinishReason>,
1506}
1507
1508/// Generates one assistant proposal block through opaque backend operations.
1509#[allow(clippy::too_many_arguments)]
1510pub fn propose_block<'a, E, S>(
1511    executor: &mut E,
1512    sampler: &S,
1513    state: &mut E::DraftState,
1514    first_previous: u32,
1515    count: usize,
1516    base_history: &[u32],
1517    temperature: f32,
1518    eos_token_ids: &[u32],
1519    draft_randomness: Option<&S::DraftRandomness>,
1520    context: E::Context<'a>,
1521) -> Result<Vec<SpeculativeProposal<S::Distribution>>, SpeculativeDriverError<E::Error>>
1522where
1523    E: SpeculativeExecutor + 'a,
1524    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error, Context<'a> = E::Context<'a>> + 'a,
1525{
1526    let mut branch_sampler = sampler.clone();
1527    let mut history = Vec::with_capacity(base_history.len() + count);
1528    history.extend_from_slice(base_history);
1529    let mut proposals: Vec<SpeculativeProposal<S::Distribution>> = Vec::with_capacity(count);
1530    for offset in 0..count {
1531        let previous = proposals
1532            .last()
1533            .map_or(first_previous, |proposal| proposal.token);
1534        let raw = executor.proposal_logits(state, previous, context)?;
1535        let distribution = branch_sampler.process_logits(
1536            &raw,
1537            temperature,
1538            &history,
1539            SamplingPlacement::Draft,
1540            context,
1541        )?;
1542        let mut position_state = draft_randomness
1543            .map(|root| S::draft_randomness_at(root, base_history.len() + offset, context))
1544            .transpose()?;
1545        let token = branch_sampler.sample(
1546            &distribution,
1547            temperature,
1548            position_state.as_mut(),
1549            SamplingPlacement::Draft,
1550            context,
1551        )?;
1552        proposals.push(SpeculativeProposal {
1553            token,
1554            distribution,
1555        });
1556        history.push(token);
1557        if eos_token_ids.contains(&token) || branch_sampler.prefix_is_complete(&history)? {
1558            break;
1559        }
1560    }
1561    Ok(proposals)
1562}
1563
1564/// Resolves one target verification transaction without backend-specific math.
1565#[allow(clippy::too_many_arguments)]
1566pub fn resolve_round<'a, E, S, C>(
1567    verification: &E::Verification,
1568    mut proposals: Vec<SpeculativeProposal<S::Distribution>>,
1569    sampler: &S,
1570    sequence: &GenerationSequence,
1571    constraint: &C,
1572    target_randomness: Option<&S::RandomState>,
1573    temperature: f32,
1574    context: E::Context<'a>,
1575) -> Result<ResolvedSpeculativeRound<S, C, S::RandomState>, SpeculativeDriverError<E::Error>>
1576where
1577    E: SpeculativeExecutor + 'a,
1578    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error, Context<'a> = E::Context<'a>> + 'a,
1579    C: SpeculativeConstraint,
1580{
1581    let mut draft_distributions = proposals
1582        .iter_mut()
1583        .map(|proposal| &mut proposal.distribution)
1584        .collect::<Vec<_>>();
1585    sampler.prepare_verification(&mut draft_distributions, temperature, context)?;
1586    let proposal_count = proposals.len();
1587    let mut sampler = sampler.clone();
1588    let mut sequence = sequence.clone();
1589    let mut constraint = constraint.fork().map_err(SpeculativeDriverError::Output)?;
1590    let mut target_randomness = target_randomness.cloned();
1591    let mut history = sequence.tokens().to_vec();
1592    let mut round =
1593        SpeculativeRound::new(proposal_count).map_err(SpeculativeDriverError::Generation)?;
1594    let mut finish_reason = None;
1595
1596    for (index, proposal) in proposals.iter().enumerate() {
1597        let raw = E::verification_logits(verification, index, context)?;
1598        let target = sampler.process_logits(
1599            &raw,
1600            temperature,
1601            &history,
1602            SamplingPlacement::Target,
1603            context,
1604        )?;
1605        match sampler.decide_proposal(
1606            &target,
1607            &proposal.distribution,
1608            proposal.token,
1609            temperature,
1610            target_randomness.as_mut(),
1611            context,
1612        )? {
1613            ProposalDecision::Accept => {
1614                sampler.commit_token(
1615                    &target,
1616                    proposal.token,
1617                    SamplingPlacement::Target,
1618                    context,
1619                )?;
1620                history.push(proposal.token);
1621                finish_reason = commit_terminal_token(
1622                    &mut sequence,
1623                    &mut sampler,
1624                    &mut constraint,
1625                    proposal.token,
1626                )?;
1627                round
1628                    .accept(proposal.token, finish_reason.is_some())
1629                    .map_err(SpeculativeDriverError::Generation)?;
1630                if finish_reason.is_some() {
1631                    break;
1632                }
1633            }
1634            ProposalDecision::Reject(replacement) => {
1635                sampler.commit_token(&target, replacement, SamplingPlacement::Target, context)?;
1636                finish_reason = commit_terminal_token(
1637                    &mut sequence,
1638                    &mut sampler,
1639                    &mut constraint,
1640                    replacement,
1641                )?;
1642                round
1643                    .reject_with(replacement, finish_reason.is_some())
1644                    .map_err(SpeculativeDriverError::Generation)?;
1645                break;
1646            }
1647        }
1648    }
1649
1650    let mut bonus_token = None;
1651    if round.is_full_acceptance() && !sequence.is_finished() {
1652        let raw = E::verification_logits(verification, proposal_count, context)?;
1653        let target = sampler.process_logits(
1654            &raw,
1655            temperature,
1656            &history,
1657            SamplingPlacement::Target,
1658            context,
1659        )?;
1660        let chosen = sampler.sample(
1661            &target,
1662            temperature,
1663            target_randomness.as_mut(),
1664            SamplingPlacement::Target,
1665            context,
1666        )?;
1667        sampler.commit_token(&target, chosen, SamplingPlacement::Target, context)?;
1668        finish_reason =
1669            commit_terminal_token(&mut sequence, &mut sampler, &mut constraint, chosen)?;
1670        round
1671            .bonus(chosen, finish_reason.is_some())
1672            .map_err(SpeculativeDriverError::Generation)?;
1673        bonus_token = Some(chosen);
1674    }
1675    let plan = round
1676        .commit_plan()
1677        .map_err(SpeculativeDriverError::Generation)?;
1678    Ok(ResolvedSpeculativeRound {
1679        sampler,
1680        constraint,
1681        sequence,
1682        target_randomness,
1683        accepted_proposals: plan.accepted_proposals,
1684        committed_tokens: plan.committed_tokens.to_vec(),
1685        verified_inputs: plan.verified_inputs,
1686        bonus_token,
1687        finish_reason,
1688    })
1689}
1690
1691/// Submits one exact target verification and takes ownership of its resources.
1692pub fn submit_verification_transaction<'a, E, D>(
1693    executor: &mut E,
1694    cache: &mut E::Cache,
1695    last_committed_token: u32,
1696    block: SpeculativeDraftBlock<E::DraftState, D>,
1697    context: E::Context<'a>,
1698) -> Result<PendingSpeculativeVerification<E, D>, SpeculativeDriverError<E::Error>>
1699where
1700    E: SpeculativeExecutor + 'a,
1701{
1702    if block.proposals.is_empty() {
1703        return Err(SpeculativeDriverError::Generation(
1704            GenerationError::EmptyProposalBlock,
1705        ));
1706    }
1707    let mut input_tokens = Vec::with_capacity(block.proposals.len() + 1);
1708    input_tokens.push(last_committed_token);
1709    input_tokens.extend(block.proposals.iter().map(|proposal| proposal.token));
1710    let checkpoint = E::checkpoint(cache);
1711    let submission = executor.submit_verification(&input_tokens, cache, context)?;
1712    Ok(PendingSpeculativeVerification {
1713        completion: submission.completion,
1714        verification: submission.output,
1715        checkpoint,
1716        block,
1717        optimistic: None,
1718        submitted: Instant::now(),
1719        submitted_tokens: input_tokens.len(),
1720    })
1721}
1722
1723/// Request state selected after committed output publication.
1724#[non_exhaustive]
1725pub enum SpeculativePublicationStatus<S, D> {
1726    /// Continue from canonical target state and an optional promoted block.
1727    Continue(SpeculativeContinuation<S, D>),
1728    /// Generation reached a normal terminal condition.
1729    Completed,
1730    /// Cancellation won at or after the exact commit boundary.
1731    Cancelled,
1732}
1733
1734/// Backend and portable state after exact commit and legal publication.
1735pub struct PublishedSpeculativeVerification<TargetState, DraftState, Distribution, RandomState, T> {
1736    /// Target state matching the committed backend cache.
1737    target_state: TargetState,
1738    /// Canonical target randomness after resolution.
1739    target_randomness: Option<RandomState>,
1740    /// Updated portable request telemetry.
1741    stats: SpeculativeStats,
1742    /// Backend component telemetry observed at exact completion.
1743    telemetry: T,
1744    /// Request continuation selected after publication.
1745    status: SpeculativePublicationStatus<DraftState, Distribution>,
1746}
1747
1748/// Publication result produced after a speculative verification commits.
1749pub type PublishedSpeculativeResult<E, S> = Result<
1750    PublishedSpeculativeVerification<
1751        <E as SpeculativeExecutor>::TargetState,
1752        <E as SpeculativeExecutor>::DraftState,
1753        <S as SpeculativeSampling>::Distribution,
1754        <S as SpeculativeSampling>::RandomState,
1755        <E as SpeculativeExecutor>::Telemetry,
1756    >,
1757    SpeculativeDriverError<<E as SpeculativeExecutor>::Error>,
1758>;
1759
1760/// Waits, resolves, commits, and only then publishes one verification.
1761///
1762/// Portable sampler, sequence, constraint, telemetry, and optimistic state are
1763/// advanced transactionally. A backend cache-commit failure leaves the
1764/// canonical output runtime unchanged and publishes nothing.
1765#[allow(clippy::too_many_arguments)]
1766pub fn resolve_commit_and_publish<'a, E, S, C, P>(
1767    executor: &mut E,
1768    cache: &mut E::Cache,
1769    pending: PendingSpeculativeVerification<E, S::Distribution>,
1770    runtime: &mut SpeculativeOutputRuntime<S, C, P>,
1771    target_randomness: Option<&S::RandomState>,
1772    temperature: f32,
1773    mut stats: SpeculativeStats,
1774    options: SpeculativeSchedulerOptions,
1775    context: E::Context<'a>,
1776) -> PublishedSpeculativeResult<E, S>
1777where
1778    E: SpeculativeExecutor + 'a,
1779    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error, Context<'a> = E::Context<'a>> + 'a,
1780    C: SpeculativeConstraint,
1781    P: SpeculativePublisher<C>,
1782{
1783    let PendingSpeculativeVerification {
1784        completion,
1785        mut verification,
1786        checkpoint,
1787        block,
1788        optimistic,
1789        submitted,
1790        submitted_tokens: _,
1791    } = pending;
1792    completion.wait()?;
1793    let telemetry = executor.take_verification_telemetry(&mut verification)?;
1794    stats.verification_in_flight_time += submitted.elapsed();
1795    let mut canonical_proposal_prefix = runtime.sequence().tokens().to_vec();
1796    canonical_proposal_prefix.extend(block.proposals.iter().map(|proposal| proposal.token));
1797    let resolved = resolve_round::<E, S, C>(
1798        &verification,
1799        block.proposals,
1800        runtime.sampler(),
1801        runtime.sequence(),
1802        runtime.constraint(),
1803        target_randomness,
1804        temperature,
1805        context,
1806    )?;
1807    let accepted = resolved.accepted_proposals;
1808    let committed_tokens = resolved.committed_tokens;
1809    let terminal = resolved.finish_reason;
1810    let mut continuation = resolve_optimistic_branch(
1811        optimistic,
1812        &canonical_proposal_prefix,
1813        resolved.bonus_token,
1814        terminal.is_some(),
1815        &mut stats,
1816    )
1817    .map_err(SpeculativeDriverError::Generation)?;
1818    stats.accepted_tokens += accepted;
1819    stats.accept_lens.push(accepted);
1820    stats.rounds += 1;
1821    let commit = executor.commit_verification(
1822        verification,
1823        block.state,
1824        cache,
1825        checkpoint,
1826        resolved.verified_inputs,
1827        context,
1828    )?;
1829    stats.target_tokens += commit.replayed_tokens;
1830    stats.emitted_tokens += committed_tokens.len();
1831    let target_randomness = resolved.target_randomness;
1832    runtime.install_committed_state(resolved.sampler, resolved.constraint, resolved.sequence);
1833    let cancelled = runtime
1834        .publish_committed(&committed_tokens)
1835        .map_err(SpeculativeDriverError::Output)?;
1836    let status = if cancelled {
1837        discard_continuation(&mut stats, continuation);
1838        SpeculativePublicationStatus::Cancelled
1839    } else if terminal.is_some() {
1840        discard_continuation(&mut stats, continuation);
1841        SpeculativePublicationStatus::Completed
1842    } else {
1843        stats.update_adaptive_lookahead(options);
1844        SpeculativePublicationStatus::Continue(std::mem::replace(
1845            &mut continuation,
1846            SpeculativeContinuation::None,
1847        ))
1848    };
1849    Ok(PublishedSpeculativeVerification {
1850        target_state: commit.state,
1851        target_randomness,
1852        stats,
1853        telemetry,
1854        status,
1855    })
1856}
1857
1858/// Resolves an exact retained verification solely to reach a safe cancellation boundary.
1859#[allow(clippy::too_many_arguments)]
1860pub fn cancel_pending_verification<'a, E, S, C, P>(
1861    executor: &mut E,
1862    cache: &mut E::Cache,
1863    pending: PendingSpeculativeVerification<E, S::Distribution>,
1864    runtime: &mut SpeculativeOutputRuntime<S, C, P>,
1865    mut stats: SpeculativeStats,
1866    context: E::Context<'a>,
1867) -> Result<(SpeculativeStats, E::Telemetry), SpeculativeDriverError<E::Error>>
1868where
1869    E: SpeculativeExecutor + 'a,
1870    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error, Context<'a> = E::Context<'a>> + 'a,
1871    C: SpeculativeConstraint,
1872    P: SpeculativePublisher<C>,
1873{
1874    let PendingSpeculativeVerification {
1875        completion,
1876        mut verification,
1877        checkpoint,
1878        block,
1879        optimistic,
1880        submitted,
1881        submitted_tokens: _,
1882    } = pending;
1883    completion.wait()?;
1884    let telemetry = executor.take_verification_telemetry(&mut verification)?;
1885    stats.verification_in_flight_time += submitted.elapsed();
1886    discard_branch(&mut stats, optimistic);
1887    let commit =
1888        executor.commit_verification(verification, block.state, cache, checkpoint, 1, context)?;
1889    stats.target_tokens += commit.replayed_tokens;
1890    runtime.cancel().map_err(SpeculativeDriverError::Output)?;
1891    Ok((stats, telemetry))
1892}
1893
1894/// Resolves, promotes, or discards one optimistic branch and updates telemetry.
1895pub fn resolve_optimistic_branch<S, D>(
1896    branch: Option<SpeculativeOptimisticBranch<S, D>>,
1897    canonical_prefix: &[u32],
1898    bonus: Option<u32>,
1899    terminal: bool,
1900    stats: &mut SpeculativeStats,
1901) -> Result<SpeculativeContinuation<S, D>, GenerationError> {
1902    let Some(branch) = branch else {
1903        return Ok(SpeculativeContinuation::None);
1904    };
1905    let Some(bonus) = bonus else {
1906        discard_branch(stats, Some(branch));
1907        return Ok(SpeculativeContinuation::None);
1908    };
1909    let optimistic_tokens = branch
1910        .block
1911        .proposals
1912        .iter()
1913        .map(|proposal| proposal.token)
1914        .collect::<Vec<_>>();
1915    let decision = crate::generation::resolve_optimistic_reuse(
1916        &branch.assumed_prefix,
1917        canonical_prefix,
1918        &optimistic_tokens,
1919        bonus,
1920        terminal,
1921    )?;
1922    stats.optimistic_target_bonus_tokens += 1;
1923    if decision == crate::generation::OptimisticReuseDecision::DiscardTerminal {
1924        discard_branch(stats, Some(branch));
1925        return Ok(SpeculativeContinuation::None);
1926    }
1927    let drafted = branch.block.proposals.len();
1928    let SpeculativeDraftBlock { state, proposals } = branch.block;
1929    let mut proposals = proposals.into_iter();
1930    let _matched_or_discarded = proposals
1931        .next()
1932        .expect("validated optimistic branch is non-empty");
1933    Ok(match decision {
1934        crate::generation::OptimisticReuseDecision::DiscardMismatch => {
1935            stats.optimistic_bonus_mismatches += 1;
1936            stats.discarded_optimistic_tokens += drafted;
1937            stats.discarded_optimistic_blocks += 1;
1938            SpeculativeContinuation::None
1939        }
1940        crate::generation::OptimisticReuseDecision::MatchedConsumed => {
1941            stats.optimistic_bonus_matches += 1;
1942            stats.consumed_optimistic_tokens += 1;
1943            SpeculativeContinuation::None
1944        }
1945        crate::generation::OptimisticReuseDecision::MatchedRetained => {
1946            stats.optimistic_bonus_matches += 1;
1947            stats.consumed_optimistic_tokens += 1;
1948            let proposals = proposals.collect::<Vec<_>>();
1949            stats.draft_tokens += proposals.len();
1950            stats.reused_optimistic_tokens += proposals.len();
1951            stats.reused_optimistic_blocks += 1;
1952            SpeculativeContinuation::Promoted(SpeculativeDraftBlock { state, proposals })
1953        }
1954        crate::generation::OptimisticReuseDecision::DiscardTerminal => {
1955            unreachable!("terminal decision handled before branch destruction")
1956        }
1957    })
1958}
1959
1960fn discard_branch<S, D>(
1961    stats: &mut SpeculativeStats,
1962    branch: Option<SpeculativeOptimisticBranch<S, D>>,
1963) {
1964    if let Some(branch) = branch {
1965        stats.discarded_optimistic_tokens += branch.block.proposals.len();
1966        stats.discarded_optimistic_blocks += 1;
1967    }
1968}
1969
1970fn discard_continuation<S, D>(
1971    stats: &mut SpeculativeStats,
1972    continuation: SpeculativeContinuation<S, D>,
1973) {
1974    if let SpeculativeContinuation::Promoted(block) = continuation {
1975        stats.discarded_optimistic_tokens += block.proposals.len();
1976        stats.discarded_optimistic_blocks += 1;
1977        stats.draft_tokens = stats.draft_tokens.saturating_sub(block.proposals.len());
1978        stats.reused_optimistic_tokens = stats
1979            .reused_optimistic_tokens
1980            .saturating_sub(block.proposals.len());
1981        stats.reused_optimistic_blocks = stats.reused_optimistic_blocks.saturating_sub(1);
1982    }
1983}
1984
1985fn commit_terminal_token<S, C>(
1986    sequence: &mut GenerationSequence,
1987    sampler: &mut S,
1988    constraint: &mut C,
1989    token: u32,
1990) -> Result<Option<FinishReason>, SpeculativeDriverError<S::Error>>
1991where
1992    S: SpeculativeSampling,
1993    C: SpeculativeConstraint,
1994{
1995    let stop_matched = constraint
1996        .push_token(token)
1997        .map_err(SpeculativeDriverError::Output)?;
1998    let grammar_complete = if stop_matched {
1999        false
2000    } else {
2001        sampler.grammar_is_complete()?
2002    };
2003    let reason = sequence
2004        .commit(
2005            token,
2006            TokenTerminalSignals {
2007                stop_sequence: stop_matched,
2008                grammar_complete,
2009            },
2010        )
2011        .map_err(SpeculativeDriverError::Generation)?
2012        .finish_reason;
2013    if let Some(reason) = reason {
2014        constraint
2015            .finish(reason)
2016            .map_err(SpeculativeDriverError::Output)?;
2017    }
2018    Ok(reason)
2019}
2020
2021/// One backend-neutral speculative request with opaque execution resources.
2022///
2023/// The request owns every resource slot whose presence is constrained by the
2024/// lifecycle: target state, canonical draft block, exact in-flight
2025/// verification, randomness, output state, and cache access. Backends choose
2026/// the concrete associated types but cannot maintain a parallel request state.
2027pub struct SpeculativeRequest<'cache, E, S, C, P>
2028where
2029    E: SpeculativeExecutor,
2030    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
2031    C: SpeculativeConstraint,
2032    P: SpeculativePublisher<C>,
2033{
2034    id: SpeculativeRequestId,
2035    cache: &'cache mut E::Cache,
2036    config: SpeculativeConfig,
2037    runtime: SpeculativeOutputRuntime<S, C, P>,
2038    target_randomness: Option<S::RandomState>,
2039    draft_randomness: Option<S::DraftRandomness>,
2040    stats: SpeculativeStats,
2041    started: Instant,
2042    target_state: Option<E::TargetState>,
2043    block: Option<SpeculativeDraftBlock<E::DraftState, S::Distribution>>,
2044    pending: Option<PendingSpeculativeVerification<E, S::Distribution>>,
2045    lifecycle: SpeculativeRequestLifecycle,
2046}
2047
2048impl<'cache, E, S, C, P> SpeculativeRequest<'cache, E, S, C, P>
2049where
2050    E: SpeculativeExecutor,
2051    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
2052    C: SpeculativeConstraint,
2053    P: SpeculativePublisher<C>,
2054{
2055    /// Stable insertion-order identity.
2056    pub const fn id(&self) -> SpeculativeRequestId {
2057        self.id
2058    }
2059
2060    /// Current validated lifecycle status.
2061    pub const fn status(&self) -> SpeculativeRequestStatus {
2062        self.lifecycle.status()
2063    }
2064
2065    /// Portable request statistics.
2066    pub const fn stats(&self) -> &SpeculativeStats {
2067        &self.stats
2068    }
2069
2070    /// Canonical committed token sequence.
2071    pub const fn sequence(&self) -> &GenerationSequence {
2072        self.runtime.sequence()
2073    }
2074
2075    /// Canonical sampler state.
2076    pub const fn sampler(&self) -> &S {
2077        self.runtime.sampler()
2078    }
2079
2080    /// Canonical proposal block awaiting submission, when present.
2081    pub const fn block(&self) -> Option<&SpeculativeDraftBlock<E::DraftState, S::Distribution>> {
2082        self.block.as_ref()
2083    }
2084
2085    /// Whether an exact target verification remains retained.
2086    pub const fn has_pending_verification(&self) -> bool {
2087        self.pending.is_some()
2088    }
2089
2090    fn transition(
2091        &mut self,
2092        next: SpeculativeRequestStatus,
2093    ) -> Result<(), SpeculativeDriverError<E::Error>> {
2094        self.lifecycle
2095            .transition(next)
2096            .map_err(SpeculativeDriverError::Generation)
2097    }
2098
2099    fn request_cancellation(&mut self) -> Result<(), SpeculativeDriverError<E::Error>> {
2100        match self
2101            .lifecycle
2102            .request_cancellation(self.pending.is_some())
2103            .map_err(SpeculativeDriverError::Generation)?
2104        {
2105            SpeculativeCancellationDisposition::AlreadyTerminal
2106            | SpeculativeCancellationDisposition::Deferred => {}
2107            SpeculativeCancellationDisposition::CancelNow => {
2108                self.block = None;
2109                self.runtime
2110                    .cancel()
2111                    .map_err(SpeculativeDriverError::Output)?;
2112                self.stats.elapsed = self.started.elapsed();
2113            }
2114        }
2115        Ok(())
2116    }
2117
2118    fn candidate<'context>(
2119        &self,
2120        executor: &E,
2121        optimistic_execution_available: bool,
2122    ) -> Result<SpeculativeCandidate, SpeculativeDriverError<E::Error>>
2123    where
2124        E: 'context,
2125        S: SpeculativeSampling<
2126                Logits = E::Logits,
2127                Error = E::Error,
2128                Context<'context> = E::Context<'context>,
2129            > + 'context,
2130    {
2131        let optimistic_eligible = if self.lifecycle.status()
2132            != SpeculativeRequestStatus::TargetVerificationInFlight
2133            || !optimistic_execution_available
2134        {
2135            false
2136        } else {
2137            let pending = self
2138                .pending
2139                .as_ref()
2140                .expect("in-flight request retains its verification transaction");
2141            let block = pending.block();
2142            let assumed_len = self.runtime.sequence().tokens().len() + block.proposals.len();
2143            let mut assumed_prefix = Vec::with_capacity(assumed_len);
2144            assumed_prefix.extend_from_slice(self.runtime.sequence().tokens());
2145            assumed_prefix.extend(block.proposals.iter().map(|proposal| proposal.token));
2146            executor.supports_exact_optimistic_promotion()
2147                && self.runtime.sampler().supports_exact_optimistic_promotion()
2148                && !self.stats.adaptive_lookahead_disabled
2149                && !block.proposals.is_empty()
2150                && !self.runtime.sampler().prefix_is_complete(&assumed_prefix)?
2151                && !block
2152                    .proposals
2153                    .last()
2154                    .is_some_and(|proposal| self.config.eos_token_ids.contains(&proposal.token))
2155                && self.config.max_tokens.saturating_sub(assumed_len) > 1
2156        };
2157        Ok(SpeculativeCandidate {
2158            status: self.lifecycle.status(),
2159            optimistic_eligible,
2160        })
2161    }
2162
2163    fn draft_committed<'context>(
2164        &mut self,
2165        executor: &mut E,
2166        context: E::Context<'context>,
2167    ) -> Result<bool, SpeculativeDriverError<E::Error>>
2168    where
2169        E: 'context,
2170        S: SpeculativeSampling<
2171                Logits = E::Logits,
2172                Error = E::Error,
2173                Context<'context> = E::Context<'context>,
2174            > + 'context,
2175    {
2176        let target_count = self
2177            .config
2178            .max_draft_tokens
2179            .min(executor.max_proposals())
2180            .min(
2181                self.config
2182                    .max_tokens
2183                    .saturating_sub(self.runtime.sequence().tokens().len()),
2184            );
2185        if target_count == 0 {
2186            self.transition(SpeculativeRequestStatus::Completed)?;
2187            self.stats.elapsed = self.started.elapsed();
2188            return Ok(false);
2189        }
2190
2191        let mut block = if let Some(block) = self.block.take() {
2192            block
2193        } else {
2194            let last = *self
2195                .runtime
2196                .sequence()
2197                .tokens()
2198                .last()
2199                .expect("prefill emitted a token");
2200            let target_state = self
2201                .target_state
2202                .as_ref()
2203                .expect("ready request has target state");
2204            SpeculativeDraftBlock {
2205                state: executor.begin_proposal(target_state, last, target_count, context)?,
2206                proposals: Vec::new(),
2207            }
2208        };
2209        if block.proposals.len() > target_count {
2210            return Err(SpeculativeDriverError::Generation(
2211                GenerationError::ProposalCapacityExceeded {
2212                    proposed: block.proposals.len(),
2213                    capacity: target_count,
2214                },
2215            ));
2216        }
2217        let additional = if block
2218            .proposals
2219            .last()
2220            .is_some_and(|proposal| self.config.eos_token_ids.contains(&proposal.token))
2221        {
2222            0
2223        } else {
2224            target_count - block.proposals.len()
2225        };
2226        if additional > 0 {
2227            let mut history =
2228                Vec::with_capacity(self.runtime.sequence().tokens().len() + block.proposals.len());
2229            history.extend_from_slice(self.runtime.sequence().tokens());
2230            history.extend(block.proposals.iter().map(|proposal| proposal.token));
2231            let previous = block.proposals.last().map_or_else(
2232                || {
2233                    *self
2234                        .runtime
2235                        .sequence()
2236                        .tokens()
2237                        .last()
2238                        .expect("prefill emitted a token")
2239                },
2240                |proposal| proposal.token,
2241            );
2242            let proposals = propose_block(
2243                executor,
2244                self.runtime.sampler(),
2245                &mut block.state,
2246                previous,
2247                additional,
2248                &history,
2249                self.config.temperature,
2250                &self.config.eos_token_ids,
2251                self.draft_randomness.as_ref(),
2252                context,
2253            )?;
2254            self.stats.draft_tokens += proposals.len();
2255            block.proposals.extend(proposals);
2256        }
2257        executor.take_telemetry()?.record(&mut self.stats);
2258        self.block = Some(block);
2259        self.transition(SpeculativeRequestStatus::ReadyToSubmitVerification)?;
2260        Ok(additional > 0)
2261    }
2262
2263    fn submit_verification<'context>(
2264        &mut self,
2265        executor: &mut E,
2266        context: E::Context<'context>,
2267    ) -> Result<(), SpeculativeDriverError<E::Error>>
2268    where
2269        E: 'context,
2270        S: SpeculativeSampling<
2271                Logits = E::Logits,
2272                Error = E::Error,
2273                Context<'context> = E::Context<'context>,
2274            > + 'context,
2275    {
2276        let block = self
2277            .block
2278            .take()
2279            .expect("verification-ready request has a draft block");
2280        let last = *self
2281            .runtime
2282            .sequence()
2283            .tokens()
2284            .last()
2285            .expect("prefill emitted a token");
2286        let pending = submit_verification_transaction(executor, self.cache, last, block, context)?;
2287        self.stats.target_tokens += pending.submitted_tokens();
2288        self.pending = Some(pending);
2289        self.transition(SpeculativeRequestStatus::TargetVerificationInFlight)
2290    }
2291
2292    fn draft_optimistic<'context>(
2293        &mut self,
2294        executor: &mut E,
2295        context: E::Context<'context>,
2296    ) -> Result<(), SpeculativeDriverError<E::Error>>
2297    where
2298        E: 'context,
2299        S: SpeculativeSampling<
2300                Logits = E::Logits,
2301                Error = E::Error,
2302                Context<'context> = E::Context<'context>,
2303            > + 'context,
2304    {
2305        let started = Instant::now();
2306        self.transition(SpeculativeRequestStatus::OptimisticDraftRunning)?;
2307        let pending = self
2308            .pending
2309            .as_mut()
2310            .expect("optimistic request has an in-flight verification");
2311        let block = pending.block();
2312        let assumed_len = self.runtime.sequence().tokens().len() + block.proposals.len();
2313        let count = self
2314            .config
2315            .max_draft_tokens
2316            .min(executor.max_proposals())
2317            .min(self.config.max_tokens.saturating_sub(assumed_len));
2318        let mut state = block.state.clone();
2319        let last = block
2320            .proposals
2321            .last()
2322            .expect("optimistic block has an assumed token")
2323            .token;
2324        let mut history = Vec::with_capacity(assumed_len);
2325        history.extend_from_slice(self.runtime.sequence().tokens());
2326        history.extend(block.proposals.iter().map(|proposal| proposal.token));
2327        let proposals = propose_block(
2328            executor,
2329            self.runtime.sampler(),
2330            &mut state,
2331            last,
2332            count,
2333            &history,
2334            self.config.temperature,
2335            &self.config.eos_token_ids,
2336            self.draft_randomness.as_ref(),
2337            context,
2338        )?;
2339        self.stats.optimistic_draft_tokens += proposals.len();
2340        self.stats.optimistic_draft_blocks += 1;
2341        self.stats.optimistic_draft_time += started.elapsed();
2342        pending
2343            .set_optimistic_branch(SpeculativeOptimisticBranch {
2344                block: SpeculativeDraftBlock { state, proposals },
2345                assumed_prefix: history,
2346            })
2347            .map_err(SpeculativeDriverError::Generation)?;
2348        self.transition(SpeculativeRequestStatus::OptimisticDraftReady)
2349    }
2350
2351    fn resolve_verification<'context>(
2352        &mut self,
2353        executor: &mut E,
2354        options: SpeculativeSchedulerOptions,
2355        context: E::Context<'context>,
2356    ) -> Result<(), SpeculativeDriverError<E::Error>>
2357    where
2358        E: 'context,
2359        S: SpeculativeSampling<
2360                Logits = E::Logits,
2361                Error = E::Error,
2362                Context<'context> = E::Context<'context>,
2363            > + 'context,
2364    {
2365        self.transition(SpeculativeRequestStatus::VerificationResolution)?;
2366        let pending = self
2367            .pending
2368            .take()
2369            .expect("resolving request has an in-flight verification");
2370        if self.lifecycle.cancellation_pending() || self.runtime.cancellation().is_cancelled() {
2371            let (mut stats, telemetry) = cancel_pending_verification(
2372                executor,
2373                self.cache,
2374                pending,
2375                &mut self.runtime,
2376                self.stats.clone(),
2377                context,
2378            )?;
2379            telemetry.record(&mut stats);
2380            self.stats = stats;
2381            self.transition(SpeculativeRequestStatus::Cancelled)?;
2382            self.stats.elapsed = self.started.elapsed();
2383            return Ok(());
2384        }
2385        let mut published = resolve_commit_and_publish(
2386            executor,
2387            self.cache,
2388            pending,
2389            &mut self.runtime,
2390            self.target_randomness.as_ref(),
2391            self.config.temperature,
2392            self.stats.clone(),
2393            options,
2394            context,
2395        )?;
2396        published.telemetry.record(&mut published.stats);
2397        self.target_state = Some(published.target_state);
2398        self.target_randomness = published.target_randomness;
2399        self.stats = published.stats;
2400        match published.status {
2401            SpeculativePublicationStatus::Continue(continuation) => {
2402                self.block = continuation.into_block();
2403                self.transition(SpeculativeRequestStatus::ReadyToDraft)?;
2404            }
2405            SpeculativePublicationStatus::Completed => {
2406                self.transition(SpeculativeRequestStatus::Completed)?;
2407                self.stats.elapsed = self.started.elapsed();
2408            }
2409            SpeculativePublicationStatus::Cancelled => {
2410                self.transition(SpeculativeRequestStatus::Cancelled)?;
2411                self.stats.elapsed = self.started.elapsed();
2412            }
2413        }
2414        Ok(())
2415    }
2416}
2417
2418/// One completed request returned in stable submission order.
2419pub struct CompletedSpeculativeRequest<S> {
2420    /// Stable request identity.
2421    id: SpeculativeRequestId,
2422    /// Canonical generated token sequence.
2423    token_ids: Vec<u32>,
2424    /// Portable request telemetry.
2425    stats: SpeculativeStats,
2426    /// Final backend sampling state.
2427    sampler: S,
2428    /// Terminal reason selected by the canonical sequence.
2429    finish_reason: Option<FinishReason>,
2430    /// Terminal lifecycle status.
2431    status: SpeculativeRequestStatus,
2432}
2433
2434impl<S> CompletedSpeculativeRequest<S> {
2435    /// Stable request identity.
2436    pub const fn id(&self) -> SpeculativeRequestId {
2437        self.id
2438    }
2439    /// Canonical emitted token ids.
2440    pub fn token_ids(&self) -> &[u32] {
2441        &self.token_ids
2442    }
2443    /// Portable request telemetry.
2444    pub const fn stats(&self) -> &SpeculativeStats {
2445        &self.stats
2446    }
2447    /// Final sampling state.
2448    pub const fn sampler(&self) -> &S {
2449        &self.sampler
2450    }
2451    /// Terminal reason, when completed normally.
2452    pub const fn finish_reason(&self) -> Option<FinishReason> {
2453        self.finish_reason
2454    }
2455    /// Terminal request status.
2456    pub const fn status(&self) -> SpeculativeRequestStatus {
2457        self.status
2458    }
2459    /// Consumes the request into a named handoff artifact.
2460    pub fn into_artifact(self) -> CompletedSpeculativeRequestArtifact<S> {
2461        CompletedSpeculativeRequestArtifact {
2462            id: self.id,
2463            token_ids: self.token_ids,
2464            stats: self.stats,
2465            sampler: self.sampler,
2466            finish_reason: self.finish_reason,
2467            status: self.status,
2468        }
2469    }
2470}
2471
2472/// Named consuming artifact for adapting one completed speculative request.
2473pub struct CompletedSpeculativeRequestArtifact<S> {
2474    id: SpeculativeRequestId,
2475    token_ids: Vec<u32>,
2476    stats: SpeculativeStats,
2477    sampler: S,
2478    finish_reason: Option<FinishReason>,
2479    status: SpeculativeRequestStatus,
2480}
2481
2482impl<S> CompletedSpeculativeRequestArtifact<S> {
2483    /// Stable request identity.
2484    pub const fn id(&self) -> SpeculativeRequestId {
2485        self.id
2486    }
2487    /// Takes canonical token ids.
2488    pub fn take_token_ids(&mut self) -> Vec<u32> {
2489        std::mem::take(&mut self.token_ids)
2490    }
2491    /// Takes request telemetry.
2492    pub fn take_stats(&mut self) -> SpeculativeStats {
2493        std::mem::take(&mut self.stats)
2494    }
2495    /// Consumes the artifact into its final sampler.
2496    pub fn into_sampler(self) -> S {
2497        self.sampler
2498    }
2499    /// Terminal finish reason.
2500    pub const fn finish_reason(&self) -> Option<FinishReason> {
2501        self.finish_reason
2502    }
2503    /// Terminal lifecycle status.
2504    pub const fn status(&self) -> SpeculativeRequestStatus {
2505        self.status
2506    }
2507}
2508
2509/// Completed request table and aggregate fair-scheduler telemetry.
2510pub struct CompletedSpeculativeSchedule<S> {
2511    /// Requests in stable submission order.
2512    requests: Vec<CompletedSpeculativeRequest<S>>,
2513    /// Aggregate scheduler telemetry.
2514    scheduler: SpeculativeSchedulerStats,
2515}
2516
2517impl<S> CompletedSpeculativeSchedule<S> {
2518    /// Consumes the schedule into request results.
2519    pub fn into_requests(self) -> Vec<CompletedSpeculativeRequest<S>> {
2520        self.requests
2521    }
2522    /// Takes completed requests while retaining access to scheduler telemetry.
2523    pub fn take_requests(&mut self) -> Vec<CompletedSpeculativeRequest<S>> {
2524        std::mem::take(&mut self.requests)
2525    }
2526    /// Takes aggregate scheduler telemetry.
2527    pub fn take_scheduler(&mut self) -> SpeculativeSchedulerStats {
2528        std::mem::take(&mut self.scheduler)
2529    }
2530    /// Aggregate scheduler telemetry.
2531    pub const fn scheduler(&self) -> &SpeculativeSchedulerStats {
2532        &self.scheduler
2533    }
2534}
2535
2536/// Canonical table and action coordinator for speculative requests.
2537pub struct SpeculativeRequestTable<'cache, E, S, C, P>
2538where
2539    E: SpeculativeExecutor,
2540    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
2541    C: SpeculativeConstraint,
2542    P: SpeculativePublisher<C>,
2543{
2544    schedule: SpeculativeSchedule,
2545    requests: Vec<SpeculativeRequest<'cache, E, S, C, P>>,
2546    stats: SpeculativeSchedulerStats,
2547}
2548
2549impl<'cache, E, S, C, P> SpeculativeRequestTable<'cache, E, S, C, P>
2550where
2551    E: SpeculativeExecutor,
2552    S: SpeculativeSampling<Logits = E::Logits, Error = E::Error>,
2553    C: SpeculativeConstraint,
2554    P: SpeculativePublisher<C>,
2555{
2556    /// Creates an empty validated request table.
2557    pub fn new(
2558        options: SpeculativeSchedulerOptions,
2559        topology: SpeculativeExecutionTopology,
2560    ) -> Result<Self, GenerationError> {
2561        Ok(Self {
2562            schedule: SpeculativeSchedule::new(options)?,
2563            requests: Vec::new(),
2564            stats: SpeculativeSchedulerStats {
2565                execution_topology: topology,
2566                ..SpeculativeSchedulerStats::default()
2567            },
2568        })
2569    }
2570
2571    /// Returns one request by stable identity.
2572    pub fn request(
2573        &self,
2574        id: SpeculativeRequestId,
2575    ) -> Option<&SpeculativeRequest<'cache, E, S, C, P>> {
2576        self.requests.get(id.index())
2577    }
2578
2579    /// Returns one request's current status.
2580    pub fn status(&self, id: SpeculativeRequestId) -> Option<SpeculativeRequestStatus> {
2581        self.request(id).map(SpeculativeRequest::status)
2582    }
2583
2584    /// Whether every request is terminal.
2585    pub fn is_finished(&self) -> bool {
2586        self.requests
2587            .iter()
2588            .all(|request| request.lifecycle.is_terminal())
2589    }
2590
2591    /// Validated scheduler options.
2592    pub const fn options(&self) -> SpeculativeSchedulerOptions {
2593        self.schedule.options()
2594    }
2595
2596    /// Prefills and inserts one request, or records its pre-existing terminal state.
2597    #[allow(clippy::too_many_arguments)]
2598    pub fn submit<'context>(
2599        &mut self,
2600        executor: &mut E,
2601        cache: &'cache mut E::Cache,
2602        input: E::Input,
2603        config: SpeculativeConfig,
2604        mut runtime: SpeculativeOutputRuntime<S, C, P>,
2605        randomness: SpeculativeRandomness<S::RandomState, S::DraftRandomness>,
2606        component_timings_collected: bool,
2607        context: E::Context<'context>,
2608    ) -> Result<SpeculativeRequestId, SpeculativeDriverError<E::Error>>
2609    where
2610        E: 'context,
2611        S: SpeculativeSampling<
2612                Logits = E::Logits,
2613                Error = E::Error,
2614                Context<'context> = E::Context<'context>,
2615            > + 'context,
2616    {
2617        config
2618            .validate()
2619            .map_err(SpeculativeDriverError::Generation)?;
2620        if executor.max_proposals() == 0 {
2621            return Err(SpeculativeDriverError::Generation(
2622                GenerationError::NoBackendDraftCapacity,
2623            ));
2624        }
2625        let id = SpeculativeRequestId::new(self.requests.len());
2626        let started = Instant::now();
2627        let mut stats = SpeculativeStats {
2628            execution_topology: self.stats.execution_topology,
2629            component_timings_collected,
2630            ..SpeculativeStats::default()
2631        };
2632        let (target_randomness, draft_randomness) = (randomness.target, randomness.draft);
2633        let (target_state, lifecycle) = if runtime.cancellation().is_cancelled() {
2634            runtime.cancel().map_err(SpeculativeDriverError::Output)?;
2635            stats.elapsed = started.elapsed();
2636            (None, SpeculativeRequestLifecycle::cancelled())
2637        } else if runtime.sequence().is_finished() {
2638            stats.elapsed = started.elapsed();
2639            (None, SpeculativeRequestLifecycle::completed())
2640        } else {
2641            let prefill = executor.prefill(input, cache, context)?;
2642            stats.target_tokens = prefill.evaluated_tokens;
2643            stats.scheduler_turns = 1;
2644            let mut sampler = runtime.sampler().clone();
2645            let mut constraint = runtime
2646                .constraint()
2647                .fork()
2648                .map_err(SpeculativeDriverError::Output)?;
2649            let mut sequence = runtime.sequence().clone();
2650            let mut target_randomness = target_randomness.clone();
2651            let first_logits = sampler.process_logits(
2652                &prefill.logits,
2653                config.temperature,
2654                &[],
2655                SamplingPlacement::Target,
2656                context,
2657            )?;
2658            let first = sampler.sample(
2659                &first_logits,
2660                config.temperature,
2661                target_randomness.as_mut(),
2662                SamplingPlacement::Target,
2663                context,
2664            )?;
2665            sampler.commit_token(&first_logits, first, SamplingPlacement::Target, context)?;
2666            let reason =
2667                commit_terminal_token(&mut sequence, &mut sampler, &mut constraint, first)?;
2668            runtime.install_committed_state(sampler, constraint, sequence);
2669            let cancelled = runtime
2670                .publish_committed(&[first])
2671                .map_err(SpeculativeDriverError::Output)?;
2672            stats.emitted_tokens = 1;
2673            let lifecycle = if cancelled {
2674                stats.elapsed = started.elapsed();
2675                SpeculativeRequestLifecycle::cancelled()
2676            } else if reason.is_some() {
2677                stats.elapsed = started.elapsed();
2678                SpeculativeRequestLifecycle::completed()
2679            } else {
2680                let mut lifecycle = SpeculativeRequestLifecycle::new();
2681                lifecycle
2682                    .transition(SpeculativeRequestStatus::ReadyToDraft)
2683                    .map_err(SpeculativeDriverError::Generation)?;
2684                lifecycle
2685            };
2686            self.stats.turns += 1;
2687            self.requests.push(SpeculativeRequest {
2688                id,
2689                cache,
2690                config,
2691                runtime,
2692                target_randomness,
2693                draft_randomness,
2694                stats,
2695                started,
2696                target_state: Some(prefill.state),
2697                block: None,
2698                pending: None,
2699                lifecycle,
2700            });
2701            return Ok(id);
2702        };
2703        self.requests.push(SpeculativeRequest {
2704            id,
2705            cache,
2706            config,
2707            runtime,
2708            target_randomness,
2709            draft_randomness,
2710            stats,
2711            started,
2712            target_state,
2713            block: None,
2714            pending: None,
2715            lifecycle,
2716        });
2717        Ok(id)
2718    }
2719
2720    /// Requests cancellation without releasing an exact in-flight transaction.
2721    pub fn cancel(
2722        &mut self,
2723        id: SpeculativeRequestId,
2724    ) -> Result<(), SpeculativeDriverError<E::Error>> {
2725        let request = self.requests.get_mut(id.index()).ok_or_else(|| {
2726            SpeculativeDriverError::Generation(GenerationError::UnknownSpeculativeRequest {
2727                index: id.index(),
2728            })
2729        })?;
2730        request.request_cancellation()
2731    }
2732
2733    /// Applies one fairly selected request action.
2734    pub fn step<'context>(
2735        &mut self,
2736        executor: &mut E,
2737        optimistic_execution_available: bool,
2738        context: E::Context<'context>,
2739    ) -> Result<bool, SpeculativeDriverError<E::Error>>
2740    where
2741        E: 'context,
2742        S: SpeculativeSampling<
2743                Logits = E::Logits,
2744                Error = E::Error,
2745                Context<'context> = E::Context<'context>,
2746            > + 'context,
2747    {
2748        let cancelled = self
2749            .requests
2750            .iter()
2751            .filter(|request| {
2752                request.runtime.cancellation().is_cancelled() && !request.lifecycle.is_terminal()
2753            })
2754            .map(|request| request.id)
2755            .collect::<Vec<_>>();
2756        for id in cancelled {
2757            self.cancel(id)?;
2758        }
2759        if self.is_finished() {
2760            return Ok(false);
2761        }
2762
2763        let candidates = self
2764            .requests
2765            .iter()
2766            .map(|request| request.candidate(executor, optimistic_execution_available))
2767            .collect::<Result<Vec<_>, _>>()?;
2768        let Some(action) = self
2769            .schedule
2770            .next_action(&candidates)
2771            .map_err(SpeculativeDriverError::Generation)?
2772        else {
2773            return Ok(false);
2774        };
2775        let index = match action {
2776            SpeculativeAction::SubmitVerification(index)
2777            | SpeculativeAction::DraftOptimistic(index)
2778            | SpeculativeAction::ResolveVerification(index)
2779            | SpeculativeAction::DraftCommitted { index, .. } => index,
2780        };
2781        self.stats.turns += 1;
2782        self.requests[index].stats.scheduler_turns += 1;
2783        match action {
2784            SpeculativeAction::SubmitVerification(index) => {
2785                self.requests[index].submit_verification(executor, context)?;
2786                let in_flight = self
2787                    .requests
2788                    .iter()
2789                    .filter(|request| request.pending.is_some())
2790                    .count();
2791                self.stats.peak_in_flight_verifications =
2792                    self.stats.peak_in_flight_verifications.max(in_flight);
2793            }
2794            SpeculativeAction::DraftCommitted {
2795                index,
2796                cross_request,
2797            } => {
2798                let drafted = self.requests[index].draft_committed(executor, context)?;
2799                if cross_request && drafted {
2800                    self.requests[index].stats.cross_request_draft_opportunities += 1;
2801                    self.stats.cross_request_draft_opportunities += 1;
2802                }
2803            }
2804            SpeculativeAction::DraftOptimistic(index) => {
2805                self.requests[index].draft_optimistic(executor, context)?;
2806                let optimistic = self
2807                    .requests
2808                    .iter()
2809                    .filter(|request| {
2810                        request
2811                            .pending
2812                            .as_ref()
2813                            .is_some_and(PendingSpeculativeVerification::has_optimistic_branch)
2814                    })
2815                    .count();
2816                self.stats.peak_optimistic_branches =
2817                    self.stats.peak_optimistic_branches.max(optimistic);
2818            }
2819            SpeculativeAction::ResolveVerification(index) => {
2820                self.requests[index].resolve_verification(
2821                    executor,
2822                    self.schedule.options(),
2823                    context,
2824                )?;
2825            }
2826        }
2827        Ok(true)
2828    }
2829
2830    /// Drives every request to a terminal state.
2831    pub fn run<'context>(
2832        &mut self,
2833        executor: &mut E,
2834        optimistic_execution_available: bool,
2835        context: E::Context<'context>,
2836    ) -> Result<(), SpeculativeDriverError<E::Error>>
2837    where
2838        E: 'context,
2839        S: SpeculativeSampling<
2840                Logits = E::Logits,
2841                Error = E::Error,
2842                Context<'context> = E::Context<'context>,
2843            > + 'context,
2844    {
2845        while self.step(executor, optimistic_execution_available, context)? {}
2846        Ok(())
2847    }
2848
2849    /// Consumes a terminal table and returns outputs in stable submission order.
2850    pub fn finish(
2851        self,
2852    ) -> Result<CompletedSpeculativeSchedule<S>, SpeculativeDriverError<E::Error>> {
2853        if !self.is_finished() {
2854            return Err(SpeculativeDriverError::Generation(
2855                GenerationError::ActiveSpeculativeRequests,
2856            ));
2857        }
2858        Ok(CompletedSpeculativeSchedule {
2859            requests: self
2860                .requests
2861                .into_iter()
2862                .map(|request| {
2863                    let (sampler, sequence, _, _) = request.runtime.into_parts();
2864                    CompletedSpeculativeRequest {
2865                        id: request.id,
2866                        finish_reason: sequence.finish_reason(),
2867                        token_ids: sequence.into_tokens(),
2868                        stats: request.stats,
2869                        sampler,
2870                        status: request.lifecycle.status(),
2871                    }
2872                })
2873                .collect(),
2874            scheduler: self.stats,
2875        })
2876    }
2877}
2878
2879/// Portable candidate snapshot used by fair speculative action selection.
2880#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2881pub struct SpeculativeCandidate {
2882    /// Current validated request status.
2883    status: SpeculativeRequestStatus,
2884    /// Whether this request may start exact optimistic work now.
2885    optimistic_eligible: bool,
2886}
2887
2888/// One backend action selected by the portable fair scheduler.
2889#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2890#[non_exhaustive]
2891pub enum SpeculativeAction {
2892    /// Submit a prepared proposal block.
2893    SubmitVerification(usize),
2894    /// Draft canonical proposals; the flag records cross-request overlap.
2895    DraftCommitted {
2896        /// Selected request index.
2897        index: usize,
2898        /// Whether target work from another request is in flight.
2899        cross_request: bool,
2900    },
2901    /// Draft against an unresolved optimistic prefix.
2902    DraftOptimistic(usize),
2903    /// Resolve one exact verification completion.
2904    ResolveVerification(usize),
2905}
2906
2907/// Backend-neutral fair action selector for speculative requests.
2908pub struct SpeculativeSchedule {
2909    options: SpeculativeSchedulerOptions,
2910    cursor: usize,
2911}
2912
2913impl SpeculativeSchedule {
2914    /// Creates a validated schedule.
2915    pub fn new(options: SpeculativeSchedulerOptions) -> Result<Self, GenerationError> {
2916        Ok(Self {
2917            options: options.validate()?,
2918            cursor: 0,
2919        })
2920    }
2921
2922    /// Validated scheduler options.
2923    pub const fn options(&self) -> SpeculativeSchedulerOptions {
2924        self.options
2925    }
2926
2927    /// Selects the next fair action, or `None` when every request is terminal.
2928    pub fn next_action(
2929        &mut self,
2930        candidates: &[SpeculativeCandidate],
2931    ) -> Result<Option<SpeculativeAction>, GenerationError> {
2932        if candidates.iter().all(|candidate| {
2933            matches!(
2934                candidate.status,
2935                SpeculativeRequestStatus::Completed | SpeculativeRequestStatus::Cancelled
2936            )
2937        }) {
2938            return Ok(None);
2939        }
2940        let in_flight = candidates
2941            .iter()
2942            .filter(|candidate| {
2943                matches!(
2944                    candidate.status,
2945                    SpeculativeRequestStatus::TargetVerificationInFlight
2946                        | SpeculativeRequestStatus::OptimisticDraftRunning
2947                        | SpeculativeRequestStatus::OptimisticDraftReady
2948                        | SpeculativeRequestStatus::VerificationResolution
2949                )
2950            })
2951            .count();
2952        let optimistic = candidates
2953            .iter()
2954            .filter(|candidate| candidate.status == SpeculativeRequestStatus::OptimisticDraftReady)
2955            .count();
2956
2957        if in_flight < self.options.max_in_flight_verifications {
2958            if let Some(index) = self.select(candidates, |candidate| {
2959                candidate.status == SpeculativeRequestStatus::ReadyToSubmitVerification
2960            }) {
2961                return Ok(Some(SpeculativeAction::SubmitVerification(index)));
2962            }
2963        }
2964        if in_flight > 0 {
2965            if optimistic < self.options.max_optimistic_branches
2966                && self.options.lookahead_blocks > 0
2967            {
2968                if let Some(index) = self.select(candidates, |candidate| {
2969                    candidate.status == SpeculativeRequestStatus::TargetVerificationInFlight
2970                        && candidate.optimistic_eligible
2971                }) {
2972                    return Ok(Some(SpeculativeAction::DraftOptimistic(index)));
2973                }
2974            }
2975            if let Some(index) = self.select(candidates, |candidate| {
2976                candidate.status == SpeculativeRequestStatus::ReadyToDraft
2977            }) {
2978                return Ok(Some(SpeculativeAction::DraftCommitted {
2979                    index,
2980                    cross_request: true,
2981                }));
2982            }
2983            if let Some(index) = self.select(candidates, |candidate| {
2984                matches!(
2985                    candidate.status,
2986                    SpeculativeRequestStatus::TargetVerificationInFlight
2987                        | SpeculativeRequestStatus::OptimisticDraftReady
2988                )
2989            }) {
2990                return Ok(Some(SpeculativeAction::ResolveVerification(index)));
2991            }
2992        } else if let Some(index) = self.select(candidates, |candidate| {
2993            candidate.status == SpeculativeRequestStatus::ReadyToDraft
2994        }) {
2995            return Ok(Some(SpeculativeAction::DraftCommitted {
2996                index,
2997                cross_request: false,
2998            }));
2999        }
3000        Err(GenerationError::StalledSpeculativeSchedule)
3001    }
3002
3003    fn select(
3004        &mut self,
3005        candidates: &[SpeculativeCandidate],
3006        predicate: impl Fn(&SpeculativeCandidate) -> bool,
3007    ) -> Option<usize> {
3008        for offset in 0..candidates.len() {
3009            let index = (self.cursor + offset) % candidates.len();
3010            if predicate(&candidates[index]) {
3011                self.cursor = (index + 1) % candidates.len();
3012                return Some(index);
3013            }
3014        }
3015        None
3016    }
3017}
3018
3019#[cfg(test)]
3020mod tests {
3021    use super::*;
3022    use std::{cell::RefCell, convert::Infallible, rc::Rc};
3023
3024    type TransactionTrace = Rc<RefCell<Vec<&'static str>>>;
3025
3026    #[test]
3027    fn speculative_capability_schema_round_trips_without_backend_identity() {
3028        let capability = SpeculativeCapability::Unsupported {
3029            draft_source: SpeculativeDraftSource::Embedded,
3030            architecture: "future_decoder".into(),
3031        };
3032        let json = serde_json::to_string(&capability).unwrap();
3033        assert_eq!(
3034            serde_json::from_str::<SpeculativeCapability>(&json).unwrap(),
3035            capability
3036        );
3037        assert!(!json.contains("mlx"));
3038    }
3039
3040    #[derive(Debug, Clone, Default)]
3041    struct Done {
3042        trace: Option<TransactionTrace>,
3043    }
3044
3045    impl Completion for Done {
3046        type Error = Infallible;
3047
3048        fn is_complete(&self) -> Result<bool, Self::Error> {
3049            Ok(true)
3050        }
3051
3052        fn wait(&self) -> Result<(), Self::Error> {
3053            if let Some(trace) = &self.trace {
3054                trace.borrow_mut().push("wait");
3055            }
3056            Ok(())
3057        }
3058    }
3059
3060    #[derive(Clone, Default)]
3061    struct PortableSemanticState {
3062        events: Vec<crate::generation::SemanticEvent>,
3063    }
3064
3065    impl SpeculativeSemanticState for PortableSemanticState {
3066        fn fork_box(&self) -> Result<Box<dyn SpeculativeSemanticState>, SpeculativeOutputError> {
3067            let mut fork = self.clone();
3068            fork.events.clear();
3069            Ok(Box::new(fork))
3070        }
3071
3072        fn push_token(&mut self, token: u32) -> Result<bool, SpeculativeOutputError> {
3073            self.events
3074                .push(crate::generation::SemanticEvent::TextDelta(
3075                    token.to_string(),
3076                ));
3077            Ok(false)
3078        }
3079
3080        fn finish(&mut self, reason: FinishReason) -> Result<(), SpeculativeOutputError> {
3081            self.events
3082                .push(crate::generation::SemanticEvent::Finished { reason });
3083            Ok(())
3084        }
3085
3086        fn cancel(&mut self) -> Result<(), SpeculativeOutputError> {
3087            self.finish(FinishReason::Cancelled)
3088        }
3089
3090        fn take_events(&mut self) -> Vec<crate::generation::SemanticEvent> {
3091            std::mem::take(&mut self.events)
3092        }
3093    }
3094
3095    #[test]
3096    fn core_semantic_publisher_commits_and_cancels_without_backend_errors() {
3097        let published = Rc::new(RefCell::new(Vec::new()));
3098        let mut constraint =
3099            SpeculativeSemanticConstraint::semantic(Box::new(PortableSemanticState::default()));
3100        constraint.push_token(7).unwrap();
3101        constraint.finish(FinishReason::MaxTokens).unwrap();
3102        {
3103            let published = Rc::clone(&published);
3104            let mut publisher = SpeculativeCallbackPublisher::semantic(move |event| {
3105                published.borrow_mut().push(event)
3106            });
3107            assert!(!publisher
3108                .publish_committed(
3109                    &mut constraint,
3110                    &[7],
3111                    &GenerationCancellationToken::new(),
3112                    true,
3113                )
3114                .unwrap());
3115        }
3116        assert_eq!(
3117            *published.borrow(),
3118            vec![
3119                crate::generation::SemanticEvent::TextDelta("7".into()),
3120                crate::generation::SemanticEvent::Finished {
3121                    reason: FinishReason::MaxTokens,
3122                },
3123            ]
3124        );
3125
3126        let cancelled = Rc::new(RefCell::new(Vec::new()));
3127        let mut constraint =
3128            SpeculativeSemanticConstraint::semantic(Box::new(PortableSemanticState::default()));
3129        {
3130            let cancelled = Rc::clone(&cancelled);
3131            let mut publisher = SpeculativeCallbackPublisher::semantic(move |event| {
3132                cancelled.borrow_mut().push(event)
3133            });
3134            publisher.publish_cancelled(&mut constraint).unwrap();
3135        }
3136        assert_eq!(
3137            *cancelled.borrow(),
3138            vec![crate::generation::SemanticEvent::Finished {
3139                reason: FinishReason::Cancelled,
3140            }]
3141        );
3142
3143        let mut constraint = SpeculativeSemanticConstraint::plain();
3144        let mut publisher = SpeculativeCallbackPublisher::tokens(|_| {
3145            Err(SpeculativeOutputError::publication("consumer closed"))
3146        });
3147        assert_eq!(
3148            publisher
3149                .publish_committed(
3150                    &mut constraint,
3151                    &[11],
3152                    &GenerationCancellationToken::new(),
3153                    false,
3154                )
3155                .unwrap_err(),
3156            SpeculativeOutputError::publication("consumer closed")
3157        );
3158    }
3159
3160    #[derive(Default)]
3161    struct MockExecutor {
3162        trace: Option<TransactionTrace>,
3163    }
3164
3165    struct MockVerification {
3166        tokens: Vec<u32>,
3167        logits: Vec<Vec<f32>>,
3168    }
3169
3170    impl SpeculativeExecutor for MockExecutor {
3171        type Input = Vec<u32>;
3172        type Cache = Vec<u32>;
3173        type TargetState = usize;
3174        type DraftState = Vec<u32>;
3175        type CacheCheckpoint = usize;
3176        type Verification = MockVerification;
3177        type Logits = Vec<f32>;
3178        type Context<'a> = ();
3179        type Completion = Done;
3180        type Telemetry = ();
3181        type Error = Infallible;
3182
3183        fn supports_exact_optimistic_promotion(&self) -> bool {
3184            true
3185        }
3186
3187        fn prefill<'context>(
3188            &mut self,
3189            input: Self::Input,
3190            cache: &mut Self::Cache,
3191            _: Self::Context<'context>,
3192        ) -> Result<SpeculativePrefill<Self::TargetState, Self::Logits>, Self::Error>
3193        where
3194            Self: 'context,
3195        {
3196            cache.extend_from_slice(&input);
3197            Ok(SpeculativePrefill {
3198                logits: vec![0.0, 1.0],
3199                state: cache.len(),
3200                evaluated_tokens: input.len(),
3201            })
3202        }
3203
3204        fn begin_proposal<'a>(
3205            &mut self,
3206            _: &Self::TargetState,
3207            last_token: u32,
3208            _: usize,
3209            _: Self::Context<'a>,
3210        ) -> Result<Self::DraftState, Self::Error> {
3211            Ok(vec![last_token])
3212        }
3213
3214        fn proposal_logits<'a>(
3215            &mut self,
3216            state: &mut Self::DraftState,
3217            last_token: u32,
3218            _: Self::Context<'a>,
3219        ) -> Result<Self::Logits, Self::Error> {
3220            state.push(last_token + 1);
3221            Ok(vec![0.0, 1.0])
3222        }
3223
3224        fn checkpoint(cache: &Self::Cache) -> Self::CacheCheckpoint {
3225            cache.len()
3226        }
3227
3228        fn submit_verification<'a>(
3229            &mut self,
3230            input_tokens: &[u32],
3231            cache: &mut Self::Cache,
3232            _: Self::Context<'a>,
3233        ) -> Result<Submission<Self::Verification, Self::Completion>, Self::Error> {
3234            cache.extend_from_slice(input_tokens);
3235            Ok(Submission {
3236                output: MockVerification {
3237                    tokens: input_tokens.to_vec(),
3238                    logits: vec![vec![0.0, 1.0], vec![1.0, 0.0], vec![0.0, 1.0]],
3239                },
3240                completion: Done {
3241                    trace: self.trace.clone(),
3242                },
3243            })
3244        }
3245
3246        fn verification_logits<'a>(
3247            output: &Self::Verification,
3248            index: usize,
3249            _: Self::Context<'a>,
3250        ) -> Result<Self::Logits, Self::Error>
3251        where
3252            Self: 'a,
3253        {
3254            Ok(output.logits[index].clone())
3255        }
3256
3257        fn commit_verification<'a>(
3258            &mut self,
3259            output: Self::Verification,
3260            draft_state: Self::DraftState,
3261            cache: &mut Self::Cache,
3262            checkpoint: Self::CacheCheckpoint,
3263            verified_inputs: usize,
3264            _: Self::Context<'a>,
3265        ) -> Result<SpeculativeCommit<Self::TargetState>, Self::Error> {
3266            assert!(!output.tokens.is_empty());
3267            if let Some(trace) = &self.trace {
3268                trace.borrow_mut().push("commit");
3269            }
3270            cache.truncate(checkpoint + verified_inputs);
3271            Ok(SpeculativeCommit {
3272                state: draft_state.len(),
3273                replayed_tokens: 0,
3274            })
3275        }
3276    }
3277
3278    #[test]
3279    fn mock_executor_prefill_propose_verify_and_commit_without_a_tensor_runtime() {
3280        let mut executor = MockExecutor::default();
3281        let mut cache = Vec::new();
3282        let prefill = executor.prefill(vec![4, 5], &mut cache, ()).unwrap();
3283        let mut draft = executor.begin_proposal(&prefill.state, 5, 2, ()).unwrap();
3284        assert_eq!(
3285            executor.proposal_logits(&mut draft, 5, ()).unwrap(),
3286            [0.0, 1.0]
3287        );
3288        let checkpoint = MockExecutor::checkpoint(&cache);
3289        let submission = executor
3290            .submit_verification(&[5, 6], &mut cache, ())
3291            .unwrap();
3292        submission.completion.wait().unwrap();
3293        let commit = executor
3294            .commit_verification(submission.output, draft, &mut cache, checkpoint, 1, ())
3295            .unwrap();
3296        assert_eq!(cache, [4, 5, 5]);
3297        assert_eq!(commit.replayed_tokens, 0);
3298    }
3299
3300    #[test]
3301    fn execution_topology_is_a_portable_schema() {
3302        let topology = SpeculativeExecutionTopology::CrossDeviceSplit;
3303        let encoded = serde_json::to_string(&topology).unwrap();
3304        assert_eq!(encoded, "\"cross_device_split\"");
3305        assert_eq!(
3306            serde_json::from_str::<SpeculativeExecutionTopology>(&encoded).unwrap(),
3307            topology
3308        );
3309    }
3310
3311    #[derive(Clone, Default)]
3312    struct MockSampling {
3313        committed: Vec<u32>,
3314    }
3315
3316    impl SpeculativeSampling for MockSampling {
3317        type Logits = Vec<f32>;
3318        type Distribution = Vec<f32>;
3319        type Seed = ();
3320        type RandomState = usize;
3321        type DraftRandomness = usize;
3322        type Context<'a> = ();
3323        type Error = Infallible;
3324
3325        fn supports_exact_optimistic_promotion(&self) -> bool {
3326            true
3327        }
3328
3329        fn initialize_randomness<'a>(
3330            _: Option<Self::Seed>,
3331            _: f32,
3332            _: Self::Context<'a>,
3333        ) -> Result<SpeculativeRandomness<Self::RandomState, Self::DraftRandomness>, Self::Error>
3334        where
3335            Self: 'a,
3336        {
3337            Ok(SpeculativeRandomness {
3338                target: Some(0),
3339                draft: Some(0),
3340            })
3341        }
3342
3343        fn draft_randomness_at<'a>(
3344            root: &Self::DraftRandomness,
3345            position: usize,
3346            _: Self::Context<'a>,
3347        ) -> Result<Self::RandomState, Self::Error>
3348        where
3349            Self: 'a,
3350        {
3351            Ok(root + position)
3352        }
3353
3354        fn process_logits<'a>(
3355            &mut self,
3356            logits: &Self::Logits,
3357            _: f32,
3358            _: &[u32],
3359            _: SamplingPlacement,
3360            _: Self::Context<'a>,
3361        ) -> Result<Self::Distribution, Self::Error>
3362        where
3363            Self: 'a,
3364        {
3365            Ok(logits.clone())
3366        }
3367
3368        fn sample<'a>(
3369            &self,
3370            distribution: &Self::Distribution,
3371            _: f32,
3372            randomness: Option<&mut Self::RandomState>,
3373            _: SamplingPlacement,
3374            _: Self::Context<'a>,
3375        ) -> Result<u32, Self::Error>
3376        where
3377            Self: 'a,
3378        {
3379            if let Some(randomness) = randomness {
3380                *randomness += 1;
3381            }
3382            Ok(argmax(distribution))
3383        }
3384
3385        fn decide_proposal<'a>(
3386            &self,
3387            target: &Self::Distribution,
3388            _: &Self::Distribution,
3389            proposed: u32,
3390            _: f32,
3391            randomness: Option<&mut Self::RandomState>,
3392            _: Self::Context<'a>,
3393        ) -> Result<ProposalDecision, Self::Error>
3394        where
3395            Self: 'a,
3396        {
3397            if let Some(randomness) = randomness {
3398                *randomness += 1;
3399            }
3400            let target = argmax(target);
3401            Ok(if target == proposed {
3402                ProposalDecision::Accept
3403            } else {
3404                ProposalDecision::Reject(target)
3405            })
3406        }
3407
3408        fn commit_token<'a>(
3409            &mut self,
3410            _: &Self::Distribution,
3411            token: u32,
3412            _: SamplingPlacement,
3413            _: Self::Context<'a>,
3414        ) -> Result<(), Self::Error>
3415        where
3416            Self: 'a,
3417        {
3418            self.committed.push(token);
3419            Ok(())
3420        }
3421    }
3422
3423    fn argmax(values: &[f32]) -> u32 {
3424        values
3425            .iter()
3426            .enumerate()
3427            .max_by(|(_, left), (_, right)| left.total_cmp(right))
3428            .map(|(index, _)| index as u32)
3429            .unwrap()
3430    }
3431
3432    #[derive(Default)]
3433    struct MockConstraint {
3434        tokens: Vec<u32>,
3435        finished: Option<FinishReason>,
3436    }
3437
3438    impl SpeculativeConstraint for MockConstraint {
3439        fn fork(&self) -> Result<Self, SpeculativeOutputError> {
3440            Ok(Self {
3441                tokens: self.tokens.clone(),
3442                finished: self.finished,
3443            })
3444        }
3445
3446        fn push_token(&mut self, token: u32) -> Result<bool, SpeculativeOutputError> {
3447            self.tokens.push(token);
3448            Ok(false)
3449        }
3450
3451        fn finish(&mut self, reason: FinishReason) -> Result<(), SpeculativeOutputError> {
3452            self.finished = Some(reason);
3453            Ok(())
3454        }
3455    }
3456
3457    #[derive(Default)]
3458    struct MockPublisher {
3459        tokens: Vec<u32>,
3460        cancelled: bool,
3461        trace: Option<TransactionTrace>,
3462    }
3463
3464    impl SpeculativePublisher<MockConstraint> for MockPublisher {
3465        fn publish_committed(
3466            &mut self,
3467            _: &mut MockConstraint,
3468            tokens: &[u32],
3469            _: &GenerationCancellationToken,
3470            _: bool,
3471        ) -> Result<bool, SpeculativeOutputError> {
3472            if let Some(trace) = &self.trace {
3473                trace.borrow_mut().push("publish");
3474            }
3475            self.tokens.extend_from_slice(tokens);
3476            Ok(false)
3477        }
3478
3479        fn publish_cancelled(
3480            &mut self,
3481            _: &mut MockConstraint,
3482        ) -> Result<(), SpeculativeOutputError> {
3483            if let Some(trace) = &self.trace {
3484                trace.borrow_mut().push("cancel");
3485            }
3486            self.cancelled = true;
3487            Ok(())
3488        }
3489    }
3490
3491    fn mock_output_runtime(
3492        cancellation: GenerationCancellationToken,
3493        trace: Option<TransactionTrace>,
3494    ) -> SpeculativeOutputRuntime<MockSampling, MockConstraint, MockPublisher> {
3495        let mut sequence = GenerationSequence::new(8, []);
3496        sequence.commit(5, TokenTerminalSignals::default()).unwrap();
3497        SpeculativeOutputRuntime::new(
3498            MockSampling::default(),
3499            sequence,
3500            MockConstraint::default(),
3501            MockPublisher {
3502                trace,
3503                ..MockPublisher::default()
3504            },
3505            cancellation,
3506        )
3507    }
3508
3509    fn empty_mock_runtime(
3510        max_tokens: usize,
3511        cancellation: GenerationCancellationToken,
3512    ) -> SpeculativeOutputRuntime<MockSampling, MockConstraint, MockPublisher> {
3513        SpeculativeOutputRuntime::new(
3514            MockSampling::default(),
3515            GenerationSequence::new(max_tokens, []),
3516            MockConstraint::default(),
3517            MockPublisher::default(),
3518            cancellation,
3519        )
3520    }
3521
3522    #[test]
3523    fn portable_driver_proposes_and_resolves_acceptance_and_replacement() {
3524        let mut executor = MockExecutor::default();
3525        let sampler = MockSampling::default();
3526        let mut draft = executor.begin_proposal(&2, 5, 2, ()).unwrap();
3527        let proposals = propose_block(
3528            &mut executor,
3529            &sampler,
3530            &mut draft,
3531            5,
3532            2,
3533            &[5],
3534            0.7,
3535            &[],
3536            Some(&0),
3537            (),
3538        )
3539        .unwrap();
3540        assert_eq!(
3541            proposals
3542                .iter()
3543                .map(|proposal| proposal.token)
3544                .collect::<Vec<_>>(),
3545            [1, 1]
3546        );
3547
3548        let mut cache = vec![4, 5];
3549        let verification = executor
3550            .submit_verification(&[5, 1, 1], &mut cache, ())
3551            .unwrap()
3552            .output;
3553        let mut sequence = GenerationSequence::new(8, []);
3554        sequence.commit(5, TokenTerminalSignals::default()).unwrap();
3555        let resolved = resolve_round::<MockExecutor, MockSampling, MockConstraint>(
3556            &verification,
3557            proposals,
3558            &sampler,
3559            &sequence,
3560            &MockConstraint::default(),
3561            Some(&0),
3562            0.7,
3563            (),
3564        )
3565        .unwrap();
3566        assert_eq!(resolved.accepted_proposals, 1);
3567        assert_eq!(resolved.committed_tokens, [1, 0]);
3568        assert_eq!(resolved.verified_inputs, 2);
3569        assert_eq!(resolved.sampler.committed, [1, 0]);
3570        assert_eq!(resolved.sequence.tokens(), [5, 1, 0]);
3571        assert_eq!(resolved.constraint.tokens, [1, 0]);
3572        assert_eq!(resolved.target_randomness, Some(2));
3573        assert_eq!(resolved.finish_reason, None);
3574    }
3575
3576    #[test]
3577    fn portable_schedule_is_fair_and_respects_retained_capacity() {
3578        let mut schedule =
3579            SpeculativeSchedule::new(SpeculativeSchedulerOptions::default()).unwrap();
3580        let ready = SpeculativeCandidate {
3581            status: SpeculativeRequestStatus::ReadyToSubmitVerification,
3582            optimistic_eligible: false,
3583        };
3584        assert_eq!(
3585            schedule.next_action(&[ready, ready]).unwrap(),
3586            Some(SpeculativeAction::SubmitVerification(0))
3587        );
3588        assert_eq!(
3589            schedule.next_action(&[ready, ready]).unwrap(),
3590            Some(SpeculativeAction::SubmitVerification(1))
3591        );
3592
3593        let in_flight = SpeculativeCandidate {
3594            status: SpeculativeRequestStatus::TargetVerificationInFlight,
3595            optimistic_eligible: false,
3596        };
3597        let draft = SpeculativeCandidate {
3598            status: SpeculativeRequestStatus::ReadyToDraft,
3599            optimistic_eligible: false,
3600        };
3601        assert_eq!(
3602            schedule.next_action(&[in_flight, ready, draft]).unwrap(),
3603            Some(SpeculativeAction::DraftCommitted {
3604                index: 2,
3605                cross_request: true,
3606            })
3607        );
3608    }
3609
3610    #[test]
3611    fn request_table_owns_actions_resources_fairness_and_deferred_cancellation() {
3612        let mut executor = MockExecutor::default();
3613        let mut first_cache = Vec::new();
3614        let mut second_cache = Vec::new();
3615        let options = SpeculativeSchedulerOptions::default().with_lookahead(false);
3616        let mut table =
3617            SpeculativeRequestTable::new(options, SpeculativeExecutionTopology::Single).unwrap();
3618        let config = SpeculativeConfig {
3619            max_tokens: 3,
3620            max_draft_tokens: 2,
3621            temperature: 0.7,
3622            eos_token_ids: Vec::new(),
3623        };
3624        let first_cancellation = GenerationCancellationToken::new();
3625        let first = table
3626            .submit(
3627                &mut executor,
3628                &mut first_cache,
3629                vec![4],
3630                config.clone(),
3631                empty_mock_runtime(config.max_tokens, first_cancellation.clone()),
3632                SpeculativeRandomness {
3633                    target: Some(0),
3634                    draft: Some(0),
3635                },
3636                false,
3637                (),
3638            )
3639            .unwrap();
3640        let second = table
3641            .submit(
3642                &mut executor,
3643                &mut second_cache,
3644                vec![8],
3645                config.clone(),
3646                empty_mock_runtime(config.max_tokens, GenerationCancellationToken::new()),
3647                SpeculativeRandomness {
3648                    target: Some(0),
3649                    draft: Some(10),
3650                },
3651                false,
3652                (),
3653            )
3654            .unwrap();
3655
3656        assert_eq!(
3657            table.status(first),
3658            Some(SpeculativeRequestStatus::ReadyToDraft)
3659        );
3660        assert_eq!(
3661            table.status(second),
3662            Some(SpeculativeRequestStatus::ReadyToDraft)
3663        );
3664        table.step(&mut executor, false, ()).unwrap();
3665        table.step(&mut executor, false, ()).unwrap();
3666        assert!(table.request(first).unwrap().has_pending_verification());
3667        first_cancellation.cancel();
3668        table.run(&mut executor, false, ()).unwrap();
3669
3670        let output = table.finish().unwrap();
3671        assert_eq!(output.requests.len(), 2);
3672        assert_eq!(output.requests[0].id, first);
3673        assert_eq!(
3674            output.requests[0].status,
3675            SpeculativeRequestStatus::Cancelled
3676        );
3677        assert_eq!(output.requests[0].token_ids, [1]);
3678        assert_eq!(output.requests[1].id, second);
3679        assert_eq!(
3680            output.requests[1].status,
3681            SpeculativeRequestStatus::Completed
3682        );
3683        assert_eq!(output.requests[1].token_ids, [1, 1, 0]);
3684        assert!(output.scheduler.cross_request_draft_opportunities > 0);
3685        assert_eq!(first_cache, [4, 1]);
3686        assert_eq!(second_cache, [8, 1, 1]);
3687    }
3688
3689    #[test]
3690    fn request_table_applies_optimistic_actions_without_backend_scheduler_state() {
3691        let mut executor = MockExecutor::default();
3692        let mut cache = Vec::new();
3693        let config = SpeculativeConfig {
3694            max_tokens: 5,
3695            max_draft_tokens: 2,
3696            temperature: 0.7,
3697            eos_token_ids: Vec::new(),
3698        };
3699        let mut table = SpeculativeRequestTable::new(
3700            SpeculativeSchedulerOptions::default(),
3701            SpeculativeExecutionTopology::SameDeviceSplit,
3702        )
3703        .unwrap();
3704        let id = table
3705            .submit(
3706                &mut executor,
3707                &mut cache,
3708                vec![4],
3709                config.clone(),
3710                empty_mock_runtime(config.max_tokens, GenerationCancellationToken::new()),
3711                SpeculativeRandomness {
3712                    target: Some(0),
3713                    draft: Some(0),
3714                },
3715                false,
3716                (),
3717            )
3718            .unwrap();
3719
3720        table.step(&mut executor, true, ()).unwrap();
3721        table.step(&mut executor, true, ()).unwrap();
3722        table.step(&mut executor, true, ()).unwrap();
3723        assert_eq!(
3724            table.status(id),
3725            Some(SpeculativeRequestStatus::OptimisticDraftReady)
3726        );
3727        table.run(&mut executor, true, ()).unwrap();
3728        let output = table.finish().unwrap();
3729        assert_eq!(
3730            output.requests[0].status,
3731            SpeculativeRequestStatus::Completed
3732        );
3733        assert!(output.requests[0].stats.optimistic_draft_blocks > 0);
3734        assert!(output.requests[0].stats.discarded_optimistic_blocks > 0);
3735        assert_eq!(output.scheduler.peak_optimistic_branches, 1);
3736    }
3737
3738    #[test]
3739    fn coordinator_commits_before_publication_and_discards_mismatched_lookahead() {
3740        let trace = TransactionTrace::default();
3741        let mut executor = MockExecutor {
3742            trace: Some(trace.clone()),
3743        };
3744        let mut cache = vec![4, 5];
3745        let block = SpeculativeDraftBlock {
3746            state: vec![5, 1, 1],
3747            proposals: vec![
3748                SpeculativeProposal {
3749                    token: 1,
3750                    distribution: vec![0.0, 1.0],
3751                },
3752                SpeculativeProposal {
3753                    token: 1,
3754                    distribution: vec![0.0, 1.0],
3755                },
3756            ],
3757        };
3758        let mut pending =
3759            submit_verification_transaction(&mut executor, &mut cache, 5, block, ()).unwrap();
3760        pending
3761            .set_optimistic_branch(SpeculativeOptimisticBranch {
3762                block: SpeculativeDraftBlock {
3763                    state: vec![5, 1, 1, 2],
3764                    proposals: vec![SpeculativeProposal {
3765                        token: 2,
3766                        distribution: vec![0.0, 0.0, 1.0],
3767                    }],
3768                },
3769                assumed_prefix: vec![5, 1, 1],
3770            })
3771            .unwrap();
3772        let mut runtime =
3773            mock_output_runtime(GenerationCancellationToken::new(), Some(trace.clone()));
3774        let published = resolve_commit_and_publish(
3775            &mut executor,
3776            &mut cache,
3777            pending,
3778            &mut runtime,
3779            Some(&0),
3780            0.7,
3781            SpeculativeStats::default(),
3782            SpeculativeSchedulerOptions::default(),
3783            (),
3784        )
3785        .unwrap();
3786
3787        assert!(matches!(
3788            published.status,
3789            SpeculativePublicationStatus::Continue(SpeculativeContinuation::None)
3790        ));
3791        assert_eq!(published.stats.accepted_tokens, 1);
3792        assert_eq!(published.stats.discarded_optimistic_tokens, 1);
3793        assert_eq!(cache, [4, 5, 5, 1]);
3794        let (_, sequence, constraint, publisher) = runtime.into_parts();
3795        assert_eq!(sequence.tokens(), [5, 1, 0]);
3796        assert_eq!(constraint.tokens, [1, 0]);
3797        assert_eq!(publisher.tokens, [1, 0]);
3798        assert!(!publisher.cancelled);
3799        assert_eq!(*trace.borrow(), ["wait", "commit", "publish"]);
3800    }
3801
3802    #[test]
3803    fn coordinator_cancels_only_after_retained_verification_is_safe() {
3804        let trace = TransactionTrace::default();
3805        let mut executor = MockExecutor {
3806            trace: Some(trace.clone()),
3807        };
3808        let mut cache = vec![4, 5];
3809        let block = SpeculativeDraftBlock {
3810            state: vec![5, 1],
3811            proposals: vec![SpeculativeProposal {
3812                token: 1,
3813                distribution: vec![0.0, 1.0],
3814            }],
3815        };
3816        let mut pending =
3817            submit_verification_transaction(&mut executor, &mut cache, 5, block, ()).unwrap();
3818        pending
3819            .set_optimistic_branch(SpeculativeOptimisticBranch {
3820                block: SpeculativeDraftBlock {
3821                    state: vec![5, 1, 2],
3822                    proposals: vec![SpeculativeProposal {
3823                        token: 2,
3824                        distribution: vec![0.0, 0.0, 1.0],
3825                    }],
3826                },
3827                assumed_prefix: vec![5, 1],
3828            })
3829            .unwrap();
3830        let cancellation = GenerationCancellationToken::new();
3831        cancellation.cancel();
3832        let mut runtime = mock_output_runtime(cancellation, Some(trace.clone()));
3833        let (stats, ()) = cancel_pending_verification(
3834            &mut executor,
3835            &mut cache,
3836            pending,
3837            &mut runtime,
3838            SpeculativeStats::default(),
3839            (),
3840        )
3841        .unwrap();
3842
3843        assert_eq!(stats.discarded_optimistic_tokens, 1);
3844        assert_eq!(cache, [4, 5, 5]);
3845        let (_, sequence, _, publisher) = runtime.into_parts();
3846        assert_eq!(sequence.finish_reason(), Some(FinishReason::Cancelled));
3847        assert!(publisher.tokens.is_empty());
3848        assert!(publisher.cancelled);
3849        assert_eq!(*trace.borrow(), ["wait", "commit", "cancel"]);
3850    }
3851}