1pub fn kv_cache_formats() -> (&'static str, &'static str) {
16 static F: std::sync::OnceLock<(&'static str, &'static str)> = std::sync::OnceLock::new();
17 *F.get_or_init(|| {
18 let k = match std::env::var("MEMRA_KV_K").as_deref() {
19 Ok("fp8") => "fp8",
20 Ok("q8_0") | Ok("") | Err(_) => "q8_0",
21 Ok(o) => panic!("MEMRA_KV_K={o} unsupported (q8_0 | fp8)"),
22 };
23 let v = match std::env::var("MEMRA_KV_V").as_deref() {
24 Ok("q4_0") => "q4_0",
25 Ok("fp8") => "fp8",
26 Ok("q5_1") | Ok("") | Err(_) => "q5_1",
27 Ok(o) => panic!("MEMRA_KV_V={o} unsupported (q5_1 | q4_0 | fp8)"),
28 };
29 if (k, v) != ("q8_0", "q5_1") {
30 eprintln!("[memra] KV cache format: K={k} V={v} (non-default — new numeric config)");
31 }
32 (k, v)
33 })
34}
35
36pub fn kv_blk_bytes() -> (usize, usize) {
38 let (k, v) = kv_cache_formats();
39 let kb = match k { "fp8" => 32, _ => 34 };
40 let vb = match v { "q4_0" => 18, "fp8" => 32, _ => 24 };
41 (kb, vb)
42}
43
44pub fn gkv_on() -> bool {
47 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
48 *ON.get_or_init(|| std::env::var("MEMRA_GEMMA_GKV").map(|v| v != "0").unwrap_or(true))
49}
50
51pub fn wkv_on() -> bool {
55 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
56 *ON.get_or_init(|| std::env::var("MEMRA_GEMMA_WKV").map(|v| v != "0")
57 .unwrap_or_else(|_| std::env::var("MEMRA_DRAFT").is_err()))
58}
59
60pub static KV_FP8_FORCE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
66
67pub fn kv_fp8_on() -> bool {
71 static ENV: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
72 if let Some(v) = *ENV.get_or_init(|| std::env::var("MEMRA_KV_FP8").ok()
73 .map(|v| v == "1")) { return v; }
74 matches!(KV_FP8_FORCE.load(std::sync::atomic::Ordering::Relaxed), 1)
75}
76
77pub fn swa_ring_on() -> bool {
80 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
81 *ON.get_or_init(|| std::env::var("MEMRA_SWA_RING").as_deref() == Ok("1"))
82}
83
84pub const PRIME_CHUNK_MAX_TOKENS: usize = 4096;
87const SWA_VIEW_ALIGNMENT_ROWS: usize = 32;
88
89pub fn swa_ring_rows(window: usize, max_ctx: usize) -> usize {
92 max_ctx.min(window + PRIME_CHUNK_MAX_TOKENS + (SWA_VIEW_ALIGNMENT_ROWS - 1))
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub struct KvRing {
97 rows: usize,
98 window: usize,
99 base: usize,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum KvRingAppend {
104 Contiguous {
105 write_row: usize,
106 },
107 Rebase {
108 src_row: usize,
109 keep_rows: usize,
110 new_base: usize,
111 write_row: usize,
112 },
113}
114
115impl KvRing {
116 pub fn new(rows: usize, window: usize) -> Self {
117 assert!(window > 0 && rows > 0, "invalid SWA ring geometry");
118 Self { rows, window, base: 0 }
119 }
120
121 pub fn rows(&self) -> usize { self.rows }
122 pub fn base(&self) -> usize { self.base }
123 pub fn window(&self) -> usize { self.window }
124
125 pub fn append_plan(
128 &self,
129 len: usize,
130 retain_from: usize,
131 append_rows: usize,
132 ) -> Result<KvRingAppend, String> {
133 if len < self.base || retain_from < self.base || retain_from > len {
134 return Err(format!(
135 "SWA ring lapped required rows (base {}, retain {retain_from}, len {len})",
136 self.base
137 ));
138 }
139 let used = len - self.base;
140 if used > self.rows {
141 return Err(format!("SWA ring state exceeds capacity ({used} > {})", self.rows));
142 }
143 if used.saturating_add(append_rows) <= self.rows {
144 return Ok(KvRingAppend::Contiguous {
145 write_row: used % self.rows,
146 });
147 }
148
149 let keep_rows = len - retain_from;
150 if keep_rows.saturating_add(append_rows) > self.rows {
151 return Err(format!(
152 "SWA ring append does not fit (keep {keep_rows} + append {append_rows} > {})",
153 self.rows
154 ));
155 }
156 Ok(KvRingAppend::Rebase {
157 src_row: retain_from - self.base,
158 keep_rows,
159 new_base: retain_from,
160 write_row: keep_rows,
161 })
162 }
163
164 pub fn apply_rebase(&mut self, new_base: usize) {
165 debug_assert!(new_base >= self.base);
166 self.base = new_base;
167 }
168
169 pub fn physical_range(
170 &self,
171 start: usize,
172 end: usize,
173 ) -> Result<std::ops::Range<usize>, String> {
174 if start < self.base || end < start || end - self.base > self.rows {
175 return Err(format!(
176 "SWA ring view [{start},{end}) is outside resident [{},{})",
177 self.base,
178 self.base + self.rows
179 ));
180 }
181 let start_row = (start - self.base) % self.rows;
182 let len = end - start;
183 debug_assert!(start_row + len <= self.rows, "ring view must be contiguous after rebase");
184 Ok(start_row..start_row + len)
185 }
186
187 pub fn can_rewind_to(&self, len: usize) -> bool {
189 let raw = len.saturating_sub(self.window - 1);
190 let view_start = raw & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
191 view_start >= self.base
192 }
193}
194
195pub trait KvDev {
200 fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
201 fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
202 fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>>;
203 fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>>;
204 fn clone_dtod(&self, src: &CudaSlice<f32>) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
205 fn copy_into(&self, dst: &mut CudaSlice<f32>, off: usize, src: &CudaSlice<f32>, len: usize)
206 -> Result<(), Box<dyn std::error::Error>>;
207 fn set_i32_one(&self, d: &mut CudaSlice<i32>, v: i32) -> Result<(), Box<dyn std::error::Error>>;
208}
209
210use memra_gguf::config::{LayerKind, ModelConfig};
211use cudarc::driver::CudaSlice;
212
213pub struct KvLayer {
218 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,
225 pub ring: Option<KvRing>,
228 pub len_d: CudaSlice<i32>,
232}
233
234impl KvLayer {
235 pub fn physical_rows(
236 &self,
237 start: usize,
238 end: usize,
239 ) -> Result<std::ops::Range<usize>, String> {
240 match &self.ring {
241 Some(ring) => ring.physical_range(start, end),
242 None => Ok(start..end),
243 }
244 }
245}
246
247pub struct RecurLayer {
251 pub conv_state: CudaSlice<f32>, pub ssm_state: CudaSlice<f32>, pub ssm_state_alt: CudaSlice<f32>,
263}
264
265pub struct Cache {
266 pub kv: Vec<Option<KvLayer>>,
267 pub recur: Vec<Option<RecurLayer>>,
268 pub pos: usize,
269 pub max_ctx: usize,
270 pub last_logits_dev: Option<CudaSlice<f32>>,
278 pub dflash_taps: Option<DflashTapSink>,
283}
284
285fn full_attention_kv_layout(cfg: &ModelConfig, il: u32) -> (usize, usize, usize, usize) {
289 debug_assert_eq!(cfg.layer_kind(il), LayerKind::FullAttention);
290 let n_head_kv = cfg.n_head_kv as usize;
291 let (kv_dim_k, kv_dim_v) = match &cfg.gemma4 {
292 Some(g) => {
293 let hd = if g.swa_pattern[il as usize] {
294 g.key_length_swa
295 } else {
296 g.key_length_global
297 } as usize;
298 let d = match g.head_count_kv.get(il as usize) {
307 Some(n) => hd * *n as usize,
308 None => hd * n_head_kv,
309 };
310 (d, d)
311 }
312 None => (
313 cfg.head_dim_k as usize * n_head_kv,
314 cfg.head_dim_v as usize * n_head_kv,
315 ),
316 };
317 assert!(
318 kv_dim_k % 32 == 0 && kv_dim_v % 32 == 0,
319 "KVQUANT requires per-layer kv_dim_k%32==0 && kv_dim_v%32==0 \
320 (layer {il}: k={kv_dim_k} v={kv_dim_v})"
321 );
322 let (kbb, vbb) = kv_blk_bytes();
323 let g4_global_fp8 = gkv_on()
324 && cfg
325 .gemma4
326 .as_ref()
327 .is_some_and(|g| !g.swa_pattern[il as usize]);
328 let g4_windowed_fp8 = wkv_on()
329 && cfg
330 .gemma4
331 .as_ref()
332 .is_some_and(|g| g.swa_pattern[il as usize]);
333 let qwen_fp8 = kv_fp8_on() && cfg.gemma4.is_none();
334 let (kbb_l, vbb_l) = if g4_global_fp8 || g4_windowed_fp8 || qwen_fp8 {
335 (32, 32)
336 } else {
337 (kbb, vbb)
338 };
339 (kv_dim_k, kv_dim_v, kbb_l, vbb_l)
340}
341
342fn kv_plane_allocation_bytes(rows: usize, token_bytes: usize) -> usize {
343 rows * token_bytes + 8
344}
345
346pub fn cache_bytes_per_token(cfg: &ModelConfig) -> usize {
353 cache_bytes_per_token_for_layers(cfg, 0, cfg.n_layer as usize)
354}
355
356pub fn cache_bytes_per_token_for_layers(cfg: &ModelConfig, lo: usize, hi: usize) -> usize {
360 assert!(lo <= hi && hi <= cfg.n_layer as usize, "cache layer range out of bounds");
361 let shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
362 (lo as u32..hi as u32)
363 .filter(|&il| cfg.layer_kind(il) == LayerKind::FullAttention)
364 .filter(|&il| shared == 0 || il < cfg.n_layer - shared)
365 .map(|il| {
366 let (kv_dim_k, kv_dim_v, kbb, vbb) = full_attention_kv_layout(cfg, il);
367 (kv_dim_k / 32) * kbb + (kv_dim_v / 32) * vbb
368 })
369 .sum()
370}
371
372pub fn cache_ring_bytes_per_token(cfg: &ModelConfig) -> usize {
375 cache_ring_bytes_per_token_for_layers(cfg, 0, cfg.n_layer as usize)
376}
377
378pub fn cache_ring_bytes_per_token_for_layers(
380 cfg: &ModelConfig,
381 lo: usize,
382 hi: usize,
383) -> usize {
384 assert!(lo <= hi && hi <= cfg.n_layer as usize, "cache layer range out of bounds");
385 if !swa_ring_on() || !cfg.arch.is_step35() {
386 return 0;
387 }
388 let shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
389 (lo as u32..hi as u32)
390 .filter(|&il| cfg.layer_kind(il) == LayerKind::FullAttention)
391 .filter(|&il| shared == 0 || il < cfg.n_layer - shared)
392 .filter(|&il| cfg.layer_geometry(il).is_some_and(|geometry| geometry.window.is_some()))
393 .map(|il| {
394 let (kv_dim_k, kv_dim_v, kbb, vbb) = full_attention_kv_layout(cfg, il);
395 (kv_dim_k / 32) * kbb + (kv_dim_v / 32) * vbb
396 })
397 .sum()
398}
399
400pub fn cache_ring_row_cap(cfg: &ModelConfig) -> usize {
402 if !swa_ring_on() || !cfg.arch.is_step35() {
403 return 0;
404 }
405 cfg.geometry
406 .as_ref()
407 .and_then(|table| table.classes().iter().find_map(|geometry| geometry.window))
408 .map(|window| swa_ring_rows(window as usize, usize::MAX))
409 .unwrap_or(0)
410}
411
412pub struct DflashTapSink {
415 pub layer_ids: Vec<usize>,
416 pub buf: CudaSlice<f32>,
417 pub hidden: usize,
418 pub t: usize,
419}
420
421pub struct CacheSnapshot {
428 pub kv_len: Vec<Option<usize>>, pub conv: Vec<Option<CudaSlice<f32>>>, pub ssm: Vec<Option<CudaSlice<f32>>>,
431 pub pos: usize,
432}
433
434impl Cache {
435 pub fn new(
437 e: &impl KvDev,
438 cfg: &ModelConfig,
439 max_ctx: usize,
440 ) -> Result<Self, Box<dyn std::error::Error>> {
441 Self::new_inner(&|_| e, cfg, max_ctx)
442 }
443
444 pub fn new_pp2(
449 dev0: &dyn KvDev,
450 dev1: &dyn KvDev,
451 split: usize,
452 cfg: &ModelConfig,
453 max_ctx: usize,
454 ) -> Result<Self, Box<dyn std::error::Error>> {
455 Self::new_inner(&|il| if il < split { dev0 } else { dev1 }, cfg, max_ctx)
456 }
457
458 pub fn new_ppn<'a>(
464 devs: &[&'a dyn KvDev],
465 fence: &[usize],
466 cfg: &ModelConfig,
467 max_ctx: usize,
468 ) -> Result<Self, Box<dyn std::error::Error>> {
469 assert_eq!(devs.len() + 1, fence.len(), "ppn cache: devs vs fence mismatch");
470 let pick = |il: usize| -> &dyn KvDev {
471 let s = match fence[1..fence.len() - 1].binary_search(&il) {
472 Ok(k) => k + 1,
473 Err(k) => k,
474 };
475 devs[s.min(devs.len() - 1)]
476 };
477 Self::new_inner(&pick, cfg, max_ctx)
478 }
479
480 fn new_inner<'a>(
483 pick: &dyn Fn(usize) -> &'a dyn KvDev,
484 cfg: &ModelConfig,
485 max_ctx: usize,
486 ) -> Result<Self, Box<dyn std::error::Error>> {
487 let n = cfg.n_layer as usize;
488 let mut kv = Vec::with_capacity(n);
489 let mut recur = Vec::with_capacity(n);
490 let head_dim_k = cfg.head_dim_k as usize;
491 let head_dim_v = cfg.head_dim_v as usize;
492 assert!(head_dim_k % 32 == 0 && head_dim_v % 32 == 0,
493 "KVQUANT requires head_dim_k%32==0 && head_dim_v%32==0 (got k={head_dim_k} v={head_dim_v})");
494 let (conv_dim, d_state, num_v, d_conv) = if let Some(s) = &cfg.ssm {
495 let num_k = s.group_count as usize;
496 let num_v = s.time_step_rank as usize;
497 let ds = s.state_size as usize;
498 (
499 ds * num_k * 2 + ds * num_v,
500 ds,
501 num_v,
502 s.conv_kernel as usize,
503 )
504 } else {
505 (0, 0, 0, 0)
506 };
507 for il in 0..cfg.n_layer {
508 let e = pick(il as usize);
510 let g4_shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
515 if g4_shared > 0 && il >= cfg.n_layer - g4_shared {
516 kv.push(None);
517 recur.push(None);
518 continue;
519 }
520 match cfg.layer_kind(il) {
521 LayerKind::FullAttention => {
522 let (kv_dim_k, kv_dim_v, kbb_l, vbb_l) =
525 full_attention_kv_layout(cfg, il);
526 let k_tok_bytes = (kv_dim_k / 32) * kbb_l;
527 let v_tok_bytes = (kv_dim_v / 32) * vbb_l;
528 let ring = if swa_ring_on() && cfg.arch.is_step35() {
529 cfg.layer_geometry(il)
530 .and_then(|geometry| geometry.window)
531 .map(|window| {
532 let window = window as usize;
533 KvRing::new(swa_ring_rows(window, max_ctx), window)
534 })
535 } else {
536 None
537 };
538 let alloc_rows = ring.as_ref().map(KvRing::rows).unwrap_or(max_ctx);
539 kv.push(Some(KvLayer {
540 k: e.alloc_u8(kv_plane_allocation_bytes(alloc_rows, k_tok_bytes))?,
544 v: e.alloc_u8(kv_plane_allocation_bytes(alloc_rows, v_tok_bytes))?,
545 kv_dim_k,
546 kv_dim_v,
547 k_tok_bytes,
548 v_tok_bytes,
549 len: 0,
550 ring,
551 len_d: e.htod_i32(&[0])?,
552 }));
553 recur.push(None);
554 }
555 LayerKind::LinearAttention => {
556 kv.push(None);
557 recur.push(Some(RecurLayer {
558 conv_state: e.zeros(conv_dim * (d_conv - 1))?,
559 ssm_state: e.zeros(d_state * d_state * num_v)?,
560 ssm_state_alt: e.zeros(d_state * d_state * num_v)?,
561 }));
562 }
563 }
564 }
565 Ok(Cache { kv, recur, pos: 0, max_ctx, dflash_taps: None, last_logits_dev: None })
566 }
567
568 pub fn has_swa_ring(&self) -> bool {
569 self.kv.iter().flatten().any(|layer| layer.ring.is_some())
570 }
571
572 pub fn can_rollback(&self, snap: &CacheSnapshot, accept_len: usize) -> bool {
573 self.kv.iter().zip(&snap.kv_len).all(|(layer, saved)| {
574 match (layer, saved) {
575 (Some(layer), Some(saved)) => layer
576 .ring
577 .as_ref()
578 .is_none_or(|ring| ring.can_rewind_to(saved + accept_len)),
579 _ => true,
580 }
581 })
582 }
583
584 pub fn snapshot(&self, e: &impl KvDev) -> Result<CacheSnapshot, Box<dyn std::error::Error>> {
588 let n = self.kv.len();
589 let mut kv_len = Vec::with_capacity(n);
590 let mut conv = Vec::with_capacity(n);
591 let mut ssm = Vec::with_capacity(n);
592 for il in 0..n {
593 match &self.kv[il] {
594 Some(kvl) => kv_len.push(Some(kvl.len)),
595 None => kv_len.push(None),
596 }
597 match &self.recur[il] {
598 Some(rl) => {
599 conv.push(Some(e.clone_dtod(&rl.conv_state)?));
600 ssm.push(Some(e.clone_dtod(&rl.ssm_state)?));
601 }
602 None => {
603 conv.push(None);
604 ssm.push(None);
605 }
606 }
607 }
608 Ok(CacheSnapshot {
609 kv_len,
610 conv,
611 ssm,
612 pos: self.pos,
613 })
614 }
615
616 pub fn snapshot_into(
621 &self,
622 e: &impl KvDev,
623 snap: &mut CacheSnapshot,
624 ) -> Result<(), Box<dyn std::error::Error>> {
625 let n = self.kv.len();
626 for il in 0..n {
627 snap.kv_len[il] = self.kv[il].as_ref().map(|kvl| kvl.len);
628 if let Some(rl) = &self.recur[il] {
629 let dc = snap.conv[il]
630 .as_mut()
631 .expect("snapshot_into: shape mismatch (conv)");
632 let ds = snap.ssm[il]
633 .as_mut()
634 .expect("snapshot_into: shape mismatch (ssm)");
635 let (cn, sn) = (rl.conv_state.len(), rl.ssm_state.len());
636 e.copy_into(dc, 0, &rl.conv_state, cn)?;
637 e.copy_into(ds, 0, &rl.ssm_state, sn)?;
638 }
639 }
640 snap.pos = self.pos;
641 Ok(())
642 }
643
644 pub fn rollback(
652 &mut self,
653 e: &impl KvDev,
654 snap: &CacheSnapshot,
655 accept_len: usize,
656 ) -> Result<(), Box<dyn std::error::Error>> {
657 if !self.can_rollback(snap, accept_len) {
658 return Err("SWA ring rewind checkpoint has been lapped; full re-prime required".into());
659 }
660 for il in 0..self.kv.len() {
661 if let (Some(kvl), Some(saved)) = (self.kv[il].as_mut(), snap.kv_len[il]) {
662 kvl.len = saved + accept_len;
663 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
668 }
669 if let Some(rl) = self.recur[il].as_mut() {
670 if let Some(c) = &snap.conv[il] {
671 e.copy_into(&mut rl.conv_state, 0, c, c.len())?;
672 }
673 if let Some(s) = &snap.ssm[il] {
674 e.copy_into(&mut rl.ssm_state, 0, s, s.len())?;
675 }
676 }
677 }
678 self.pos = snap.pos;
679 Ok(())
680 }
681}
682
683#[cfg(test)]
684mod swa_ring_tests {
685 use super::{kv_plane_allocation_bytes, swa_ring_rows, KvRing, KvRingAppend};
686
687 #[test]
688 fn allocation_rows_cover_window_max_prime_and_alignment_slack() {
689 assert_eq!(swa_ring_rows(512, 262_144), 512 + 4096 + 31);
690 assert_eq!(swa_ring_rows(512, 4096), 4096);
691 assert_eq!(
692 kv_plane_allocation_bytes(4639, 1088),
693 4639 * 1088 + 8,
694 "the Step35 session plane allocates ring rows plus the existing tail pad",
695 );
696 }
697
698 #[test]
699 fn ring_matches_flat_bytes_before_wrap() {
700 let ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
701 let flat: Vec<u32> = (0..1024).collect();
702 let mut physical = vec![u32::MAX; ring.rows()];
703 let KvRingAppend::Contiguous { write_row } = ring.append_plan(0, 0, flat.len()).unwrap()
704 else { panic!("first append unexpectedly wrapped") };
705 physical[write_row..write_row + flat.len()].copy_from_slice(&flat);
706 let view = ring.physical_range(0, flat.len()).unwrap();
707 assert_eq!(&physical[view], flat.as_slice());
708 }
709
710 #[test]
711 fn wrap_rebases_the_exact_aligned_prime_view() {
712 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
713 let flat: Vec<u32> = (0..8192).collect();
714 let mut physical = vec![u32::MAX; ring.rows()];
715 let KvRingAppend::Contiguous { write_row } = ring.append_plan(0, 0, 4096).unwrap()
716 else { panic!("first prime chunk unexpectedly wrapped") };
717 physical[write_row..write_row + 4096].copy_from_slice(&flat[..4096]);
718
719 let off = (4096usize - (512 - 1)) & !31usize;
720 let KvRingAppend::Rebase {
721 src_row,
722 keep_rows,
723 new_base,
724 write_row,
725 } = ring.append_plan(4096, off, 4096).unwrap()
726 else { panic!("second prime chunk did not wrap") };
727 let retained = physical[src_row..src_row + keep_rows].to_vec();
728 physical[..keep_rows].copy_from_slice(&retained);
729 ring.apply_rebase(new_base);
730 physical[write_row..write_row + 4096].copy_from_slice(&flat[4096..8192]);
731
732 let view = ring.physical_range(off, 8192).unwrap();
733 assert_eq!(&physical[view], &flat[off..8192]);
734 assert_eq!(ring.base(), off);
735 }
736
737 #[test]
738 fn rewind_declines_once_the_required_window_was_lapped() {
739 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
740 let KvRingAppend::Rebase { new_base, .. } =
741 ring.append_plan(4096, 3584, 4096).unwrap()
742 else { panic!("expected wrap") };
743 ring.apply_rebase(new_base);
744 assert!(ring.can_rewind_to(4095));
745 assert!(!ring.can_rewind_to(4094));
746 assert!(!ring.can_rewind_to(0));
747 }
748}