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