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