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
50pub fn gkv_on() -> bool {
53 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
54 *ON.get_or_init(|| {
55 std::env::var("MEMRA_GEMMA_GKV")
56 .map(|v| v != "0")
57 .unwrap_or(true)
58 })
59}
60
61pub fn wkv_on() -> bool {
65 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
66 *ON.get_or_init(|| {
67 std::env::var("MEMRA_GEMMA_WKV")
68 .map(|v| v != "0")
69 .unwrap_or_else(|_| std::env::var("MEMRA_DRAFT").is_err())
70 })
71}
72
73pub static KV_FP8_FORCE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
79
80pub fn kv_fp8_on() -> bool {
84 static ENV: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
85 if let Some(v) = *ENV.get_or_init(|| std::env::var("MEMRA_KV_FP8").ok().map(|v| v == "1")) {
86 return v;
87 }
88 matches!(KV_FP8_FORCE.load(std::sync::atomic::Ordering::Relaxed), 1)
89}
90
91pub fn swa_ring_on() -> bool {
94 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
95 *ON.get_or_init(|| std::env::var("MEMRA_SWA_RING").as_deref() == Ok("1"))
96}
97
98pub const PRIME_CHUNK_MAX_TOKENS: usize = 4096;
101const SWA_VIEW_ALIGNMENT_ROWS: usize = 32;
102
103pub fn swa_ring_rows(window: usize, max_ctx: usize) -> usize {
106 max_ctx.min(window + PRIME_CHUNK_MAX_TOKENS + (SWA_VIEW_ALIGNMENT_ROWS - 1))
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct KvRing {
111 rows: usize,
112 window: usize,
113 base: usize,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum KvRingAppend {
118 Contiguous {
119 write_row: usize,
120 },
121 Rebase {
122 src_row: usize,
123 keep_rows: usize,
124 new_base: usize,
125 write_row: usize,
126 },
127}
128
129impl KvRing {
130 pub fn new(rows: usize, window: usize) -> Self {
131 assert!(window > 0 && rows > 0, "invalid SWA ring geometry");
132 Self {
133 rows,
134 window,
135 base: 0,
136 }
137 }
138
139 pub fn rows(&self) -> usize {
140 self.rows
141 }
142 pub fn base(&self) -> usize {
143 self.base
144 }
145 pub fn window(&self) -> usize {
146 self.window
147 }
148
149 pub fn append_plan(
152 &self,
153 len: usize,
154 retain_from: usize,
155 append_rows: usize,
156 ) -> Result<KvRingAppend, String> {
157 if len < self.base || retain_from < self.base || retain_from > len {
158 return Err(format!(
159 "SWA ring lapped required rows (base {}, retain {retain_from}, len {len})",
160 self.base
161 ));
162 }
163 let used = len - self.base;
164 if used > self.rows {
165 return Err(format!(
166 "SWA ring state exceeds capacity ({used} > {})",
167 self.rows
168 ));
169 }
170 if used.saturating_add(append_rows) <= self.rows {
171 return Ok(KvRingAppend::Contiguous {
172 write_row: used % self.rows,
173 });
174 }
175
176 let keep_rows = len - retain_from;
177 if keep_rows.saturating_add(append_rows) > self.rows {
178 return Err(format!(
179 "SWA ring append does not fit (keep {keep_rows} + append {append_rows} > {})",
180 self.rows
181 ));
182 }
183 Ok(KvRingAppend::Rebase {
184 src_row: retain_from - self.base,
185 keep_rows,
186 new_base: retain_from,
187 write_row: keep_rows,
188 })
189 }
190
191 pub fn apply_rebase(&mut self, new_base: usize) {
192 debug_assert!(new_base >= self.base);
193 self.base = new_base;
194 }
195
196 pub fn physical_range(
197 &self,
198 start: usize,
199 end: usize,
200 ) -> Result<std::ops::Range<usize>, String> {
201 if start < self.base || end < start || end - self.base > self.rows {
202 return Err(format!(
203 "SWA ring view [{start},{end}) is outside resident [{},{})",
204 self.base,
205 self.base + self.rows
206 ));
207 }
208 let start_row = (start - self.base) % self.rows;
209 let len = end - start;
210 debug_assert!(
211 start_row + len <= self.rows,
212 "ring view must be contiguous after rebase"
213 );
214 Ok(start_row..start_row + len)
215 }
216
217 pub fn can_rewind_to(&self, len: usize) -> bool {
219 let raw = len.saturating_sub(self.window - 1);
220 let view_start = raw & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
221 view_start >= self.base
222 }
223}
224
225pub trait KvDev {
230 fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
231 fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
232 fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>>;
233 fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>>;
234 fn clone_dtod(
235 &self,
236 src: &CudaSlice<f32>,
237 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
238 fn copy_into(
239 &self,
240 dst: &mut CudaSlice<f32>,
241 off: usize,
242 src: &CudaSlice<f32>,
243 len: usize,
244 ) -> Result<(), Box<dyn std::error::Error>>;
245 fn set_i32_one(&self, d: &mut CudaSlice<i32>, v: i32)
246 -> Result<(), Box<dyn std::error::Error>>;
247}
248
249use cudarc::driver::CudaSlice;
250use memra_gguf::config::{LayerKind, ModelConfig};
251
252pub struct KvLayer {
257 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,
264 pub ring: Option<KvRing>,
267 pub len_d: CudaSlice<i32>,
271}
272
273impl KvLayer {
274 pub fn physical_rows(
275 &self,
276 start: usize,
277 end: usize,
278 ) -> Result<std::ops::Range<usize>, String> {
279 match &self.ring {
280 Some(ring) => ring.physical_range(start, end),
281 None => Ok(start..end),
282 }
283 }
284}
285
286pub struct RecurLayer {
290 pub conv_state: CudaSlice<f32>, pub ssm_state: CudaSlice<f32>, pub ssm_state_alt: CudaSlice<f32>,
302}
303
304pub struct Cache {
305 pub kv: Vec<Option<KvLayer>>,
306 pub recur: Vec<Option<RecurLayer>>,
307 pub pos: usize,
308 pub max_ctx: usize,
309 pub last_logits_dev: Option<CudaSlice<f32>>,
317 pub dflash_taps: Option<DflashTapSink>,
322}
323
324fn full_attention_kv_layout(cfg: &ModelConfig, il: u32) -> (usize, usize, usize, usize) {
328 debug_assert_eq!(cfg.layer_kind(il), LayerKind::FullAttention);
329 let n_head_kv = cfg.n_head_kv as usize;
330 let (kv_dim_k, kv_dim_v) = match &cfg.gemma4 {
331 Some(g) => {
332 let hd = if g.swa_pattern[il as usize] {
333 g.key_length_swa
334 } else {
335 g.key_length_global
336 } as usize;
337 let d = match g.head_count_kv.get(il as usize) {
346 Some(n) => hd * *n as usize,
347 None => hd * n_head_kv,
348 };
349 (d, d)
350 }
351 None => (
352 cfg.head_dim_k as usize * n_head_kv,
353 cfg.head_dim_v as usize * n_head_kv,
354 ),
355 };
356 assert!(
357 kv_dim_k % 32 == 0 && kv_dim_v % 32 == 0,
358 "KVQUANT requires per-layer kv_dim_k%32==0 && kv_dim_v%32==0 \
359 (layer {il}: k={kv_dim_k} v={kv_dim_v})"
360 );
361 let (kbb, vbb) = kv_blk_bytes();
362 let g4_global_fp8 = gkv_on()
363 && cfg
364 .gemma4
365 .as_ref()
366 .is_some_and(|g| !g.swa_pattern[il as usize]);
367 let g4_windowed_fp8 = wkv_on()
368 && cfg
369 .gemma4
370 .as_ref()
371 .is_some_and(|g| g.swa_pattern[il as usize]);
372 let qwen_fp8 = kv_fp8_on() && cfg.gemma4.is_none();
373 let (kbb_l, vbb_l) = if g4_global_fp8 || g4_windowed_fp8 || qwen_fp8 {
374 (32, 32)
375 } else {
376 (kbb, vbb)
377 };
378 (kv_dim_k, kv_dim_v, kbb_l, vbb_l)
379}
380
381fn kv_plane_allocation_bytes(rows: usize, token_bytes: usize) -> usize {
382 rows * token_bytes + 8
383}
384
385pub fn cache_bytes_per_token(cfg: &ModelConfig) -> usize {
392 cache_bytes_per_token_for_layers(cfg, 0, cfg.n_layer as usize)
393}
394
395pub fn cache_bytes_per_token_for_layers(cfg: &ModelConfig, lo: usize, hi: usize) -> usize {
399 assert!(
400 lo <= hi && hi <= cfg.n_layer as usize,
401 "cache layer range out of bounds"
402 );
403 let shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
404 (lo as u32..hi as u32)
405 .filter(|&il| cfg.layer_kind(il) == LayerKind::FullAttention)
406 .filter(|&il| shared == 0 || il < cfg.n_layer - shared)
407 .map(|il| {
408 let (kv_dim_k, kv_dim_v, kbb, vbb) = full_attention_kv_layout(cfg, il);
409 (kv_dim_k / 32) * kbb + (kv_dim_v / 32) * vbb
410 })
411 .sum()
412}
413
414pub fn cache_ring_bytes_per_token(cfg: &ModelConfig) -> usize {
417 cache_ring_bytes_per_token_for_layers(cfg, 0, cfg.n_layer as usize)
418}
419
420pub fn cache_ring_bytes_per_token_for_layers(cfg: &ModelConfig, lo: usize, hi: usize) -> usize {
422 assert!(
423 lo <= hi && hi <= cfg.n_layer as usize,
424 "cache layer range out of bounds"
425 );
426 if !swa_ring_on() || !cfg.arch.is_step35() {
427 return 0;
428 }
429 let shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
430 (lo as u32..hi as u32)
431 .filter(|&il| cfg.layer_kind(il) == LayerKind::FullAttention)
432 .filter(|&il| shared == 0 || il < cfg.n_layer - shared)
433 .filter(|&il| {
434 cfg.layer_geometry(il)
435 .is_some_and(|geometry| geometry.window.is_some())
436 })
437 .map(|il| {
438 let (kv_dim_k, kv_dim_v, kbb, vbb) = full_attention_kv_layout(cfg, il);
439 (kv_dim_k / 32) * kbb + (kv_dim_v / 32) * vbb
440 })
441 .sum()
442}
443
444pub fn cache_ring_row_cap(cfg: &ModelConfig) -> usize {
446 if !swa_ring_on() || !cfg.arch.is_step35() {
447 return 0;
448 }
449 cfg.geometry
450 .as_ref()
451 .and_then(|table| table.classes().iter().find_map(|geometry| geometry.window))
452 .map(|window| swa_ring_rows(window as usize, usize::MAX))
453 .unwrap_or(0)
454}
455
456pub struct DflashTapSink {
459 pub layer_ids: Vec<usize>,
460 pub buf: CudaSlice<f32>,
461 pub hidden: usize,
462 pub t: usize,
463}
464
465pub struct CacheSnapshot {
472 pub kv_len: Vec<Option<usize>>, pub conv: Vec<Option<CudaSlice<f32>>>, pub ssm: Vec<Option<CudaSlice<f32>>>,
475 pub pos: usize,
476}
477
478impl Cache {
479 pub fn new(
481 e: &impl KvDev,
482 cfg: &ModelConfig,
483 max_ctx: usize,
484 ) -> Result<Self, Box<dyn std::error::Error>> {
485 Self::new_inner(&|_| e, cfg, max_ctx)
486 }
487
488 pub fn new_pp2(
493 dev0: &dyn KvDev,
494 dev1: &dyn KvDev,
495 split: usize,
496 cfg: &ModelConfig,
497 max_ctx: usize,
498 ) -> Result<Self, Box<dyn std::error::Error>> {
499 Self::new_inner(&|il| if il < split { dev0 } else { dev1 }, cfg, max_ctx)
500 }
501
502 pub fn new_ppn<'a>(
508 devs: &[&'a dyn KvDev],
509 fence: &[usize],
510 cfg: &ModelConfig,
511 max_ctx: usize,
512 ) -> Result<Self, Box<dyn std::error::Error>> {
513 assert_eq!(
514 devs.len() + 1,
515 fence.len(),
516 "ppn cache: devs vs fence mismatch"
517 );
518 let pick = |il: usize| -> &dyn KvDev {
519 let s = match fence[1..fence.len() - 1].binary_search(&il) {
520 Ok(k) => k + 1,
521 Err(k) => k,
522 };
523 devs[s.min(devs.len() - 1)]
524 };
525 Self::new_inner(&pick, cfg, max_ctx)
526 }
527
528 fn new_inner<'a>(
531 pick: &dyn Fn(usize) -> &'a dyn KvDev,
532 cfg: &ModelConfig,
533 max_ctx: usize,
534 ) -> Result<Self, Box<dyn std::error::Error>> {
535 let n = cfg.n_layer as usize;
536 let mut kv = Vec::with_capacity(n);
537 let mut recur = Vec::with_capacity(n);
538 let head_dim_k = cfg.head_dim_k as usize;
539 let head_dim_v = cfg.head_dim_v as usize;
540 assert!(
541 head_dim_k % 32 == 0 && head_dim_v % 32 == 0,
542 "KVQUANT requires head_dim_k%32==0 && head_dim_v%32==0 (got k={head_dim_k} v={head_dim_v})"
543 );
544 let (conv_dim, d_state, num_v, d_conv) = if let Some(s) = &cfg.ssm {
545 let num_k = s.group_count as usize;
546 let num_v = s.time_step_rank as usize;
547 let ds = s.state_size as usize;
548 (
549 ds * num_k * 2 + ds * num_v,
550 ds,
551 num_v,
552 s.conv_kernel as usize,
553 )
554 } else {
555 (0, 0, 0, 0)
556 };
557 for il in 0..cfg.n_layer {
558 let e = pick(il as usize);
560 let g4_shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
565 if g4_shared > 0 && il >= cfg.n_layer - g4_shared {
566 kv.push(None);
567 recur.push(None);
568 continue;
569 }
570 match cfg.layer_kind(il) {
571 LayerKind::FullAttention => {
572 let (kv_dim_k, kv_dim_v, kbb_l, vbb_l) = full_attention_kv_layout(cfg, il);
575 let k_tok_bytes = (kv_dim_k / 32) * kbb_l;
576 let v_tok_bytes = (kv_dim_v / 32) * vbb_l;
577 let ring = if swa_ring_on() && cfg.arch.is_step35() {
578 cfg.layer_geometry(il)
579 .and_then(|geometry| geometry.window)
580 .map(|window| {
581 let window = window as usize;
582 KvRing::new(swa_ring_rows(window, max_ctx), window)
583 })
584 } else {
585 None
586 };
587 let alloc_rows = ring.as_ref().map(KvRing::rows).unwrap_or(max_ctx);
588 kv.push(Some(KvLayer {
589 k: e.alloc_u8(kv_plane_allocation_bytes(alloc_rows, k_tok_bytes))?,
593 v: e.alloc_u8(kv_plane_allocation_bytes(alloc_rows, v_tok_bytes))?,
594 kv_dim_k,
595 kv_dim_v,
596 k_tok_bytes,
597 v_tok_bytes,
598 len: 0,
599 ring,
600 len_d: e.htod_i32(&[0])?,
601 }));
602 recur.push(None);
603 }
604 LayerKind::LinearAttention => {
605 kv.push(None);
606 recur.push(Some(RecurLayer {
607 conv_state: e.zeros(conv_dim * (d_conv - 1))?,
608 ssm_state: e.zeros(d_state * d_state * num_v)?,
609 ssm_state_alt: e.zeros(d_state * d_state * num_v)?,
610 }));
611 }
612 }
613 }
614 Ok(Cache {
615 kv,
616 recur,
617 pos: 0,
618 max_ctx,
619 dflash_taps: None,
620 last_logits_dev: None,
621 })
622 }
623
624 pub fn has_swa_ring(&self) -> bool {
625 self.kv.iter().flatten().any(|layer| layer.ring.is_some())
626 }
627
628 pub fn can_rollback(&self, snap: &CacheSnapshot, accept_len: usize) -> bool {
629 self.kv
630 .iter()
631 .zip(&snap.kv_len)
632 .all(|(layer, saved)| match (layer, saved) {
633 (Some(layer), Some(saved)) => layer
634 .ring
635 .as_ref()
636 .is_none_or(|ring| ring.can_rewind_to(saved + accept_len)),
637 _ => true,
638 })
639 }
640
641 pub fn snapshot(&self, e: &impl KvDev) -> Result<CacheSnapshot, Box<dyn std::error::Error>> {
645 let n = self.kv.len();
646 let mut kv_len = Vec::with_capacity(n);
647 let mut conv = Vec::with_capacity(n);
648 let mut ssm = Vec::with_capacity(n);
649 for il in 0..n {
650 match &self.kv[il] {
651 Some(kvl) => kv_len.push(Some(kvl.len)),
652 None => kv_len.push(None),
653 }
654 match &self.recur[il] {
655 Some(rl) => {
656 conv.push(Some(e.clone_dtod(&rl.conv_state)?));
657 ssm.push(Some(e.clone_dtod(&rl.ssm_state)?));
658 }
659 None => {
660 conv.push(None);
661 ssm.push(None);
662 }
663 }
664 }
665 Ok(CacheSnapshot {
666 kv_len,
667 conv,
668 ssm,
669 pos: self.pos,
670 })
671 }
672
673 pub fn snapshot_into(
678 &self,
679 e: &impl KvDev,
680 snap: &mut CacheSnapshot,
681 ) -> Result<(), Box<dyn std::error::Error>> {
682 let n = self.kv.len();
683 for il in 0..n {
684 snap.kv_len[il] = self.kv[il].as_ref().map(|kvl| kvl.len);
685 if let Some(rl) = &self.recur[il] {
686 let dc = snap.conv[il]
687 .as_mut()
688 .expect("snapshot_into: shape mismatch (conv)");
689 let ds = snap.ssm[il]
690 .as_mut()
691 .expect("snapshot_into: shape mismatch (ssm)");
692 let (cn, sn) = (rl.conv_state.len(), rl.ssm_state.len());
693 e.copy_into(dc, 0, &rl.conv_state, cn)?;
694 e.copy_into(ds, 0, &rl.ssm_state, sn)?;
695 }
696 }
697 snap.pos = self.pos;
698 Ok(())
699 }
700
701 pub fn rollback(
709 &mut self,
710 e: &impl KvDev,
711 snap: &CacheSnapshot,
712 accept_len: usize,
713 ) -> Result<(), Box<dyn std::error::Error>> {
714 if !self.can_rollback(snap, accept_len) {
715 return Err(
716 "SWA ring rewind checkpoint has been lapped; full re-prime required".into(),
717 );
718 }
719 for il in 0..self.kv.len() {
720 if let (Some(kvl), Some(saved)) = (self.kv[il].as_mut(), snap.kv_len[il]) {
721 kvl.len = saved + accept_len;
722 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
727 }
728 if let Some(rl) = self.recur[il].as_mut() {
729 if let Some(c) = &snap.conv[il] {
730 e.copy_into(&mut rl.conv_state, 0, c, c.len())?;
731 }
732 if let Some(s) = &snap.ssm[il] {
733 e.copy_into(&mut rl.ssm_state, 0, s, s.len())?;
734 }
735 }
736 }
737 self.pos = snap.pos;
738 Ok(())
739 }
740}
741
742#[cfg(test)]
743mod swa_ring_tests {
744 use super::{KvRing, KvRingAppend, kv_plane_allocation_bytes, swa_ring_rows};
745
746 #[test]
747 fn allocation_rows_cover_window_max_prime_and_alignment_slack() {
748 assert_eq!(swa_ring_rows(512, 262_144), 512 + 4096 + 31);
749 assert_eq!(swa_ring_rows(512, 4096), 4096);
750 assert_eq!(
751 kv_plane_allocation_bytes(4639, 1088),
752 4639 * 1088 + 8,
753 "the Step35 session plane allocates ring rows plus the existing tail pad",
754 );
755 }
756
757 #[test]
758 fn ring_matches_flat_bytes_before_wrap() {
759 let ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
760 let flat: Vec<u32> = (0..1024).collect();
761 let mut physical = vec![u32::MAX; ring.rows()];
762 let KvRingAppend::Contiguous { write_row } = ring.append_plan(0, 0, flat.len()).unwrap()
763 else {
764 panic!("first append unexpectedly wrapped")
765 };
766 physical[write_row..write_row + flat.len()].copy_from_slice(&flat);
767 let view = ring.physical_range(0, flat.len()).unwrap();
768 assert_eq!(&physical[view], flat.as_slice());
769 }
770
771 #[test]
772 fn wrap_rebases_the_exact_aligned_prime_view() {
773 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
774 let flat: Vec<u32> = (0..8192).collect();
775 let mut physical = vec![u32::MAX; ring.rows()];
776 let KvRingAppend::Contiguous { write_row } = ring.append_plan(0, 0, 4096).unwrap() else {
777 panic!("first prime chunk unexpectedly wrapped")
778 };
779 physical[write_row..write_row + 4096].copy_from_slice(&flat[..4096]);
780
781 let off = (4096usize - (512 - 1)) & !31usize;
782 let KvRingAppend::Rebase {
783 src_row,
784 keep_rows,
785 new_base,
786 write_row,
787 } = ring.append_plan(4096, off, 4096).unwrap()
788 else {
789 panic!("second prime chunk did not wrap")
790 };
791 let retained = physical[src_row..src_row + keep_rows].to_vec();
792 physical[..keep_rows].copy_from_slice(&retained);
793 ring.apply_rebase(new_base);
794 physical[write_row..write_row + 4096].copy_from_slice(&flat[4096..8192]);
795
796 let view = ring.physical_range(off, 8192).unwrap();
797 assert_eq!(&physical[view], &flat[off..8192]);
798 assert_eq!(ring.base(), off);
799 }
800
801 #[test]
802 fn rewind_declines_once_the_required_window_was_lapped() {
803 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
804 let KvRingAppend::Rebase { new_base, .. } = ring.append_plan(4096, 3584, 4096).unwrap()
805 else {
806 panic!("expected wrap")
807 };
808 ring.apply_rebase(new_base);
809 assert!(ring.can_rewind_to(4095));
810 assert!(!ring.can_rewind_to(4094));
811 assert!(!ring.can_rewind_to(0));
812 }
813}