1use crate::common::protocols::MoveBlock;
5use derive_getters::Getters;
6use dynamo_tokens::blocks::UniqueBlock;
7use dynamo_tokens::{
8 BlockHash, PositionalLineageHash, SaltHash, TokenBlockSequence, Tokens,
9 compute_block_hash_for_tokens, compute_next_sequence_hash,
10};
11use rand::random;
12use validator::Validate;
13
14const MOCKER_SALT_HASH: SaltHash = 1337;
15
16#[derive(Debug)]
17struct FlatTokens {
18 retained: Vec<u32>,
19 retained_start: usize,
20}
21
22impl FlatTokens {
23 fn new(
24 mut tokens: Vec<u32>,
25 output_capacity_hint: usize,
26 block_size: usize,
27 retain_history: bool,
28 ) -> Self {
29 if retain_history {
30 tokens.reserve_exact(output_capacity_hint);
31 return Self {
32 retained: tokens,
33 retained_start: 0,
34 };
35 }
36
37 let retained_start = tokens.len() - (tokens.len() % block_size);
38 let mut retained = Vec::with_capacity(
41 block_size
42 .checked_add(1)
43 .expect("flat token tail capacity overflow"),
44 );
45 retained.extend_from_slice(&tokens[retained_start..]);
46 Self {
47 retained,
48 retained_start,
49 }
50 }
51
52 fn len(&self) -> usize {
53 self.retained_start
54 .checked_add(self.retained.len())
55 .expect("flat token length overflow")
56 }
57
58 fn push(&mut self, token: u32) {
59 self.retained.push(token);
60 }
61
62 fn pop(&mut self) -> Option<u32> {
63 self.retained.pop()
64 }
65
66 fn complete_block(&self, position: usize, block_size: usize) -> Option<&[u32]> {
67 let start = position.checked_mul(block_size)?;
68 debug_assert!(
69 start >= self.retained_start,
70 "promoted block precedes retained flat-token window"
71 );
72 let end = start.checked_add(block_size)?;
73 let local_start = start.checked_sub(self.retained_start)?;
74 let local_end = end.checked_sub(self.retained_start)?;
75 self.retained.get(local_start..local_end)
76 }
77
78 fn discard_through(&mut self, absolute_end: usize) {
79 let local_end = absolute_end
80 .checked_sub(self.retained_start)
81 .expect("promoted block precedes retained flat-token window");
82 assert!(
83 local_end <= self.retained.len(),
84 "promoted block extends beyond retained flat-token window"
85 );
86 self.retained.drain(..local_end);
87 self.retained_start = absolute_end;
88 }
89}
90
91#[derive(Debug)]
92enum SequenceTokens {
93 Legacy(TokenBlockSequence),
94 Flat(FlatTokens),
95}
96
97fn create_sequence_cache(
100 tokens: &TokenBlockSequence,
101 block_size: usize,
102 enable_prefix_caching: bool,
103) -> (Vec<UniqueBlock>, Vec<BlockHash>, Vec<PositionalLineageHash>) {
104 let mut unique_blocks = Vec::with_capacity(tokens.blocks().len() + 1);
105 let mut block_hashes = Vec::new();
106 if enable_prefix_caching {
107 block_hashes.reserve(tokens.blocks().len());
108 }
109 let mut plhs = Vec::with_capacity(tokens.blocks().len());
110
111 for (pos, block) in tokens.blocks().iter().enumerate() {
112 if enable_prefix_caching {
113 block_hashes.push(block.block_hash());
114 unique_blocks.push(UniqueBlock::FullBlock(block.sequence_hash()));
115 plhs.push(block.positional_lineage_hash());
116 } else {
117 unique_blocks.push(UniqueBlock::FullBlock(random::<u64>()));
118 plhs.push(PositionalLineageHash::new(
119 random::<u64>(),
120 None,
121 pos as u64,
122 ));
123 }
124 }
125
126 if !tokens.total_tokens().is_multiple_of(block_size) {
128 unique_blocks.push(UniqueBlock::default());
129 }
130 (unique_blocks, block_hashes, plhs)
131}
132
133fn create_flat_sequence_cache(
135 tokens: &[u32],
136 block_size: usize,
137 enable_prefix_caching: bool,
138 completion_blocks: usize,
139) -> (Vec<UniqueBlock>, Vec<BlockHash>, Vec<PositionalLineageHash>) {
140 let mut unique_blocks = Vec::with_capacity(completion_blocks);
141 let mut block_hashes = if enable_prefix_caching {
142 Vec::with_capacity(completion_blocks)
143 } else {
144 Vec::new()
145 };
146 let mut plhs = Vec::with_capacity(completion_blocks);
147 let mut parent_hash = None;
148
149 for (position, block) in tokens.chunks_exact(block_size).enumerate() {
150 if enable_prefix_caching {
151 let block_hash = compute_block_hash_for_tokens(block, MOCKER_SALT_HASH);
152 let sequence_hash = parent_hash
153 .map(|parent| compute_next_sequence_hash(parent, block_hash))
154 .unwrap_or(block_hash);
155 block_hashes.push(block_hash);
156 unique_blocks.push(UniqueBlock::FullBlock(sequence_hash));
157 plhs.push(PositionalLineageHash::new(
158 sequence_hash,
159 parent_hash,
160 position as u64,
161 ));
162 parent_hash = Some(sequence_hash);
163 } else {
164 unique_blocks.push(UniqueBlock::FullBlock(random::<u64>()));
165 plhs.push(PositionalLineageHash::new(
166 random::<u64>(),
167 None,
168 position as u64,
169 ));
170 }
171 }
172
173 if !tokens.len().is_multiple_of(block_size) {
174 unique_blocks.push(UniqueBlock::default());
175 }
176
177 (unique_blocks, block_hashes, plhs)
178}
179
180#[derive(Debug, Getters, Validate)]
183pub struct ActiveSequence {
184 unique_blocks: Vec<UniqueBlock>,
185 block_hashes: Vec<BlockHash>,
186 plhs: Vec<PositionalLineageHash>,
187
188 #[getter(skip)]
189 tokens: SequenceTokens,
190
191 #[getter(copy)]
192 #[validate(range(min = 2))]
193 block_size: usize,
194
195 #[getter(copy)]
196 max_output_tokens: usize,
197
198 #[getter(copy)]
199 generated_tokens: usize,
200
201 planned_output_ids: Option<Vec<u32>>,
202
203 #[getter(copy)]
204 num_input_tokens: usize,
205
206 #[getter(copy)]
207 num_allocated_tokens: usize,
208
209 #[getter(copy)]
210 enable_prefix_caching: bool,
211
212 #[getter(copy)]
213 emit_token_ids: bool,
214}
215
216impl ActiveSequence {
217 pub(crate) fn promote_computed_tail(
227 &mut self,
228 cumulative_computed_tokens: usize,
229 ) -> Option<MoveBlock> {
230 if cumulative_computed_tokens == 0
231 || cumulative_computed_tokens != self.len()
232 || !cumulative_computed_tokens.is_multiple_of(self.block_size)
233 {
234 return None;
235 }
236 self.promote_last_partial()
237 }
238
239 fn promote_last_partial(&mut self) -> Option<MoveBlock> {
240 let UniqueBlock::PartialBlock(uuid) = self.unique_blocks.last().cloned()? else {
241 return None;
242 };
243
244 let parent_hash = self.unique_blocks[..self.unique_blocks.len() - 1]
245 .last()
246 .map(|block| match block {
247 UniqueBlock::FullBlock(hash) => *hash,
248 UniqueBlock::PartialBlock(_) => panic!("partial block cannot be a parent"),
249 });
250 let position = self.plhs.len();
251 debug_assert_eq!(position + 1, self.len() / self.block_size);
252 let (last_seq_hash, last_block_hash, last_plh, promote_token_ids) = match &self.tokens {
253 SequenceTokens::Legacy(tokens) => {
254 let last_complete = tokens.last_complete_block().unwrap_or_else(|| {
255 panic!(
256 "partial sequence tail cannot be promoted without a complete token block"
257 )
258 });
259 let last_seq_hash = if self.enable_prefix_caching {
260 last_complete.sequence_hash()
261 } else {
262 random::<u64>()
263 };
264 let last_block_hash = self
265 .enable_prefix_caching
266 .then(|| last_complete.block_hash());
267 let last_plh = if self.enable_prefix_caching {
270 last_complete.positional_lineage_hash()
271 } else {
272 PositionalLineageHash::new(random::<u64>(), None, position as u64)
273 };
274 let promote_token_ids = if self.emit_token_ids {
275 Some(last_complete.tokens().to_vec())
276 } else {
277 None
278 };
279 (last_seq_hash, last_block_hash, last_plh, promote_token_ids)
280 }
281 SequenceTokens::Flat(tokens) => {
282 let complete = tokens
283 .complete_block(position, self.block_size)
284 .unwrap_or_else(|| {
285 panic!(
286 "partial flat sequence tail cannot be promoted without a complete token block"
287 )
288 });
289 let last_block_hash = self
290 .enable_prefix_caching
291 .then(|| compute_block_hash_for_tokens(complete, MOCKER_SALT_HASH));
292 let last_seq_hash = last_block_hash
293 .map(|block_hash| {
294 parent_hash
295 .map(|parent| compute_next_sequence_hash(parent, block_hash))
296 .unwrap_or(block_hash)
297 })
298 .unwrap_or_else(random::<u64>);
299 let last_plh = if self.enable_prefix_caching {
300 PositionalLineageHash::new(last_seq_hash, parent_hash, position as u64)
301 } else {
302 PositionalLineageHash::new(random::<u64>(), None, position as u64)
303 };
304 let promote_token_ids = self.emit_token_ids.then(|| complete.to_vec());
305 (last_seq_hash, last_block_hash, last_plh, promote_token_ids)
306 }
307 };
308 if let Some(last_block_hash) = last_block_hash {
309 self.block_hashes.push(last_block_hash);
310 }
311 self.plhs.push(last_plh);
312 self.unique_blocks.pop();
313
314 self.unique_blocks
315 .push(UniqueBlock::FullBlock(last_seq_hash));
316
317 if !self.emit_token_ids {
318 let promoted_end = position
319 .checked_add(1)
320 .and_then(|blocks| blocks.checked_mul(self.block_size))
321 .expect("promoted flat-token boundary overflow");
322 if let SequenceTokens::Flat(tokens) = &mut self.tokens {
323 tokens.discard_through(promoted_end);
324 }
325 }
326 self.debug_assert_flat_token_invariants();
327
328 Some(MoveBlock::Promote(
329 uuid,
330 last_seq_hash,
331 parent_hash,
332 last_block_hash,
333 last_plh,
334 promote_token_ids,
335 ))
336 }
337
338 pub fn new(
340 tokens: Vec<u32>,
341 max_output_tokens: usize,
342 block_size: Option<usize>,
343 enable_prefix_caching: bool,
344 emit_token_ids: bool,
345 ) -> Self {
346 Self::new_with_planned_output_ids(
347 tokens,
348 max_output_tokens,
349 block_size,
350 enable_prefix_caching,
351 emit_token_ids,
352 None,
353 )
354 }
355
356 pub fn new_with_planned_output_ids(
357 tokens: Vec<u32>,
358 max_output_tokens: usize,
359 block_size: Option<usize>,
360 enable_prefix_caching: bool,
361 emit_token_ids: bool,
362 planned_output_ids: Option<Vec<u32>>,
363 ) -> Self {
364 let block_size = block_size.unwrap_or(64);
365 let num_input_tokens = tokens.len();
366
367 let tokens = Tokens::from(tokens).into_sequence(block_size as u32, Some(MOCKER_SALT_HASH));
368 let (unique_blocks, block_hashes, plhs) =
369 create_sequence_cache(&tokens, block_size, enable_prefix_caching);
370
371 let seq = Self {
372 unique_blocks,
373 block_hashes,
374 plhs,
375 tokens: SequenceTokens::Legacy(tokens),
376 block_size,
377 max_output_tokens,
378 generated_tokens: 0,
379 planned_output_ids,
380 num_input_tokens,
381 num_allocated_tokens: 0,
382 enable_prefix_caching,
383 emit_token_ids: emit_token_ids && enable_prefix_caching,
384 };
385 seq.validate().expect("invalid ActiveSequence");
386 seq
387 }
388
389 pub(crate) fn new_flat_with_planned_output_ids(
395 tokens: Vec<u32>,
396 max_output_tokens: usize,
397 output_capacity_hint: usize,
398 block_size: usize,
399 enable_prefix_caching: bool,
400 emit_token_ids: bool,
401 planned_output_ids: Option<Vec<u32>>,
402 ) -> Self {
403 let num_input_tokens = tokens.len();
404 let emit_token_ids = emit_token_ids && enable_prefix_caching;
405 let output_capacity_hint = output_capacity_hint.min(max_output_tokens);
406 let completion_blocks = num_input_tokens
407 .checked_add(output_capacity_hint)
408 .expect("native sequence completion length overflow")
409 .div_ceil(block_size);
410 let (unique_blocks, block_hashes, plhs) = create_flat_sequence_cache(
411 &tokens,
412 block_size,
413 enable_prefix_caching,
414 completion_blocks,
415 );
416 let tokens = FlatTokens::new(tokens, output_capacity_hint, block_size, emit_token_ids);
417
418 let seq = Self {
419 unique_blocks,
420 block_hashes,
421 plhs,
422 tokens: SequenceTokens::Flat(tokens),
423 block_size,
424 max_output_tokens,
425 generated_tokens: 0,
426 planned_output_ids,
427 num_input_tokens,
428 num_allocated_tokens: 0,
429 enable_prefix_caching,
430 emit_token_ids,
431 };
432 seq.validate().expect("invalid flat ActiveSequence");
433 seq.debug_assert_flat_token_invariants();
434 seq
435 }
436
437 pub fn extra_tokens(&self) -> u32 {
438 (self.len() % self.block_size) as u32
439 }
440
441 pub fn len(&self) -> usize {
442 match &self.tokens {
443 SequenceTokens::Legacy(tokens) => tokens.total_tokens(),
444 SequenceTokens::Flat(tokens) => tokens.len(),
445 }
446 }
447
448 pub fn is_empty(&self) -> bool {
449 self.len() == 0
450 }
451
452 pub(crate) fn current_known_blocks(&self) -> usize {
454 self.len().div_ceil(self.block_size)
455 }
456
457 pub(crate) fn to_completion_blocks(&self) -> usize {
463 (self.num_input_tokens + self.max_output_tokens).div_ceil(self.block_size)
464 }
465
466 pub fn prepare_allocation(&self, cumulative_tokens: usize) -> Option<MoveBlock> {
470 let prev_blocks = self
471 .num_allocated_tokens
472 .div_ceil(self.block_size)
473 .min(self.unique_blocks.len());
474 let target_blocks = cumulative_tokens
475 .div_ceil(self.block_size)
476 .min(self.unique_blocks.len());
477 if target_blocks <= prev_blocks {
478 return None;
479 }
480
481 let range = prev_blocks..target_blocks;
482 let blocks = self.unique_blocks[range.clone()].to_vec();
483
484 let hash_start = prev_blocks.min(self.block_hashes.len());
485 let hash_end = target_blocks.min(self.block_hashes.len());
486 let hashes = self.block_hashes[hash_start..hash_end].to_vec();
487 let plh_start = prev_blocks.min(self.plhs.len());
489 let plh_end = target_blocks.min(self.plhs.len());
490 let plhs = self.plhs[plh_start..plh_end].to_vec();
491
492 let token_ids = if self.emit_token_ids && hash_start < hash_end {
493 Some(self.block_token_ids_in(hash_start, hash_end))
494 } else {
495 None
496 };
497
498 let parent = if prev_blocks > 0 {
499 Some(self.unique_blocks[prev_blocks - 1].clone())
500 } else {
501 None
502 };
503 Some(MoveBlock::Use(blocks, hashes, plhs, token_ids, parent))
504 }
505
506 pub fn positional_lineage_hashes(&self) -> &[PositionalLineageHash] {
509 &self.plhs
510 }
511
512 fn block_token_ids_in(&self, start: usize, end: usize) -> Vec<Vec<u32>> {
513 match &self.tokens {
514 SequenceTokens::Legacy(tokens) => tokens.blocks()[start..end]
515 .iter()
516 .map(|block| block.tokens().to_vec())
517 .collect(),
518 SequenceTokens::Flat(tokens) => {
519 assert!(
520 self.emit_token_ids && tokens.retained_start == 0,
521 "flat sequences retain full token history only when token-ID events are enabled"
522 );
523 tokens
524 .retained
525 .chunks_exact(self.block_size)
526 .skip(start)
527 .take(end - start)
528 .map(<[u32]>::to_vec)
529 .collect()
530 }
531 }
532 }
533
534 pub fn block_token_ids(&self) -> Vec<Vec<u32>> {
542 self.block_token_ids_in(0, self.len() / self.block_size)
543 }
544
545 pub fn commit_allocation(&mut self, cumulative_tokens: usize) {
547 self.num_allocated_tokens = cumulative_tokens;
548 }
549
550 pub fn allocate_blocks_for_chunk(&mut self, cumulative_tokens: usize) -> Option<MoveBlock> {
552 let signal = self.prepare_allocation(cumulative_tokens);
553 self.commit_allocation(cumulative_tokens);
554 signal
555 }
556
557 pub fn take_creation_signal(&mut self) -> Option<MoveBlock> {
559 self.allocate_blocks_for_chunk(self.len())
560 }
561
562 pub fn new_with_signal(
564 tokens: Vec<u32>,
565 max_output_tokens: usize,
566 block_size: Option<usize>,
567 enable_prefix_caching: bool,
568 ) -> (Self, Option<MoveBlock>) {
569 let mut sequence = Self::new(
570 tokens,
571 max_output_tokens,
572 block_size,
573 enable_prefix_caching,
574 false,
575 );
576 let signal = sequence.take_creation_signal();
577 (sequence, signal)
578 }
579
580 #[cfg_attr(feature = "profile", inline(never))]
582 pub fn push(&mut self, token: u32) -> Option<Vec<MoveBlock>> {
583 match &mut self.tokens {
584 SequenceTokens::Legacy(tokens) => {
585 tokens.append(token).expect("Token push failed.");
586 }
587 SequenceTokens::Flat(tokens) => tokens.push(token),
588 }
589 self.generated_tokens += 1;
590 self.debug_assert_flat_token_invariants();
591
592 if self.len() % self.block_size != 1 {
593 return None;
594 }
595
596 let mut signals = Vec::new();
599
600 if let Some(promote) = self.promote_last_partial() {
603 signals.push(promote);
604 }
605
606 let new_partial_block = UniqueBlock::default();
607 self.unique_blocks.push(new_partial_block.clone());
608 signals.push(MoveBlock::Use(
609 vec![new_partial_block],
610 vec![],
611 vec![],
612 None,
613 None,
614 ));
615 self.debug_assert_flat_token_invariants();
616 Some(signals)
617 }
618
619 #[cfg_attr(feature = "profile", inline(never))]
631 pub fn generate(&mut self) -> Vec<MoveBlock> {
632 self.generate_token().1
633 }
634
635 #[cfg_attr(feature = "profile", inline(never))]
638 pub fn generate_token(&mut self) -> (u32, Vec<MoveBlock>) {
639 assert!(
641 self.generated_tokens < self.max_output_tokens,
642 "Cannot generate more tokens: reached max_output_tokens limit"
643 );
644
645 let token = self
646 .planned_output_ids
647 .as_ref()
648 .and_then(|ids| ids.get(self.generated_tokens).copied())
649 .unwrap_or_else(random::<u32>);
650
651 let mut signals = Vec::new();
653
654 if let Some(move_blocks) = self.push(token) {
656 signals.extend(move_blocks);
657 }
658
659 if self.generated_tokens != self.max_output_tokens {
661 return (token, signals);
662 }
663
664 signals.extend(self.terminal_signals());
666 (token, signals)
667 }
668
669 pub(crate) fn terminal_signals(&self) -> Vec<MoveBlock> {
672 self.free_signal_for_tokens(self.len())
673 }
674
675 fn free_signal_for_tokens(&self, active_tokens: usize) -> Vec<MoveBlock> {
676 let active_blocks = active_tokens
677 .div_ceil(self.block_size)
678 .min(self.unique_blocks.len());
679 if active_blocks == 0 {
680 return Vec::new();
681 }
682
683 let blocks = self.unique_blocks[..active_blocks]
684 .iter()
685 .rev()
686 .cloned()
687 .collect();
688 vec![MoveBlock::Deref(blocks)]
689 }
690
691 pub fn free_signal(&self) -> Vec<MoveBlock> {
693 self.free_signal_for_tokens(self.num_allocated_tokens)
694 }
695
696 pub fn reset_with_signal(&mut self) -> Vec<MoveBlock> {
700 let free_signal = self.free_signal();
701 self.num_allocated_tokens = 0;
702 free_signal
703 }
704
705 pub fn pop(&mut self) {
717 debug_assert!(
718 self.generated_tokens > 0,
719 "sequence rollback requires a freshly generated token"
720 );
721 match &mut self.tokens {
722 SequenceTokens::Legacy(tokens) => {
723 tokens.pop();
724 }
725 SequenceTokens::Flat(tokens) => {
726 debug_assert!(
727 !tokens.retained.is_empty(),
728 "flat rollback token must be retained"
729 );
730 tokens.pop().expect("flat rollback token must be retained");
731 }
732 }
733 self.generated_tokens = self.generated_tokens.saturating_sub(1);
734
735 if self.len().is_multiple_of(self.block_size) {
737 self.unique_blocks.pop();
738 }
739 self.debug_assert_flat_token_invariants();
740 }
741
742 fn debug_assert_flat_token_invariants(&self) {
743 if let SequenceTokens::Flat(tokens) = &self.tokens {
744 debug_assert_eq!(
745 tokens.len(),
746 self.num_input_tokens + self.generated_tokens,
747 "flat retained-token window must cover the logical sequence suffix"
748 );
749 if self.emit_token_ids {
750 debug_assert_eq!(
751 tokens.retained_start, 0,
752 "token-ID events require complete flat-token history"
753 );
754 } else {
755 debug_assert!(
756 tokens.retained.len() <= self.block_size + 1,
757 "non-emitting flat sequences retain at most one block plus the next token"
758 );
759 }
760 }
761 }
762
763 #[cfg(test)]
764 pub(crate) fn uses_flat_tokens(&self) -> bool {
765 matches!(self.tokens, SequenceTokens::Flat(_))
766 }
767
768 #[cfg(test)]
769 pub(crate) fn flat_storage_capacities(&self) -> Option<(usize, usize, usize, usize)> {
770 let SequenceTokens::Flat(tokens) = &self.tokens else {
771 return None;
772 };
773 Some((
774 tokens.retained.capacity(),
775 self.unique_blocks.capacity(),
776 self.block_hashes.capacity(),
777 self.plhs.capacity(),
778 ))
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use super::*;
785 use dynamo_tokens::SequenceHash;
786
787 fn block_hashes_from_tokens(seq: &ActiveSequence) -> Vec<BlockHash> {
788 match &seq.tokens {
789 SequenceTokens::Legacy(tokens) => tokens
790 .blocks()
791 .iter()
792 .map(|block| block.block_hash())
793 .collect(),
794 SequenceTokens::Flat(_) => seq.block_hashes().clone(),
795 }
796 }
797
798 fn assert_cached_hashes_match_promoted_blocks(seq: &ActiveSequence) {
799 let num_full_unique_blocks = seq
800 .unique_blocks()
801 .iter()
802 .filter(|block| matches!(block, UniqueBlock::FullBlock(_)))
803 .count();
804 assert_eq!(
805 seq.block_hashes().as_slice(),
806 &block_hashes_from_tokens(seq)[..num_full_unique_blocks],
807 "cached block hashes should match the promoted full blocks"
808 );
809 }
810
811 fn assert_use_signal(
812 signal: &MoveBlock,
813 expected_blocks: &[UniqueBlock],
814 expected_hashes: &[BlockHash],
815 ) {
816 match signal {
817 MoveBlock::Use(blocks, hashes, ..) => {
818 assert_eq!(blocks, expected_blocks);
819 assert_eq!(hashes, expected_hashes);
820 }
821 _ => panic!("Expected MoveBlock::Use"),
822 }
823 }
824
825 fn assert_single_partial_use(signal: &MoveBlock) {
826 match signal {
827 MoveBlock::Use(blocks, hashes, ..) => {
828 assert_eq!(blocks.len(), 1);
829 assert!(matches!(blocks[0], UniqueBlock::PartialBlock(_)));
830 assert!(hashes.is_empty());
831 }
832 _ => panic!("Expected MoveBlock::Use with a single partial block"),
833 }
834 }
835
836 fn assert_promote_parent(signal: &MoveBlock, expected_parent: Option<u64>) {
837 match signal {
838 MoveBlock::Promote(_, _, parent_hash, _hash, ..) => {
839 assert_eq!(*parent_hash, expected_parent);
840 }
841 _ => panic!("Expected MoveBlock::Promote"),
842 }
843 }
844
845 fn assert_deref_blocks(signal: &MoveBlock, expected: &[UniqueBlock]) {
846 match signal {
847 MoveBlock::Deref(blocks) => {
848 assert_eq!(blocks, expected);
849 }
850 _ => panic!("Expected MoveBlock::Deref"),
851 }
852 }
853
854 #[test]
855 fn test_new_with_signal_creates_initial_partial_block() {
856 let initial_tokens: Vec<u32> = (0..15).collect();
857 let (seq, signal) = ActiveSequence::new_with_signal(initial_tokens, 100, Some(16), true);
858
859 assert_eq!(seq.num_input_tokens(), 15);
860 assert_eq!(seq.len(), 15);
861 assert_single_partial_use(signal.as_ref().expect("Expected initial Use signal"));
862 }
863
864 #[test]
865 fn test_push_across_block_boundary_promotes_and_allocates_partial() {
866 let initial_tokens: Vec<u32> = (0..15).collect();
867 let (mut seq, _) = ActiveSequence::new_with_signal(initial_tokens, 100, Some(16), true);
868
869 let signal_15 = seq.push(15);
870 assert!(
871 signal_15.is_none(),
872 "Completing a block should not trigger signals"
873 );
874
875 let signal_16 = seq.push(16).expect("Expected boundary crossing signals");
876 assert_eq!(signal_16.len(), 2);
877 assert_promote_parent(&signal_16[0], None);
878 assert_single_partial_use(&signal_16[1]);
879
880 assert_eq!(
881 seq.unique_blocks().len(),
882 2,
883 "sequence should have one full block and one partial block"
884 );
885 assert_eq!(
886 seq.len() % seq.block_size(),
887 1,
888 "sequence should have one token in the new partial block"
889 );
890 }
891
892 #[test]
893 fn test_equivalent_histories_preserve_full_block_identity() {
894 let initial_tokens: Vec<u32> = (0..15).collect();
895 let (mut seq1, _) = ActiveSequence::new_with_signal(initial_tokens, 100, Some(16), true);
896 seq1.push(15);
897 seq1.push(16);
898
899 let extended_tokens: Vec<u32> = (0..16).collect();
900 let (mut seq2, _) = ActiveSequence::new_with_signal(extended_tokens, 100, Some(16), true);
901 seq2.push(16);
902 seq2.pop();
903 seq2.push(16);
904
905 assert_eq!(seq1.unique_blocks()[0], seq2.unique_blocks()[0]);
906 assert_ne!(seq1.unique_blocks()[1], seq2.unique_blocks()[1]);
907 }
908
909 #[test]
910 fn test_promote_uses_previous_full_block_as_parent() {
911 let initial_tokens: Vec<u32> = (0..15).collect();
912 let (mut seq, _) = ActiveSequence::new_with_signal(initial_tokens, 100, Some(16), true);
913 seq.push(15);
914 seq.push(16);
915
916 seq.push(17);
917 seq.pop();
918 seq.pop();
919 seq.push(16);
920
921 let extended_tokens: Vec<u32> = (0..16).collect();
922 let (mut seq_equiv, _) =
923 ActiveSequence::new_with_signal(extended_tokens, 100, Some(16), true);
924 seq_equiv.push(16);
925 seq_equiv.pop();
926 seq_equiv.push(16);
927 for token in 17..33 {
928 seq.push(token);
929 seq_equiv.push(token);
930 }
931
932 assert_eq!(
933 &seq.unique_blocks()[0..2],
934 &seq_equiv.unique_blocks()[0..2],
935 "first two full blocks should remain identical"
936 );
937
938 for token in 33..48 {
939 seq.push(token);
940 }
941
942 let signal = seq
943 .push(48)
944 .expect("Expected promote when opening next partial");
945
946 let UniqueBlock::FullBlock(expected_hash) = seq.unique_blocks()[1] else {
947 panic!("unique_blocks[1] should be a full block");
948 };
949 assert_promote_parent(&signal[0], Some(expected_hash));
950 assert_single_partial_use(&signal[1]);
951 }
952
953 #[test]
954 fn test_reset_with_signal_frees_blocks_and_resets_allocation() {
955 let initial_tokens: Vec<u32> = (0..15).collect();
956 let (mut seq, _) = ActiveSequence::new_with_signal(initial_tokens, 100, Some(16), true);
957 seq.push(15);
958 seq.push(16);
959 seq.commit_allocation(seq.len());
960
961 let free_signals = seq.reset_with_signal();
962
963 assert_eq!(free_signals.len(), 1);
964 let expected = seq
965 .unique_blocks()
966 .iter()
967 .rev()
968 .cloned()
969 .collect::<Vec<_>>();
970 assert_deref_blocks(&free_signals[0], &expected);
971 assert_eq!(seq.num_allocated_tokens(), 0);
972 assert_eq!(seq.generated_tokens(), 2);
973 }
974
975 #[test]
976 fn test_free_signal_is_empty_without_an_active_allocation() {
977 let seq = ActiveSequence::new((0..10).collect(), 4, Some(4), true, false);
978
979 assert!(seq.free_signal().is_empty());
980 }
981
982 #[test]
983 fn test_free_signal_batches_allocated_blocks_in_reverse_order() {
984 let mut seq = ActiveSequence::new((0..10).collect(), 4, Some(4), true, false);
985 seq.commit_allocation(seq.len());
986
987 let expected = seq
988 .unique_blocks()
989 .iter()
990 .rev()
991 .cloned()
992 .collect::<Vec<_>>();
993 let signals = seq.free_signal();
994
995 assert_eq!(signals.len(), 1);
996 assert_deref_blocks(&signals[0], &expected);
997 }
998
999 #[test]
1000 fn test_active_sequence_generate_signals() {
1001 let initial_tokens: Vec<u32> = (0..14).collect();
1003 let (mut seq, signal) = ActiveSequence::new_with_signal(initial_tokens, 5, Some(16), true);
1004
1005 assert_single_partial_use(signal.as_ref().expect("Expected initial Use signal"));
1007
1008 seq.generate();
1010 let signals_first = seq.generate();
1011 assert_eq!(signals_first.len(), 0);
1012
1013 let signals_second = seq.generate();
1015 assert_eq!(signals_second.len(), 2);
1016
1017 assert_promote_parent(&signals_second[0], None);
1019
1020 assert_single_partial_use(&signals_second[1]);
1022
1023 let signals_third = seq.generate();
1025 assert_eq!(signals_third.len(), 0);
1026
1027 let expected = seq
1030 .unique_blocks()
1031 .iter()
1032 .rev()
1033 .cloned()
1034 .collect::<Vec<_>>();
1035 let signals_last = seq.generate();
1036 assert_eq!(signals_last.len(), 1);
1037 assert_deref_blocks(&signals_last[0], &expected);
1038 }
1039
1040 #[test]
1041 fn test_prepare_allocation_slices_full_and_partial_blocks() {
1042 let tokens: Vec<u32> = (0..10).collect();
1043 let seq = ActiveSequence::new(tokens, 4, Some(4), true, false);
1044
1045 let first = seq.prepare_allocation(4).unwrap();
1046 assert_use_signal(
1047 &first,
1048 &seq.unique_blocks()[0..1],
1049 &seq.block_hashes()[0..1],
1050 );
1051
1052 let second = seq.prepare_allocation(8).unwrap();
1053 assert_use_signal(
1054 &second,
1055 &seq.unique_blocks()[0..2],
1056 &seq.block_hashes()[0..2],
1057 );
1058
1059 let third = seq.prepare_allocation(10).unwrap();
1060 assert_use_signal(
1061 &third,
1062 &seq.unique_blocks()[0..3],
1063 &seq.block_hashes()[0..2],
1064 );
1065 }
1066
1067 #[test]
1068 fn test_prepare_allocation_is_stable_until_commit() {
1069 let tokens: Vec<u32> = (0..10).collect();
1070 let mut seq = ActiveSequence::new(tokens, 4, Some(4), true, false);
1071
1072 let first = seq.prepare_allocation(4).unwrap();
1073 let second = seq.prepare_allocation(4).unwrap();
1074 assert_eq!(first, second);
1075
1076 seq.commit_allocation(4);
1077 let next = seq.prepare_allocation(8).unwrap();
1078 assert_use_signal(&next, &seq.unique_blocks()[1..2], &seq.block_hashes()[1..2]);
1079 }
1080
1081 #[test]
1082 fn test_block_hash_cache_stays_in_sync_after_promote_and_pop() {
1083 let initial_tokens: Vec<u32> = (0..15).collect();
1084 let (mut seq, _) = ActiveSequence::new_with_signal(initial_tokens, 4, Some(16), true);
1085
1086 assert_cached_hashes_match_promoted_blocks(&seq);
1087
1088 seq.push(15);
1089 assert_cached_hashes_match_promoted_blocks(&seq);
1090
1091 let promote_signals = seq.push(16).unwrap();
1092 assert_eq!(promote_signals.len(), 2);
1093 assert_cached_hashes_match_promoted_blocks(&seq);
1094
1095 seq.pop();
1098 assert_cached_hashes_match_promoted_blocks(&seq);
1099 }
1100
1101 #[derive(Debug, PartialEq)]
1102 enum SignalShape {
1103 Use {
1104 blocks: Vec<Option<u64>>,
1105 hashes: Vec<BlockHash>,
1106 plhs: Vec<PositionalLineageHash>,
1107 token_ids: Option<Vec<Vec<u32>>>,
1108 parent: Option<Option<u64>>,
1109 },
1110 Deref(Vec<Option<u64>>),
1111 Promote {
1112 sequence_hash: SequenceHash,
1113 parent_hash: Option<u64>,
1114 block_hash: Option<BlockHash>,
1115 plh: PositionalLineageHash,
1116 token_ids: Option<Vec<u32>>,
1117 },
1118 }
1119
1120 fn block_shape(block: &UniqueBlock) -> Option<u64> {
1121 match block {
1122 UniqueBlock::FullBlock(hash) => Some(*hash),
1123 UniqueBlock::PartialBlock(_) => None,
1124 }
1125 }
1126
1127 fn signal_shape(signal: MoveBlock) -> SignalShape {
1128 match signal {
1129 MoveBlock::Use(blocks, hashes, plhs, token_ids, parent) => SignalShape::Use {
1130 blocks: blocks.iter().map(block_shape).collect(),
1131 hashes,
1132 plhs,
1133 token_ids,
1134 parent: parent.as_ref().map(block_shape),
1135 },
1136 MoveBlock::Deref(blocks) => {
1137 SignalShape::Deref(blocks.iter().map(block_shape).collect())
1138 }
1139 MoveBlock::Promote(_, sequence_hash, parent_hash, block_hash, plh, token_ids) => {
1140 SignalShape::Promote {
1141 sequence_hash,
1142 parent_hash,
1143 block_hash,
1144 plh,
1145 token_ids,
1146 }
1147 }
1148 }
1149 }
1150
1151 fn signal_shapes(signals: impl IntoIterator<Item = MoveBlock>) -> Vec<SignalShape> {
1152 signals.into_iter().map(signal_shape).collect()
1153 }
1154
1155 fn sequence_pair(
1156 prompt: Vec<u32>,
1157 output_ids: Vec<u32>,
1158 block_size: usize,
1159 emit_token_ids: bool,
1160 ) -> (ActiveSequence, ActiveSequence) {
1161 let legacy = ActiveSequence::new_with_planned_output_ids(
1162 prompt.clone(),
1163 output_ids.len(),
1164 Some(block_size),
1165 true,
1166 emit_token_ids,
1167 Some(output_ids.clone()),
1168 );
1169 let flat = ActiveSequence::new_flat_with_planned_output_ids(
1170 prompt,
1171 output_ids.len(),
1172 output_ids.len(),
1173 block_size,
1174 true,
1175 emit_token_ids,
1176 Some(output_ids),
1177 );
1178 assert!(!legacy.uses_flat_tokens());
1179 assert!(flat.uses_flat_tokens());
1180 (legacy, flat)
1181 }
1182
1183 fn assert_sequence_parity(legacy: &ActiveSequence, flat: &ActiveSequence) {
1184 assert_eq!(legacy.len(), flat.len());
1185 assert_eq!(legacy.extra_tokens(), flat.extra_tokens());
1186 assert_eq!(legacy.emit_token_ids(), flat.emit_token_ids());
1187 assert_eq!(legacy.block_hashes(), flat.block_hashes());
1188 assert_eq!(
1189 legacy.positional_lineage_hashes(),
1190 flat.positional_lineage_hashes()
1191 );
1192 assert_eq!(
1193 legacy
1194 .unique_blocks()
1195 .iter()
1196 .map(block_shape)
1197 .collect::<Vec<_>>(),
1198 flat.unique_blocks()
1199 .iter()
1200 .map(block_shape)
1201 .collect::<Vec<_>>()
1202 );
1203 assert_eq!(legacy.generated_tokens(), flat.generated_tokens());
1204 assert_eq!(legacy.num_input_tokens(), flat.num_input_tokens());
1205 assert_eq!(legacy.num_allocated_tokens(), flat.num_allocated_tokens());
1206 if flat.emit_token_ids() {
1207 assert_eq!(legacy.block_token_ids(), flat.block_token_ids());
1208 } else {
1209 let SequenceTokens::Flat(tokens) = &flat.tokens else {
1210 panic!("expected flat token storage");
1211 };
1212 assert!(
1213 tokens.retained.len() <= flat.block_size() + 1,
1214 "non-emitting flat storage exceeded one block plus one token"
1215 );
1216 }
1217 }
1218
1219 #[test]
1220 fn flat_sequence_matches_legacy_across_prompt_and_output_boundaries() {
1221 const BLOCK_SIZE: usize = 16;
1222 for promote_eagerly in [true, false] {
1223 for emit_token_ids in [false, true] {
1224 for prompt_len in [0, 1, BLOCK_SIZE - 1, BLOCK_SIZE, BLOCK_SIZE + 1, 53] {
1225 let prompt: Vec<u32> = (0..prompt_len as u32).collect();
1226 let outputs: Vec<u32> =
1227 (10_000..10_000 + (BLOCK_SIZE * 2 + 3) as u32).collect();
1228 let (mut legacy, mut flat) =
1229 sequence_pair(prompt, outputs.clone(), BLOCK_SIZE, emit_token_ids);
1230 let SequenceTokens::Flat(tokens) = &flat.tokens else {
1231 panic!("expected flat token storage");
1232 };
1233 let token_capacity = tokens.retained.capacity();
1234 let mut push_promotions = 0;
1235
1236 assert_sequence_parity(&legacy, &flat);
1237 assert_eq!(
1238 legacy.take_creation_signal().map(signal_shape),
1239 flat.take_creation_signal().map(signal_shape)
1240 );
1241
1242 for expected_token in outputs {
1243 let (legacy_token, legacy_signals) = legacy.generate_token();
1244 let (flat_token, flat_signals) = flat.generate_token();
1245 assert_eq!(legacy_token, expected_token);
1246 assert_eq!(flat_token, expected_token);
1247
1248 let legacy_push_promoted = legacy_signals
1249 .iter()
1250 .any(|signal| matches!(signal, MoveBlock::Promote(..)));
1251 let flat_push_promoted = flat_signals
1252 .iter()
1253 .any(|signal| matches!(signal, MoveBlock::Promote(..)));
1254 assert_eq!(legacy_push_promoted, flat_push_promoted);
1255 if flat_push_promoted {
1256 push_promotions += 1;
1257 }
1258 assert_eq!(signal_shapes(legacy_signals), signal_shapes(flat_signals));
1259 assert_sequence_parity(&legacy, &flat);
1260
1261 let SequenceTokens::Flat(tokens) = &flat.tokens else {
1262 panic!("expected flat token storage");
1263 };
1264 assert_eq!(tokens.retained.capacity(), token_capacity);
1265 if flat_push_promoted && !emit_token_ids {
1266 assert_eq!(tokens.retained.len(), 1);
1267 assert_eq!(tokens.retained_start + 1, flat.len());
1268 }
1269
1270 if promote_eagerly
1271 && legacy.generated_tokens() < legacy.max_output_tokens()
1272 && legacy.len().is_multiple_of(BLOCK_SIZE)
1273 {
1274 assert_eq!(
1275 legacy.promote_computed_tail(legacy.len()).map(signal_shape),
1276 flat.promote_computed_tail(flat.len()).map(signal_shape)
1277 );
1278 assert_sequence_parity(&legacy, &flat);
1279 }
1280 }
1281
1282 if promote_eagerly {
1283 assert_eq!(push_promotions, 0);
1284 } else {
1285 assert!(push_promotions > 0);
1286 }
1287 assert_eq!(
1288 signal_shapes(legacy.terminal_signals()),
1289 signal_shapes(flat.terminal_signals())
1290 );
1291 }
1292 }
1293 }
1294 }
1295
1296 #[test]
1297 fn flat_sequence_matches_chunked_token_id_allocations() {
1298 const BLOCK_SIZE: usize = 4;
1299 let prompt: Vec<u32> = (0..12).collect();
1300 let (mut legacy, mut flat) = sequence_pair(prompt.clone(), Vec::new(), BLOCK_SIZE, true);
1301
1302 for cumulative_tokens in [4, 8, 12] {
1303 let legacy_signal = legacy
1304 .prepare_allocation(cumulative_tokens)
1305 .expect("legacy chunk should allocate");
1306 let flat_signal = flat
1307 .prepare_allocation(cumulative_tokens)
1308 .expect("flat chunk should allocate");
1309 assert_eq!(
1310 signal_shape(legacy_signal),
1311 signal_shape(flat_signal.clone())
1312 );
1313 let MoveBlock::Use(_, _, _, Some(token_ids), _) = flat_signal else {
1314 panic!("chunked native allocation must include token IDs");
1315 };
1316 let start = cumulative_tokens - BLOCK_SIZE;
1317 assert_eq!(token_ids, vec![prompt[start..cumulative_tokens].to_vec()]);
1318 legacy.commit_allocation(cumulative_tokens);
1319 flat.commit_allocation(cumulative_tokens);
1320 }
1321 }
1322
1323 #[test]
1324 fn flat_sequence_capacities_do_not_grow_during_decode() {
1325 const BLOCK_SIZE: usize = 16;
1326 let prompt: Vec<u32> = (0..17).collect();
1327 let outputs: Vec<u32> = (1_000..1_037).collect();
1328
1329 for emit_token_ids in [false, true] {
1330 let mut flat = ActiveSequence::new_flat_with_planned_output_ids(
1331 prompt.clone(),
1332 outputs.len(),
1333 outputs.len(),
1334 BLOCK_SIZE,
1335 true,
1336 emit_token_ids,
1337 Some(outputs.clone()),
1338 );
1339 let metadata_capacities = (
1340 flat.unique_blocks.capacity(),
1341 flat.block_hashes.capacity(),
1342 flat.plhs.capacity(),
1343 );
1344 let SequenceTokens::Flat(tokens) = &flat.tokens else {
1345 panic!("expected flat token storage");
1346 };
1347 let token_capacity = tokens.retained.capacity();
1348 if emit_token_ids {
1349 assert!(token_capacity >= prompt.len() + outputs.len());
1350 } else {
1351 assert!(token_capacity > BLOCK_SIZE);
1352 assert_eq!(tokens.retained_start, BLOCK_SIZE);
1353 assert_eq!(tokens.retained, prompt[BLOCK_SIZE..]);
1354 }
1355
1356 for _ in &outputs {
1357 flat.generate_token();
1358 if flat.generated_tokens() < flat.max_output_tokens()
1359 && flat.len().is_multiple_of(BLOCK_SIZE)
1360 {
1361 flat.promote_computed_tail(flat.len());
1362 }
1363
1364 assert_eq!(
1365 metadata_capacities,
1366 (
1367 flat.unique_blocks.capacity(),
1368 flat.block_hashes.capacity(),
1369 flat.plhs.capacity(),
1370 )
1371 );
1372 let SequenceTokens::Flat(tokens) = &flat.tokens else {
1373 panic!("expected flat token storage");
1374 };
1375 assert_eq!(tokens.retained.capacity(), token_capacity);
1376 if emit_token_ids {
1377 assert_eq!(tokens.retained_start, 0);
1378 } else {
1379 assert!(tokens.retained.len() <= BLOCK_SIZE + 1);
1380 }
1381 }
1382 }
1383 }
1384
1385 fn assert_uncached_signal_parity(left: Vec<MoveBlock>, right: Vec<MoveBlock>) {
1386 assert_eq!(left.len(), right.len());
1387 for (left, right) in left.into_iter().zip(right) {
1388 match (left, right) {
1389 (
1390 MoveBlock::Use(lb, lh, lp, lt, lparent),
1391 MoveBlock::Use(rb, rh, rp, rt, rparent),
1392 ) => {
1393 assert_eq!(
1394 lb.iter()
1395 .map(block_shape)
1396 .map(|hash| hash.is_some())
1397 .collect::<Vec<_>>(),
1398 rb.iter()
1399 .map(block_shape)
1400 .map(|hash| hash.is_some())
1401 .collect::<Vec<_>>()
1402 );
1403 assert_eq!(lh.len(), rh.len());
1404 assert_eq!(lp.len(), rp.len());
1405 assert_eq!(lt.is_some(), rt.is_some());
1406 assert_eq!(lparent.is_some(), rparent.is_some());
1407 }
1408 (
1409 MoveBlock::Promote(_, _, lp, lbh, lplh, lt),
1410 MoveBlock::Promote(_, _, rp, rbh, rplh, rt),
1411 ) => {
1412 assert_eq!(lp.is_some(), rp.is_some());
1413 assert_eq!(lbh.is_some(), rbh.is_some());
1414 assert_eq!(lplh.position(), rplh.position());
1415 assert_eq!(lt.is_some(), rt.is_some());
1416 }
1417 (MoveBlock::Deref(lb), MoveBlock::Deref(rb)) => {
1418 assert_eq!(lb.len(), rb.len());
1419 assert_eq!(
1420 lb.iter()
1421 .map(block_shape)
1422 .map(|hash| hash.is_some())
1423 .collect::<Vec<_>>(),
1424 rb.iter()
1425 .map(block_shape)
1426 .map(|hash| hash.is_some())
1427 .collect::<Vec<_>>()
1428 );
1429 }
1430 (left, right) => panic!("signal variants differ: {left:?} != {right:?}"),
1431 }
1432 }
1433 }
1434
1435 #[test]
1436 fn flat_sequence_matches_uncached_legacy_structure() {
1437 const BLOCK_SIZE: usize = 4;
1438 let prompt: Vec<u32> = (0..9).collect();
1439 let outputs: Vec<u32> = (100..108).collect();
1440 let mut legacy = ActiveSequence::new_with_planned_output_ids(
1441 prompt.clone(),
1442 outputs.len(),
1443 Some(BLOCK_SIZE),
1444 false,
1445 true,
1446 Some(outputs.clone()),
1447 );
1448 let mut flat = ActiveSequence::new_flat_with_planned_output_ids(
1449 prompt,
1450 outputs.len(),
1451 outputs.len(),
1452 BLOCK_SIZE,
1453 false,
1454 true,
1455 Some(outputs),
1456 );
1457
1458 assert!(!legacy.emit_token_ids());
1459 assert!(!flat.emit_token_ids());
1460 assert!(legacy.block_hashes().is_empty());
1461 assert!(flat.block_hashes().is_empty());
1462 assert_eq!(legacy.unique_blocks().len(), flat.unique_blocks().len());
1463 assert_eq!(
1464 legacy
1465 .positional_lineage_hashes()
1466 .iter()
1467 .map(PositionalLineageHash::position)
1468 .collect::<Vec<_>>(),
1469 flat.positional_lineage_hashes()
1470 .iter()
1471 .map(PositionalLineageHash::position)
1472 .collect::<Vec<_>>()
1473 );
1474 assert_uncached_signal_parity(
1475 legacy.take_creation_signal().into_iter().collect(),
1476 flat.take_creation_signal().into_iter().collect(),
1477 );
1478
1479 while legacy.generated_tokens() < legacy.max_output_tokens() {
1480 let (_, legacy_signals) = legacy.generate_token();
1481 let (_, flat_signals) = flat.generate_token();
1482 assert_uncached_signal_parity(legacy_signals, flat_signals);
1483 }
1484 assert_uncached_signal_parity(legacy.terminal_signals(), flat.terminal_signals());
1485 }
1486
1487 #[test]
1488 #[should_panic(expected = "partial block cannot be a parent")]
1489 fn flat_promotion_rejects_partial_parent() {
1490 let mut flat = ActiveSequence::new_flat_with_planned_output_ids(
1491 (0..15).collect(),
1492 1,
1493 1,
1494 16,
1495 true,
1496 false,
1497 Some(vec![99]),
1498 );
1499 flat.push(99);
1500 flat.unique_blocks.insert(0, UniqueBlock::default());
1501 flat.promote_computed_tail(flat.len());
1502 }
1503
1504 #[test]
1505 #[should_panic(expected = "flat sequences retain full token history")]
1506 fn non_emitting_flat_sequence_rejects_token_materialization() {
1507 let flat = ActiveSequence::new_flat_with_planned_output_ids(
1508 (0..16).collect(),
1509 1,
1510 1,
1511 16,
1512 true,
1513 false,
1514 None,
1515 );
1516 flat.block_token_ids();
1517 }
1518
1519 #[test]
1520 fn flat_sequence_matches_legacy_reset_and_one_token_rollback() {
1521 const BLOCK_SIZE: usize = 16;
1522 let prompt: Vec<u32> = (0..BLOCK_SIZE as u32).collect();
1523 let (mut legacy, mut flat) = sequence_pair(prompt, vec![1_001, 1_002], BLOCK_SIZE, false);
1524
1525 legacy.take_creation_signal();
1526 flat.take_creation_signal();
1527 assert_eq!(
1528 signal_shapes(legacy.push(1_001).unwrap()),
1529 signal_shapes(flat.push(1_001).unwrap())
1530 );
1531 legacy.pop();
1532 flat.pop();
1533 assert_sequence_parity(&legacy, &flat);
1534
1535 legacy.commit_allocation(legacy.len());
1536 flat.commit_allocation(flat.len());
1537 assert_eq!(
1538 signal_shapes(legacy.reset_with_signal()),
1539 signal_shapes(flat.reset_with_signal())
1540 );
1541 }
1542
1543 #[test]
1544 fn flat_sequence_preserves_uncached_random_identity_behavior() {
1545 let make = || {
1546 ActiveSequence::new_flat_with_planned_output_ids(
1547 (0..17).collect(),
1548 16,
1549 16,
1550 16,
1551 false,
1552 true,
1553 None,
1554 )
1555 };
1556 let mut first = make();
1557 let second = make();
1558
1559 assert!(first.block_hashes().is_empty());
1560 assert_ne!(first.unique_blocks()[0], second.unique_blocks()[0]);
1561 assert_ne!(
1562 first.positional_lineage_hashes()[0],
1563 second.positional_lineage_hashes()[0]
1564 );
1565
1566 for token in 0..15 {
1567 first.push(token);
1568 }
1569 let promote = first
1570 .promote_computed_tail(first.len())
1571 .expect("completed uncached block should promote");
1572 let MoveBlock::Promote(_, _, _, block_hash, plh, token_ids) = promote else {
1573 panic!("expected promote signal");
1574 };
1575 assert!(block_hash.is_none());
1576 assert_eq!(plh.parent_hash_fragment(), 0);
1577 assert!(token_ids.is_none());
1578 }
1579}