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