1use crate::{
8 Sampler, SamplingBackend, SequentialDecisionDriver, SequentialDecisionError,
9 SequentialDecisionPlan, SequentialDecisionPlanError, SubmissionBackend,
10};
11use eredu_core::{
12 scheduler::SemanticStateTransaction, Completion, RealtimeFrameScheduleState,
13 RealtimeInputFrame, RealtimeSampling, RealtimeScheduleError, RealtimeSpeechConfig,
14};
15use std::marker::PhantomData;
16
17#[derive(Debug)]
23pub struct RealtimeGenerationState<M, S, R, C> {
24 model_state: M,
25 schedule_state: RealtimeFrameScheduleState,
26 sampling: RealtimeSampling,
27 samplers: Vec<S>,
28 random_state: Option<R>,
29 completion: PhantomData<fn() -> C>,
30}
31
32#[derive(Debug)]
34pub struct RealtimeGenerationBranch<MB, S, R, C> {
35 model_state: MB,
36 schedule_state: RealtimeFrameScheduleState,
37 sampling: RealtimeSampling,
38 samplers: Vec<S>,
39 random_state: Option<R>,
40 completion: Option<C>,
41}
42
43pub trait RealtimeFrameTransition<MB, S, R, C> {
48 type Output;
50 type Error;
52
53 fn execute(
56 &mut self,
57 frame: &RealtimeInputFrame,
58 branch: &mut RealtimeGenerationBranch<MB, S, R, C>,
59 ) -> Result<(Self::Output, C), Self::Error>;
60}
61
62#[derive(Debug, thiserror::Error)]
64#[non_exhaustive]
65pub enum RealtimeFrameExecutionError<TransitionError, TransactionError> {
66 #[error("realtime frame transition failed")]
68 Transition(#[source] TransitionError),
69 #[error(transparent)]
71 CompletionAttachment(#[from] RealtimeCompletionAttachmentError),
72 #[error("realtime frame publication failed")]
74 Publication(#[source] TransactionError),
75}
76
77#[derive(Debug, Clone, Copy, Eq, PartialEq, thiserror::Error)]
79#[non_exhaustive]
80pub enum RealtimeCompletionAttachmentError {
81 #[error("realtime generation branch already has an exact submission completion")]
83 AlreadyAttached,
84}
85
86#[derive(Debug, thiserror::Error)]
88#[non_exhaustive]
89pub enum RealtimeGenerationTransactionError<ModelError, CompletionError> {
90 #[error("realtime model-state transaction failed: {0}")]
92 Model(#[source] ModelError),
93 #[error(transparent)]
95 Schedule(#[from] RealtimeScheduleError),
96 #[error("realtime generation requires {expected} sampler states, received {actual}")]
98 SamplerCardinality {
99 expected: usize,
101 actual: usize,
103 },
104 #[error("realtime generation branch has no exact submission completion")]
106 MissingCompletion,
107 #[error("realtime generation submission is still pending")]
109 CompletionPending,
110 #[error("realtime generation submission failed: {0}")]
112 Completion(#[source] CompletionError),
113}
114
115impl<M, S, R, C> RealtimeGenerationState<M, S, R, C>
116where
117 M: SemanticStateTransaction,
118 M::Error: 'static,
119 C: Completion,
120{
121 pub fn new(
123 model_state: M,
124 schedule: RealtimeSpeechConfig,
125 sampling: RealtimeSampling,
126 samplers: Vec<S>,
127 random_state: Option<R>,
128 ) -> Result<Self, RealtimeGenerationTransactionError<M::Error, C::Error>> {
129 Self::from_parts(
130 model_state,
131 &schedule,
132 RealtimeFrameScheduleState::new(schedule.clone()),
133 sampling,
134 samplers,
135 random_state,
136 )
137 }
138
139 pub fn from_parts(
144 model_state: M,
145 schedule: &RealtimeSpeechConfig,
146 schedule_state: RealtimeFrameScheduleState,
147 sampling: RealtimeSampling,
148 samplers: Vec<S>,
149 random_state: Option<R>,
150 ) -> Result<Self, RealtimeGenerationTransactionError<M::Error, C::Error>> {
151 schedule_state.validate_schedule(schedule)?;
152 validate_sampler_cardinality(schedule, samplers.len())?;
153 Ok(Self {
154 model_state,
155 schedule_state,
156 sampling,
157 samplers,
158 random_state,
159 completion: PhantomData,
160 })
161 }
162
163 pub const fn model_state(&self) -> &M {
165 &self.model_state
166 }
167
168 pub const fn schedule_state(&self) -> &RealtimeFrameScheduleState {
170 &self.schedule_state
171 }
172
173 pub const fn sampling(&self) -> RealtimeSampling {
175 self.sampling
176 }
177
178 pub fn samplers(&self) -> &[S] {
180 &self.samplers
181 }
182
183 pub const fn random_state(&self) -> Option<&R> {
185 self.random_state.as_ref()
186 }
187
188 pub fn replace_sampling(
190 &mut self,
191 sampling: RealtimeSampling,
192 samplers: Vec<S>,
193 random_state: Option<R>,
194 ) -> Result<(), RealtimeGenerationTransactionError<M::Error, C::Error>> {
195 validate_sampler_cardinality(self.schedule_state.schedule(), samplers.len())?;
196 self.sampling = sampling;
197 self.samplers = samplers;
198 self.random_state = random_state;
199 Ok(())
200 }
201
202 #[allow(
205 clippy::type_complexity,
206 reason = "the signature preserves the exact transition, model, and completion error types"
207 )]
208 pub fn execute_frame_transition<T>(
209 &mut self,
210 frame: &RealtimeInputFrame,
211 transition: &mut T,
212 ) -> Result<
213 T::Output,
214 RealtimeFrameExecutionError<
215 T::Error,
216 RealtimeGenerationTransactionError<M::Error, C::Error>,
217 >,
218 >
219 where
220 S: Clone,
221 R: Clone,
222 T: RealtimeFrameTransition<M::Branch, S, R, C>,
223 {
224 let mut branch = SemanticStateTransaction::branch(self)
225 .map_err(RealtimeFrameExecutionError::Publication)?;
226 let (output, completion) = match transition.execute(frame, &mut branch) {
227 Ok(submission) => submission,
228 Err(error) => {
229 if let Err(discard) = <Self as SemanticStateTransaction>::discard_branch(branch) {
230 return Err(RealtimeFrameExecutionError::Publication(discard));
231 }
232 return Err(RealtimeFrameExecutionError::Transition(error));
233 }
234 };
235 if let Err(error) = branch.attach_submission_completion(completion) {
236 if let Err(discard) = <Self as SemanticStateTransaction>::discard_branch(branch) {
237 return Err(RealtimeFrameExecutionError::Publication(discard));
238 }
239 return Err(RealtimeFrameExecutionError::CompletionAttachment(error));
240 }
241 SemanticStateTransaction::commit_branch(self, branch)
242 .map_err(RealtimeFrameExecutionError::Publication)?;
243 Ok(output)
244 }
245
246 pub fn commit_submission_branch<B>(
252 &mut self,
253 branch: RealtimeGenerationBranch<M::Branch, S, R, C>,
254 ) -> Result<(), RealtimeGenerationTransactionError<M::Error, C::Error>>
255 where
256 B: SubmissionBackend<Completion = C>,
257 S: Clone,
258 R: Clone,
259 {
260 self.commit_branch(branch)
261 }
262}
263
264impl<MB, S, R, C> RealtimeGenerationBranch<MB, S, R, C> {
265 pub const fn model_state(&self) -> &MB {
267 &self.model_state
268 }
269
270 pub fn model_state_mut(&mut self) -> &mut MB {
272 &mut self.model_state
273 }
274
275 pub const fn schedule_state(&self) -> &RealtimeFrameScheduleState {
277 &self.schedule_state
278 }
279
280 pub const fn sampling(&self) -> RealtimeSampling {
282 self.sampling
283 }
284
285 pub fn schedule_state_mut(&mut self) -> &mut RealtimeFrameScheduleState {
287 &mut self.schedule_state
288 }
289
290 pub fn state_parts_mut(&mut self) -> (&mut MB, &mut RealtimeFrameScheduleState) {
295 (&mut self.model_state, &mut self.schedule_state)
296 }
297
298 pub fn samplers(&self) -> &[S] {
300 &self.samplers
301 }
302
303 pub const fn random_state(&self) -> Option<&R> {
305 self.random_state.as_ref()
306 }
307
308 pub const fn has_submission_completion(&self) -> bool {
310 self.completion.is_some()
311 }
312
313 pub fn attach_submission_completion(
315 &mut self,
316 completion: C,
317 ) -> Result<(), RealtimeCompletionAttachmentError> {
318 if self.completion.is_some() {
319 return Err(RealtimeCompletionAttachmentError::AlreadyAttached);
320 }
321 self.completion = Some(completion);
322 Ok(())
323 }
324
325 pub fn decision_driver<B>(
331 &self,
332 plan: SequentialDecisionPlan<B::Token>,
333 temperatures: Vec<f32>,
334 ) -> Result<SequentialDecisionDriver<B, S>, SequentialDecisionPlanError>
335 where
336 B: SamplingBackend<RandomState = R>,
337 S: Sampler<B> + Clone,
338 R: Clone,
339 {
340 SequentialDecisionDriver::new(
341 plan,
342 self.samplers.clone(),
343 temperatures,
344 self.random_state.clone(),
345 )
346 }
347
348 pub fn adopt_decision_driver<B>(
353 &mut self,
354 driver: SequentialDecisionDriver<B, S>,
355 ) -> Result<(), SequentialDecisionError<B::Error>>
356 where
357 B: SamplingBackend<RandomState = R>,
358 S: Sampler<B>,
359 {
360 let (samplers, random_state) = driver.finish_into_sampling_state()?;
361 self.samplers = samplers;
362 self.random_state = random_state;
363 Ok(())
364 }
365}
366
367impl<M, S, R, C> SemanticStateTransaction for RealtimeGenerationState<M, S, R, C>
368where
369 M: SemanticStateTransaction,
370 M::Error: 'static,
371 S: Clone,
372 R: Clone,
373 C: Completion,
374{
375 type Branch = RealtimeGenerationBranch<M::Branch, S, R, C>;
376 type Error = RealtimeGenerationTransactionError<M::Error, C::Error>;
377
378 fn branch(&self) -> Result<Self::Branch, Self::Error> {
379 Ok(RealtimeGenerationBranch {
380 model_state: self.model_state.branch().map_err(Self::Error::Model)?,
381 schedule_state: self.schedule_state.branch()?,
382 sampling: self.sampling,
383 samplers: self.samplers.clone(),
384 random_state: self.random_state.clone(),
385 completion: None,
386 })
387 }
388
389 fn commit_branch(&mut self, branch: Self::Branch) -> Result<(), Self::Error> {
390 let RealtimeGenerationBranch {
391 model_state,
392 schedule_state,
393 sampling,
394 samplers,
395 random_state,
396 completion,
397 } = branch;
398
399 let rollback = |model_state, error| match M::discard_branch(model_state) {
400 Ok(()) => error,
401 Err(error) => Self::Error::Model(error),
402 };
403 let Some(completion) = completion else {
404 return Err(rollback(model_state, Self::Error::MissingCompletion));
405 };
406 let complete = match completion.is_complete() {
407 Ok(complete) => complete,
408 Err(error) => {
409 return Err(rollback(model_state, Self::Error::Completion(error)));
410 }
411 };
412 if !complete {
413 return Err(rollback(model_state, Self::Error::CompletionPending));
414 }
415 if let Err(error) = completion.wait() {
416 return Err(rollback(model_state, Self::Error::Completion(error)));
417 }
418 if let Err(error) = self
419 .schedule_state
420 .validate_schedule(schedule_state.schedule())
421 {
422 return Err(rollback(model_state, error.into()));
423 }
424 if let Err(error) =
425 validate_sampler_cardinality(self.schedule_state.schedule(), samplers.len())
426 {
427 return Err(rollback(model_state, error));
428 }
429
430 self.model_state
431 .commit_branch(model_state)
432 .map_err(Self::Error::Model)?;
433 self.schedule_state.commit_branch(schedule_state)?;
434 self.sampling = sampling;
435 self.samplers = samplers;
436 self.random_state = random_state;
437 Ok(())
438 }
439
440 fn discard_branch(branch: Self::Branch) -> Result<(), Self::Error> {
441 let RealtimeGenerationBranch {
442 model_state,
443 completion,
444 ..
445 } = branch;
446 let completion_error = completion.and_then(|completion| completion.wait().err());
447 M::discard_branch(model_state).map_err(Self::Error::Model)?;
448 if let Some(error) = completion_error {
449 return Err(Self::Error::Completion(error));
450 }
451 Ok(())
452 }
453
454 fn permits_parallel_branches(&self) -> bool {
455 self.model_state.permits_parallel_branches()
456 }
457}
458
459fn validate_sampler_cardinality<ModelError, CompletionError>(
460 schedule: &RealtimeSpeechConfig,
461 actual: usize,
462) -> Result<(), RealtimeGenerationTransactionError<ModelError, CompletionError>> {
463 let expected = schedule.depth_audio_codebooks().checked_add(1).ok_or(
464 RealtimeGenerationTransactionError::SamplerCardinality {
465 expected: usize::MAX,
466 actual,
467 },
468 )?;
469 if actual != expected {
470 return Err(RealtimeGenerationTransactionError::SamplerCardinality { expected, actual });
471 }
472 Ok(())
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use crate::{PenaltyConfig, PredictionDirective};
479 use eredu_core::{
480 scheduler::SemanticStateTransaction, RealtimeFrameConvention, RealtimeFrameForcing,
481 RealtimeInputFrame, TokenFilter,
482 };
483 use std::{cell::Cell, convert::Infallible, fmt, rc::Rc};
484
485 #[derive(Debug, Clone, Eq, PartialEq)]
486 struct ModelState {
487 model_step: i32,
488 cache_offset: i32,
489 }
490
491 #[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
492 #[error("model transaction failed")]
493 struct ModelError;
494
495 impl SemanticStateTransaction for ModelState {
496 type Branch = Self;
497 type Error = ModelError;
498
499 fn branch(&self) -> Result<Self::Branch, Self::Error> {
500 Ok(self.clone())
501 }
502
503 fn commit_branch(&mut self, branch: Self::Branch) -> Result<(), Self::Error> {
504 *self = branch;
505 Ok(())
506 }
507 }
508
509 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
510 enum CompletionOutcome {
511 Success,
512 Pending,
513 Failure,
514 }
515
516 #[derive(Debug, Clone)]
517 struct MockCompletion {
518 outcome: CompletionOutcome,
519 waits: Rc<Cell<usize>>,
520 }
521
522 #[derive(Debug, Clone, Eq, PartialEq)]
523 struct CompletionError;
524
525 impl fmt::Display for CompletionError {
526 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
527 formatter.write_str("mock submission failed")
528 }
529 }
530
531 impl std::error::Error for CompletionError {}
532
533 impl MockCompletion {
534 fn new(outcome: CompletionOutcome) -> (Self, Rc<Cell<usize>>) {
535 let waits = Rc::new(Cell::new(0));
536 (
537 Self {
538 outcome,
539 waits: waits.clone(),
540 },
541 waits,
542 )
543 }
544 }
545
546 impl Completion for MockCompletion {
547 type Error = CompletionError;
548
549 fn is_complete(&self) -> Result<bool, Self::Error> {
550 Ok(self.outcome != CompletionOutcome::Pending)
551 }
552
553 fn wait(&self) -> Result<(), Self::Error> {
554 self.waits.set(self.waits.get() + 1);
555 match self.outcome {
556 CompletionOutcome::Success => Ok(()),
557 CompletionOutcome::Failure => Err(CompletionError),
558 CompletionOutcome::Pending => panic!("pending completion must not be waited"),
559 }
560 }
561 }
562
563 struct Backend;
564
565 impl SamplingBackend for Backend {
566 type Logits = i32;
567 type Token = i32;
568 type RandomState = i32;
569 type Context = ();
570 type Error = String;
571
572 fn error(message: String) -> Self::Error {
573 message
574 }
575
576 fn validate_token(
577 token: &Self::Token,
578 domain: crate::TokenDomain,
579 _: &Self::Context,
580 ) -> Result<Self::Token, Self::Error> {
581 usize::try_from(*token)
582 .ok()
583 .filter(|token| *token < domain.cardinality())
584 .map(|_| *token)
585 .ok_or_else(|| "token is outside its decision domain".into())
586 }
587
588 fn scale_temperature(
589 logits: &Self::Logits,
590 _: f32,
591 _: &Self::Context,
592 ) -> Result<Self::Logits, Self::Error> {
593 Ok(*logits)
594 }
595
596 fn apply_penalties(
597 logits: &Self::Logits,
598 _: &[u32],
599 _: PenaltyConfig,
600 _: &Self::Context,
601 ) -> Result<Self::Logits, Self::Error> {
602 Ok(*logits)
603 }
604
605 fn apply_top_k(
606 logits: Self::Logits,
607 _: i32,
608 _: &Self::Context,
609 ) -> Result<Self::Logits, Self::Error> {
610 Ok(logits)
611 }
612
613 fn apply_top_p(
614 logits: Self::Logits,
615 _: f32,
616 _: &Self::Context,
617 ) -> Result<Self::Logits, Self::Error> {
618 Ok(logits)
619 }
620
621 fn apply_min_p(
622 logits: Self::Logits,
623 _: f32,
624 _: &Self::Context,
625 ) -> Result<Self::Logits, Self::Error> {
626 Ok(logits)
627 }
628
629 fn apply_token_filter(
630 logits: &Self::Logits,
631 _: &TokenFilter,
632 _: &Self::Context,
633 ) -> Result<Self::Logits, Self::Error> {
634 Ok(*logits)
635 }
636
637 fn apply_mirostat(
638 logits: &Self::Logits,
639 _: &[u32],
640 _: PenaltyConfig,
641 _: f32,
642 _: f32,
643 _: &Self::Context,
644 ) -> Result<Self::Logits, Self::Error> {
645 Ok(*logits)
646 }
647
648 fn sample_raw(
649 logits: &Self::Logits,
650 _: f32,
651 random: Option<&mut Self::RandomState>,
652 _: &Self::Context,
653 ) -> Result<Self::Token, Self::Error> {
654 if let Some(random) = random {
655 *random += 10;
656 }
657 Ok(*logits)
658 }
659
660 fn sample_processed(
661 logits: &Self::Logits,
662 temperature: f32,
663 random: Option<&mut Self::RandomState>,
664 context: &Self::Context,
665 ) -> Result<Self::Token, Self::Error> {
666 Self::sample_raw(logits, temperature, random, context)
667 }
668
669 fn token_id(token: &Self::Token, _: &Self::Context) -> Result<u32, Self::Error> {
670 u32::try_from(*token).map_err(|error| error.to_string())
671 }
672
673 fn token_probability(
674 _: &Self::Logits,
675 _: u32,
676 _: &Self::Context,
677 ) -> Result<f32, Self::Error> {
678 Ok(1.0)
679 }
680 }
681
682 #[derive(Debug, Clone, Eq, PartialEq)]
683 struct StatefulSampler(i32);
684
685 impl Sampler<Backend> for StatefulSampler {
686 fn sample(
687 &mut self,
688 logits: &i32,
689 _: f32,
690 random: Option<&mut i32>,
691 _: &(),
692 ) -> Result<i32, String> {
693 self.0 += 1;
694 if let Some(random) = random {
695 *random += 10;
696 }
697 Ok(*logits + self.0)
698 }
699 }
700
701 type State = RealtimeGenerationState<ModelState, StatefulSampler, i32, MockCompletion>;
702
703 fn schedule() -> RealtimeSpeechConfig {
704 RealtimeSpeechConfig::new(
705 2,
706 1,
707 1,
708 1,
709 100,
710 64,
711 RealtimeFrameConvention::FeedbackAlignedHistory,
712 vec![0, 0, 1],
713 )
714 .unwrap()
715 }
716
717 fn state() -> State {
718 State::new(
719 ModelState {
720 model_step: 1,
721 cache_offset: 2,
722 },
723 schedule(),
724 RealtimeSampling::greedy(),
725 vec![StatefulSampler(0), StatefulSampler(5)],
726 Some(7),
727 )
728 .unwrap()
729 }
730
731 fn mutate_every_component(
732 frame: &RealtimeInputFrame,
733 branch: &mut RealtimeGenerationBranch<ModelState, StatefulSampler, i32, MockCompletion>,
734 ) {
735 branch.model_state_mut().model_step = frame.input_audio_tokens()[0];
736 branch.model_state_mut().cache_offset = frame.forced_text_tokens().unwrap()[0];
737 branch
738 .schedule_state_mut()
739 .advance(&schedule(), &RealtimeFrameForcing::none(&schedule()))
740 .unwrap();
741 let plan = SequentialDecisionPlan::new(
742 [PredictionDirective::Sample, PredictionDirective::Sample],
743 false,
744 false,
745 )
746 .unwrap();
747 let mut driver = branch
748 .decision_driver::<Backend>(plan, vec![1.0, 1.0])
749 .unwrap();
750 assert_eq!(
751 driver
752 .resolve(0, &10, crate::TokenDomain::new(100), &())
753 .unwrap(),
754 11
755 );
756 assert_eq!(
757 driver
758 .resolve(1, &20, crate::TokenDomain::new(100), &())
759 .unwrap(),
760 26
761 );
762 branch.adopt_decision_driver(driver).unwrap();
763 }
764
765 struct FrameTransition {
766 outcome: CompletionOutcome,
767 waits: Rc<Cell<usize>>,
768 }
769
770 impl RealtimeFrameTransition<ModelState, StatefulSampler, i32, MockCompletion> for FrameTransition {
771 type Output = i32;
772 type Error = Infallible;
773
774 fn execute(
775 &mut self,
776 frame: &RealtimeInputFrame,
777 branch: &mut RealtimeGenerationBranch<ModelState, StatefulSampler, i32, MockCompletion>,
778 ) -> Result<(Self::Output, MockCompletion), Self::Error> {
779 mutate_every_component(frame, branch);
780 let output = branch.model_state.model_step + branch.model_state.cache_offset;
781 Ok((
782 output,
783 MockCompletion {
784 outcome: self.outcome,
785 waits: Rc::clone(&self.waits),
786 },
787 ))
788 }
789 }
790
791 #[test]
792 fn realtime_frame_extension_publishes_ingress_and_state_atomically() {
793 let mut state = state();
794 let waits = Rc::new(Cell::new(0));
795 let mut transition = FrameTransition {
796 outcome: CompletionOutcome::Success,
797 waits: Rc::clone(&waits),
798 };
799 let output = state
800 .execute_frame_transition(
801 &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
802 &mut transition,
803 )
804 .unwrap();
805
806 assert_eq!(waits.get(), 1);
807 assert_eq!(output, 19);
808 assert_eq!(
809 state.model_state(),
810 &ModelState {
811 model_step: 9,
812 cache_offset: 10
813 }
814 );
815 assert_eq!(state.schedule_state().frontier(), 1);
816 assert_eq!(state.samplers(), [StatefulSampler(1), StatefulSampler(6)]);
817 assert_eq!(state.random_state(), Some(&27));
818 }
819
820 #[test]
821 fn failed_completion_rolls_back_every_component() {
822 let mut state = state();
823 let waits = Rc::new(Cell::new(0));
824 let mut transition = FrameTransition {
825 outcome: CompletionOutcome::Failure,
826 waits: Rc::clone(&waits),
827 };
828 assert!(matches!(
829 state.execute_frame_transition(
830 &RealtimeInputFrame::new(1, vec![40]).with_forced_text(vec![50]),
831 &mut transition,
832 ),
833 Err(RealtimeFrameExecutionError::Publication(
834 RealtimeGenerationTransactionError::Completion(_)
835 ))
836 ));
837
838 assert_eq!(waits.get(), 1);
839 assert_eq!(
840 state.model_state(),
841 &ModelState {
842 model_step: 1,
843 cache_offset: 2
844 }
845 );
846 assert_eq!(state.schedule_state().frontier(), 0);
847 assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
848 assert_eq!(state.random_state(), Some(&7));
849 }
850
851 #[test]
852 fn pending_completion_is_not_waited_or_published() {
853 let mut state = state();
854 let mut branch = state.branch().unwrap();
855 mutate_every_component(
856 &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
857 &mut branch,
858 );
859 let (completion, waits) = MockCompletion::new(CompletionOutcome::Pending);
860 branch.attach_submission_completion(completion).unwrap();
861 assert!(matches!(
862 state.commit_branch(branch),
863 Err(RealtimeGenerationTransactionError::CompletionPending)
864 ));
865 assert_eq!(waits.get(), 0);
866 assert_eq!(
867 state.model_state(),
868 &ModelState {
869 model_step: 1,
870 cache_offset: 2
871 }
872 );
873 assert_eq!(state.schedule_state().frontier(), 0);
874 assert_eq!(state.random_state(), Some(&7));
875 }
876
877 #[test]
878 fn discard_leaves_canonical_state_unchanged() {
879 let state = state();
880 let mut branch = state.branch().unwrap();
881 mutate_every_component(
882 &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
883 &mut branch,
884 );
885 State::discard_branch(branch).unwrap();
886 assert_eq!(
887 state.model_state(),
888 &ModelState {
889 model_step: 1,
890 cache_offset: 2
891 }
892 );
893 assert_eq!(state.schedule_state().frontier(), 0);
894 assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
895 assert_eq!(state.random_state(), Some(&7));
896 }
897
898 #[test]
899 fn composite_discard_and_failed_commit_delegate_model_rollback() {
900 #[derive(Debug, Clone)]
901 struct TrackingModel(Rc<Cell<usize>>);
902
903 impl SemanticStateTransaction for TrackingModel {
904 type Branch = Self;
905 type Error = ModelError;
906
907 fn branch(&self) -> Result<Self::Branch, Self::Error> {
908 Ok(self.clone())
909 }
910
911 fn commit_branch(&mut self, branch: Self::Branch) -> Result<(), Self::Error> {
912 *self = branch;
913 Ok(())
914 }
915
916 fn discard_branch(branch: Self::Branch) -> Result<(), Self::Error> {
917 branch.0.set(branch.0.get() + 1);
918 Ok(())
919 }
920 }
921
922 type TrackingState =
923 RealtimeGenerationState<TrackingModel, StatefulSampler, i32, MockCompletion>;
924 let rollbacks = Rc::new(Cell::new(0));
925 let mut state = TrackingState::new(
926 TrackingModel(rollbacks.clone()),
927 schedule(),
928 RealtimeSampling::greedy(),
929 vec![StatefulSampler(0), StatefulSampler(0)],
930 None,
931 )
932 .unwrap();
933
934 struct FailingTransition;
935
936 impl RealtimeFrameTransition<TrackingModel, StatefulSampler, i32, MockCompletion>
937 for FailingTransition
938 {
939 type Output = ();
940 type Error = ModelError;
941
942 fn execute(
943 &mut self,
944 _frame: &RealtimeInputFrame,
945 _branch: &mut RealtimeGenerationBranch<
946 TrackingModel,
947 StatefulSampler,
948 i32,
949 MockCompletion,
950 >,
951 ) -> Result<(Self::Output, MockCompletion), Self::Error> {
952 Err(ModelError)
953 }
954 }
955
956 assert!(matches!(
957 state.execute_frame_transition(
958 &RealtimeInputFrame::new(1, vec![9]),
959 &mut FailingTransition,
960 ),
961 Err(RealtimeFrameExecutionError::Transition(ModelError))
962 ));
963 assert_eq!(rollbacks.get(), 1);
964
965 TrackingState::discard_branch(state.branch().unwrap()).unwrap();
966 assert_eq!(rollbacks.get(), 2);
967
968 let missing_completion = state.branch().unwrap();
969 assert!(matches!(
970 state.commit_branch(missing_completion),
971 Err(RealtimeGenerationTransactionError::MissingCompletion)
972 ));
973 assert_eq!(rollbacks.get(), 3);
974 }
975
976 #[test]
977 fn missing_completion_cannot_publish() {
978 let mut state = state();
979 let mut branch = state.branch().unwrap();
980 mutate_every_component(
981 &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
982 &mut branch,
983 );
984 assert!(matches!(
985 state.commit_branch(branch),
986 Err(RealtimeGenerationTransactionError::MissingCompletion)
987 ));
988 assert_eq!(state.schedule_state().frontier(), 0);
989 }
990
991 #[test]
992 fn schedule_identity_and_sampler_cardinality_are_exact() {
993 let other = RealtimeSpeechConfig::new(
994 2,
995 1,
996 1,
997 1,
998 100,
999 64,
1000 RealtimeFrameConvention::AbsoluteDelayedSlots,
1001 vec![0, 0, 1],
1002 )
1003 .unwrap();
1004 assert!(matches!(
1005 State::from_parts(
1006 ModelState {
1007 model_step: 1,
1008 cache_offset: 2,
1009 },
1010 &schedule(),
1011 RealtimeFrameScheduleState::new(other),
1012 RealtimeSampling::greedy(),
1013 vec![StatefulSampler(0), StatefulSampler(0)],
1014 Some(0),
1015 ),
1016 Err(RealtimeGenerationTransactionError::Schedule(
1017 RealtimeScheduleError::ScheduleMismatch
1018 ))
1019 ));
1020 assert!(matches!(
1021 State::new(
1022 ModelState {
1023 model_step: 1,
1024 cache_offset: 2,
1025 },
1026 schedule(),
1027 RealtimeSampling::greedy(),
1028 vec![StatefulSampler(0)],
1029 Some(0),
1030 ),
1031 Err(RealtimeGenerationTransactionError::SamplerCardinality {
1032 expected: 2,
1033 actual: 1
1034 })
1035 ));
1036
1037 let mut state = state();
1038 let mut branch = state.branch().unwrap();
1039 branch.samplers.pop();
1040 let (completion, _) = MockCompletion::new(CompletionOutcome::Success);
1041 branch.attach_submission_completion(completion).unwrap();
1042 assert!(matches!(
1043 state.commit_branch(branch),
1044 Err(RealtimeGenerationTransactionError::SamplerCardinality {
1045 expected: 2,
1046 actual: 1
1047 })
1048 ));
1049 assert_eq!(state.samplers().len(), 2);
1050 }
1051
1052 #[test]
1053 fn incomplete_decision_driver_does_not_change_branch_sampling_state() {
1054 let state = state();
1055 let mut branch = state.branch().unwrap();
1056 let plan = SequentialDecisionPlan::new(
1057 [PredictionDirective::Sample, PredictionDirective::Sample],
1058 false,
1059 false,
1060 )
1061 .unwrap();
1062 let mut driver = branch
1063 .decision_driver::<Backend>(plan, vec![1.0, 1.0])
1064 .unwrap();
1065 driver
1066 .resolve(0, &10, crate::TokenDomain::new(100), &())
1067 .unwrap();
1068 assert!(matches!(
1069 branch.adopt_decision_driver(driver),
1070 Err(SequentialDecisionError::Incomplete { .. })
1071 ));
1072 assert_eq!(branch.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
1073 assert_eq!(branch.random_state(), Some(&7));
1074 }
1075
1076 #[test]
1077 fn invalid_decision_tokens_leave_canonical_realtime_state_unchanged() {
1078 let state = state();
1079 let mut branch = state.branch().unwrap();
1080 branch.model_state_mut().model_step = 9;
1081 branch.model_state_mut().cache_offset = 10;
1082 branch
1083 .schedule_state_mut()
1084 .advance(&schedule(), &RealtimeFrameForcing::none(&schedule()))
1085 .unwrap();
1086 let plan = SequentialDecisionPlan::new(
1087 [PredictionDirective::Sample, PredictionDirective::Force(100)],
1088 false,
1089 true,
1090 )
1091 .unwrap();
1092 let mut driver = branch
1093 .decision_driver::<Backend>(plan, vec![1.0, 1.0])
1094 .unwrap();
1095 assert!(matches!(
1096 driver.resolve(0, &100, crate::TokenDomain::new(100), &()),
1097 Err(SequentialDecisionError::Backend(_))
1098 ));
1099 assert!(driver.decisions().is_empty());
1100 drop(driver);
1101 drop(branch);
1102
1103 assert_eq!(state.model_state().model_step, 1);
1104 assert_eq!(state.model_state().cache_offset, 2);
1105 assert_eq!(state.schedule_state().frontier(), 0);
1106 assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
1107 assert_eq!(state.random_state(), Some(&7));
1108
1109 let mut branch = state.branch().unwrap();
1110 branch.model_state_mut().cache_offset = 11;
1111 branch
1112 .schedule_state_mut()
1113 .advance(&schedule(), &RealtimeFrameForcing::none(&schedule()))
1114 .unwrap();
1115 let plan = SequentialDecisionPlan::new(
1116 [
1117 PredictionDirective::Force(1),
1118 PredictionDirective::Force(100),
1119 ],
1120 false,
1121 true,
1122 )
1123 .unwrap();
1124 let driver = branch
1125 .decision_driver::<Backend>(plan, vec![1.0, 1.0])
1126 .unwrap();
1127 assert!(matches!(
1128 driver.forced_tail_tokens(0, 2, [crate::TokenDomain::new(100); 2], &()),
1129 Err(SequentialDecisionError::Backend(_))
1130 ));
1131 drop(driver);
1132 drop(branch);
1133 assert_eq!(state.model_state().cache_offset, 2);
1134 assert_eq!(state.schedule_state().frontier(), 0);
1135 assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
1136 assert_eq!(state.random_state(), Some(&7));
1137 }
1138
1139 #[test]
1140 fn duplicate_completion_evidence_is_rejected() {
1141 let state = state();
1142 let mut branch = state.branch().unwrap();
1143 branch
1144 .attach_submission_completion(MockCompletion::new(CompletionOutcome::Success).0)
1145 .unwrap();
1146 assert_eq!(
1147 branch.attach_submission_completion(MockCompletion::new(CompletionOutcome::Success).0),
1148 Err(RealtimeCompletionAttachmentError::AlreadyAttached)
1149 );
1150 }
1151
1152 #[test]
1153 fn sampling_replacement_binds_controls_samplers_and_randomness_atomically() {
1154 let mut state = state();
1155 let sampling = RealtimeSampling::new(0.25, 0.75, 42)
1156 .unwrap()
1157 .with_top_k(Some(3), Some(5))
1158 .unwrap();
1159 assert!(matches!(
1160 state.replace_sampling(sampling, vec![StatefulSampler(9)], Some(11)),
1161 Err(RealtimeGenerationTransactionError::SamplerCardinality {
1162 expected: 2,
1163 actual: 1
1164 })
1165 ));
1166 assert_eq!(state.sampling(), RealtimeSampling::greedy());
1167 assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
1168 assert_eq!(state.random_state(), Some(&7));
1169
1170 state
1171 .replace_sampling(
1172 sampling,
1173 vec![StatefulSampler(9), StatefulSampler(10)],
1174 Some(11),
1175 )
1176 .unwrap();
1177 assert_eq!(state.sampling(), sampling);
1178 assert_eq!(state.samplers(), [StatefulSampler(9), StatefulSampler(10)]);
1179 assert_eq!(state.random_state(), Some(&11));
1180 assert_eq!(state.branch().unwrap().sampling(), sampling);
1181 }
1182}