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 pub base: usize,
466}
467
468pub struct CacheSnapshot {
475 pub kv_len: Vec<Option<usize>>, pub conv: Vec<Option<CudaSlice<f32>>>, pub ssm: Vec<Option<CudaSlice<f32>>>,
478 pub pos: usize,
479}
480
481impl Cache {
482 pub fn new(
484 e: &impl KvDev,
485 cfg: &ModelConfig,
486 max_ctx: usize,
487 ) -> Result<Self, Box<dyn std::error::Error>> {
488 Self::new_inner(&|_| e, cfg, max_ctx)
489 }
490
491 pub fn new_pp2(
496 dev0: &dyn KvDev,
497 dev1: &dyn KvDev,
498 split: usize,
499 cfg: &ModelConfig,
500 max_ctx: usize,
501 ) -> Result<Self, Box<dyn std::error::Error>> {
502 Self::new_inner(&|il| if il < split { dev0 } else { dev1 }, cfg, max_ctx)
503 }
504
505 pub fn new_ppn<'a>(
511 devs: &[&'a dyn KvDev],
512 fence: &[usize],
513 cfg: &ModelConfig,
514 max_ctx: usize,
515 ) -> Result<Self, Box<dyn std::error::Error>> {
516 assert_eq!(
517 devs.len() + 1,
518 fence.len(),
519 "ppn cache: devs vs fence mismatch"
520 );
521 let pick = |il: usize| -> &dyn KvDev {
522 let s = match fence[1..fence.len() - 1].binary_search(&il) {
523 Ok(k) => k + 1,
524 Err(k) => k,
525 };
526 devs[s.min(devs.len() - 1)]
527 };
528 Self::new_inner(&pick, cfg, max_ctx)
529 }
530
531 fn new_inner<'a>(
534 pick: &dyn Fn(usize) -> &'a dyn KvDev,
535 cfg: &ModelConfig,
536 max_ctx: usize,
537 ) -> Result<Self, Box<dyn std::error::Error>> {
538 let n = cfg.n_layer as usize;
539 let mut kv = Vec::with_capacity(n);
540 let mut recur = Vec::with_capacity(n);
541 let head_dim_k = cfg.head_dim_k as usize;
542 let head_dim_v = cfg.head_dim_v as usize;
543 assert!(
544 head_dim_k % 32 == 0 && head_dim_v % 32 == 0,
545 "KVQUANT requires head_dim_k%32==0 && head_dim_v%32==0 (got k={head_dim_k} v={head_dim_v})"
546 );
547 let (conv_dim, d_state, num_v, d_conv) = if let Some(s) = &cfg.ssm {
548 let num_k = s.group_count as usize;
549 let num_v = s.time_step_rank as usize;
550 let ds = s.state_size as usize;
551 (
552 ds * num_k * 2 + ds * num_v,
553 ds,
554 num_v,
555 s.conv_kernel as usize,
556 )
557 } else {
558 (0, 0, 0, 0)
559 };
560 for il in 0..cfg.n_layer {
561 let e = pick(il as usize);
563 let g4_shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
568 if g4_shared > 0 && il >= cfg.n_layer - g4_shared {
569 kv.push(None);
570 recur.push(None);
571 continue;
572 }
573 match cfg.layer_kind(il) {
574 LayerKind::FullAttention => {
575 let (kv_dim_k, kv_dim_v, kbb_l, vbb_l) = full_attention_kv_layout(cfg, il);
578 let k_tok_bytes = (kv_dim_k / 32) * kbb_l;
579 let v_tok_bytes = (kv_dim_v / 32) * vbb_l;
580 let ring = if swa_ring_on() && cfg.arch.is_step35() {
581 cfg.layer_geometry(il)
582 .and_then(|geometry| geometry.window)
583 .map(|window| {
584 let window = window as usize;
585 KvRing::new(swa_ring_rows(window, max_ctx), window)
586 })
587 } else {
588 None
589 };
590 let alloc_rows = ring.as_ref().map(KvRing::rows).unwrap_or(max_ctx);
591 kv.push(Some(KvLayer {
592 k: e.alloc_u8(kv_plane_allocation_bytes(alloc_rows, k_tok_bytes))?,
596 v: e.alloc_u8(kv_plane_allocation_bytes(alloc_rows, v_tok_bytes))?,
597 kv_dim_k,
598 kv_dim_v,
599 k_tok_bytes,
600 v_tok_bytes,
601 len: 0,
602 ring,
603 len_d: e.htod_i32(&[0])?,
604 }));
605 recur.push(None);
606 }
607 LayerKind::LinearAttention => {
608 kv.push(None);
609 recur.push(Some(RecurLayer {
610 conv_state: e.zeros(conv_dim * (d_conv - 1))?,
611 ssm_state: e.zeros(d_state * d_state * num_v)?,
612 ssm_state_alt: e.zeros(d_state * d_state * num_v)?,
613 }));
614 }
615 }
616 }
617 Ok(Cache {
618 kv,
619 recur,
620 pos: 0,
621 max_ctx,
622 dflash_taps: None,
623 last_logits_dev: None,
624 })
625 }
626
627 pub fn has_swa_ring(&self) -> bool {
628 self.kv.iter().flatten().any(|layer| layer.ring.is_some())
629 }
630
631 pub fn can_rollback(&self, snap: &CacheSnapshot, accept_len: usize) -> bool {
632 self.kv
633 .iter()
634 .zip(&snap.kv_len)
635 .all(|(layer, saved)| match (layer, saved) {
636 (Some(layer), Some(saved)) => layer
637 .ring
638 .as_ref()
639 .is_none_or(|ring| ring.can_rewind_to(saved + accept_len)),
640 _ => true,
641 })
642 }
643
644 pub fn snapshot(&self, e: &impl KvDev) -> Result<CacheSnapshot, Box<dyn std::error::Error>> {
648 let n = self.kv.len();
649 let mut kv_len = Vec::with_capacity(n);
650 let mut conv = Vec::with_capacity(n);
651 let mut ssm = Vec::with_capacity(n);
652 for il in 0..n {
653 match &self.kv[il] {
654 Some(kvl) => kv_len.push(Some(kvl.len)),
655 None => kv_len.push(None),
656 }
657 match &self.recur[il] {
658 Some(rl) => {
659 conv.push(Some(e.clone_dtod(&rl.conv_state)?));
660 ssm.push(Some(e.clone_dtod(&rl.ssm_state)?));
661 }
662 None => {
663 conv.push(None);
664 ssm.push(None);
665 }
666 }
667 }
668 Ok(CacheSnapshot {
669 kv_len,
670 conv,
671 ssm,
672 pos: self.pos,
673 })
674 }
675
676 pub fn snapshot_into(
681 &self,
682 e: &impl KvDev,
683 snap: &mut CacheSnapshot,
684 ) -> Result<(), Box<dyn std::error::Error>> {
685 let n = self.kv.len();
686 for il in 0..n {
687 snap.kv_len[il] = self.kv[il].as_ref().map(|kvl| kvl.len);
688 if let Some(rl) = &self.recur[il] {
689 let dc = snap.conv[il]
690 .as_mut()
691 .expect("snapshot_into: shape mismatch (conv)");
692 let ds = snap.ssm[il]
693 .as_mut()
694 .expect("snapshot_into: shape mismatch (ssm)");
695 let (cn, sn) = (rl.conv_state.len(), rl.ssm_state.len());
696 e.copy_into(dc, 0, &rl.conv_state, cn)?;
697 e.copy_into(ds, 0, &rl.ssm_state, sn)?;
698 }
699 }
700 snap.pos = self.pos;
701 Ok(())
702 }
703
704 pub fn rollback(
712 &mut self,
713 e: &impl KvDev,
714 snap: &CacheSnapshot,
715 accept_len: usize,
716 ) -> Result<(), Box<dyn std::error::Error>> {
717 if !self.can_rollback(snap, accept_len) {
718 return Err(
719 "SWA ring rewind checkpoint has been lapped; full re-prime required".into(),
720 );
721 }
722 for il in 0..self.kv.len() {
723 if let (Some(kvl), Some(saved)) = (self.kv[il].as_mut(), snap.kv_len[il]) {
724 kvl.len = saved + accept_len;
725 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
730 }
731 if let Some(rl) = self.recur[il].as_mut() {
732 if let Some(c) = &snap.conv[il] {
733 e.copy_into(&mut rl.conv_state, 0, c, c.len())?;
734 }
735 if let Some(s) = &snap.ssm[il] {
736 e.copy_into(&mut rl.ssm_state, 0, s, s.len())?;
737 }
738 }
739 }
740 self.pos = snap.pos;
741 Ok(())
742 }
743}
744
745#[cfg(test)]
746mod swa_ring_tests {
747 use super::{KvRing, KvRingAppend, kv_plane_allocation_bytes, swa_ring_rows};
748
749 #[test]
750 fn allocation_rows_cover_window_max_prime_and_alignment_slack() {
751 assert_eq!(swa_ring_rows(512, 262_144), 512 + 4096 + 31);
752 assert_eq!(swa_ring_rows(512, 4096), 4096);
753 assert_eq!(
754 kv_plane_allocation_bytes(4639, 1088),
755 4639 * 1088 + 8,
756 "the Step35 session plane allocates ring rows plus the existing tail pad",
757 );
758 }
759
760 #[test]
761 fn ring_matches_flat_bytes_before_wrap() {
762 let ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
763 let flat: Vec<u32> = (0..1024).collect();
764 let mut physical = vec![u32::MAX; ring.rows()];
765 let KvRingAppend::Contiguous { write_row } = ring.append_plan(0, 0, flat.len()).unwrap()
766 else {
767 panic!("first append unexpectedly wrapped")
768 };
769 physical[write_row..write_row + flat.len()].copy_from_slice(&flat);
770 let view = ring.physical_range(0, flat.len()).unwrap();
771 assert_eq!(&physical[view], flat.as_slice());
772 }
773
774 #[test]
775 fn wrap_rebases_the_exact_aligned_prime_view() {
776 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
777 let flat: Vec<u32> = (0..8192).collect();
778 let mut physical = vec![u32::MAX; ring.rows()];
779 let KvRingAppend::Contiguous { write_row } = ring.append_plan(0, 0, 4096).unwrap() else {
780 panic!("first prime chunk unexpectedly wrapped")
781 };
782 physical[write_row..write_row + 4096].copy_from_slice(&flat[..4096]);
783
784 let off = (4096usize - (512 - 1)) & !31usize;
785 let KvRingAppend::Rebase {
786 src_row,
787 keep_rows,
788 new_base,
789 write_row,
790 } = ring.append_plan(4096, off, 4096).unwrap()
791 else {
792 panic!("second prime chunk did not wrap")
793 };
794 let retained = physical[src_row..src_row + keep_rows].to_vec();
795 physical[..keep_rows].copy_from_slice(&retained);
796 ring.apply_rebase(new_base);
797 physical[write_row..write_row + 4096].copy_from_slice(&flat[4096..8192]);
798
799 let view = ring.physical_range(off, 8192).unwrap();
800 assert_eq!(&physical[view], &flat[off..8192]);
801 assert_eq!(ring.base(), off);
802 }
803
804 #[test]
805 fn rewind_declines_once_the_required_window_was_lapped() {
806 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
807 let KvRingAppend::Rebase { new_base, .. } = ring.append_plan(4096, 3584, 4096).unwrap()
808 else {
809 panic!("expected wrap")
810 };
811 ring.apply_rebase(new_base);
812 assert!(ring.can_rewind_to(4095));
813 assert!(!ring.can_rewind_to(4094));
814 assert!(!ring.can_rewind_to(0));
815 }
816}