1use crate::backend::{
4 BoundedCompletionWait, BoundedCompletionWaitError, CompletionCancellationMode,
5};
6use serde::{Deserialize, Serialize};
7use std::sync::{
8 atomic::{AtomicBool, Ordering},
9 Arc,
10};
11
12#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum FinishReason {
16 Eos,
18 StopSequence,
20 GrammarComplete,
22 MaxTokens,
24 Cancelled,
26}
27
28#[derive(Debug, Clone, Default)]
30pub struct GenerationCancellationToken {
31 cancelled: Arc<AtomicBool>,
32}
33
34impl GenerationCancellationToken {
35 pub fn new() -> Self {
37 Self::default()
38 }
39
40 pub fn cancel(&self) {
42 self.cancelled.store(true, Ordering::Release);
43 }
44
45 pub fn is_cancelled(&self) -> bool {
47 self.cancelled.load(Ordering::Acquire)
48 }
49}
50
51#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
53pub struct TokenTerminalSignals {
54 pub stop_sequence: bool,
56 pub grammar_complete: bool,
58}
59
60#[derive(Debug, Clone, Copy, Eq, PartialEq)]
62pub struct TokenCommit {
63 pub token_id: u32,
65 pub position: usize,
67 pub finish_reason: Option<FinishReason>,
69}
70
71#[derive(Debug, Clone, Eq, PartialEq)]
73pub struct GenerationSequence {
74 max_tokens: usize,
75 eos_token_ids: Vec<u32>,
76 tokens: Vec<u32>,
77 finish_reason: Option<FinishReason>,
78}
79
80impl GenerationSequence {
81 pub fn new(max_tokens: usize, eos_token_ids: impl IntoIterator<Item = u32>) -> Self {
83 let mut eos_token_ids = eos_token_ids.into_iter().collect::<Vec<_>>();
84 eos_token_ids.sort_unstable();
85 eos_token_ids.dedup();
86 Self {
87 max_tokens,
88 eos_token_ids,
89 tokens: Vec::with_capacity(max_tokens),
90 finish_reason: (max_tokens == 0).then_some(FinishReason::MaxTokens),
91 }
92 }
93
94 pub fn commit(
96 &mut self,
97 token_id: u32,
98 signals: TokenTerminalSignals,
99 ) -> Result<TokenCommit, GenerationError> {
100 if self.finish_reason.is_some() {
101 return Err(GenerationError::AlreadyFinished);
102 }
103 let position = self.tokens.len();
104 self.tokens.push(token_id);
105 let finish_reason = signals
106 .stop_sequence
107 .then_some(FinishReason::StopSequence)
108 .or_else(|| {
109 signals
110 .grammar_complete
111 .then_some(FinishReason::GrammarComplete)
112 })
113 .or_else(|| {
114 self.eos_token_ids
115 .binary_search(&token_id)
116 .is_ok()
117 .then_some(FinishReason::Eos)
118 })
119 .or_else(|| (self.tokens.len() == self.max_tokens).then_some(FinishReason::MaxTokens));
120 self.finish_reason = finish_reason;
121 Ok(TokenCommit {
122 token_id,
123 position,
124 finish_reason,
125 })
126 }
127
128 pub fn cancel(&mut self) -> bool {
130 if self.tokens.is_empty() && self.finish_reason == Some(FinishReason::MaxTokens) {
131 self.finish_reason = Some(FinishReason::Cancelled);
132 true
133 } else if self.finish_reason.is_some() {
134 false
135 } else {
136 self.finish_reason = Some(FinishReason::Cancelled);
137 true
138 }
139 }
140
141 pub fn observe_cancellation(&mut self, cancellation: &GenerationCancellationToken) -> bool {
143 cancellation.is_cancelled() && self.cancel()
144 }
145
146 pub fn tokens(&self) -> &[u32] {
148 &self.tokens
149 }
150
151 pub fn into_tokens(self) -> Vec<u32> {
153 self.tokens
154 }
155
156 pub fn remaining(&self) -> usize {
158 self.max_tokens.saturating_sub(self.tokens.len())
159 }
160
161 pub const fn max_tokens(&self) -> usize {
163 self.max_tokens
164 }
165
166 pub const fn finish_reason(&self) -> Option<FinishReason> {
168 self.finish_reason
169 }
170
171 pub const fn is_finished(&self) -> bool {
173 self.finish_reason.is_some()
174 }
175}
176
177#[derive(Debug, Clone, Copy, Eq, PartialEq)]
179pub enum SpeculativeTail {
180 Replacement,
182 Bonus,
184}
185
186#[derive(Debug, Clone, Eq, PartialEq)]
188pub struct SpeculativeRound {
189 proposal_count: usize,
190 accepted: usize,
191 committed_tokens: Vec<u32>,
192 tail: Option<SpeculativeTail>,
193 terminal: bool,
194}
195
196impl SpeculativeRound {
197 pub fn new(proposal_count: usize) -> Result<Self, GenerationError> {
199 if proposal_count == 0 {
200 return Err(GenerationError::EmptyProposalBlock);
201 }
202 Ok(Self {
203 proposal_count,
204 accepted: 0,
205 committed_tokens: Vec::with_capacity(proposal_count + 1),
206 tail: None,
207 terminal: false,
208 })
209 }
210
211 pub fn accept(&mut self, token: u32, terminal: bool) -> Result<(), GenerationError> {
213 if self.tail.is_some() || self.accepted == self.proposal_count || self.terminal {
214 return Err(GenerationError::InvalidSpeculativeTransition);
215 }
216 self.accepted += 1;
217 self.committed_tokens.push(token);
218 self.terminal = terminal;
219 Ok(())
220 }
221
222 pub fn reject_with(&mut self, token: u32, terminal: bool) -> Result<(), GenerationError> {
224 if self.tail.is_some() || self.accepted == self.proposal_count || self.terminal {
225 return Err(GenerationError::InvalidSpeculativeTransition);
226 }
227 self.tail = Some(SpeculativeTail::Replacement);
228 self.committed_tokens.push(token);
229 self.terminal = terminal;
230 Ok(())
231 }
232
233 pub fn bonus(&mut self, token: u32, terminal: bool) -> Result<(), GenerationError> {
235 if self.tail.is_some() || self.accepted != self.proposal_count || self.terminal {
236 return Err(GenerationError::InvalidSpeculativeTransition);
237 }
238 self.tail = Some(SpeculativeTail::Bonus);
239 self.committed_tokens.push(token);
240 self.terminal = terminal;
241 Ok(())
242 }
243
244 pub const fn is_full_acceptance(&self) -> bool {
246 self.accepted == self.proposal_count
247 }
248
249 pub fn commit_plan(&self) -> Result<SpeculativeCommitPlan<'_>, GenerationError> {
251 if !self.terminal && self.tail.is_none() {
252 return Err(GenerationError::IncompleteSpeculativeRound);
253 }
254 Ok(SpeculativeCommitPlan {
255 accepted_proposals: self.accepted,
256 committed_tokens: &self.committed_tokens,
257 verified_inputs: if self.tail.is_some() {
258 1 + self.accepted
259 } else {
260 self.accepted
261 },
262 full_acceptance: self.accepted == self.proposal_count,
263 tail: self.tail,
264 terminal: self.terminal,
265 })
266 }
267}
268
269#[derive(Debug, Clone, Copy, Eq, PartialEq)]
271pub struct SpeculativeCommitPlan<'a> {
272 pub accepted_proposals: usize,
274 pub committed_tokens: &'a [u32],
276 pub verified_inputs: usize,
278 pub full_acceptance: bool,
280 pub tail: Option<SpeculativeTail>,
282 pub terminal: bool,
284}
285
286#[derive(Debug, Clone, Copy, Eq, PartialEq)]
288pub enum OptimisticReuseDecision {
289 DiscardTerminal,
291 DiscardMismatch,
293 MatchedConsumed,
295 MatchedRetained,
297}
298
299pub fn resolve_optimistic_reuse(
301 assumed_prefix: &[u32],
302 canonical_prefix: &[u32],
303 optimistic_tokens: &[u32],
304 bonus: u32,
305 terminal: bool,
306) -> Result<OptimisticReuseDecision, GenerationError> {
307 if assumed_prefix != canonical_prefix {
308 return Err(GenerationError::OptimisticPrefixDiverged);
309 }
310 let first = optimistic_tokens
311 .first()
312 .ok_or(GenerationError::EmptyOptimisticBranch)?;
313 if terminal {
314 return Ok(OptimisticReuseDecision::DiscardTerminal);
315 }
316 if *first != bonus {
317 return Ok(OptimisticReuseDecision::DiscardMismatch);
318 }
319 Ok(if optimistic_tokens.len() == 1 {
320 OptimisticReuseDecision::MatchedConsumed
321 } else {
322 OptimisticReuseDecision::MatchedRetained
323 })
324}
325
326#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
328pub struct SpeculativeConfig {
329 pub max_tokens: usize,
331 pub max_draft_tokens: usize,
333 pub temperature: f32,
335 pub eos_token_ids: Vec<u32>,
337}
338
339impl Default for SpeculativeConfig {
340 fn default() -> Self {
341 Self {
342 max_tokens: 256,
343 max_draft_tokens: 4,
344 temperature: 0.0,
345 eos_token_ids: Vec::new(),
346 }
347 }
348}
349
350impl SpeculativeConfig {
351 pub fn validate(&self) -> Result<(), GenerationError> {
353 if self.max_draft_tokens == 0 {
354 return Err(GenerationError::ZeroDraftTokens);
355 }
356 if !self.temperature.is_finite() || self.temperature < 0.0 {
357 return Err(GenerationError::InvalidTemperature(self.temperature));
358 }
359 Ok(())
360 }
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
365#[serde(default)]
366pub struct SpeculativeSchedulerOptions {
367 pub max_in_flight_verifications: usize,
369 pub max_optimistic_branches: usize,
371 pub lookahead_blocks: usize,
373 pub adaptive_lookahead: bool,
375 pub adaptive_lookahead_min_blocks: usize,
377 pub completion_timeout_milliseconds: u64,
379 pub completion_cancellation: CompletionCancellationMode,
381}
382
383impl Default for SpeculativeSchedulerOptions {
384 fn default() -> Self {
385 Self {
386 max_in_flight_verifications: 1,
387 max_optimistic_branches: 1,
388 lookahead_blocks: 1,
389 adaptive_lookahead: true,
390 adaptive_lookahead_min_blocks: 4,
391 completion_timeout_milliseconds: 30_000,
392 completion_cancellation: CompletionCancellationMode::QuarantineUntilComplete,
393 }
394 }
395}
396
397impl SpeculativeSchedulerOptions {
398 pub fn with_lookahead(mut self, enabled: bool) -> Self {
400 self.lookahead_blocks = usize::from(enabled);
401 if enabled {
402 self.max_optimistic_branches = self.max_optimistic_branches.max(1);
403 }
404 self
405 }
406
407 pub fn with_completion_wait(
409 mut self,
410 timeout: std::time::Duration,
411 cancellation: CompletionCancellationMode,
412 ) -> Result<Self, GenerationError> {
413 let timeout = u64::try_from(timeout.as_millis())
414 .map_err(|_| GenerationError::SpeculativeCompletionTimeoutTooLarge)?;
415 if timeout == 0 {
416 return Err(GenerationError::ZeroSpeculativeCompletionTimeout);
417 }
418 self.completion_timeout_milliseconds = timeout;
419 self.completion_cancellation = cancellation;
420 Ok(self)
421 }
422
423 pub fn completion_wait(self) -> Result<BoundedCompletionWait, GenerationError> {
425 BoundedCompletionWait::new(
426 std::time::Duration::from_millis(self.completion_timeout_milliseconds),
427 self.completion_cancellation,
428 )
429 .map_err(|error| match error {
430 BoundedCompletionWaitError::ZeroTimeout => {
431 GenerationError::ZeroSpeculativeCompletionTimeout
432 }
433 })
434 }
435
436 pub fn validate(self) -> Result<Self, GenerationError> {
438 if self.max_in_flight_verifications == 0 {
439 return Err(GenerationError::ZeroInFlightVerifications);
440 }
441 if self.lookahead_blocks > 1 {
442 return Err(GenerationError::TooManyLookaheadBlocks);
443 }
444 if self.lookahead_blocks > 0 && self.max_optimistic_branches == 0 {
445 return Err(GenerationError::LookaheadWithoutBranchCapacity);
446 }
447 if self.lookahead_blocks > 0
448 && self.adaptive_lookahead
449 && self.adaptive_lookahead_min_blocks == 0
450 {
451 return Err(GenerationError::ZeroAdaptiveLookaheadWindow);
452 }
453 self.completion_wait()?;
454 Ok(self)
455 }
456}
457
458#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)]
460pub struct SpeculativeRequestId(usize);
461
462impl SpeculativeRequestId {
463 pub const fn new(index: usize) -> Self {
465 Self(index)
466 }
467
468 pub const fn index(self) -> usize {
470 self.0
471 }
472}
473
474#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
476#[serde(rename_all = "snake_case")]
477#[non_exhaustive]
478pub enum SpeculativeRequestStatus {
479 Prefill,
481 ReadyToDraft,
483 ReadyToSubmitVerification,
485 TargetVerificationInFlight,
487 OptimisticDraftRunning,
489 OptimisticDraftReady,
491 VerificationResolution,
493 Completed,
495 Cancelled,
497}
498
499#[derive(Debug, Clone, Copy, Eq, PartialEq)]
501pub enum SpeculativeCancellationDisposition {
502 AlreadyTerminal,
504 CancelNow,
506 Deferred,
508}
509
510#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
512pub struct SpeculativeRequestLifecycle {
513 status: SpeculativeRequestStatus,
514 cancellation_pending: bool,
515}
516
517impl Default for SpeculativeRequestLifecycle {
518 fn default() -> Self {
519 Self::new()
520 }
521}
522
523impl SpeculativeRequestLifecycle {
524 pub const fn new() -> Self {
526 Self {
527 status: SpeculativeRequestStatus::Prefill,
528 cancellation_pending: false,
529 }
530 }
531
532 pub const fn completed() -> Self {
534 Self {
535 status: SpeculativeRequestStatus::Completed,
536 cancellation_pending: false,
537 }
538 }
539
540 pub const fn cancelled() -> Self {
542 Self {
543 status: SpeculativeRequestStatus::Cancelled,
544 cancellation_pending: false,
545 }
546 }
547
548 pub const fn status(&self) -> SpeculativeRequestStatus {
550 self.status
551 }
552
553 pub const fn cancellation_pending(&self) -> bool {
555 self.cancellation_pending
556 }
557
558 pub const fn is_terminal(&self) -> bool {
560 matches!(
561 self.status,
562 SpeculativeRequestStatus::Completed | SpeculativeRequestStatus::Cancelled
563 )
564 }
565
566 pub fn request_cancellation(
568 &mut self,
569 submission_retained: bool,
570 ) -> Result<SpeculativeCancellationDisposition, GenerationError> {
571 if self.is_terminal() {
572 return Ok(SpeculativeCancellationDisposition::AlreadyTerminal);
573 }
574 if submission_retained {
575 self.cancellation_pending = true;
576 Ok(SpeculativeCancellationDisposition::Deferred)
577 } else {
578 self.transition(SpeculativeRequestStatus::Cancelled)?;
579 Ok(SpeculativeCancellationDisposition::CancelNow)
580 }
581 }
582
583 pub fn transition(&mut self, next: SpeculativeRequestStatus) -> Result<(), GenerationError> {
585 let allowed = matches!(
586 (self.status, next),
587 (
588 SpeculativeRequestStatus::Prefill,
589 SpeculativeRequestStatus::ReadyToDraft
590 ) | (
591 SpeculativeRequestStatus::Prefill,
592 SpeculativeRequestStatus::Completed
593 ) | (
594 SpeculativeRequestStatus::Prefill,
595 SpeculativeRequestStatus::Cancelled
596 ) | (
597 SpeculativeRequestStatus::ReadyToDraft,
598 SpeculativeRequestStatus::ReadyToSubmitVerification
599 ) | (
600 SpeculativeRequestStatus::ReadyToDraft,
601 SpeculativeRequestStatus::Completed
602 ) | (
603 SpeculativeRequestStatus::ReadyToDraft,
604 SpeculativeRequestStatus::Cancelled
605 ) | (
606 SpeculativeRequestStatus::ReadyToSubmitVerification,
607 SpeculativeRequestStatus::TargetVerificationInFlight
608 ) | (
609 SpeculativeRequestStatus::ReadyToSubmitVerification,
610 SpeculativeRequestStatus::Cancelled
611 ) | (
612 SpeculativeRequestStatus::TargetVerificationInFlight,
613 SpeculativeRequestStatus::OptimisticDraftRunning
614 ) | (
615 SpeculativeRequestStatus::TargetVerificationInFlight,
616 SpeculativeRequestStatus::VerificationResolution
617 ) | (
618 SpeculativeRequestStatus::OptimisticDraftRunning,
619 SpeculativeRequestStatus::OptimisticDraftReady
620 ) | (
621 SpeculativeRequestStatus::OptimisticDraftReady,
622 SpeculativeRequestStatus::VerificationResolution
623 ) | (
624 SpeculativeRequestStatus::VerificationResolution,
625 SpeculativeRequestStatus::ReadyToDraft
626 ) | (
627 SpeculativeRequestStatus::VerificationResolution,
628 SpeculativeRequestStatus::Completed
629 ) | (
630 SpeculativeRequestStatus::VerificationResolution,
631 SpeculativeRequestStatus::Cancelled
632 )
633 );
634 if !allowed {
635 return Err(GenerationError::InvalidSpeculativeStatusTransition {
636 from: self.status,
637 to: next,
638 });
639 }
640 self.status = next;
641 if self.is_terminal() {
642 self.cancellation_pending = false;
643 }
644 Ok(())
645 }
646}
647
648#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
650pub struct CheckpointGenerationConfig {
651 #[serde(default)]
653 pub do_sample: Option<bool>,
654 #[serde(default)]
656 pub temperature: Option<f32>,
657 #[serde(default)]
659 pub top_k: Option<i32>,
660 #[serde(default)]
662 pub top_p: Option<f32>,
663 #[serde(default)]
665 pub min_p: Option<f32>,
666 #[serde(default)]
668 pub repetition_penalty: Option<f32>,
669 #[serde(default)]
671 pub repeat_last_n: Option<i32>,
672 #[serde(default)]
674 pub frequency_penalty: Option<f32>,
675 #[serde(default)]
677 pub presence_penalty: Option<f32>,
678 #[serde(default)]
680 pub max_new_tokens: Option<usize>,
681}
682
683#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
685pub struct GenerationConfigOverrides {
686 pub do_sample: Option<bool>,
688 pub temperature: Option<f32>,
690 pub top_k: Option<i32>,
692 pub top_p: Option<f32>,
694 pub min_p: Option<f32>,
696 pub repetition_penalty: Option<f32>,
698 pub repeat_last_n: Option<i32>,
700 pub frequency_penalty: Option<f32>,
702 pub presence_penalty: Option<f32>,
704 pub max_new_tokens: Option<usize>,
706}
707
708#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
710pub struct ResolvedGenerationConfig {
711 pub do_sample: bool,
713 pub temperature: f32,
715 pub top_k: i32,
717 pub top_p: f32,
719 pub min_p: f32,
721 pub repetition_penalty: f32,
723 pub repeat_last_n: i32,
725 pub frequency_penalty: f32,
727 pub presence_penalty: f32,
729 pub max_new_tokens: Option<usize>,
731}
732
733pub fn resolve_generation_config(
735 checkpoint: Option<&CheckpointGenerationConfig>,
736 overrides: GenerationConfigOverrides,
737) -> Result<ResolvedGenerationConfig, GenerationError> {
738 let checkpoint_present = checkpoint.is_some();
739 let checkpoint = checkpoint.cloned().unwrap_or_default();
740 let (do_sample, temperature) = if let Some(do_sample) = overrides.do_sample {
741 if do_sample {
742 (
743 true,
744 overrides
745 .temperature
746 .or(checkpoint.temperature)
747 .unwrap_or(1.0),
748 )
749 } else {
750 (false, 0.0)
751 }
752 } else if let Some(temperature) = overrides.temperature {
753 (temperature > 0.0, temperature)
754 } else if checkpoint.do_sample.unwrap_or(false) {
755 (true, checkpoint.temperature.unwrap_or(1.0))
756 } else {
757 (false, 0.0)
758 };
759 let resolved = ResolvedGenerationConfig {
760 do_sample,
761 temperature,
762 top_k: overrides
763 .top_k
764 .or(checkpoint.top_k)
765 .unwrap_or(if checkpoint_present { 50 } else { 40 }),
766 top_p: overrides
767 .top_p
768 .or(checkpoint.top_p)
769 .unwrap_or(if checkpoint_present { 1.0 } else { 0.95 }),
770 min_p: overrides
771 .min_p
772 .or(checkpoint.min_p)
773 .unwrap_or(if checkpoint_present { 0.0 } else { 0.05 }),
774 repetition_penalty: overrides
775 .repetition_penalty
776 .or(checkpoint.repetition_penalty)
777 .unwrap_or(1.0),
778 repeat_last_n: overrides
779 .repeat_last_n
780 .or(checkpoint.repeat_last_n)
781 .unwrap_or(64),
782 frequency_penalty: overrides
783 .frequency_penalty
784 .or(checkpoint.frequency_penalty)
785 .unwrap_or(0.0),
786 presence_penalty: overrides
787 .presence_penalty
788 .or(checkpoint.presence_penalty)
789 .unwrap_or(0.0),
790 max_new_tokens: overrides.max_new_tokens.or(checkpoint.max_new_tokens),
791 };
792 if !resolved.temperature.is_finite() || resolved.temperature < 0.0 {
793 return Err(GenerationError::InvalidTemperature(resolved.temperature));
794 }
795 if resolved.do_sample && resolved.temperature == 0.0 {
796 return Err(GenerationError::StochasticZeroTemperature);
797 }
798 if resolved.top_k < 0 {
799 return Err(GenerationError::InvalidTopK(resolved.top_k));
800 }
801 if !resolved.top_p.is_finite() || !(0.0..=1.0).contains(&resolved.top_p) {
802 return Err(GenerationError::InvalidTopP(resolved.top_p));
803 }
804 if !resolved.min_p.is_finite() || !(0.0..=1.0).contains(&resolved.min_p) {
805 return Err(GenerationError::InvalidMinP(resolved.min_p));
806 }
807 if !resolved.repetition_penalty.is_finite() || resolved.repetition_penalty <= 0.0 {
808 return Err(GenerationError::InvalidRepetitionPenalty(
809 resolved.repetition_penalty,
810 ));
811 }
812 if !resolved.frequency_penalty.is_finite() {
813 return Err(GenerationError::InvalidFrequencyPenalty(
814 resolved.frequency_penalty,
815 ));
816 }
817 if !resolved.presence_penalty.is_finite() {
818 return Err(GenerationError::InvalidPresencePenalty(
819 resolved.presence_penalty,
820 ));
821 }
822 if resolved.max_new_tokens == Some(0) {
823 return Err(GenerationError::ZeroTokenBudget);
824 }
825 Ok(resolved)
826}
827
828#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
830pub enum SemanticEvent {
831 ReasoningDelta(String),
833 TextDelta(String),
835 ToolCallStart {
837 index: usize,
839 id: String,
841 name: String,
843 },
844 ToolArgumentsDelta {
846 index: usize,
848 json_fragment: String,
850 },
851 ToolCallEnd,
853 Finished {
855 reason: FinishReason,
857 },
858}
859
860#[derive(Debug, Clone, PartialEq, thiserror::Error)]
862pub enum GenerationError {
863 #[error("generation has already finished")]
865 AlreadyFinished,
866 #[error("speculative verification requires at least one proposal")]
868 EmptyProposalBlock,
869 #[error("invalid speculative verification state transition")]
871 InvalidSpeculativeTransition,
872 #[error("speculative verification round is not resolved")]
874 IncompleteSpeculativeRound,
875 #[error("optimistic proposal prefix diverged from the canonical committed prefix")]
877 OptimisticPrefixDiverged,
878 #[error("optimistic proposal branch is empty")]
880 EmptyOptimisticBranch,
881 #[error("speculative verification already retains an optimistic branch")]
883 OptimisticBranchAlreadyPresent,
884 #[error("unknown speculative request id {index}")]
886 UnknownSpeculativeRequest {
887 index: usize,
889 },
890 #[error(
892 "promoted speculative block has {proposed} proposals but canonical capacity is {capacity}"
893 )]
894 ProposalCapacityExceeded {
895 proposed: usize,
897 capacity: usize,
899 },
900 #[error("cannot finish a speculative scheduler with active requests")]
902 ActiveSpeculativeRequests,
903 #[error("completed speculative request {index} has no finish reason")]
905 MissingSpeculativeFinishReason {
906 index: usize,
908 },
909 #[error("speculative max_draft_tokens must be positive")]
911 ZeroDraftTokens,
912 #[error("speculative backend does not permit any draft tokens")]
914 NoBackendDraftCapacity,
915 #[error("temperature must be finite and non-negative, got {0}")]
917 InvalidTemperature(f32),
918 #[error("do_sample=true requires a temperature greater than zero")]
920 StochasticZeroTemperature,
921 #[error("Mirostat V2 tau must be finite and positive, got {0}")]
923 InvalidMirostatTau(f32),
924 #[error("Mirostat V2 eta must be finite and positive, got {0}")]
926 InvalidMirostatEta(f32),
927 #[error("top_k must be non-negative, got {0}")]
929 InvalidTopK(i32),
930 #[error("top_p must be between zero and one, got {0}")]
932 InvalidTopP(f32),
933 #[error("min_p must be between zero and one, got {0}")]
935 InvalidMinP(f32),
936 #[error("repetition_penalty must be finite and positive, got {0}")]
938 InvalidRepetitionPenalty(f32),
939 #[error("frequency_penalty must be finite, got {0}")]
941 InvalidFrequencyPenalty(f32),
942 #[error("presence_penalty must be finite, got {0}")]
944 InvalidPresencePenalty(f32),
945 #[error("max_new_tokens must be positive when supplied")]
947 ZeroTokenBudget,
948 #[error("speculative max_in_flight_verifications must be positive")]
950 ZeroInFlightVerifications,
951 #[error("speculative scheduler currently supports at most one lookahead block")]
953 TooManyLookaheadBlocks,
954 #[error("speculative lookahead requires at least one optimistic branch slot")]
956 LookaheadWithoutBranchCapacity,
957 #[error("speculative adaptive_lookahead_min_blocks must be positive")]
959 ZeroAdaptiveLookaheadWindow,
960 #[error("speculative completion timeout must be positive")]
962 ZeroSpeculativeCompletionTimeout,
963 #[error("speculative completion timeout is too large")]
965 SpeculativeCompletionTimeoutTooLarge,
966 #[error("speculative scheduler reached a non-terminal state with no eligible operation")]
968 StalledSpeculativeSchedule,
969 #[error("invalid speculative request status transition from {from:?} to {to:?}")]
971 InvalidSpeculativeStatusTransition {
972 from: SpeculativeRequestStatus,
974 to: SpeculativeRequestStatus,
976 },
977}
978
979#[cfg(test)]
980mod tests {
981 use super::*;
982
983 #[test]
984 fn terminal_precedence_and_cancellation_are_canonical() {
985 let mut sequence = GenerationSequence::new(2, [7]);
986 let first = sequence.commit(1, TokenTerminalSignals::default()).unwrap();
987 assert_eq!(first.finish_reason, None);
988 let second = sequence
989 .commit(
990 7,
991 TokenTerminalSignals {
992 stop_sequence: true,
993 grammar_complete: true,
994 },
995 )
996 .unwrap();
997 assert_eq!(second.finish_reason, Some(FinishReason::StopSequence));
998 assert!(!sequence.cancel());
999
1000 let token = GenerationCancellationToken::new();
1001 let mut active = GenerationSequence::new(3, []);
1002 token.cancel();
1003 assert!(active.observe_cancellation(&token));
1004 assert_eq!(active.finish_reason(), Some(FinishReason::Cancelled));
1005 }
1006
1007 #[test]
1008 fn speculative_commit_plan_preserves_trailing_token_cache_semantics() {
1009 let mut rejected = SpeculativeRound::new(3).unwrap();
1010 rejected.accept(10, false).unwrap();
1011 rejected.reject_with(99, false).unwrap();
1012 let plan = rejected.commit_plan().unwrap();
1013 assert_eq!(plan.accepted_proposals, 1);
1014 assert_eq!(plan.committed_tokens, &[10, 99]);
1015 assert_eq!(plan.verified_inputs, 2);
1016 assert_eq!(plan.tail, Some(SpeculativeTail::Replacement));
1017
1018 let mut terminal_accept = SpeculativeRound::new(2).unwrap();
1019 terminal_accept.accept(10, false).unwrap();
1020 terminal_accept.accept(11, true).unwrap();
1021 let plan = terminal_accept.commit_plan().unwrap();
1022 assert!(plan.full_acceptance);
1023 assert_eq!(plan.verified_inputs, 2);
1024 assert_eq!(plan.tail, None);
1025
1026 let mut bonus = SpeculativeRound::new(2).unwrap();
1027 bonus.accept(10, false).unwrap();
1028 bonus.accept(11, false).unwrap();
1029 bonus.bonus(12, false).unwrap();
1030 assert_eq!(bonus.commit_plan().unwrap().verified_inputs, 3);
1031
1032 let mut incomplete = SpeculativeRound::new(2).unwrap();
1033 incomplete.accept(10, false).unwrap();
1034 assert!(matches!(
1035 incomplete.commit_plan(),
1036 Err(GenerationError::IncompleteSpeculativeRound)
1037 ));
1038 }
1039
1040 #[test]
1041 fn optimistic_reuse_is_pure_and_fail_closed() {
1042 assert_eq!(
1043 resolve_optimistic_reuse(&[1], &[1], &[2, 3], 2, false).unwrap(),
1044 OptimisticReuseDecision::MatchedRetained
1045 );
1046 assert_eq!(
1047 resolve_optimistic_reuse(&[1], &[1], &[2], 2, false).unwrap(),
1048 OptimisticReuseDecision::MatchedConsumed
1049 );
1050 assert_eq!(
1051 resolve_optimistic_reuse(&[1], &[1], &[2], 9, false).unwrap(),
1052 OptimisticReuseDecision::DiscardMismatch
1053 );
1054 assert!(matches!(
1055 resolve_optimistic_reuse(&[1], &[9], &[2], 2, false),
1056 Err(GenerationError::OptimisticPrefixDiverged)
1057 ));
1058 }
1059
1060 #[test]
1061 fn sampler_and_scheduler_configuration_validate_without_a_backend() {
1062 let checkpoint = CheckpointGenerationConfig {
1063 do_sample: Some(true),
1064 temperature: Some(0.8),
1065 top_k: Some(64),
1066 repetition_penalty: Some(1.1),
1067 ..CheckpointGenerationConfig::default()
1068 };
1069 let resolved =
1070 resolve_generation_config(Some(&checkpoint), GenerationConfigOverrides::default())
1071 .unwrap();
1072 assert!(resolved.do_sample);
1073 assert_eq!(resolved.top_k, 64);
1074 assert_eq!(resolved.repetition_penalty, 1.1);
1075 assert_eq!(resolved.repeat_last_n, 64);
1076 assert!(matches!(
1077 resolve_generation_config(
1078 None,
1079 GenerationConfigOverrides {
1080 frequency_penalty: Some(f32::NAN),
1081 ..GenerationConfigOverrides::default()
1082 }
1083 ),
1084 Err(GenerationError::InvalidFrequencyPenalty(value)) if value.is_nan()
1085 ));
1086 assert!(SpeculativeConfig::default().validate().is_ok());
1087 assert!(SpeculativeSchedulerOptions::default().validate().is_ok());
1088 assert!(matches!(
1089 SpeculativeSchedulerOptions {
1090 max_in_flight_verifications: 0,
1091 ..SpeculativeSchedulerOptions::default()
1092 }
1093 .validate(),
1094 Err(GenerationError::ZeroInFlightVerifications)
1095 ));
1096
1097 let config_json = serde_json::to_string(&resolved).unwrap();
1098 assert_eq!(
1099 serde_json::from_str::<ResolvedGenerationConfig>(&config_json).unwrap(),
1100 resolved
1101 );
1102 let options = SpeculativeSchedulerOptions::default();
1103 let options_json = serde_json::to_string(&options).unwrap();
1104 assert_eq!(
1105 serde_json::from_str::<SpeculativeSchedulerOptions>(&options_json).unwrap(),
1106 options
1107 );
1108 }
1109
1110 #[test]
1111 fn semantic_events_round_trip_without_a_backend() {
1112 let event = SemanticEvent::ToolCallStart {
1113 index: 2,
1114 id: "call_2".into(),
1115 name: "lookup".into(),
1116 };
1117 let json = serde_json::to_string(&event).unwrap();
1118 assert_eq!(serde_json::from_str::<SemanticEvent>(&json).unwrap(), event);
1119
1120 let mut zero_budget = GenerationSequence::new(0, []);
1121 assert_eq!(zero_budget.finish_reason(), Some(FinishReason::MaxTokens));
1122 assert!(zero_budget.cancel());
1123 assert_eq!(zero_budget.finish_reason(), Some(FinishReason::Cancelled));
1124 }
1125
1126 #[test]
1127 fn speculative_request_lifecycle_defers_cancellation_exactly() {
1128 let mut lifecycle = SpeculativeRequestLifecycle::new();
1129 lifecycle
1130 .transition(SpeculativeRequestStatus::ReadyToDraft)
1131 .unwrap();
1132 lifecycle
1133 .transition(SpeculativeRequestStatus::ReadyToSubmitVerification)
1134 .unwrap();
1135 lifecycle
1136 .transition(SpeculativeRequestStatus::TargetVerificationInFlight)
1137 .unwrap();
1138 assert_eq!(
1139 lifecycle.request_cancellation(true).unwrap(),
1140 SpeculativeCancellationDisposition::Deferred
1141 );
1142 assert!(lifecycle.cancellation_pending());
1143 lifecycle
1144 .transition(SpeculativeRequestStatus::VerificationResolution)
1145 .unwrap();
1146 lifecycle
1147 .transition(SpeculativeRequestStatus::Cancelled)
1148 .unwrap();
1149 assert!(lifecycle.is_terminal());
1150 assert!(!lifecycle.cancellation_pending());
1151 assert!(matches!(
1152 lifecycle.transition(SpeculativeRequestStatus::ReadyToDraft),
1153 Err(GenerationError::InvalidSpeculativeStatusTransition { .. })
1154 ));
1155 }
1156}