1pub fn kv_cache_formats() -> (&'static str, &'static str) {
15 static F: std::sync::OnceLock<(&'static str, &'static str)> = std::sync::OnceLock::new();
16 *F.get_or_init(|| {
17 let k = match std::env::var("MEMRA_KV_K").as_deref() {
18 Ok("fp8") => "fp8",
19 Ok("q8_0") | Ok("") | Err(_) => "q8_0",
20 Ok(o) => panic!("MEMRA_KV_K={o} unsupported (q8_0 | fp8)"),
21 };
22 let v = match std::env::var("MEMRA_KV_V").as_deref() {
23 Ok("q4_0") => "q4_0",
24 Ok("fp8") => "fp8",
25 Ok("q5_1") | Ok("") | Err(_) => "q5_1",
26 Ok(o) => panic!("MEMRA_KV_V={o} unsupported (q5_1 | q4_0 | fp8)"),
27 };
28 if (k, v) != ("q8_0", "q5_1") {
29 eprintln!("[memra] KV cache format: K={k} V={v} (non-default — new numeric config)");
30 }
31 (k, v)
32 })
33}
34
35pub fn kv_blk_bytes() -> (usize, usize) {
37 let (k, v) = kv_cache_formats();
38 let kb = match k {
39 "fp8" => 32,
40 _ => 34,
41 };
42 let vb = match v {
43 "q4_0" => 18,
44 "fp8" => 32,
45 _ => 24,
46 };
47 (kb, vb)
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct TpKvRankAllocationShape {
57 pub kv_dim_k: usize,
58 pub kv_dim_v: usize,
59 pub k_token_bytes: usize,
60 pub v_token_bytes: usize,
61 pub fixed_bytes: usize,
62}
63
64impl TpKvRankAllocationShape {
65 pub fn bytes_per_token(self) -> usize {
66 self.k_token_bytes.saturating_add(self.v_token_bytes)
67 }
68
69 pub fn allocation_bytes(self, capacity: usize) -> usize {
70 self.bytes_per_token()
71 .saturating_mul(capacity)
72 .saturating_add(self.fixed_bytes)
73 }
74}
75
76pub fn tp_kv_rank_allocation_shape(
77 kv_dim_k: usize,
78 kv_dim_v: usize,
79 ranks: usize,
80) -> Result<TpKvRankAllocationShape, String> {
81 if ranks == 0 || kv_dim_k == 0 || kv_dim_v == 0 {
82 return Err(format!(
83 "TP KV dimensions and rank count must be nonzero: k={kv_dim_k} v={kv_dim_v} \
84 ranks={ranks}"
85 ));
86 }
87 if kv_dim_k % ranks != 0 || kv_dim_v % ranks != 0 {
88 return Err(format!(
89 "TP KV dimensions k={kv_dim_k} v={kv_dim_v} are not divisible by TP={ranks}"
90 ));
91 }
92 let local_k = kv_dim_k / ranks;
93 let local_v = kv_dim_v / ranks;
94 if local_k % 32 != 0 || local_v % 32 != 0 {
95 return Err(format!(
96 "TP KV local dimensions k={local_k} v={local_v} must be 32-aligned"
97 ));
98 }
99 let (k_block_bytes, v_block_bytes) = kv_blk_bytes();
100 let k_token_bytes = (local_k / 32)
101 .checked_mul(k_block_bytes)
102 .ok_or("TP KV K token-byte overflow")?;
103 let v_token_bytes = (local_v / 32)
104 .checked_mul(v_block_bytes)
105 .ok_or("TP KV V token-byte overflow")?;
106 Ok(TpKvRankAllocationShape {
107 kv_dim_k: local_k,
108 kv_dim_v: local_v,
109 k_token_bytes,
110 v_token_bytes,
111 fixed_bytes: 8 + 8 + std::mem::size_of::<i32>(),
114 })
115}
116
117pub fn gkv_on() -> bool {
120 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
121 *ON.get_or_init(|| {
122 std::env::var("MEMRA_GEMMA_GKV")
123 .map(|v| v != "0")
124 .unwrap_or(true)
125 })
126}
127
128pub fn wkv_on() -> bool {
132 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
133 *ON.get_or_init(|| {
134 std::env::var("MEMRA_GEMMA_WKV")
135 .map(|v| v != "0")
136 .unwrap_or_else(|_| std::env::var("MEMRA_DRAFT").is_err())
137 })
138}
139
140pub static KV_FP8_FORCE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
146
147pub fn kv_fp8_on() -> bool {
151 static ENV: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
152 if let Some(v) = *ENV.get_or_init(|| std::env::var("MEMRA_KV_FP8").ok().map(|v| v == "1")) {
153 return v;
154 }
155 matches!(KV_FP8_FORCE.load(std::sync::atomic::Ordering::Relaxed), 1)
156}
157
158static SWA_RING_DEFAULT: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
165
166pub fn set_swa_ring_default(on: bool) {
167 SWA_RING_DEFAULT.store(on, std::sync::atomic::Ordering::Relaxed);
168}
169
170pub fn swa_ring_on() -> bool {
171 static ENV: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
172 match *ENV.get_or_init(|| match std::env::var("MEMRA_SWA_RING").ok().as_deref() {
173 Some("1") => Some(true),
174 Some("0") => Some(false),
175 _ => None,
176 }) {
177 Some(forced) => forced,
178 None => SWA_RING_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
179 }
180}
181
182pub const PRIME_CHUNK_MAX_TOKENS: usize = 4096;
185const SWA_VIEW_ALIGNMENT_ROWS: usize = 32;
186
187pub fn swa_ring_rows(window: usize, max_ctx: usize) -> usize {
190 max_ctx.min(
195 window + PRIME_CHUNK_MAX_TOKENS + SWA_REWIND_SLACK_ROWS + (SWA_VIEW_ALIGNMENT_ROWS - 1),
196 )
197}
198
199pub const SWA_REWIND_SLACK_ROWS: usize = 512;
209
210pub fn swa_retain_from(first_row: usize, window: usize, base: usize) -> usize {
220 let ideal = first_row
221 .saturating_sub(window.saturating_sub(1))
222 .saturating_sub(SWA_REWIND_SLACK_ROWS)
223 & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
224 ideal.max(base)
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub struct KvRing {
229 rows: usize,
230 window: usize,
231 base: usize,
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum KvRingAppend {
236 Contiguous {
237 write_row: usize,
238 },
239 Rebase {
240 src_row: usize,
241 keep_rows: usize,
242 new_base: usize,
243 write_row: usize,
244 },
245}
246
247impl KvRing {
248 pub fn new(rows: usize, window: usize) -> Self {
249 assert!(window > 0 && rows > 0, "invalid SWA ring geometry");
250 Self {
251 rows,
252 window,
253 base: 0,
254 }
255 }
256
257 pub fn rows(&self) -> usize {
258 self.rows
259 }
260 pub fn base(&self) -> usize {
261 self.base
262 }
263 pub fn window(&self) -> usize {
264 self.window
265 }
266
267 pub fn append_plan(
270 &self,
271 len: usize,
272 retain_from: usize,
273 append_rows: usize,
274 ) -> Result<KvRingAppend, String> {
275 if len < self.base || retain_from < self.base || retain_from > len {
276 return Err(format!(
277 "SWA ring lapped required rows (base {}, retain {retain_from}, len {len})",
278 self.base
279 ));
280 }
281 let used = len - self.base;
282 if used > self.rows {
283 return Err(format!(
284 "SWA ring state exceeds capacity ({used} > {})",
285 self.rows
286 ));
287 }
288 if used.saturating_add(append_rows) <= self.rows {
289 return Ok(KvRingAppend::Contiguous {
290 write_row: used % self.rows,
291 });
292 }
293
294 let keep_rows = len - retain_from;
295 if keep_rows.saturating_add(append_rows) > self.rows {
296 return Err(format!(
297 "SWA ring append does not fit (keep {keep_rows} + append {append_rows} > {})",
298 self.rows
299 ));
300 }
301 Ok(KvRingAppend::Rebase {
302 src_row: retain_from - self.base,
303 keep_rows,
304 new_base: retain_from,
305 write_row: keep_rows,
306 })
307 }
308
309 pub fn apply_rebase(&mut self, new_base: usize) {
310 debug_assert!(new_base >= self.base);
311 self.base = new_base;
312 }
313
314 pub fn physical_range(
315 &self,
316 start: usize,
317 end: usize,
318 ) -> Result<std::ops::Range<usize>, String> {
319 if start < self.base || end < start || end - self.base > self.rows {
320 return Err(format!(
321 "SWA ring view [{start},{end}) is outside resident [{},{})",
322 self.base,
323 self.base + self.rows
324 ));
325 }
326 let start_row = (start - self.base) % self.rows;
327 let len = end - start;
328 debug_assert!(
329 start_row + len <= self.rows,
330 "ring view must be contiguous after rebase"
331 );
332 Ok(start_row..start_row + len)
333 }
334
335 pub fn can_rewind_to(&self, len: usize) -> bool {
337 let raw = len.saturating_sub(self.window - 1);
338 let view_start = raw & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
339 view_start >= self.base
340 }
341
342 pub fn restore_plan(&self, len: usize) -> Result<(usize, std::ops::Range<usize>), String> {
349 let raw = len.saturating_sub(self.window.saturating_sub(1));
350 let new_base = raw & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
351 let physical = self.physical_range(new_base, len)?;
352 Ok((new_base, physical))
353 }
354}
355
356pub trait KvDev {
361 fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
362 fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
363 fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>>;
364 fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>>;
365 fn clone_dtod(
366 &self,
367 src: &CudaSlice<f32>,
368 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
369 fn copy_into(
370 &self,
371 dst: &mut CudaSlice<f32>,
372 off: usize,
373 src: &CudaSlice<f32>,
374 len: usize,
375 ) -> Result<(), Box<dyn std::error::Error>>;
376 fn set_i32_one(&self, d: &mut CudaSlice<i32>, v: i32)
377 -> Result<(), Box<dyn std::error::Error>>;
378}
379
380use cudarc::driver::CudaSlice;
381use memra_gguf::config::{LayerKind, ModelConfig};
382use memra_gguf::model_plan::{ModelPlan, ResidualTopology, StatePlan};
383
384pub struct KvLayer {
389 pub k: CudaSlice<u8>, pub v: CudaSlice<u8>, pub kv_dim_k: usize, pub kv_dim_v: usize, pub k_tok_bytes: usize, pub v_tok_bytes: usize, pub len: usize,
396 pub ring: Option<KvRing>,
399 pub len_d: CudaSlice<i32>,
403 pub base_d: Option<CudaSlice<i32>>,
412}
413
414impl KvLayer {
415 pub fn physical_rows(
416 &self,
417 start: usize,
418 end: usize,
419 ) -> Result<std::ops::Range<usize>, String> {
420 match &self.ring {
421 Some(ring) => ring.physical_range(start, end),
422 None => Ok(start..end),
423 }
424 }
425}
426
427pub struct RecurLayer {
431 pub conv_state: CudaSlice<f32>, pub ssm_state: CudaSlice<f32>, pub ssm_state_alt: CudaSlice<f32>,
443}
444
445pub struct ResidentTpKvCacheRank {
446 k: CudaSlice<u8>,
447 v: CudaSlice<u8>,
448 len_d: CudaSlice<i32>,
449 base_d: Option<CudaSlice<i32>>,
454}
455
456impl ResidentTpKvCacheRank {
457 pub fn new(k: CudaSlice<u8>, v: CudaSlice<u8>, len_d: CudaSlice<i32>) -> Self {
458 Self {
459 k,
460 v,
461 len_d,
462 base_d: None,
463 }
464 }
465
466 pub fn base_d(&self) -> Option<&CudaSlice<i32>> {
467 self.base_d.as_ref()
468 }
469
470 pub fn base_d_mut(&mut self) -> Option<&mut CudaSlice<i32>> {
471 self.base_d.as_mut()
472 }
473
474 pub fn arm_base_d(&mut self, buf: CudaSlice<i32>) {
475 self.base_d = Some(buf);
476 }
477
478 pub fn k(&self) -> &CudaSlice<u8> {
479 &self.k
480 }
481
482 pub fn v(&self) -> &CudaSlice<u8> {
483 &self.v
484 }
485
486 pub fn len_d(&self) -> &CudaSlice<i32> {
487 &self.len_d
488 }
489
490 pub fn k_mut(&mut self) -> &mut CudaSlice<u8> {
491 &mut self.k
492 }
493
494 pub fn v_mut(&mut self) -> &mut CudaSlice<u8> {
495 &mut self.v
496 }
497
498 pub fn planes_mut(&mut self) -> (&mut CudaSlice<u8>, &mut CudaSlice<u8>) {
499 (&mut self.k, &mut self.v)
500 }
501
502 #[allow(clippy::type_complexity)]
504 pub fn planes_and_counters_mut(
505 &mut self,
506 ) -> (
507 &mut CudaSlice<u8>,
508 &mut CudaSlice<u8>,
509 &CudaSlice<i32>,
510 Option<&CudaSlice<i32>>,
511 ) {
512 (&mut self.k, &mut self.v, &self.len_d, self.base_d.as_ref())
513 }
514
515 pub fn len_d_mut(&mut self) -> &mut CudaSlice<i32> {
516 &mut self.len_d
517 }
518}
519
520#[derive(Clone, Copy, Debug, PartialEq, Eq)]
521pub struct TpKvTransaction {
522 generation: u64,
523 base_len: usize,
524}
525
526impl TpKvTransaction {
527 pub fn generation(self) -> u64 {
528 self.generation
529 }
530
531 pub fn base_len(self) -> usize {
532 self.base_len
533 }
534}
535
536#[derive(Clone, Copy, Debug, PartialEq, Eq)]
537pub struct TpKvAppendPlan {
538 transaction: TpKvTransaction,
539 target: usize,
540 write_row: usize,
541 ring_append: Option<KvRingAppend>,
542}
543
544impl TpKvAppendPlan {
545 pub fn target(self) -> usize {
546 self.target
547 }
548
549 pub fn write_row(self) -> usize {
550 self.write_row
551 }
552
553 pub fn ring_append(self) -> Option<KvRingAppend> {
554 self.ring_append
555 }
556}
557
558#[derive(Debug, PartialEq, Eq)]
559pub struct TpKvGrowPlan {
560 rows: usize,
561 source_row: usize,
562 copy_rows: usize,
563 target_base: usize,
564 k_bytes: usize,
565 v_bytes: usize,
566 source_capacity: usize,
567 target_capacity: usize,
568 ring_window: Option<usize>,
569 target_physical_rows: usize,
570 kv_dim_k: usize,
571 kv_dim_v: usize,
572 k_tok_bytes: usize,
573 v_tok_bytes: usize,
574 ranks: usize,
575 next_generation: u64,
576}
577
578impl TpKvGrowPlan {
579 pub fn rows(&self) -> usize {
580 self.rows
581 }
582
583 pub fn source_row(&self) -> usize {
584 self.source_row
585 }
586
587 pub fn copy_rows(&self) -> usize {
588 self.copy_rows
589 }
590
591 pub fn k_bytes(&self) -> usize {
592 self.k_bytes
593 }
594
595 pub fn v_bytes(&self) -> usize {
596 self.v_bytes
597 }
598}
599
600#[derive(Clone, Debug, PartialEq, Eq)]
601struct TpKvTransactionState {
602 committed_len: usize,
603 staged_len: usize,
604 next_generation: u64,
605 active: Option<TpKvTransaction>,
606}
607
608impl TpKvTransactionState {
609 fn new() -> Self {
610 Self {
611 committed_len: 0,
612 staged_len: 0,
613 next_generation: 1,
614 active: None,
615 }
616 }
617
618 fn begin(&mut self) -> Result<TpKvTransaction, String> {
619 if let Some(active) = self.active {
620 return Err(format!(
621 "TP KV transaction generation {} is already active at base {}",
622 active.generation, active.base_len
623 ));
624 }
625 if self.staged_len != self.committed_len {
626 return Err(format!(
627 "TP KV cache is half-committed: staged {} != committed {}",
628 self.staged_len, self.committed_len
629 ));
630 }
631 let transaction = TpKvTransaction {
632 generation: self.next_generation,
633 base_len: self.committed_len,
634 };
635 self.next_generation = self
636 .next_generation
637 .checked_add(1)
638 .ok_or("TP KV transaction generation overflow")?;
639 self.active = Some(transaction);
640 Ok(transaction)
641 }
642
643 fn validate(&self, transaction: TpKvTransaction) -> Result<(), String> {
644 if self.active != Some(transaction) {
645 return Err(format!(
646 "stale TP KV transaction generation {} at base {}",
647 transaction.generation, transaction.base_len
648 ));
649 }
650 if transaction.base_len != self.committed_len {
651 return Err(format!(
652 "TP KV transaction base {} != committed length {}",
653 transaction.base_len, self.committed_len
654 ));
655 }
656 Ok(())
657 }
658
659 fn append_target(
660 &self,
661 transaction: TpKvTransaction,
662 rows: usize,
663 capacity: usize,
664 ) -> Result<usize, String> {
665 self.validate(transaction)?;
666 if rows == 0 {
667 return Err("TP KV append must contain at least one row".into());
668 }
669 let target = self
670 .staged_len
671 .checked_add(rows)
672 .ok_or("TP KV staged length overflow")?;
673 if target > capacity {
674 return Err(format!(
675 "TP KV append exceeds capacity: {target} > {capacity}"
676 ));
677 }
678 Ok(target)
679 }
680
681 fn publish_append(
682 &mut self,
683 transaction: TpKvTransaction,
684 target: usize,
685 ) -> Result<(), String> {
686 self.validate(transaction)?;
687 if target <= self.staged_len {
688 return Err(format!(
689 "TP KV append target {target} must exceed staged length {}",
690 self.staged_len
691 ));
692 }
693 self.staged_len = target;
694 Ok(())
695 }
696
697 fn commit_target(
698 &self,
699 transaction: TpKvTransaction,
700 accepted_rows: usize,
701 ) -> Result<usize, String> {
702 self.validate(transaction)?;
703 let staged_rows = self
704 .staged_len
705 .checked_sub(transaction.base_len)
706 .ok_or("TP KV staged length precedes its transaction base")?;
707 if accepted_rows > staged_rows {
708 return Err(format!(
709 "TP KV commit accepts {accepted_rows} rows from a {staged_rows}-row transaction"
710 ));
711 }
712 transaction
713 .base_len
714 .checked_add(accepted_rows)
715 .ok_or_else(|| "TP KV committed length overflow".to_string())
716 }
717
718 fn publish_finalize(
719 &mut self,
720 transaction: TpKvTransaction,
721 target: usize,
722 ) -> Result<(), String> {
723 self.validate(transaction)?;
724 if target < transaction.base_len || target > self.staged_len {
725 return Err(format!(
726 "TP KV finalize target {target} outside transaction range {}..={}",
727 transaction.base_len, self.staged_len
728 ));
729 }
730 self.committed_len = target;
731 self.staged_len = target;
732 self.active = None;
733 Ok(())
734 }
735
736 fn rewind(&mut self, target: usize, capacity: usize) -> Result<(), String> {
737 if target > capacity {
738 return Err(format!(
739 "TP KV rewind target {target} exceeds capacity {capacity}"
740 ));
741 }
742 self.committed_len = target;
743 self.staged_len = target;
744 self.active = None;
745 Ok(())
746 }
747}
748
749pub struct ResidentTpKvCache {
750 ranks: Vec<ResidentTpKvCacheRank>,
751 kv_dim_k: usize,
752 kv_dim_v: usize,
753 k_tok_bytes: usize,
754 v_tok_bytes: usize,
755 capacity: usize,
756 ring: Option<KvRing>,
757 state: TpKvTransactionState,
758}
759
760impl ResidentTpKvCache {
761 #[allow(clippy::too_many_arguments)]
762 pub fn new(
763 ranks: Vec<ResidentTpKvCacheRank>,
764 kv_dim_k: usize,
765 kv_dim_v: usize,
766 k_tok_bytes: usize,
767 v_tok_bytes: usize,
768 capacity: usize,
769 ) -> Self {
770 Self::new_inner(
771 ranks,
772 kv_dim_k,
773 kv_dim_v,
774 k_tok_bytes,
775 v_tok_bytes,
776 capacity,
777 None,
778 )
779 }
780
781 #[allow(clippy::too_many_arguments)]
782 pub fn new_swa(
783 ranks: Vec<ResidentTpKvCacheRank>,
784 kv_dim_k: usize,
785 kv_dim_v: usize,
786 k_tok_bytes: usize,
787 v_tok_bytes: usize,
788 capacity: usize,
789 window: usize,
790 ) -> Self {
791 Self::new_inner(
792 ranks,
793 kv_dim_k,
794 kv_dim_v,
795 k_tok_bytes,
796 v_tok_bytes,
797 capacity,
798 Some(KvRing::new(swa_ring_rows(window, capacity), window)),
799 )
800 }
801
802 #[allow(clippy::too_many_arguments)]
803 fn new_inner(
804 ranks: Vec<ResidentTpKvCacheRank>,
805 kv_dim_k: usize,
806 kv_dim_v: usize,
807 k_tok_bytes: usize,
808 v_tok_bytes: usize,
809 capacity: usize,
810 ring: Option<KvRing>,
811 ) -> Self {
812 Self {
813 ranks,
814 kv_dim_k,
815 kv_dim_v,
816 k_tok_bytes,
817 v_tok_bytes,
818 capacity,
819 ring,
820 state: TpKvTransactionState::new(),
821 }
822 }
823
824 pub fn begin_transaction(&mut self) -> Result<TpKvTransaction, String> {
825 self.state.begin()
826 }
827
828 pub fn committed_len(&self) -> usize {
829 self.state.committed_len
830 }
831
832 pub fn staged_len(&self) -> usize {
833 self.state.staged_len
834 }
835
836 pub fn capacity(&self) -> usize {
837 self.capacity
838 }
839
840 pub fn physical_capacity(&self) -> usize {
841 self.ring
842 .as_ref()
843 .map(KvRing::rows)
844 .unwrap_or(self.capacity)
845 }
846
847 pub fn ring_window(&self) -> Option<usize> {
848 self.ring.as_ref().map(KvRing::window)
849 }
850
851 pub fn ring_base(&self) -> Option<usize> {
852 self.ring.as_ref().map(KvRing::base)
853 }
854
855 pub fn physical_range(
856 &self,
857 start: usize,
858 end: usize,
859 ) -> Result<std::ops::Range<usize>, String> {
860 match &self.ring {
861 Some(ring) => ring.physical_range(start, end),
862 None => {
863 if end < start || end > self.capacity {
864 return Err(format!(
865 "TP KV linear view [{start},{end}) exceeds capacity {}",
866 self.capacity
867 ));
868 }
869 Ok(start..end)
870 }
871 }
872 }
873
874 pub fn can_rewind_to(&self, target: usize) -> bool {
875 target <= self.capacity
876 && self
877 .ring
878 .as_ref()
879 .is_none_or(|ring| ring.can_rewind_to(target))
880 }
881
882 pub fn kv_dim_k(&self) -> usize {
883 self.kv_dim_k
884 }
885
886 pub fn kv_dim_v(&self) -> usize {
887 self.kv_dim_v
888 }
889
890 pub fn k_tok_bytes(&self) -> usize {
891 self.k_tok_bytes
892 }
893
894 pub fn v_tok_bytes(&self) -> usize {
895 self.v_tok_bytes
896 }
897
898 pub fn ranks_len(&self) -> usize {
899 self.ranks.len()
900 }
901
902 pub fn rank(&self, rank: usize) -> Option<&ResidentTpKvCacheRank> {
903 self.ranks.get(rank)
904 }
905
906 pub fn rank_mut(&mut self, rank: usize) -> Option<&mut ResidentTpKvCacheRank> {
907 self.ranks.get_mut(rank)
908 }
909
910 pub fn ranks(&self) -> &[ResidentTpKvCacheRank] {
911 &self.ranks
912 }
913
914 pub fn ranks_mut(&mut self) -> &mut [ResidentTpKvCacheRank] {
915 &mut self.ranks
916 }
917
918 pub fn prepare_grow(
919 &self,
920 target_capacity: usize,
921 rows: usize,
922 ) -> Result<TpKvGrowPlan, String> {
923 if let Some(active) = self.state.active {
924 return Err(format!(
925 "TP KV grow refuses active transaction generation {} at base {}",
926 active.generation, active.base_len
927 ));
928 }
929 if self.state.staged_len != self.state.committed_len {
930 return Err(format!(
931 "TP KV grow requires quiescent state, got committed/staged={}/{}",
932 self.state.committed_len, self.state.staged_len
933 ));
934 }
935 if target_capacity <= self.capacity {
936 return Err(format!(
937 "TP KV grow target capacity {target_capacity} must exceed source capacity {}",
938 self.capacity
939 ));
940 }
941 if target_capacity > i32::MAX as usize {
942 return Err(format!(
943 "TP KV grow target capacity {target_capacity} exceeds i32 device mirrors"
944 ));
945 }
946 if rows > self.state.committed_len {
947 return Err(format!(
948 "TP KV grow rows {rows} exceed committed length {}",
949 self.state.committed_len
950 ));
951 }
952 let (source_row, copy_rows, target_base, ring_window, target_physical_rows) =
953 match &self.ring {
954 Some(ring) => {
955 let raw = rows.saturating_sub(ring.window().saturating_sub(1));
956 let target_base = raw & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
957 let physical = ring.physical_range(target_base, rows)?;
958 (
959 physical.start,
960 physical.len(),
961 target_base,
962 Some(ring.window()),
963 swa_ring_rows(ring.window(), target_capacity),
964 )
965 }
966 None => (0, rows, 0, None, target_capacity),
967 };
968 let k_bytes = copy_rows
969 .checked_mul(self.k_tok_bytes)
970 .ok_or("TP KV grow K byte extent overflow")?;
971 let v_bytes = copy_rows
972 .checked_mul(self.v_tok_bytes)
973 .ok_or("TP KV grow V byte extent overflow")?;
974 Ok(TpKvGrowPlan {
975 rows,
976 source_row,
977 copy_rows,
978 target_base,
979 k_bytes,
980 v_bytes,
981 source_capacity: self.capacity,
982 target_capacity,
983 ring_window,
984 target_physical_rows,
985 kv_dim_k: self.kv_dim_k,
986 kv_dim_v: self.kv_dim_v,
987 k_tok_bytes: self.k_tok_bytes,
988 v_tok_bytes: self.v_tok_bytes,
989 ranks: self.ranks.len(),
990 next_generation: self.state.next_generation,
991 })
992 }
993
994 pub fn publish_grow(&mut self, plan: TpKvGrowPlan) -> Result<(), String> {
995 if self.state != TpKvTransactionState::new() {
996 return Err(format!(
997 "TP KV grow target must be fresh, got committed/staged={}/{} active={}",
998 self.state.committed_len,
999 self.state.staged_len,
1000 self.state.active.is_some()
1001 ));
1002 }
1003 if self.capacity != plan.target_capacity
1004 || self.capacity <= plan.source_capacity
1005 || self.kv_dim_k != plan.kv_dim_k
1006 || self.kv_dim_v != plan.kv_dim_v
1007 || self.k_tok_bytes != plan.k_tok_bytes
1008 || self.v_tok_bytes != plan.v_tok_bytes
1009 || self.ranks.len() != plan.ranks
1010 || self.ring.as_ref().map(KvRing::window) != plan.ring_window
1011 || self.physical_capacity() != plan.target_physical_rows
1012 {
1013 return Err("TP KV grow target layout does not match its source plan".into());
1014 }
1015 if plan.rows > self.capacity {
1016 return Err(format!(
1017 "TP KV grow rows {} exceed target capacity {}",
1018 plan.rows, self.capacity
1019 ));
1020 }
1021 if let Some(ring) = self.ring.as_mut() {
1022 let mut target_ring = *ring;
1023 target_ring.apply_rebase(plan.target_base);
1024 if !target_ring.can_rewind_to(plan.rows) {
1025 return Err(format!(
1026 "TP KV grow target ring base {} cannot expose committed length {}",
1027 target_ring.base(),
1028 plan.rows
1029 ));
1030 }
1031 *ring = target_ring;
1032 }
1033 self.state.committed_len = plan.rows;
1034 self.state.staged_len = plan.rows;
1035 self.state.next_generation = plan.next_generation;
1036 self.state.active = None;
1037 Ok(())
1038 }
1039
1040 pub fn prepare_append(
1041 &self,
1042 transaction: TpKvTransaction,
1043 rows: usize,
1044 ) -> Result<TpKvAppendPlan, String> {
1045 let target = self.state.append_target(transaction, rows, self.capacity)?;
1046 let ring_append = self
1047 .ring
1048 .as_ref()
1049 .map(|ring| {
1050 let staged_retain =
1051 target.saturating_sub(ring.window()) & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
1052 let rollback_retain = transaction
1053 .base_len
1054 .saturating_sub(ring.window().saturating_sub(1))
1055 & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
1056 ring.append_plan(
1057 self.state.staged_len,
1058 staged_retain.min(rollback_retain),
1059 rows,
1060 )
1061 })
1062 .transpose()?;
1063 let write_row = match ring_append {
1064 Some(KvRingAppend::Contiguous { write_row })
1065 | Some(KvRingAppend::Rebase { write_row, .. }) => write_row,
1066 None => self.state.staged_len,
1067 };
1068 Ok(TpKvAppendPlan {
1069 transaction,
1070 target,
1071 write_row,
1072 ring_append,
1073 })
1074 }
1075
1076 pub fn peek_append_ring(&self, rows: usize) -> Result<(usize, bool), String> {
1080 let target = self.state.staged_len + rows;
1081 let plan = self
1082 .ring
1083 .as_ref()
1084 .map(|ring| {
1085 let staged_retain =
1086 target.saturating_sub(ring.window()) & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
1087 let rollback_retain = self
1088 .state
1089 .staged_len
1090 .saturating_sub(ring.window().saturating_sub(1))
1091 & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
1092 ring.append_plan(
1093 self.state.staged_len,
1094 staged_retain.min(rollback_retain),
1095 rows,
1096 )
1097 })
1098 .transpose()?;
1099 Ok(match plan {
1100 Some(KvRingAppend::Contiguous { write_row }) => (write_row, false),
1101 Some(KvRingAppend::Rebase { write_row, .. }) => (write_row, true),
1102 None => (self.state.staged_len, false),
1103 })
1104 }
1105
1106 pub fn publish_append_rebase(&mut self, plan: TpKvAppendPlan) -> Result<(), String> {
1107 self.state.validate(plan.transaction)?;
1108 match (self.ring.as_mut(), plan.ring_append) {
1109 (
1110 Some(ring),
1111 Some(KvRingAppend::Rebase {
1112 new_base,
1113 keep_rows,
1114 ..
1115 }),
1116 ) => {
1117 if keep_rows > ring.rows() {
1118 return Err(format!(
1119 "TP KV ring rebase keeps {keep_rows} rows in {} physical rows",
1120 ring.rows()
1121 ));
1122 }
1123 let mut target_ring = *ring;
1124 target_ring.apply_rebase(new_base);
1125 if !target_ring.can_rewind_to(plan.transaction.base_len) {
1126 return Err(format!(
1127 "TP KV ring rebase to {new_base} laps transaction base {}",
1128 plan.transaction.base_len
1129 ));
1130 }
1131 *ring = target_ring;
1132 Ok(())
1133 }
1134 (Some(_), Some(KvRingAppend::Contiguous { .. })) | (None, None) => Ok(()),
1135 _ => Err("TP KV append plan does not match cache ring layout".into()),
1136 }
1137 }
1138
1139 pub fn publish_append_plan(&mut self, plan: TpKvAppendPlan) -> Result<(), String> {
1140 if let Some(KvRingAppend::Rebase { new_base, .. }) = plan.ring_append {
1141 if self.ring.as_ref().map(KvRing::base) != Some(new_base) {
1142 return Err(format!(
1143 "TP KV append rebase {new_base} was not published before its state"
1144 ));
1145 }
1146 }
1147 self.state.publish_append(plan.transaction, plan.target)
1148 }
1149
1150 pub fn publish_hydration(
1151 &mut self,
1152 logical_len: usize,
1153 resident_start: usize,
1154 ) -> Result<(), Box<dyn std::error::Error>> {
1155 if self.state != TpKvTransactionState::new() {
1156 return Err("TP KV hydration target must be fresh".into());
1157 }
1158 if resident_start > logical_len || logical_len > self.capacity {
1159 return Err(format!(
1160 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
1161 self.capacity
1162 )
1163 .into());
1164 }
1165 match self.ring.as_mut() {
1166 Some(ring) => {
1167 let rows = logical_len - resident_start;
1168 if rows > ring.rows() {
1169 return Err(format!(
1170 "TP KV hydration requires {rows} rows in a {}-row ring",
1171 ring.rows()
1172 )
1173 .into());
1174 }
1175 let mut hydrated_ring = *ring;
1176 hydrated_ring.apply_rebase(resident_start);
1177 if !hydrated_ring.can_rewind_to(logical_len) {
1178 return Err(format!(
1179 "TP KV hydration base {resident_start} cannot expose logical length \
1180 {logical_len}"
1181 )
1182 .into());
1183 }
1184 *ring = hydrated_ring;
1185 }
1186 None if resident_start != 0 => {
1187 return Err("linear TP KV hydration must start at absolute row zero".into());
1188 }
1189 None => {}
1190 }
1191 self.rewind_to(logical_len)
1192 }
1193
1194 pub fn append_target(
1195 &self,
1196 transaction: TpKvTransaction,
1197 rows: usize,
1198 ) -> Result<usize, String> {
1199 self.state.append_target(transaction, rows, self.capacity)
1200 }
1201
1202 pub fn publish_append(
1203 &mut self,
1204 transaction: TpKvTransaction,
1205 target: usize,
1206 ) -> Result<(), String> {
1207 self.state.publish_append(transaction, target)
1208 }
1209
1210 pub fn commit_target(
1211 &self,
1212 transaction: TpKvTransaction,
1213 accepted_rows: usize,
1214 ) -> Result<usize, String> {
1215 self.state.commit_target(transaction, accepted_rows)
1216 }
1217
1218 pub fn validate_transaction(&self, transaction: TpKvTransaction) -> Result<(), String> {
1219 self.state.validate(transaction)
1220 }
1221
1222 pub fn publish_finalize(
1223 &mut self,
1224 transaction: TpKvTransaction,
1225 target: usize,
1226 ) -> Result<(), String> {
1227 if !self.can_rewind_to(target) {
1228 return Err(format!(
1229 "TP KV finalize target {target} is outside the resident cache window/capacity"
1230 ));
1231 }
1232 self.state.publish_finalize(transaction, target)
1233 }
1234
1235 pub fn rewind_to(&mut self, target: usize) -> Result<(), Box<dyn std::error::Error>> {
1236 if !self.can_rewind_to(target) {
1237 return Err(format!(
1238 "TP KV rewind target {target} is outside the resident cache window/capacity"
1239 )
1240 .into());
1241 }
1242 let target_i32 =
1243 i32::try_from(target).map_err(|_| "TP KV length exceeds i32 device mirror")?;
1244 for rank in &mut self.ranks {
1245 let stream = rank.len_d.stream().clone();
1246 stream.memcpy_htod(&[target_i32], &mut rank.len_d)?;
1247 }
1248 self.state.rewind(target, self.capacity)?;
1249 Ok(())
1250 }
1251}
1252
1253pub struct Cache {
1254 pub kv: Vec<Option<KvLayer>>,
1255 pub recur: Vec<Option<RecurLayer>>,
1256 pub tp_kv: Vec<Option<ResidentTpKvCache>>,
1259 pub pos: usize,
1260 pub max_ctx: usize,
1261 pub last_logits_dev: Option<CudaSlice<f32>>,
1269 pub dflash_taps: Option<DflashTapSink>,
1274}
1275
1276#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1280enum FullAttentionClass {
1281 Ordinary,
1282 GemmaGlobal,
1283 GemmaWindowed,
1284}
1285
1286fn full_attention_class(plan: &ModelPlan, il: u32) -> FullAttentionClass {
1287 let layer = plan
1288 .layers
1289 .iter()
1290 .chain(plan.mtp_blocks.iter().map(|block| &block.layer))
1291 .find(|layer| layer.index == il)
1292 .unwrap_or_else(|| panic!("ModelPlan has no layer {il}"));
1293 if !matches!(layer.residual, ResidualTopology::Gemma { .. }) {
1294 return FullAttentionClass::Ordinary;
1295 }
1296 match layer.state {
1297 StatePlan::SlidingKvCache { .. } => FullAttentionClass::GemmaWindowed,
1298 StatePlan::KvCache { .. } => FullAttentionClass::GemmaGlobal,
1299 _ => panic!("Gemma layer {il} does not declare a KV-cache state"),
1300 }
1301}
1302
1303fn full_attention_kv_layout(
1304 cfg: &ModelConfig,
1305 plan: &ModelPlan,
1306 il: u32,
1307) -> (usize, usize, usize, usize) {
1308 debug_assert_eq!(cfg.layer_kind(il), LayerKind::FullAttention);
1309 let class = full_attention_class(plan, il);
1310 let n_head_kv = cfg.n_head_kv as usize;
1311 let (kv_dim_k, kv_dim_v) = match class {
1312 FullAttentionClass::GemmaGlobal | FullAttentionClass::GemmaWindowed => {
1313 let g = cfg
1314 .gemma4
1315 .as_ref()
1316 .expect("Gemma ModelPlan layer requires Gemma cache geometry");
1317 let hd = match class {
1318 FullAttentionClass::GemmaWindowed => g.key_length_swa,
1319 FullAttentionClass::GemmaGlobal => g.key_length_global,
1320 FullAttentionClass::Ordinary => unreachable!(),
1321 } as usize;
1322 let d = match g.head_count_kv.get(il as usize) {
1331 Some(n) => hd * *n as usize,
1332 None => hd * n_head_kv,
1333 };
1334 (d, d)
1335 }
1336 FullAttentionClass::Ordinary => (
1337 cfg.head_dim_k as usize * n_head_kv,
1338 cfg.head_dim_v as usize * n_head_kv,
1339 ),
1340 };
1341 assert!(
1342 kv_dim_k % 32 == 0 && kv_dim_v % 32 == 0,
1343 "KVQUANT requires per-layer kv_dim_k%32==0 && kv_dim_v%32==0 \
1344 (layer {il}: k={kv_dim_k} v={kv_dim_v})"
1345 );
1346 let (kbb, vbb) = kv_blk_bytes();
1347 let g4_global_fp8 = gkv_on() && class == FullAttentionClass::GemmaGlobal;
1348 let g4_windowed_fp8 = wkv_on() && class == FullAttentionClass::GemmaWindowed;
1349 let qwen_fp8 = kv_fp8_on() && class == FullAttentionClass::Ordinary;
1350 let (kbb_l, vbb_l) = if g4_global_fp8 || g4_windowed_fp8 || qwen_fp8 {
1351 (32, 32)
1352 } else {
1353 (kbb, vbb)
1354 };
1355 (kv_dim_k, kv_dim_v, kbb_l, vbb_l)
1356}
1357
1358fn kv_plane_allocation_bytes(rows: usize, token_bytes: usize) -> usize {
1359 rows * token_bytes + 8
1360}
1361
1362pub fn cache_bytes_per_token(cfg: &ModelConfig) -> usize {
1369 cache_bytes_per_token_for_layers(cfg, 0, cfg.n_layer as usize)
1370}
1371
1372pub fn cache_bytes_per_token_for_layers(cfg: &ModelConfig, lo: usize, hi: usize) -> usize {
1376 let plan = ModelPlan::compile(cfg).expect("cache sizing requires a compilable ModelPlan");
1377 cache_bytes_per_token_for_plan(cfg, &plan, lo, hi)
1378}
1379
1380pub fn cache_bytes_per_token_for_plan(
1381 cfg: &ModelConfig,
1382 plan: &ModelPlan,
1383 lo: usize,
1384 hi: usize,
1385) -> usize {
1386 assert!(
1387 lo <= hi && hi <= cfg.n_layer as usize,
1388 "cache layer range out of bounds"
1389 );
1390 let shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
1391 (lo as u32..hi as u32)
1392 .filter(|&il| cfg.layer_kind(il) == LayerKind::FullAttention)
1393 .filter(|&il| shared == 0 || il < cfg.n_layer - shared)
1394 .map(|il| {
1395 let (kv_dim_k, kv_dim_v, kbb, vbb) = full_attention_kv_layout(cfg, plan, il);
1396 (kv_dim_k / 32) * kbb + (kv_dim_v / 32) * vbb
1397 })
1398 .sum()
1399}
1400
1401pub fn cache_ring_bytes_per_token(cfg: &ModelConfig) -> usize {
1404 cache_ring_bytes_per_token_for_layers(cfg, 0, cfg.n_layer as usize)
1405}
1406
1407pub fn cache_ring_bytes_per_token_for_layers(cfg: &ModelConfig, lo: usize, hi: usize) -> usize {
1409 assert!(
1410 lo <= hi && hi <= cfg.n_layer as usize,
1411 "cache layer range out of bounds"
1412 );
1413 let Ok(plan) = memra_gguf::model_plan::ModelPlan::compile(cfg) else {
1414 return 0;
1415 };
1416 cache_ring_bytes_per_token_for_plan(cfg, &plan, lo, hi)
1417}
1418
1419pub fn cache_ring_bytes_per_token_for_plan(
1420 cfg: &ModelConfig,
1421 plan: &ModelPlan,
1422 lo: usize,
1423 hi: usize,
1424) -> usize {
1425 let total = plan.layers.len() + plan.mtp_blocks.len();
1426 assert!(
1427 lo <= hi && hi <= total,
1428 "cache plan layer range out of bounds"
1429 );
1430 if !swa_ring_on() {
1431 return 0;
1432 }
1433 let shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
1434 plan.layers
1435 .iter()
1436 .chain(plan.mtp_blocks.iter().map(|block| &block.layer))
1437 .filter(|layer| (lo..hi).contains(&(layer.index as usize)))
1438 .filter(|layer| {
1439 matches!(
1440 layer.state,
1441 memra_gguf::model_plan::StatePlan::SlidingKvCache { .. }
1442 )
1443 })
1444 .filter(|layer| shared == 0 || layer.index < cfg.n_layer - shared)
1445 .map(|layer| {
1446 let (kv_dim_k, kv_dim_v, kbb, vbb) = full_attention_kv_layout(cfg, plan, layer.index);
1447 (kv_dim_k / 32) * kbb + (kv_dim_v / 32) * vbb
1448 })
1449 .sum()
1450}
1451
1452pub fn cache_ring_row_cap(cfg: &ModelConfig) -> usize {
1454 let Ok(plan) = memra_gguf::model_plan::ModelPlan::compile(cfg) else {
1455 return 0;
1456 };
1457 cache_ring_row_cap_for_plan(&plan)
1458}
1459
1460pub fn cache_ring_row_cap_for_plan(plan: &memra_gguf::model_plan::ModelPlan) -> usize {
1461 if !swa_ring_on() {
1462 return 0;
1463 }
1464 plan.layers
1465 .iter()
1466 .chain(plan.mtp_blocks.iter().map(|block| &block.layer))
1467 .filter_map(|layer| match layer.state {
1468 memra_gguf::model_plan::StatePlan::SlidingKvCache { window, .. } => {
1469 Some(window as usize)
1470 }
1471 _ => None,
1472 })
1473 .map(|window| swa_ring_rows(window, usize::MAX))
1474 .max()
1475 .unwrap_or(0)
1476}
1477
1478pub struct DflashTapSink {
1481 pub layer_ids: Vec<usize>,
1482 pub buf: CudaSlice<f32>,
1483 pub hidden: usize,
1484 pub t: usize,
1485 pub base: usize,
1488}
1489
1490pub struct CacheSnapshot {
1497 pub kv_len: Vec<Option<usize>>, pub tp_kv_len: Vec<Option<usize>>, pub conv: Vec<Option<CudaSlice<f32>>>, pub ssm: Vec<Option<CudaSlice<f32>>>,
1501 pub pos: usize,
1502}
1503
1504impl Cache {
1505 pub fn new(
1507 e: &impl KvDev,
1508 cfg: &ModelConfig,
1509 max_ctx: usize,
1510 ) -> Result<Self, Box<dyn std::error::Error>> {
1511 Self::new_inner(&|_| e, cfg, None, max_ctx)
1512 }
1513
1514 pub fn new_planned(
1515 e: &impl KvDev,
1516 cfg: &ModelConfig,
1517 plan: &memra_gguf::model_plan::ModelPlan,
1518 max_ctx: usize,
1519 ) -> Result<Self, Box<dyn std::error::Error>> {
1520 Self::new_inner(&|_| e, cfg, Some(plan), max_ctx)
1521 }
1522
1523 pub fn new_pp2(
1528 dev0: &dyn KvDev,
1529 dev1: &dyn KvDev,
1530 split: usize,
1531 cfg: &ModelConfig,
1532 max_ctx: usize,
1533 ) -> Result<Self, Box<dyn std::error::Error>> {
1534 Self::new_inner(
1535 &|il| if il < split { dev0 } else { dev1 },
1536 cfg,
1537 None,
1538 max_ctx,
1539 )
1540 }
1541
1542 pub fn new_ppn<'a>(
1548 devs: &[&'a dyn KvDev],
1549 fence: &[usize],
1550 cfg: &ModelConfig,
1551 max_ctx: usize,
1552 ) -> Result<Self, Box<dyn std::error::Error>> {
1553 assert_eq!(
1554 devs.len() + 1,
1555 fence.len(),
1556 "ppn cache: devs vs fence mismatch"
1557 );
1558 let pick = |il: usize| -> &dyn KvDev {
1559 let s = match fence[1..fence.len() - 1].binary_search(&il) {
1560 Ok(k) => k + 1,
1561 Err(k) => k,
1562 };
1563 devs[s.min(devs.len() - 1)]
1564 };
1565 Self::new_inner(&pick, cfg, None, max_ctx)
1566 }
1567
1568 pub fn new_ppn_planned<'a>(
1569 devs: &[&'a dyn KvDev],
1570 fence: &[usize],
1571 cfg: &ModelConfig,
1572 plan: &memra_gguf::model_plan::ModelPlan,
1573 max_ctx: usize,
1574 ) -> Result<Self, Box<dyn std::error::Error>> {
1575 assert_eq!(
1576 devs.len() + 1,
1577 fence.len(),
1578 "ppn cache: devs vs fence mismatch"
1579 );
1580 let pick = |il: usize| -> &dyn KvDev {
1581 let stage = match fence[1..fence.len() - 1].binary_search(&il) {
1582 Ok(index) => index + 1,
1583 Err(index) => index,
1584 };
1585 devs[stage.min(devs.len() - 1)]
1586 };
1587 Self::new_inner(&pick, cfg, Some(plan), max_ctx)
1588 }
1589
1590 fn new_inner<'a>(
1593 pick: &dyn Fn(usize) -> &'a dyn KvDev,
1594 cfg: &ModelConfig,
1595 plan: Option<&memra_gguf::model_plan::ModelPlan>,
1596 max_ctx: usize,
1597 ) -> Result<Self, Box<dyn std::error::Error>> {
1598 let fallback_plan = if plan.is_none() {
1599 Some(ModelPlan::compile(cfg)?)
1600 } else {
1601 None
1602 };
1603 let plan = plan
1604 .or(fallback_plan.as_ref())
1605 .expect("cache allocation requires a ModelPlan");
1606 let n = cfg.n_layer as usize;
1607 let mut kv = Vec::with_capacity(n);
1608 let mut recur = Vec::with_capacity(n);
1609 let head_dim_k = cfg.head_dim_k as usize;
1610 let head_dim_v = cfg.head_dim_v as usize;
1611 assert!(
1612 head_dim_k % 32 == 0 && head_dim_v % 32 == 0,
1613 "KVQUANT requires head_dim_k%32==0 && head_dim_v%32==0 (got k={head_dim_k} v={head_dim_v})"
1614 );
1615 for il in 0..cfg.n_layer {
1616 let e = pick(il as usize);
1618 let layer = plan
1619 .layers
1620 .iter()
1621 .chain(plan.mtp_blocks.iter().map(|block| &block.layer))
1622 .find(|layer| layer.index == il)
1623 .ok_or_else(|| format!("cache ModelPlan has no layer {il}"))?;
1624 let g4_shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
1629 if g4_shared > 0 && il >= cfg.n_layer - g4_shared {
1630 kv.push(None);
1631 recur.push(None);
1632 continue;
1633 }
1634 match layer.state {
1635 StatePlan::KvCache { .. } | StatePlan::SlidingKvCache { .. } => {
1636 let (kv_dim_k, kv_dim_v, kbb_l, vbb_l) =
1639 full_attention_kv_layout(cfg, plan, il);
1640 let k_tok_bytes = (kv_dim_k / 32) * kbb_l;
1641 let v_tok_bytes = (kv_dim_v / 32) * vbb_l;
1642 let planned_window = plan
1643 .layers
1644 .iter()
1645 .chain(plan.mtp_blocks.iter().map(|block| &block.layer))
1646 .find(|layer| layer.index == il)
1647 .and_then(|layer| match layer.state {
1648 StatePlan::SlidingKvCache { window, .. } => Some(window),
1649 _ => None,
1650 });
1651 let ring = if swa_ring_on() {
1652 planned_window.map(|window| {
1653 let window = window as usize;
1654 KvRing::new(swa_ring_rows(window, max_ctx), window)
1655 })
1656 } else {
1657 None
1658 };
1659 let alloc_rows = ring.as_ref().map(KvRing::rows).unwrap_or(max_ctx);
1660 kv.push(Some(KvLayer {
1661 k: e.alloc_u8(kv_plane_allocation_bytes(alloc_rows, k_tok_bytes))?,
1665 v: e.alloc_u8(kv_plane_allocation_bytes(alloc_rows, v_tok_bytes))?,
1666 kv_dim_k,
1667 kv_dim_v,
1668 k_tok_bytes,
1669 v_tok_bytes,
1670 len: 0,
1671 ring,
1672 len_d: e.htod_i32(&[0])?,
1673 base_d: None,
1674 }));
1675 recur.push(None);
1676 }
1677 StatePlan::Recurrent {
1678 conv_width,
1679 conv_kernel,
1680 state_width,
1681 } => {
1682 kv.push(None);
1683 recur.push(Some(RecurLayer {
1684 conv_state: e.zeros(
1685 conv_width as usize * (conv_kernel as usize).saturating_sub(1),
1686 )?,
1687 ssm_state: e.zeros(state_width as usize)?,
1688 ssm_state_alt: e.zeros(state_width as usize)?,
1689 }));
1690 }
1691 ref state => {
1692 return Err(format!(
1693 "native cache allocator has no implementation for layer {il} state {state:?}"
1694 )
1695 .into());
1696 }
1697 }
1698 }
1699 Ok(Cache {
1700 kv,
1701 recur,
1702 tp_kv: (0..n).map(|_| None).collect(),
1703 pos: 0,
1704 max_ctx,
1705 dflash_taps: None,
1706 last_logits_dev: None,
1707 })
1708 }
1709
1710 pub fn has_swa_ring(&self) -> bool {
1711 self.kv.iter().flatten().any(|layer| layer.ring.is_some())
1712 || self
1713 .tp_kv
1714 .iter()
1715 .flatten()
1716 .any(|layer| layer.ring_window().is_some())
1717 }
1718
1719 pub fn can_rollback(&self, snap: &CacheSnapshot, accept_len: usize) -> bool {
1720 let local = self
1721 .kv
1722 .iter()
1723 .zip(&snap.kv_len)
1724 .all(|(layer, saved)| match (layer, saved) {
1725 (Some(layer), Some(saved)) => layer
1726 .ring
1727 .as_ref()
1728 .is_none_or(|ring| ring.can_rewind_to(saved + accept_len)),
1729 _ => true,
1730 });
1731 let tensor = self
1732 .tp_kv
1733 .iter()
1734 .zip(&snap.tp_kv_len)
1735 .all(|(layer, saved)| match (layer, saved) {
1736 (Some(layer), Some(saved)) => saved
1737 .checked_add(accept_len)
1738 .is_some_and(|target| layer.can_rewind_to(target)),
1739 _ => true,
1740 });
1741 local && tensor
1742 }
1743
1744 pub fn snapshot(&self, e: &impl KvDev) -> Result<CacheSnapshot, Box<dyn std::error::Error>> {
1748 let n = self.kv.len();
1749 let mut kv_len = Vec::with_capacity(n);
1750 let mut tp_kv_len = Vec::with_capacity(n);
1751 let mut conv = Vec::with_capacity(n);
1752 let mut ssm = Vec::with_capacity(n);
1753 for il in 0..n {
1754 match &self.kv[il] {
1755 Some(kvl) => kv_len.push(Some(kvl.len)),
1756 None => kv_len.push(None),
1757 }
1758 tp_kv_len.push(
1759 self.tp_kv[il]
1760 .as_ref()
1761 .map(ResidentTpKvCache::committed_len),
1762 );
1763 match &self.recur[il] {
1764 Some(rl) => {
1765 conv.push(Some(e.clone_dtod(&rl.conv_state)?));
1766 ssm.push(Some(e.clone_dtod(&rl.ssm_state)?));
1767 }
1768 None => {
1769 conv.push(None);
1770 ssm.push(None);
1771 }
1772 }
1773 }
1774 Ok(CacheSnapshot {
1775 kv_len,
1776 tp_kv_len,
1777 conv,
1778 ssm,
1779 pos: self.pos,
1780 })
1781 }
1782
1783 pub fn snapshot_into(
1788 &self,
1789 e: &impl KvDev,
1790 snap: &mut CacheSnapshot,
1791 ) -> Result<(), Box<dyn std::error::Error>> {
1792 let n = self.kv.len();
1793 for il in 0..n {
1794 snap.kv_len[il] = self.kv[il].as_ref().map(|kvl| kvl.len);
1795 snap.tp_kv_len[il] = self.tp_kv[il]
1796 .as_ref()
1797 .map(ResidentTpKvCache::committed_len);
1798 if let Some(rl) = &self.recur[il] {
1799 let dc = snap.conv[il]
1800 .as_mut()
1801 .expect("snapshot_into: shape mismatch (conv)");
1802 let ds = snap.ssm[il]
1803 .as_mut()
1804 .expect("snapshot_into: shape mismatch (ssm)");
1805 let (cn, sn) = (rl.conv_state.len(), rl.ssm_state.len());
1806 e.copy_into(dc, 0, &rl.conv_state, cn)?;
1807 e.copy_into(ds, 0, &rl.ssm_state, sn)?;
1808 }
1809 }
1810 snap.pos = self.pos;
1811 Ok(())
1812 }
1813
1814 pub fn rollback(
1822 &mut self,
1823 e: &impl KvDev,
1824 snap: &CacheSnapshot,
1825 accept_len: usize,
1826 ) -> Result<(), Box<dyn std::error::Error>> {
1827 if !self.can_rollback(snap, accept_len) {
1828 return Err(
1829 "SWA ring rewind checkpoint has been lapped; full re-prime required".into(),
1830 );
1831 }
1832 for il in 0..self.kv.len() {
1833 if let (Some(kvl), Some(saved)) = (self.kv[il].as_mut(), snap.kv_len[il]) {
1834 kvl.len = saved + accept_len;
1835 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
1840 }
1841 if let (Some(kvl), Some(saved)) = (self.tp_kv[il].as_mut(), snap.tp_kv_len[il]) {
1842 kvl.rewind_to(saved + accept_len)?;
1843 }
1844 if let Some(rl) = self.recur[il].as_mut() {
1845 if let Some(c) = &snap.conv[il] {
1846 e.copy_into(&mut rl.conv_state, 0, c, c.len())?;
1847 }
1848 if let Some(s) = &snap.ssm[il] {
1849 e.copy_into(&mut rl.ssm_state, 0, s, s.len())?;
1850 }
1851 }
1852 }
1853 self.pos = snap.pos;
1854 Ok(())
1855 }
1856}
1857
1858#[cfg(test)]
1859mod tp_transaction_tests {
1860 use super::{
1861 Cache, KvRingAppend, ResidentTpKvCache, TpKvTransactionState, tp_kv_rank_allocation_shape,
1862 };
1863
1864 fn empty_tp_cache(capacity: usize) -> ResidentTpKvCache {
1865 ResidentTpKvCache::new(Vec::new(), 128, 128, 136, 96, capacity)
1866 }
1867
1868 #[test]
1869 fn step_tp8_rank_allocation_matches_the_official_kv_geometry() {
1870 let shape = tp_kv_rank_allocation_shape(8 * 128, 8 * 128, 8).unwrap();
1871 assert_eq!((shape.kv_dim_k, shape.kv_dim_v), (128, 128));
1872 assert_eq!((shape.k_token_bytes, shape.v_token_bytes), (136, 96));
1873 assert_eq!(shape.bytes_per_token(), 232);
1874 assert_eq!(shape.fixed_bytes, 20);
1875 assert_eq!(shape.allocation_bytes(262_144), 232 * 262_144 + 20);
1876 }
1877
1878 #[test]
1879 fn tp_rank_allocation_refuses_non_divisible_and_non_block_aligned_shards() {
1880 assert!(tp_kv_rank_allocation_shape(1024, 1024, 3).is_err());
1881 assert!(tp_kv_rank_allocation_shape(1024, 1024, 64).is_err());
1882 assert!(tp_kv_rank_allocation_shape(0, 1024, 8).is_err());
1883 }
1884
1885 #[test]
1886 fn partial_commit_publishes_only_the_accepted_prefix() {
1887 let mut state = TpKvTransactionState::new();
1888 let transaction = state.begin().unwrap();
1889 let staged = state.append_target(transaction, 3, 8).unwrap();
1890 state.publish_append(transaction, staged).unwrap();
1891 assert_eq!(state.committed_len, 0);
1892 assert_eq!(state.staged_len, 3);
1893
1894 let committed = state.commit_target(transaction, 2).unwrap();
1895 state.publish_finalize(transaction, committed).unwrap();
1896 assert_eq!(state.committed_len, 2);
1897 assert_eq!(state.staged_len, 2);
1898 assert!(state.active.is_none());
1899 assert!(state.validate(transaction).is_err());
1900 }
1901
1902 #[test]
1903 fn rollback_restores_the_committed_boundary() {
1904 let mut state = TpKvTransactionState::new();
1905 let first = state.begin().unwrap();
1906 let staged = state.append_target(first, 1, 8).unwrap();
1907 state.publish_append(first, staged).unwrap();
1908 let committed = state.commit_target(first, 1).unwrap();
1909 state.publish_finalize(first, committed).unwrap();
1910
1911 let speculative = state.begin().unwrap();
1912 let staged = state.append_target(speculative, 2, 8).unwrap();
1913 state.publish_append(speculative, staged).unwrap();
1914 assert_eq!(state.committed_len, 1);
1915 assert_eq!(state.staged_len, 3);
1916 state
1917 .publish_finalize(speculative, speculative.base_len)
1918 .unwrap();
1919 assert_eq!(state.committed_len, 1);
1920 assert_eq!(state.staged_len, 1);
1921 assert!(state.validate(speculative).is_err());
1922 }
1923
1924 #[test]
1925 fn rejects_nested_stale_and_out_of_range_actions() {
1926 let mut state = TpKvTransactionState::new();
1927 let transaction = state.begin().unwrap();
1928 assert!(state.begin().is_err());
1929 assert!(state.append_target(transaction, 0, 2).is_err());
1930 assert!(state.append_target(transaction, 3, 2).is_err());
1931 let staged = state.append_target(transaction, 2, 2).unwrap();
1932 state.publish_append(transaction, staged).unwrap();
1933 assert!(state.commit_target(transaction, 3).is_err());
1934 state.publish_finalize(transaction, 0).unwrap();
1935 assert!(state.publish_append(transaction, 1).is_err());
1936 }
1937
1938 #[test]
1939 fn rewind_resets_visibility_and_invalidates_an_active_transaction() {
1940 let mut state = TpKvTransactionState::new();
1941 let transaction = state.begin().unwrap();
1942 let staged = state.append_target(transaction, 3, 8).unwrap();
1943 state.publish_append(transaction, staged).unwrap();
1944 state.rewind(1, 8).unwrap();
1945 assert_eq!(state.committed_len, 1);
1946 assert_eq!(state.staged_len, 1);
1947 assert!(state.active.is_none());
1948 assert!(state.validate(transaction).is_err());
1949 assert!(state.rewind(9, 8).is_err());
1950 }
1951
1952 #[test]
1953 fn grow_preserves_generation_and_publishes_only_the_checkpoint_prefix() {
1954 let mut source = empty_tp_cache(8);
1955 let first = source.begin_transaction().unwrap();
1956 let staged = source.append_target(first, 5).unwrap();
1957 source.publish_append(first, staged).unwrap();
1958 let committed = source.commit_target(first, 5).unwrap();
1959 source.publish_finalize(first, committed).unwrap();
1960
1961 let rolled_back = source.begin_transaction().unwrap();
1962 source
1963 .publish_finalize(rolled_back, rolled_back.base_len())
1964 .unwrap();
1965 let plan = source.prepare_grow(16, 3).unwrap();
1966 assert_eq!(plan.rows(), 3);
1967 assert_eq!(plan.k_bytes(), 3 * 136);
1968 assert_eq!(plan.v_bytes(), 3 * 96);
1969
1970 let mut target = empty_tp_cache(16);
1971 target.publish_grow(plan).unwrap();
1972 assert_eq!(target.committed_len(), 3);
1973 assert_eq!(target.staged_len(), 3);
1974 assert_eq!(target.capacity(), 16);
1975 let next = target.begin_transaction().unwrap();
1976 assert_eq!(next.generation(), rolled_back.generation() + 1);
1977 assert_eq!(next.base_len(), 3);
1978 }
1979
1980 #[test]
1981 fn grow_refuses_active_source_and_invalid_target_state_or_layout() {
1982 let mut active = empty_tp_cache(8);
1983 active.begin_transaction().unwrap();
1984 assert!(active.prepare_grow(16, 0).is_err());
1985
1986 let mut source = empty_tp_cache(8);
1987 source.rewind_to(5).unwrap();
1988 assert!(source.prepare_grow(8, 5).is_err());
1989 assert!(source.prepare_grow(16, 6).is_err());
1990 let plan = source.prepare_grow(16, 4).unwrap();
1991
1992 let mut wrong_layout = ResidentTpKvCache::new(Vec::new(), 128, 128, 144, 96, 16);
1993 assert!(wrong_layout.publish_grow(plan).is_err());
1994
1995 let plan = source.prepare_grow(16, 4).unwrap();
1996 let mut dirty_target = empty_tp_cache(16);
1997 dirty_target.rewind_to(1).unwrap();
1998 assert!(dirty_target.publish_grow(plan).is_err());
1999 }
2000
2001 #[test]
2002 fn swa_transaction_rebase_preserves_the_rollback_window() {
2003 let mut cache = ResidentTpKvCache::new_swa(Vec::new(), 128, 128, 136, 96, 10_000, 32);
2004 assert_eq!(cache.physical_capacity(), 32 + 4096 + 512 + 31);
2005 cache.publish_hydration(8762, 4096).unwrap();
2009 assert_eq!(cache.ring_base(), Some(4096));
2010
2011 let transaction = cache.begin_transaction().unwrap();
2012 let plan = cache.prepare_append(transaction, 10).unwrap();
2013 assert_eq!(plan.target(), 8772);
2014 assert_eq!(plan.write_row(), 58);
2015 assert_eq!(
2016 plan.ring_append(),
2017 Some(KvRingAppend::Rebase {
2018 src_row: 4608,
2019 keep_rows: 58,
2020 new_base: 8704,
2021 write_row: 58,
2022 })
2023 );
2024 cache.publish_append_rebase(plan).unwrap();
2025 cache.publish_append_plan(plan).unwrap();
2026 assert_eq!(cache.ring_base(), Some(8704));
2027 assert_eq!(cache.physical_range(8740, 8772).unwrap(), 36..68);
2028
2029 let rollback = cache.commit_target(transaction, 0).unwrap();
2030 cache.publish_finalize(transaction, rollback).unwrap();
2031 assert_eq!((cache.committed_len(), cache.staged_len()), (8762, 8762));
2032 assert!(cache.rewind_to(8200).is_err());
2033 }
2034
2035 #[test]
2036 fn swa_grow_normalizes_only_the_live_prefix_and_preserves_generation() {
2037 let mut source = ResidentTpKvCache::new_swa(Vec::new(), 128, 128, 136, 96, 10_000, 32);
2038 source.publish_hydration(8250, 4096).unwrap();
2039 let transaction = source.begin_transaction().unwrap();
2040 source
2041 .publish_finalize(transaction, transaction.base_len())
2042 .unwrap();
2043
2044 let plan = source.prepare_grow(20_000, 8250).unwrap();
2045 assert_eq!(plan.source_row(), 4096);
2046 assert_eq!(plan.copy_rows(), 58);
2047 assert_eq!(plan.k_bytes(), 58 * 136);
2048 assert_eq!(plan.v_bytes(), 58 * 96);
2049
2050 let mut target = ResidentTpKvCache::new_swa(Vec::new(), 128, 128, 136, 96, 20_000, 32);
2051 target.publish_grow(plan).unwrap();
2052 assert_eq!(target.ring_base(), Some(8192));
2053 assert_eq!((target.committed_len(), target.staged_len()), (8250, 8250));
2054 assert_eq!(target.physical_range(8192, 8250).unwrap(), 0..58);
2055 let next = target.begin_transaction().unwrap();
2056 assert_eq!(next.generation(), transaction.generation() + 1);
2057 }
2058
2059 #[test]
2060 fn cache_reports_a_materialized_distributed_swa_ring() {
2061 let mut cache = Cache {
2062 kv: Vec::new(),
2063 recur: Vec::new(),
2064 tp_kv: vec![None],
2065 pos: 0,
2066 max_ctx: 10_000,
2067 dflash_taps: None,
2068 last_logits_dev: None,
2069 };
2070 assert!(!cache.has_swa_ring());
2071 cache.tp_kv[0] = Some(ResidentTpKvCache::new_swa(
2072 Vec::new(),
2073 128,
2074 128,
2075 136,
2076 96,
2077 10_000,
2078 512,
2079 ));
2080 assert!(cache.has_swa_ring());
2081 }
2082}
2083
2084#[cfg(test)]
2085mod swa_ring_tests {
2086 use super::{
2087 KvRing, KvRingAppend, PRIME_CHUNK_MAX_TOKENS, SWA_REWIND_SLACK_ROWS,
2088 SWA_VIEW_ALIGNMENT_ROWS, kv_plane_allocation_bytes, swa_retain_from, swa_ring_rows,
2089 };
2090
2091 #[test]
2092 fn allocation_rows_cover_window_max_prime_and_alignment_slack() {
2093 assert_eq!(swa_ring_rows(512, 262_144), 512 + 4096 + 512 + 31);
2094 assert_eq!(swa_ring_rows(512, 4096), 4096);
2095 assert_eq!(
2096 kv_plane_allocation_bytes(5151, 1088),
2097 5151 * 1088 + 8,
2098 "the Step35 session plane allocates ring rows plus the existing tail pad",
2099 );
2100 }
2101
2102 #[test]
2115 fn retain_grants_rewind_slack_but_never_asks_below_base() {
2116 const WINDOW: usize = 512;
2117 let rows = swa_ring_rows(WINDOW, 262_144);
2118 let len = rows;
2119 let aligned = |pos: usize| (pos - (WINDOW - 1)) & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
2120
2121 let retain = swa_retain_from(len, WINDOW, 0);
2124 assert!(retain <= aligned(len) - SWA_REWIND_SLACK_ROWS);
2125 let mut ring = KvRing::new(rows, WINDOW);
2126 ring.apply_rebase(retain);
2127 assert!(
2128 ring.can_rewind_to(len - 1),
2129 "a one-token rewind must survive the rebase"
2130 );
2131 assert!(ring.can_rewind_to(len - SWA_REWIND_SLACK_ROWS));
2132
2133 assert!(len - retain + PRIME_CHUNK_MAX_TOKENS <= rows);
2135
2136 for depth in [1usize, 32, 256, SWA_REWIND_SLACK_ROWS] {
2140 assert!(
2141 ring.can_rewind_to(len - depth),
2142 "a {depth}-row rewind must be resident, not clamped away",
2143 );
2144 }
2145
2146 let base = ring.base();
2153 for depth in [1usize, 32, 256, SWA_REWIND_SLACK_ROWS] {
2154 let window_start = (len - depth - (WINDOW - 1)) & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
2155 assert!(
2156 window_start >= base,
2157 "after a {depth}-row rewind the window starts at {window_start}, below base \
2158 {base} — clamping here would serve rows the ring no longer holds (the pos-8661 \
2159 all-NaN case)",
2160 );
2161 assert!(swa_retain_from(len - depth, WINDOW, base) >= base);
2162 }
2163
2164 let past = len - (SWA_REWIND_SLACK_ROWS + WINDOW);
2167 let past_start = (past - (WINDOW - 1)) & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
2168 assert!(
2169 past_start < base,
2170 "beyond the headroom the window must fall below base"
2171 );
2172 assert!(
2173 !ring.can_rewind_to(past),
2174 "and can_rewind_to must refuse it"
2175 );
2176 }
2177
2178 #[test]
2179 fn ring_matches_flat_bytes_before_wrap() {
2180 let ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
2181 let flat: Vec<u32> = (0..1024).collect();
2182 let mut physical = vec![u32::MAX; ring.rows()];
2183 let KvRingAppend::Contiguous { write_row } = ring.append_plan(0, 0, flat.len()).unwrap()
2184 else {
2185 panic!("first append unexpectedly wrapped")
2186 };
2187 physical[write_row..write_row + flat.len()].copy_from_slice(&flat);
2188 let view = ring.physical_range(0, flat.len()).unwrap();
2189 assert_eq!(&physical[view], flat.as_slice());
2190 }
2191
2192 #[test]
2193 fn wrap_rebases_the_exact_aligned_prime_view() {
2194 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
2195 let flat: Vec<u32> = (0..8192).collect();
2196 let mut physical = vec![u32::MAX; ring.rows()];
2197 let KvRingAppend::Contiguous { write_row } = ring.append_plan(0, 0, 4096).unwrap() else {
2198 panic!("first prime chunk unexpectedly wrapped")
2199 };
2200 physical[write_row..write_row + 4096].copy_from_slice(&flat[..4096]);
2201
2202 let off = (4096usize - (512 - 1)) & !31usize;
2203 let KvRingAppend::Rebase {
2204 src_row,
2205 keep_rows,
2206 new_base,
2207 write_row,
2208 } = ring.append_plan(4096, off, 4096).unwrap()
2209 else {
2210 panic!("second prime chunk did not wrap")
2211 };
2212 let retained = physical[src_row..src_row + keep_rows].to_vec();
2213 physical[..keep_rows].copy_from_slice(&retained);
2214 ring.apply_rebase(new_base);
2215 physical[write_row..write_row + 4096].copy_from_slice(&flat[4096..8192]);
2216
2217 let view = ring.physical_range(off, 8192).unwrap();
2218 assert_eq!(&physical[view], &flat[off..8192]);
2219 assert_eq!(ring.base(), off);
2220 }
2221
2222 #[test]
2223 fn rewind_declines_once_the_required_window_was_lapped() {
2224 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
2225 let KvRingAppend::Rebase { new_base, .. } = ring.append_plan(4096, 3584, 4096).unwrap()
2226 else {
2227 panic!("expected wrap")
2228 };
2229 ring.apply_rebase(new_base);
2230 assert!(ring.can_rewind_to(4095));
2231 assert!(!ring.can_rewind_to(4094));
2232 assert!(!ring.can_rewind_to(0));
2233 }
2234
2235 #[test]
2240 fn restore_plan_copies_the_window_not_the_absolute_length() {
2241 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
2242 let (base, phys) = ring.restore_plan(400).unwrap();
2244 assert_eq!((base, phys), (0, 0..400));
2245
2246 let mut live = 0usize;
2249 while live < 40_960 {
2250 let retain = swa_retain_from(live, 512, ring.base());
2251 if let KvRingAppend::Rebase { new_base, .. } =
2252 ring.append_plan(live, retain, 4096).unwrap()
2253 {
2254 ring.apply_rebase(new_base);
2255 }
2256 live += 4096;
2257 }
2258 assert!(ring.base() > 0, "a 40k walk must have lapped the ring");
2259 let (base, phys) = ring.restore_plan(live).unwrap();
2260 assert_eq!(base, (live - (512 - 1)) & !31usize);
2261 assert!(
2262 base >= ring.base(),
2263 "the plan must stay above the ring floor"
2264 );
2265 assert_eq!(phys.len(), live - base);
2266 assert!(
2267 phys.end <= ring.rows(),
2268 "the copy must fit the physical buffer ({} rows), got {:?}",
2269 ring.rows(),
2270 phys
2271 );
2272
2273 assert!(ring.restore_plan(400).is_err());
2275 }
2276}