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
158pub fn swa_ring_on() -> bool {
161 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
162 *ON.get_or_init(|| std::env::var("MEMRA_SWA_RING").as_deref() == Ok("1"))
163}
164
165pub const PRIME_CHUNK_MAX_TOKENS: usize = 4096;
168const SWA_VIEW_ALIGNMENT_ROWS: usize = 32;
169
170pub fn swa_ring_rows(window: usize, max_ctx: usize) -> usize {
173 max_ctx.min(window + PRIME_CHUNK_MAX_TOKENS + (SWA_VIEW_ALIGNMENT_ROWS - 1))
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub struct KvRing {
178 rows: usize,
179 window: usize,
180 base: usize,
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub enum KvRingAppend {
185 Contiguous {
186 write_row: usize,
187 },
188 Rebase {
189 src_row: usize,
190 keep_rows: usize,
191 new_base: usize,
192 write_row: usize,
193 },
194}
195
196impl KvRing {
197 pub fn new(rows: usize, window: usize) -> Self {
198 assert!(window > 0 && rows > 0, "invalid SWA ring geometry");
199 Self {
200 rows,
201 window,
202 base: 0,
203 }
204 }
205
206 pub fn rows(&self) -> usize {
207 self.rows
208 }
209 pub fn base(&self) -> usize {
210 self.base
211 }
212 pub fn window(&self) -> usize {
213 self.window
214 }
215
216 pub fn append_plan(
219 &self,
220 len: usize,
221 retain_from: usize,
222 append_rows: usize,
223 ) -> Result<KvRingAppend, String> {
224 if len < self.base || retain_from < self.base || retain_from > len {
225 return Err(format!(
226 "SWA ring lapped required rows (base {}, retain {retain_from}, len {len})",
227 self.base
228 ));
229 }
230 let used = len - self.base;
231 if used > self.rows {
232 return Err(format!(
233 "SWA ring state exceeds capacity ({used} > {})",
234 self.rows
235 ));
236 }
237 if used.saturating_add(append_rows) <= self.rows {
238 return Ok(KvRingAppend::Contiguous {
239 write_row: used % self.rows,
240 });
241 }
242
243 let keep_rows = len - retain_from;
244 if keep_rows.saturating_add(append_rows) > self.rows {
245 return Err(format!(
246 "SWA ring append does not fit (keep {keep_rows} + append {append_rows} > {})",
247 self.rows
248 ));
249 }
250 Ok(KvRingAppend::Rebase {
251 src_row: retain_from - self.base,
252 keep_rows,
253 new_base: retain_from,
254 write_row: keep_rows,
255 })
256 }
257
258 pub fn apply_rebase(&mut self, new_base: usize) {
259 debug_assert!(new_base >= self.base);
260 self.base = new_base;
261 }
262
263 pub fn physical_range(
264 &self,
265 start: usize,
266 end: usize,
267 ) -> Result<std::ops::Range<usize>, String> {
268 if start < self.base || end < start || end - self.base > self.rows {
269 return Err(format!(
270 "SWA ring view [{start},{end}) is outside resident [{},{})",
271 self.base,
272 self.base + self.rows
273 ));
274 }
275 let start_row = (start - self.base) % self.rows;
276 let len = end - start;
277 debug_assert!(
278 start_row + len <= self.rows,
279 "ring view must be contiguous after rebase"
280 );
281 Ok(start_row..start_row + len)
282 }
283
284 pub fn can_rewind_to(&self, len: usize) -> bool {
286 let raw = len.saturating_sub(self.window - 1);
287 let view_start = raw & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
288 view_start >= self.base
289 }
290}
291
292pub trait KvDev {
297 fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
298 fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
299 fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>>;
300 fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>>;
301 fn clone_dtod(
302 &self,
303 src: &CudaSlice<f32>,
304 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>;
305 fn copy_into(
306 &self,
307 dst: &mut CudaSlice<f32>,
308 off: usize,
309 src: &CudaSlice<f32>,
310 len: usize,
311 ) -> Result<(), Box<dyn std::error::Error>>;
312 fn set_i32_one(&self, d: &mut CudaSlice<i32>, v: i32)
313 -> Result<(), Box<dyn std::error::Error>>;
314}
315
316use cudarc::driver::CudaSlice;
317use memra_gguf::config::{LayerKind, ModelConfig};
318use memra_gguf::model_plan::{ModelPlan, ResidualTopology, StatePlan};
319
320pub struct KvLayer {
325 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,
332 pub ring: Option<KvRing>,
335 pub len_d: CudaSlice<i32>,
339}
340
341impl KvLayer {
342 pub fn physical_rows(
343 &self,
344 start: usize,
345 end: usize,
346 ) -> Result<std::ops::Range<usize>, String> {
347 match &self.ring {
348 Some(ring) => ring.physical_range(start, end),
349 None => Ok(start..end),
350 }
351 }
352}
353
354pub struct RecurLayer {
358 pub conv_state: CudaSlice<f32>, pub ssm_state: CudaSlice<f32>, pub ssm_state_alt: CudaSlice<f32>,
370}
371
372pub struct ResidentTpKvCacheRank {
373 k: CudaSlice<u8>,
374 v: CudaSlice<u8>,
375 len_d: CudaSlice<i32>,
376 base_d: Option<CudaSlice<i32>>,
381}
382
383impl ResidentTpKvCacheRank {
384 pub fn new(k: CudaSlice<u8>, v: CudaSlice<u8>, len_d: CudaSlice<i32>) -> Self {
385 Self {
386 k,
387 v,
388 len_d,
389 base_d: None,
390 }
391 }
392
393 pub fn base_d(&self) -> Option<&CudaSlice<i32>> {
394 self.base_d.as_ref()
395 }
396
397 pub fn base_d_mut(&mut self) -> Option<&mut CudaSlice<i32>> {
398 self.base_d.as_mut()
399 }
400
401 pub fn arm_base_d(&mut self, buf: CudaSlice<i32>) {
402 self.base_d = Some(buf);
403 }
404
405 pub fn k(&self) -> &CudaSlice<u8> {
406 &self.k
407 }
408
409 pub fn v(&self) -> &CudaSlice<u8> {
410 &self.v
411 }
412
413 pub fn len_d(&self) -> &CudaSlice<i32> {
414 &self.len_d
415 }
416
417 pub fn k_mut(&mut self) -> &mut CudaSlice<u8> {
418 &mut self.k
419 }
420
421 pub fn v_mut(&mut self) -> &mut CudaSlice<u8> {
422 &mut self.v
423 }
424
425 pub fn planes_mut(&mut self) -> (&mut CudaSlice<u8>, &mut CudaSlice<u8>) {
426 (&mut self.k, &mut self.v)
427 }
428
429 #[allow(clippy::type_complexity)]
431 pub fn planes_and_counters_mut(
432 &mut self,
433 ) -> (
434 &mut CudaSlice<u8>,
435 &mut CudaSlice<u8>,
436 &CudaSlice<i32>,
437 Option<&CudaSlice<i32>>,
438 ) {
439 (&mut self.k, &mut self.v, &self.len_d, self.base_d.as_ref())
440 }
441
442 pub fn len_d_mut(&mut self) -> &mut CudaSlice<i32> {
443 &mut self.len_d
444 }
445}
446
447#[derive(Clone, Copy, Debug, PartialEq, Eq)]
448pub struct TpKvTransaction {
449 generation: u64,
450 base_len: usize,
451}
452
453impl TpKvTransaction {
454 pub fn generation(self) -> u64 {
455 self.generation
456 }
457
458 pub fn base_len(self) -> usize {
459 self.base_len
460 }
461}
462
463#[derive(Clone, Copy, Debug, PartialEq, Eq)]
464pub struct TpKvAppendPlan {
465 transaction: TpKvTransaction,
466 target: usize,
467 write_row: usize,
468 ring_append: Option<KvRingAppend>,
469}
470
471impl TpKvAppendPlan {
472 pub fn target(self) -> usize {
473 self.target
474 }
475
476 pub fn write_row(self) -> usize {
477 self.write_row
478 }
479
480 pub fn ring_append(self) -> Option<KvRingAppend> {
481 self.ring_append
482 }
483}
484
485#[derive(Debug, PartialEq, Eq)]
486pub struct TpKvGrowPlan {
487 rows: usize,
488 source_row: usize,
489 copy_rows: usize,
490 target_base: usize,
491 k_bytes: usize,
492 v_bytes: usize,
493 source_capacity: usize,
494 target_capacity: usize,
495 ring_window: Option<usize>,
496 target_physical_rows: usize,
497 kv_dim_k: usize,
498 kv_dim_v: usize,
499 k_tok_bytes: usize,
500 v_tok_bytes: usize,
501 ranks: usize,
502 next_generation: u64,
503}
504
505impl TpKvGrowPlan {
506 pub fn rows(&self) -> usize {
507 self.rows
508 }
509
510 pub fn source_row(&self) -> usize {
511 self.source_row
512 }
513
514 pub fn copy_rows(&self) -> usize {
515 self.copy_rows
516 }
517
518 pub fn k_bytes(&self) -> usize {
519 self.k_bytes
520 }
521
522 pub fn v_bytes(&self) -> usize {
523 self.v_bytes
524 }
525}
526
527#[derive(Clone, Debug, PartialEq, Eq)]
528struct TpKvTransactionState {
529 committed_len: usize,
530 staged_len: usize,
531 next_generation: u64,
532 active: Option<TpKvTransaction>,
533}
534
535impl TpKvTransactionState {
536 fn new() -> Self {
537 Self {
538 committed_len: 0,
539 staged_len: 0,
540 next_generation: 1,
541 active: None,
542 }
543 }
544
545 fn begin(&mut self) -> Result<TpKvTransaction, String> {
546 if let Some(active) = self.active {
547 return Err(format!(
548 "TP KV transaction generation {} is already active at base {}",
549 active.generation, active.base_len
550 ));
551 }
552 if self.staged_len != self.committed_len {
553 return Err(format!(
554 "TP KV cache is half-committed: staged {} != committed {}",
555 self.staged_len, self.committed_len
556 ));
557 }
558 let transaction = TpKvTransaction {
559 generation: self.next_generation,
560 base_len: self.committed_len,
561 };
562 self.next_generation = self
563 .next_generation
564 .checked_add(1)
565 .ok_or("TP KV transaction generation overflow")?;
566 self.active = Some(transaction);
567 Ok(transaction)
568 }
569
570 fn validate(&self, transaction: TpKvTransaction) -> Result<(), String> {
571 if self.active != Some(transaction) {
572 return Err(format!(
573 "stale TP KV transaction generation {} at base {}",
574 transaction.generation, transaction.base_len
575 ));
576 }
577 if transaction.base_len != self.committed_len {
578 return Err(format!(
579 "TP KV transaction base {} != committed length {}",
580 transaction.base_len, self.committed_len
581 ));
582 }
583 Ok(())
584 }
585
586 fn append_target(
587 &self,
588 transaction: TpKvTransaction,
589 rows: usize,
590 capacity: usize,
591 ) -> Result<usize, String> {
592 self.validate(transaction)?;
593 if rows == 0 {
594 return Err("TP KV append must contain at least one row".into());
595 }
596 let target = self
597 .staged_len
598 .checked_add(rows)
599 .ok_or("TP KV staged length overflow")?;
600 if target > capacity {
601 return Err(format!(
602 "TP KV append exceeds capacity: {target} > {capacity}"
603 ));
604 }
605 Ok(target)
606 }
607
608 fn publish_append(
609 &mut self,
610 transaction: TpKvTransaction,
611 target: usize,
612 ) -> Result<(), String> {
613 self.validate(transaction)?;
614 if target <= self.staged_len {
615 return Err(format!(
616 "TP KV append target {target} must exceed staged length {}",
617 self.staged_len
618 ));
619 }
620 self.staged_len = target;
621 Ok(())
622 }
623
624 fn commit_target(
625 &self,
626 transaction: TpKvTransaction,
627 accepted_rows: usize,
628 ) -> Result<usize, String> {
629 self.validate(transaction)?;
630 let staged_rows = self
631 .staged_len
632 .checked_sub(transaction.base_len)
633 .ok_or("TP KV staged length precedes its transaction base")?;
634 if accepted_rows > staged_rows {
635 return Err(format!(
636 "TP KV commit accepts {accepted_rows} rows from a {staged_rows}-row transaction"
637 ));
638 }
639 transaction
640 .base_len
641 .checked_add(accepted_rows)
642 .ok_or_else(|| "TP KV committed length overflow".to_string())
643 }
644
645 fn publish_finalize(
646 &mut self,
647 transaction: TpKvTransaction,
648 target: usize,
649 ) -> Result<(), String> {
650 self.validate(transaction)?;
651 if target < transaction.base_len || target > self.staged_len {
652 return Err(format!(
653 "TP KV finalize target {target} outside transaction range {}..={}",
654 transaction.base_len, self.staged_len
655 ));
656 }
657 self.committed_len = target;
658 self.staged_len = target;
659 self.active = None;
660 Ok(())
661 }
662
663 fn rewind(&mut self, target: usize, capacity: usize) -> Result<(), String> {
664 if target > capacity {
665 return Err(format!(
666 "TP KV rewind target {target} exceeds capacity {capacity}"
667 ));
668 }
669 self.committed_len = target;
670 self.staged_len = target;
671 self.active = None;
672 Ok(())
673 }
674}
675
676pub struct ResidentTpKvCache {
677 ranks: Vec<ResidentTpKvCacheRank>,
678 kv_dim_k: usize,
679 kv_dim_v: usize,
680 k_tok_bytes: usize,
681 v_tok_bytes: usize,
682 capacity: usize,
683 ring: Option<KvRing>,
684 state: TpKvTransactionState,
685}
686
687impl ResidentTpKvCache {
688 #[allow(clippy::too_many_arguments)]
689 pub fn new(
690 ranks: Vec<ResidentTpKvCacheRank>,
691 kv_dim_k: usize,
692 kv_dim_v: usize,
693 k_tok_bytes: usize,
694 v_tok_bytes: usize,
695 capacity: usize,
696 ) -> Self {
697 Self::new_inner(
698 ranks,
699 kv_dim_k,
700 kv_dim_v,
701 k_tok_bytes,
702 v_tok_bytes,
703 capacity,
704 None,
705 )
706 }
707
708 #[allow(clippy::too_many_arguments)]
709 pub fn new_swa(
710 ranks: Vec<ResidentTpKvCacheRank>,
711 kv_dim_k: usize,
712 kv_dim_v: usize,
713 k_tok_bytes: usize,
714 v_tok_bytes: usize,
715 capacity: usize,
716 window: usize,
717 ) -> Self {
718 Self::new_inner(
719 ranks,
720 kv_dim_k,
721 kv_dim_v,
722 k_tok_bytes,
723 v_tok_bytes,
724 capacity,
725 Some(KvRing::new(swa_ring_rows(window, capacity), window)),
726 )
727 }
728
729 #[allow(clippy::too_many_arguments)]
730 fn new_inner(
731 ranks: Vec<ResidentTpKvCacheRank>,
732 kv_dim_k: usize,
733 kv_dim_v: usize,
734 k_tok_bytes: usize,
735 v_tok_bytes: usize,
736 capacity: usize,
737 ring: Option<KvRing>,
738 ) -> Self {
739 Self {
740 ranks,
741 kv_dim_k,
742 kv_dim_v,
743 k_tok_bytes,
744 v_tok_bytes,
745 capacity,
746 ring,
747 state: TpKvTransactionState::new(),
748 }
749 }
750
751 pub fn begin_transaction(&mut self) -> Result<TpKvTransaction, String> {
752 self.state.begin()
753 }
754
755 pub fn committed_len(&self) -> usize {
756 self.state.committed_len
757 }
758
759 pub fn staged_len(&self) -> usize {
760 self.state.staged_len
761 }
762
763 pub fn capacity(&self) -> usize {
764 self.capacity
765 }
766
767 pub fn physical_capacity(&self) -> usize {
768 self.ring
769 .as_ref()
770 .map(KvRing::rows)
771 .unwrap_or(self.capacity)
772 }
773
774 pub fn ring_window(&self) -> Option<usize> {
775 self.ring.as_ref().map(KvRing::window)
776 }
777
778 pub fn ring_base(&self) -> Option<usize> {
779 self.ring.as_ref().map(KvRing::base)
780 }
781
782 pub fn physical_range(
783 &self,
784 start: usize,
785 end: usize,
786 ) -> Result<std::ops::Range<usize>, String> {
787 match &self.ring {
788 Some(ring) => ring.physical_range(start, end),
789 None => {
790 if end < start || end > self.capacity {
791 return Err(format!(
792 "TP KV linear view [{start},{end}) exceeds capacity {}",
793 self.capacity
794 ));
795 }
796 Ok(start..end)
797 }
798 }
799 }
800
801 pub fn can_rewind_to(&self, target: usize) -> bool {
802 target <= self.capacity
803 && self
804 .ring
805 .as_ref()
806 .is_none_or(|ring| ring.can_rewind_to(target))
807 }
808
809 pub fn kv_dim_k(&self) -> usize {
810 self.kv_dim_k
811 }
812
813 pub fn kv_dim_v(&self) -> usize {
814 self.kv_dim_v
815 }
816
817 pub fn k_tok_bytes(&self) -> usize {
818 self.k_tok_bytes
819 }
820
821 pub fn v_tok_bytes(&self) -> usize {
822 self.v_tok_bytes
823 }
824
825 pub fn ranks_len(&self) -> usize {
826 self.ranks.len()
827 }
828
829 pub fn rank(&self, rank: usize) -> Option<&ResidentTpKvCacheRank> {
830 self.ranks.get(rank)
831 }
832
833 pub fn rank_mut(&mut self, rank: usize) -> Option<&mut ResidentTpKvCacheRank> {
834 self.ranks.get_mut(rank)
835 }
836
837 pub fn ranks(&self) -> &[ResidentTpKvCacheRank] {
838 &self.ranks
839 }
840
841 pub fn ranks_mut(&mut self) -> &mut [ResidentTpKvCacheRank] {
842 &mut self.ranks
843 }
844
845 pub fn prepare_grow(
846 &self,
847 target_capacity: usize,
848 rows: usize,
849 ) -> Result<TpKvGrowPlan, String> {
850 if let Some(active) = self.state.active {
851 return Err(format!(
852 "TP KV grow refuses active transaction generation {} at base {}",
853 active.generation, active.base_len
854 ));
855 }
856 if self.state.staged_len != self.state.committed_len {
857 return Err(format!(
858 "TP KV grow requires quiescent state, got committed/staged={}/{}",
859 self.state.committed_len, self.state.staged_len
860 ));
861 }
862 if target_capacity <= self.capacity {
863 return Err(format!(
864 "TP KV grow target capacity {target_capacity} must exceed source capacity {}",
865 self.capacity
866 ));
867 }
868 if target_capacity > i32::MAX as usize {
869 return Err(format!(
870 "TP KV grow target capacity {target_capacity} exceeds i32 device mirrors"
871 ));
872 }
873 if rows > self.state.committed_len {
874 return Err(format!(
875 "TP KV grow rows {rows} exceed committed length {}",
876 self.state.committed_len
877 ));
878 }
879 let (source_row, copy_rows, target_base, ring_window, target_physical_rows) =
880 match &self.ring {
881 Some(ring) => {
882 let raw = rows.saturating_sub(ring.window().saturating_sub(1));
883 let target_base = raw & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
884 let physical = ring.physical_range(target_base, rows)?;
885 (
886 physical.start,
887 physical.len(),
888 target_base,
889 Some(ring.window()),
890 swa_ring_rows(ring.window(), target_capacity),
891 )
892 }
893 None => (0, rows, 0, None, target_capacity),
894 };
895 let k_bytes = copy_rows
896 .checked_mul(self.k_tok_bytes)
897 .ok_or("TP KV grow K byte extent overflow")?;
898 let v_bytes = copy_rows
899 .checked_mul(self.v_tok_bytes)
900 .ok_or("TP KV grow V byte extent overflow")?;
901 Ok(TpKvGrowPlan {
902 rows,
903 source_row,
904 copy_rows,
905 target_base,
906 k_bytes,
907 v_bytes,
908 source_capacity: self.capacity,
909 target_capacity,
910 ring_window,
911 target_physical_rows,
912 kv_dim_k: self.kv_dim_k,
913 kv_dim_v: self.kv_dim_v,
914 k_tok_bytes: self.k_tok_bytes,
915 v_tok_bytes: self.v_tok_bytes,
916 ranks: self.ranks.len(),
917 next_generation: self.state.next_generation,
918 })
919 }
920
921 pub fn publish_grow(&mut self, plan: TpKvGrowPlan) -> Result<(), String> {
922 if self.state != TpKvTransactionState::new() {
923 return Err(format!(
924 "TP KV grow target must be fresh, got committed/staged={}/{} active={}",
925 self.state.committed_len,
926 self.state.staged_len,
927 self.state.active.is_some()
928 ));
929 }
930 if self.capacity != plan.target_capacity
931 || self.capacity <= plan.source_capacity
932 || self.kv_dim_k != plan.kv_dim_k
933 || self.kv_dim_v != plan.kv_dim_v
934 || self.k_tok_bytes != plan.k_tok_bytes
935 || self.v_tok_bytes != plan.v_tok_bytes
936 || self.ranks.len() != plan.ranks
937 || self.ring.as_ref().map(KvRing::window) != plan.ring_window
938 || self.physical_capacity() != plan.target_physical_rows
939 {
940 return Err("TP KV grow target layout does not match its source plan".into());
941 }
942 if plan.rows > self.capacity {
943 return Err(format!(
944 "TP KV grow rows {} exceed target capacity {}",
945 plan.rows, self.capacity
946 ));
947 }
948 if let Some(ring) = self.ring.as_mut() {
949 let mut target_ring = *ring;
950 target_ring.apply_rebase(plan.target_base);
951 if !target_ring.can_rewind_to(plan.rows) {
952 return Err(format!(
953 "TP KV grow target ring base {} cannot expose committed length {}",
954 target_ring.base(),
955 plan.rows
956 ));
957 }
958 *ring = target_ring;
959 }
960 self.state.committed_len = plan.rows;
961 self.state.staged_len = plan.rows;
962 self.state.next_generation = plan.next_generation;
963 self.state.active = None;
964 Ok(())
965 }
966
967 pub fn prepare_append(
968 &self,
969 transaction: TpKvTransaction,
970 rows: usize,
971 ) -> Result<TpKvAppendPlan, String> {
972 let target = self.state.append_target(transaction, rows, self.capacity)?;
973 let ring_append = self
974 .ring
975 .as_ref()
976 .map(|ring| {
977 let staged_retain =
978 target.saturating_sub(ring.window()) & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
979 let rollback_retain = transaction
980 .base_len
981 .saturating_sub(ring.window().saturating_sub(1))
982 & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
983 ring.append_plan(
984 self.state.staged_len,
985 staged_retain.min(rollback_retain),
986 rows,
987 )
988 })
989 .transpose()?;
990 let write_row = match ring_append {
991 Some(KvRingAppend::Contiguous { write_row })
992 | Some(KvRingAppend::Rebase { write_row, .. }) => write_row,
993 None => self.state.staged_len,
994 };
995 Ok(TpKvAppendPlan {
996 transaction,
997 target,
998 write_row,
999 ring_append,
1000 })
1001 }
1002
1003 pub fn peek_append_ring(&self, rows: usize) -> Result<(usize, bool), String> {
1007 let target = self.state.staged_len + rows;
1008 let plan = self
1009 .ring
1010 .as_ref()
1011 .map(|ring| {
1012 let staged_retain =
1013 target.saturating_sub(ring.window()) & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
1014 let rollback_retain = self
1015 .state
1016 .staged_len
1017 .saturating_sub(ring.window().saturating_sub(1))
1018 & !(SWA_VIEW_ALIGNMENT_ROWS - 1);
1019 ring.append_plan(
1020 self.state.staged_len,
1021 staged_retain.min(rollback_retain),
1022 rows,
1023 )
1024 })
1025 .transpose()?;
1026 Ok(match plan {
1027 Some(KvRingAppend::Contiguous { write_row }) => (write_row, false),
1028 Some(KvRingAppend::Rebase { write_row, .. }) => (write_row, true),
1029 None => (self.state.staged_len, false),
1030 })
1031 }
1032
1033 pub fn publish_append_rebase(&mut self, plan: TpKvAppendPlan) -> Result<(), String> {
1034 self.state.validate(plan.transaction)?;
1035 match (self.ring.as_mut(), plan.ring_append) {
1036 (
1037 Some(ring),
1038 Some(KvRingAppend::Rebase {
1039 new_base,
1040 keep_rows,
1041 ..
1042 }),
1043 ) => {
1044 if keep_rows > ring.rows() {
1045 return Err(format!(
1046 "TP KV ring rebase keeps {keep_rows} rows in {} physical rows",
1047 ring.rows()
1048 ));
1049 }
1050 let mut target_ring = *ring;
1051 target_ring.apply_rebase(new_base);
1052 if !target_ring.can_rewind_to(plan.transaction.base_len) {
1053 return Err(format!(
1054 "TP KV ring rebase to {new_base} laps transaction base {}",
1055 plan.transaction.base_len
1056 ));
1057 }
1058 *ring = target_ring;
1059 Ok(())
1060 }
1061 (Some(_), Some(KvRingAppend::Contiguous { .. })) | (None, None) => Ok(()),
1062 _ => Err("TP KV append plan does not match cache ring layout".into()),
1063 }
1064 }
1065
1066 pub fn publish_append_plan(&mut self, plan: TpKvAppendPlan) -> Result<(), String> {
1067 if let Some(KvRingAppend::Rebase { new_base, .. }) = plan.ring_append {
1068 if self.ring.as_ref().map(KvRing::base) != Some(new_base) {
1069 return Err(format!(
1070 "TP KV append rebase {new_base} was not published before its state"
1071 ));
1072 }
1073 }
1074 self.state.publish_append(plan.transaction, plan.target)
1075 }
1076
1077 pub fn publish_hydration(
1078 &mut self,
1079 logical_len: usize,
1080 resident_start: usize,
1081 ) -> Result<(), Box<dyn std::error::Error>> {
1082 if self.state != TpKvTransactionState::new() {
1083 return Err("TP KV hydration target must be fresh".into());
1084 }
1085 if resident_start > logical_len || logical_len > self.capacity {
1086 return Err(format!(
1087 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
1088 self.capacity
1089 )
1090 .into());
1091 }
1092 match self.ring.as_mut() {
1093 Some(ring) => {
1094 let rows = logical_len - resident_start;
1095 if rows > ring.rows() {
1096 return Err(format!(
1097 "TP KV hydration requires {rows} rows in a {}-row ring",
1098 ring.rows()
1099 )
1100 .into());
1101 }
1102 let mut hydrated_ring = *ring;
1103 hydrated_ring.apply_rebase(resident_start);
1104 if !hydrated_ring.can_rewind_to(logical_len) {
1105 return Err(format!(
1106 "TP KV hydration base {resident_start} cannot expose logical length \
1107 {logical_len}"
1108 )
1109 .into());
1110 }
1111 *ring = hydrated_ring;
1112 }
1113 None if resident_start != 0 => {
1114 return Err("linear TP KV hydration must start at absolute row zero".into());
1115 }
1116 None => {}
1117 }
1118 self.rewind_to(logical_len)
1119 }
1120
1121 pub fn append_target(
1122 &self,
1123 transaction: TpKvTransaction,
1124 rows: usize,
1125 ) -> Result<usize, String> {
1126 self.state.append_target(transaction, rows, self.capacity)
1127 }
1128
1129 pub fn publish_append(
1130 &mut self,
1131 transaction: TpKvTransaction,
1132 target: usize,
1133 ) -> Result<(), String> {
1134 self.state.publish_append(transaction, target)
1135 }
1136
1137 pub fn commit_target(
1138 &self,
1139 transaction: TpKvTransaction,
1140 accepted_rows: usize,
1141 ) -> Result<usize, String> {
1142 self.state.commit_target(transaction, accepted_rows)
1143 }
1144
1145 pub fn validate_transaction(&self, transaction: TpKvTransaction) -> Result<(), String> {
1146 self.state.validate(transaction)
1147 }
1148
1149 pub fn publish_finalize(
1150 &mut self,
1151 transaction: TpKvTransaction,
1152 target: usize,
1153 ) -> Result<(), String> {
1154 if !self.can_rewind_to(target) {
1155 return Err(format!(
1156 "TP KV finalize target {target} is outside the resident cache window/capacity"
1157 ));
1158 }
1159 self.state.publish_finalize(transaction, target)
1160 }
1161
1162 pub fn rewind_to(&mut self, target: usize) -> Result<(), Box<dyn std::error::Error>> {
1163 if !self.can_rewind_to(target) {
1164 return Err(format!(
1165 "TP KV rewind target {target} is outside the resident cache window/capacity"
1166 )
1167 .into());
1168 }
1169 let target_i32 =
1170 i32::try_from(target).map_err(|_| "TP KV length exceeds i32 device mirror")?;
1171 for rank in &mut self.ranks {
1172 let stream = rank.len_d.stream().clone();
1173 stream.memcpy_htod(&[target_i32], &mut rank.len_d)?;
1174 }
1175 self.state.rewind(target, self.capacity)?;
1176 Ok(())
1177 }
1178}
1179
1180pub struct Cache {
1181 pub kv: Vec<Option<KvLayer>>,
1182 pub recur: Vec<Option<RecurLayer>>,
1183 pub tp_kv: Vec<Option<ResidentTpKvCache>>,
1186 pub pos: usize,
1187 pub max_ctx: usize,
1188 pub last_logits_dev: Option<CudaSlice<f32>>,
1196 pub dflash_taps: Option<DflashTapSink>,
1201}
1202
1203#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1207enum FullAttentionClass {
1208 Ordinary,
1209 GemmaGlobal,
1210 GemmaWindowed,
1211}
1212
1213fn full_attention_class(plan: &ModelPlan, il: u32) -> FullAttentionClass {
1214 let layer = plan
1215 .layers
1216 .iter()
1217 .chain(plan.mtp_blocks.iter().map(|block| &block.layer))
1218 .find(|layer| layer.index == il)
1219 .unwrap_or_else(|| panic!("ModelPlan has no layer {il}"));
1220 if !matches!(layer.residual, ResidualTopology::Gemma { .. }) {
1221 return FullAttentionClass::Ordinary;
1222 }
1223 match layer.state {
1224 StatePlan::SlidingKvCache { .. } => FullAttentionClass::GemmaWindowed,
1225 StatePlan::KvCache { .. } => FullAttentionClass::GemmaGlobal,
1226 _ => panic!("Gemma layer {il} does not declare a KV-cache state"),
1227 }
1228}
1229
1230fn full_attention_kv_layout(
1231 cfg: &ModelConfig,
1232 plan: &ModelPlan,
1233 il: u32,
1234) -> (usize, usize, usize, usize) {
1235 debug_assert_eq!(cfg.layer_kind(il), LayerKind::FullAttention);
1236 let class = full_attention_class(plan, il);
1237 let n_head_kv = cfg.n_head_kv as usize;
1238 let (kv_dim_k, kv_dim_v) = match class {
1239 FullAttentionClass::GemmaGlobal | FullAttentionClass::GemmaWindowed => {
1240 let g = cfg
1241 .gemma4
1242 .as_ref()
1243 .expect("Gemma ModelPlan layer requires Gemma cache geometry");
1244 let hd = match class {
1245 FullAttentionClass::GemmaWindowed => g.key_length_swa,
1246 FullAttentionClass::GemmaGlobal => g.key_length_global,
1247 FullAttentionClass::Ordinary => unreachable!(),
1248 } as usize;
1249 let d = match g.head_count_kv.get(il as usize) {
1258 Some(n) => hd * *n as usize,
1259 None => hd * n_head_kv,
1260 };
1261 (d, d)
1262 }
1263 FullAttentionClass::Ordinary => (
1264 cfg.head_dim_k as usize * n_head_kv,
1265 cfg.head_dim_v as usize * n_head_kv,
1266 ),
1267 };
1268 assert!(
1269 kv_dim_k % 32 == 0 && kv_dim_v % 32 == 0,
1270 "KVQUANT requires per-layer kv_dim_k%32==0 && kv_dim_v%32==0 \
1271 (layer {il}: k={kv_dim_k} v={kv_dim_v})"
1272 );
1273 let (kbb, vbb) = kv_blk_bytes();
1274 let g4_global_fp8 = gkv_on() && class == FullAttentionClass::GemmaGlobal;
1275 let g4_windowed_fp8 = wkv_on() && class == FullAttentionClass::GemmaWindowed;
1276 let qwen_fp8 = kv_fp8_on() && class == FullAttentionClass::Ordinary;
1277 let (kbb_l, vbb_l) = if g4_global_fp8 || g4_windowed_fp8 || qwen_fp8 {
1278 (32, 32)
1279 } else {
1280 (kbb, vbb)
1281 };
1282 (kv_dim_k, kv_dim_v, kbb_l, vbb_l)
1283}
1284
1285fn kv_plane_allocation_bytes(rows: usize, token_bytes: usize) -> usize {
1286 rows * token_bytes + 8
1287}
1288
1289pub fn cache_bytes_per_token(cfg: &ModelConfig) -> usize {
1296 cache_bytes_per_token_for_layers(cfg, 0, cfg.n_layer as usize)
1297}
1298
1299pub fn cache_bytes_per_token_for_layers(cfg: &ModelConfig, lo: usize, hi: usize) -> usize {
1303 let plan = ModelPlan::compile(cfg).expect("cache sizing requires a compilable ModelPlan");
1304 cache_bytes_per_token_for_plan(cfg, &plan, lo, hi)
1305}
1306
1307pub fn cache_bytes_per_token_for_plan(
1308 cfg: &ModelConfig,
1309 plan: &ModelPlan,
1310 lo: usize,
1311 hi: usize,
1312) -> usize {
1313 assert!(
1314 lo <= hi && hi <= cfg.n_layer as usize,
1315 "cache layer range out of bounds"
1316 );
1317 let shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
1318 (lo as u32..hi as u32)
1319 .filter(|&il| cfg.layer_kind(il) == LayerKind::FullAttention)
1320 .filter(|&il| shared == 0 || il < cfg.n_layer - shared)
1321 .map(|il| {
1322 let (kv_dim_k, kv_dim_v, kbb, vbb) = full_attention_kv_layout(cfg, plan, il);
1323 (kv_dim_k / 32) * kbb + (kv_dim_v / 32) * vbb
1324 })
1325 .sum()
1326}
1327
1328pub fn cache_ring_bytes_per_token(cfg: &ModelConfig) -> usize {
1331 cache_ring_bytes_per_token_for_layers(cfg, 0, cfg.n_layer as usize)
1332}
1333
1334pub fn cache_ring_bytes_per_token_for_layers(cfg: &ModelConfig, lo: usize, hi: usize) -> usize {
1336 assert!(
1337 lo <= hi && hi <= cfg.n_layer as usize,
1338 "cache layer range out of bounds"
1339 );
1340 let Ok(plan) = memra_gguf::model_plan::ModelPlan::compile(cfg) else {
1341 return 0;
1342 };
1343 cache_ring_bytes_per_token_for_plan(cfg, &plan, lo, hi)
1344}
1345
1346pub fn cache_ring_bytes_per_token_for_plan(
1347 cfg: &ModelConfig,
1348 plan: &ModelPlan,
1349 lo: usize,
1350 hi: usize,
1351) -> usize {
1352 let total = plan.layers.len() + plan.mtp_blocks.len();
1353 assert!(
1354 lo <= hi && hi <= total,
1355 "cache plan layer range out of bounds"
1356 );
1357 if !swa_ring_on() {
1358 return 0;
1359 }
1360 let shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
1361 plan.layers
1362 .iter()
1363 .chain(plan.mtp_blocks.iter().map(|block| &block.layer))
1364 .filter(|layer| (lo..hi).contains(&(layer.index as usize)))
1365 .filter(|layer| {
1366 matches!(
1367 layer.state,
1368 memra_gguf::model_plan::StatePlan::SlidingKvCache { .. }
1369 )
1370 })
1371 .filter(|layer| shared == 0 || layer.index < cfg.n_layer - shared)
1372 .map(|layer| {
1373 let (kv_dim_k, kv_dim_v, kbb, vbb) = full_attention_kv_layout(cfg, plan, layer.index);
1374 (kv_dim_k / 32) * kbb + (kv_dim_v / 32) * vbb
1375 })
1376 .sum()
1377}
1378
1379pub fn cache_ring_row_cap(cfg: &ModelConfig) -> usize {
1381 let Ok(plan) = memra_gguf::model_plan::ModelPlan::compile(cfg) else {
1382 return 0;
1383 };
1384 cache_ring_row_cap_for_plan(&plan)
1385}
1386
1387pub fn cache_ring_row_cap_for_plan(plan: &memra_gguf::model_plan::ModelPlan) -> usize {
1388 if !swa_ring_on() {
1389 return 0;
1390 }
1391 plan.layers
1392 .iter()
1393 .chain(plan.mtp_blocks.iter().map(|block| &block.layer))
1394 .filter_map(|layer| match layer.state {
1395 memra_gguf::model_plan::StatePlan::SlidingKvCache { window, .. } => {
1396 Some(window as usize)
1397 }
1398 _ => None,
1399 })
1400 .map(|window| swa_ring_rows(window, usize::MAX))
1401 .max()
1402 .unwrap_or(0)
1403}
1404
1405pub struct DflashTapSink {
1408 pub layer_ids: Vec<usize>,
1409 pub buf: CudaSlice<f32>,
1410 pub hidden: usize,
1411 pub t: usize,
1412 pub base: usize,
1415}
1416
1417pub struct CacheSnapshot {
1424 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>>>,
1428 pub pos: usize,
1429}
1430
1431impl Cache {
1432 pub fn new(
1434 e: &impl KvDev,
1435 cfg: &ModelConfig,
1436 max_ctx: usize,
1437 ) -> Result<Self, Box<dyn std::error::Error>> {
1438 Self::new_inner(&|_| e, cfg, None, max_ctx)
1439 }
1440
1441 pub fn new_planned(
1442 e: &impl KvDev,
1443 cfg: &ModelConfig,
1444 plan: &memra_gguf::model_plan::ModelPlan,
1445 max_ctx: usize,
1446 ) -> Result<Self, Box<dyn std::error::Error>> {
1447 Self::new_inner(&|_| e, cfg, Some(plan), max_ctx)
1448 }
1449
1450 pub fn new_pp2(
1455 dev0: &dyn KvDev,
1456 dev1: &dyn KvDev,
1457 split: usize,
1458 cfg: &ModelConfig,
1459 max_ctx: usize,
1460 ) -> Result<Self, Box<dyn std::error::Error>> {
1461 Self::new_inner(
1462 &|il| if il < split { dev0 } else { dev1 },
1463 cfg,
1464 None,
1465 max_ctx,
1466 )
1467 }
1468
1469 pub fn new_ppn<'a>(
1475 devs: &[&'a dyn KvDev],
1476 fence: &[usize],
1477 cfg: &ModelConfig,
1478 max_ctx: usize,
1479 ) -> Result<Self, Box<dyn std::error::Error>> {
1480 assert_eq!(
1481 devs.len() + 1,
1482 fence.len(),
1483 "ppn cache: devs vs fence mismatch"
1484 );
1485 let pick = |il: usize| -> &dyn KvDev {
1486 let s = match fence[1..fence.len() - 1].binary_search(&il) {
1487 Ok(k) => k + 1,
1488 Err(k) => k,
1489 };
1490 devs[s.min(devs.len() - 1)]
1491 };
1492 Self::new_inner(&pick, cfg, None, max_ctx)
1493 }
1494
1495 pub fn new_ppn_planned<'a>(
1496 devs: &[&'a dyn KvDev],
1497 fence: &[usize],
1498 cfg: &ModelConfig,
1499 plan: &memra_gguf::model_plan::ModelPlan,
1500 max_ctx: usize,
1501 ) -> Result<Self, Box<dyn std::error::Error>> {
1502 assert_eq!(
1503 devs.len() + 1,
1504 fence.len(),
1505 "ppn cache: devs vs fence mismatch"
1506 );
1507 let pick = |il: usize| -> &dyn KvDev {
1508 let stage = match fence[1..fence.len() - 1].binary_search(&il) {
1509 Ok(index) => index + 1,
1510 Err(index) => index,
1511 };
1512 devs[stage.min(devs.len() - 1)]
1513 };
1514 Self::new_inner(&pick, cfg, Some(plan), max_ctx)
1515 }
1516
1517 fn new_inner<'a>(
1520 pick: &dyn Fn(usize) -> &'a dyn KvDev,
1521 cfg: &ModelConfig,
1522 plan: Option<&memra_gguf::model_plan::ModelPlan>,
1523 max_ctx: usize,
1524 ) -> Result<Self, Box<dyn std::error::Error>> {
1525 let fallback_plan = if plan.is_none() {
1526 Some(ModelPlan::compile(cfg)?)
1527 } else {
1528 None
1529 };
1530 let plan = plan
1531 .or(fallback_plan.as_ref())
1532 .expect("cache allocation requires a ModelPlan");
1533 let n = cfg.n_layer as usize;
1534 let mut kv = Vec::with_capacity(n);
1535 let mut recur = Vec::with_capacity(n);
1536 let head_dim_k = cfg.head_dim_k as usize;
1537 let head_dim_v = cfg.head_dim_v as usize;
1538 assert!(
1539 head_dim_k % 32 == 0 && head_dim_v % 32 == 0,
1540 "KVQUANT requires head_dim_k%32==0 && head_dim_v%32==0 (got k={head_dim_k} v={head_dim_v})"
1541 );
1542 for il in 0..cfg.n_layer {
1543 let e = pick(il as usize);
1545 let layer = plan
1546 .layers
1547 .iter()
1548 .chain(plan.mtp_blocks.iter().map(|block| &block.layer))
1549 .find(|layer| layer.index == il)
1550 .ok_or_else(|| format!("cache ModelPlan has no layer {il}"))?;
1551 let g4_shared = cfg.gemma4.as_ref().map(|g| g.shared_kv_layers).unwrap_or(0);
1556 if g4_shared > 0 && il >= cfg.n_layer - g4_shared {
1557 kv.push(None);
1558 recur.push(None);
1559 continue;
1560 }
1561 match layer.state {
1562 StatePlan::KvCache { .. } | StatePlan::SlidingKvCache { .. } => {
1563 let (kv_dim_k, kv_dim_v, kbb_l, vbb_l) =
1566 full_attention_kv_layout(cfg, plan, il);
1567 let k_tok_bytes = (kv_dim_k / 32) * kbb_l;
1568 let v_tok_bytes = (kv_dim_v / 32) * vbb_l;
1569 let planned_window = plan
1570 .layers
1571 .iter()
1572 .chain(plan.mtp_blocks.iter().map(|block| &block.layer))
1573 .find(|layer| layer.index == il)
1574 .and_then(|layer| match layer.state {
1575 StatePlan::SlidingKvCache { window, .. } => Some(window),
1576 _ => None,
1577 });
1578 let ring = if swa_ring_on() {
1579 planned_window.map(|window| {
1580 let window = window as usize;
1581 KvRing::new(swa_ring_rows(window, max_ctx), window)
1582 })
1583 } else {
1584 None
1585 };
1586 let alloc_rows = ring.as_ref().map(KvRing::rows).unwrap_or(max_ctx);
1587 kv.push(Some(KvLayer {
1588 k: e.alloc_u8(kv_plane_allocation_bytes(alloc_rows, k_tok_bytes))?,
1592 v: e.alloc_u8(kv_plane_allocation_bytes(alloc_rows, v_tok_bytes))?,
1593 kv_dim_k,
1594 kv_dim_v,
1595 k_tok_bytes,
1596 v_tok_bytes,
1597 len: 0,
1598 ring,
1599 len_d: e.htod_i32(&[0])?,
1600 }));
1601 recur.push(None);
1602 }
1603 StatePlan::Recurrent {
1604 conv_width,
1605 conv_kernel,
1606 state_width,
1607 } => {
1608 kv.push(None);
1609 recur.push(Some(RecurLayer {
1610 conv_state: e.zeros(
1611 conv_width as usize * (conv_kernel as usize).saturating_sub(1),
1612 )?,
1613 ssm_state: e.zeros(state_width as usize)?,
1614 ssm_state_alt: e.zeros(state_width as usize)?,
1615 }));
1616 }
1617 ref state => {
1618 return Err(format!(
1619 "native cache allocator has no implementation for layer {il} state {state:?}"
1620 )
1621 .into());
1622 }
1623 }
1624 }
1625 Ok(Cache {
1626 kv,
1627 recur,
1628 tp_kv: (0..n).map(|_| None).collect(),
1629 pos: 0,
1630 max_ctx,
1631 dflash_taps: None,
1632 last_logits_dev: None,
1633 })
1634 }
1635
1636 pub fn has_swa_ring(&self) -> bool {
1637 self.kv.iter().flatten().any(|layer| layer.ring.is_some())
1638 || self
1639 .tp_kv
1640 .iter()
1641 .flatten()
1642 .any(|layer| layer.ring_window().is_some())
1643 }
1644
1645 pub fn can_rollback(&self, snap: &CacheSnapshot, accept_len: usize) -> bool {
1646 let local = self
1647 .kv
1648 .iter()
1649 .zip(&snap.kv_len)
1650 .all(|(layer, saved)| match (layer, saved) {
1651 (Some(layer), Some(saved)) => layer
1652 .ring
1653 .as_ref()
1654 .is_none_or(|ring| ring.can_rewind_to(saved + accept_len)),
1655 _ => true,
1656 });
1657 let tensor = self
1658 .tp_kv
1659 .iter()
1660 .zip(&snap.tp_kv_len)
1661 .all(|(layer, saved)| match (layer, saved) {
1662 (Some(layer), Some(saved)) => saved
1663 .checked_add(accept_len)
1664 .is_some_and(|target| layer.can_rewind_to(target)),
1665 _ => true,
1666 });
1667 local && tensor
1668 }
1669
1670 pub fn snapshot(&self, e: &impl KvDev) -> Result<CacheSnapshot, Box<dyn std::error::Error>> {
1674 let n = self.kv.len();
1675 let mut kv_len = Vec::with_capacity(n);
1676 let mut tp_kv_len = Vec::with_capacity(n);
1677 let mut conv = Vec::with_capacity(n);
1678 let mut ssm = Vec::with_capacity(n);
1679 for il in 0..n {
1680 match &self.kv[il] {
1681 Some(kvl) => kv_len.push(Some(kvl.len)),
1682 None => kv_len.push(None),
1683 }
1684 tp_kv_len.push(
1685 self.tp_kv[il]
1686 .as_ref()
1687 .map(ResidentTpKvCache::committed_len),
1688 );
1689 match &self.recur[il] {
1690 Some(rl) => {
1691 conv.push(Some(e.clone_dtod(&rl.conv_state)?));
1692 ssm.push(Some(e.clone_dtod(&rl.ssm_state)?));
1693 }
1694 None => {
1695 conv.push(None);
1696 ssm.push(None);
1697 }
1698 }
1699 }
1700 Ok(CacheSnapshot {
1701 kv_len,
1702 tp_kv_len,
1703 conv,
1704 ssm,
1705 pos: self.pos,
1706 })
1707 }
1708
1709 pub fn snapshot_into(
1714 &self,
1715 e: &impl KvDev,
1716 snap: &mut CacheSnapshot,
1717 ) -> Result<(), Box<dyn std::error::Error>> {
1718 let n = self.kv.len();
1719 for il in 0..n {
1720 snap.kv_len[il] = self.kv[il].as_ref().map(|kvl| kvl.len);
1721 snap.tp_kv_len[il] = self.tp_kv[il]
1722 .as_ref()
1723 .map(ResidentTpKvCache::committed_len);
1724 if let Some(rl) = &self.recur[il] {
1725 let dc = snap.conv[il]
1726 .as_mut()
1727 .expect("snapshot_into: shape mismatch (conv)");
1728 let ds = snap.ssm[il]
1729 .as_mut()
1730 .expect("snapshot_into: shape mismatch (ssm)");
1731 let (cn, sn) = (rl.conv_state.len(), rl.ssm_state.len());
1732 e.copy_into(dc, 0, &rl.conv_state, cn)?;
1733 e.copy_into(ds, 0, &rl.ssm_state, sn)?;
1734 }
1735 }
1736 snap.pos = self.pos;
1737 Ok(())
1738 }
1739
1740 pub fn rollback(
1748 &mut self,
1749 e: &impl KvDev,
1750 snap: &CacheSnapshot,
1751 accept_len: usize,
1752 ) -> Result<(), Box<dyn std::error::Error>> {
1753 if !self.can_rollback(snap, accept_len) {
1754 return Err(
1755 "SWA ring rewind checkpoint has been lapped; full re-prime required".into(),
1756 );
1757 }
1758 for il in 0..self.kv.len() {
1759 if let (Some(kvl), Some(saved)) = (self.kv[il].as_mut(), snap.kv_len[il]) {
1760 kvl.len = saved + accept_len;
1761 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
1766 }
1767 if let (Some(kvl), Some(saved)) = (self.tp_kv[il].as_mut(), snap.tp_kv_len[il]) {
1768 kvl.rewind_to(saved + accept_len)?;
1769 }
1770 if let Some(rl) = self.recur[il].as_mut() {
1771 if let Some(c) = &snap.conv[il] {
1772 e.copy_into(&mut rl.conv_state, 0, c, c.len())?;
1773 }
1774 if let Some(s) = &snap.ssm[il] {
1775 e.copy_into(&mut rl.ssm_state, 0, s, s.len())?;
1776 }
1777 }
1778 }
1779 self.pos = snap.pos;
1780 Ok(())
1781 }
1782}
1783
1784#[cfg(test)]
1785mod tp_transaction_tests {
1786 use super::{
1787 Cache, KvRingAppend, ResidentTpKvCache, TpKvTransactionState, tp_kv_rank_allocation_shape,
1788 };
1789
1790 fn empty_tp_cache(capacity: usize) -> ResidentTpKvCache {
1791 ResidentTpKvCache::new(Vec::new(), 128, 128, 136, 96, capacity)
1792 }
1793
1794 #[test]
1795 fn step_tp8_rank_allocation_matches_the_official_kv_geometry() {
1796 let shape = tp_kv_rank_allocation_shape(8 * 128, 8 * 128, 8).unwrap();
1797 assert_eq!((shape.kv_dim_k, shape.kv_dim_v), (128, 128));
1798 assert_eq!((shape.k_token_bytes, shape.v_token_bytes), (136, 96));
1799 assert_eq!(shape.bytes_per_token(), 232);
1800 assert_eq!(shape.fixed_bytes, 20);
1801 assert_eq!(shape.allocation_bytes(262_144), 232 * 262_144 + 20);
1802 }
1803
1804 #[test]
1805 fn tp_rank_allocation_refuses_non_divisible_and_non_block_aligned_shards() {
1806 assert!(tp_kv_rank_allocation_shape(1024, 1024, 3).is_err());
1807 assert!(tp_kv_rank_allocation_shape(1024, 1024, 64).is_err());
1808 assert!(tp_kv_rank_allocation_shape(0, 1024, 8).is_err());
1809 }
1810
1811 #[test]
1812 fn partial_commit_publishes_only_the_accepted_prefix() {
1813 let mut state = TpKvTransactionState::new();
1814 let transaction = state.begin().unwrap();
1815 let staged = state.append_target(transaction, 3, 8).unwrap();
1816 state.publish_append(transaction, staged).unwrap();
1817 assert_eq!(state.committed_len, 0);
1818 assert_eq!(state.staged_len, 3);
1819
1820 let committed = state.commit_target(transaction, 2).unwrap();
1821 state.publish_finalize(transaction, committed).unwrap();
1822 assert_eq!(state.committed_len, 2);
1823 assert_eq!(state.staged_len, 2);
1824 assert!(state.active.is_none());
1825 assert!(state.validate(transaction).is_err());
1826 }
1827
1828 #[test]
1829 fn rollback_restores_the_committed_boundary() {
1830 let mut state = TpKvTransactionState::new();
1831 let first = state.begin().unwrap();
1832 let staged = state.append_target(first, 1, 8).unwrap();
1833 state.publish_append(first, staged).unwrap();
1834 let committed = state.commit_target(first, 1).unwrap();
1835 state.publish_finalize(first, committed).unwrap();
1836
1837 let speculative = state.begin().unwrap();
1838 let staged = state.append_target(speculative, 2, 8).unwrap();
1839 state.publish_append(speculative, staged).unwrap();
1840 assert_eq!(state.committed_len, 1);
1841 assert_eq!(state.staged_len, 3);
1842 state
1843 .publish_finalize(speculative, speculative.base_len)
1844 .unwrap();
1845 assert_eq!(state.committed_len, 1);
1846 assert_eq!(state.staged_len, 1);
1847 assert!(state.validate(speculative).is_err());
1848 }
1849
1850 #[test]
1851 fn rejects_nested_stale_and_out_of_range_actions() {
1852 let mut state = TpKvTransactionState::new();
1853 let transaction = state.begin().unwrap();
1854 assert!(state.begin().is_err());
1855 assert!(state.append_target(transaction, 0, 2).is_err());
1856 assert!(state.append_target(transaction, 3, 2).is_err());
1857 let staged = state.append_target(transaction, 2, 2).unwrap();
1858 state.publish_append(transaction, staged).unwrap();
1859 assert!(state.commit_target(transaction, 3).is_err());
1860 state.publish_finalize(transaction, 0).unwrap();
1861 assert!(state.publish_append(transaction, 1).is_err());
1862 }
1863
1864 #[test]
1865 fn rewind_resets_visibility_and_invalidates_an_active_transaction() {
1866 let mut state = TpKvTransactionState::new();
1867 let transaction = state.begin().unwrap();
1868 let staged = state.append_target(transaction, 3, 8).unwrap();
1869 state.publish_append(transaction, staged).unwrap();
1870 state.rewind(1, 8).unwrap();
1871 assert_eq!(state.committed_len, 1);
1872 assert_eq!(state.staged_len, 1);
1873 assert!(state.active.is_none());
1874 assert!(state.validate(transaction).is_err());
1875 assert!(state.rewind(9, 8).is_err());
1876 }
1877
1878 #[test]
1879 fn grow_preserves_generation_and_publishes_only_the_checkpoint_prefix() {
1880 let mut source = empty_tp_cache(8);
1881 let first = source.begin_transaction().unwrap();
1882 let staged = source.append_target(first, 5).unwrap();
1883 source.publish_append(first, staged).unwrap();
1884 let committed = source.commit_target(first, 5).unwrap();
1885 source.publish_finalize(first, committed).unwrap();
1886
1887 let rolled_back = source.begin_transaction().unwrap();
1888 source
1889 .publish_finalize(rolled_back, rolled_back.base_len())
1890 .unwrap();
1891 let plan = source.prepare_grow(16, 3).unwrap();
1892 assert_eq!(plan.rows(), 3);
1893 assert_eq!(plan.k_bytes(), 3 * 136);
1894 assert_eq!(plan.v_bytes(), 3 * 96);
1895
1896 let mut target = empty_tp_cache(16);
1897 target.publish_grow(plan).unwrap();
1898 assert_eq!(target.committed_len(), 3);
1899 assert_eq!(target.staged_len(), 3);
1900 assert_eq!(target.capacity(), 16);
1901 let next = target.begin_transaction().unwrap();
1902 assert_eq!(next.generation(), rolled_back.generation() + 1);
1903 assert_eq!(next.base_len(), 3);
1904 }
1905
1906 #[test]
1907 fn grow_refuses_active_source_and_invalid_target_state_or_layout() {
1908 let mut active = empty_tp_cache(8);
1909 active.begin_transaction().unwrap();
1910 assert!(active.prepare_grow(16, 0).is_err());
1911
1912 let mut source = empty_tp_cache(8);
1913 source.rewind_to(5).unwrap();
1914 assert!(source.prepare_grow(8, 5).is_err());
1915 assert!(source.prepare_grow(16, 6).is_err());
1916 let plan = source.prepare_grow(16, 4).unwrap();
1917
1918 let mut wrong_layout = ResidentTpKvCache::new(Vec::new(), 128, 128, 144, 96, 16);
1919 assert!(wrong_layout.publish_grow(plan).is_err());
1920
1921 let plan = source.prepare_grow(16, 4).unwrap();
1922 let mut dirty_target = empty_tp_cache(16);
1923 dirty_target.rewind_to(1).unwrap();
1924 assert!(dirty_target.publish_grow(plan).is_err());
1925 }
1926
1927 #[test]
1928 fn swa_transaction_rebase_preserves_the_rollback_window() {
1929 let mut cache = ResidentTpKvCache::new_swa(Vec::new(), 128, 128, 136, 96, 10_000, 32);
1930 assert_eq!(cache.physical_capacity(), 32 + 4096 + 31);
1931 cache.publish_hydration(8250, 4096).unwrap();
1932 assert_eq!(cache.ring_base(), Some(4096));
1933
1934 let transaction = cache.begin_transaction().unwrap();
1935 let plan = cache.prepare_append(transaction, 10).unwrap();
1936 assert_eq!(plan.target(), 8260);
1937 assert_eq!(plan.write_row(), 58);
1938 assert_eq!(
1939 plan.ring_append(),
1940 Some(KvRingAppend::Rebase {
1941 src_row: 4096,
1942 keep_rows: 58,
1943 new_base: 8192,
1944 write_row: 58,
1945 })
1946 );
1947 cache.publish_append_rebase(plan).unwrap();
1948 cache.publish_append_plan(plan).unwrap();
1949 assert_eq!(cache.ring_base(), Some(8192));
1950 assert_eq!(cache.physical_range(8228, 8260).unwrap(), 36..68);
1951
1952 let rollback = cache.commit_target(transaction, 0).unwrap();
1953 cache.publish_finalize(transaction, rollback).unwrap();
1954 assert_eq!((cache.committed_len(), cache.staged_len()), (8250, 8250));
1955 assert!(cache.rewind_to(8200).is_err());
1956 }
1957
1958 #[test]
1959 fn swa_grow_normalizes_only_the_live_prefix_and_preserves_generation() {
1960 let mut source = ResidentTpKvCache::new_swa(Vec::new(), 128, 128, 136, 96, 10_000, 32);
1961 source.publish_hydration(8250, 4096).unwrap();
1962 let transaction = source.begin_transaction().unwrap();
1963 source
1964 .publish_finalize(transaction, transaction.base_len())
1965 .unwrap();
1966
1967 let plan = source.prepare_grow(20_000, 8250).unwrap();
1968 assert_eq!(plan.source_row(), 4096);
1969 assert_eq!(plan.copy_rows(), 58);
1970 assert_eq!(plan.k_bytes(), 58 * 136);
1971 assert_eq!(plan.v_bytes(), 58 * 96);
1972
1973 let mut target = ResidentTpKvCache::new_swa(Vec::new(), 128, 128, 136, 96, 20_000, 32);
1974 target.publish_grow(plan).unwrap();
1975 assert_eq!(target.ring_base(), Some(8192));
1976 assert_eq!((target.committed_len(), target.staged_len()), (8250, 8250));
1977 assert_eq!(target.physical_range(8192, 8250).unwrap(), 0..58);
1978 let next = target.begin_transaction().unwrap();
1979 assert_eq!(next.generation(), transaction.generation() + 1);
1980 }
1981
1982 #[test]
1983 fn cache_reports_a_materialized_distributed_swa_ring() {
1984 let mut cache = Cache {
1985 kv: Vec::new(),
1986 recur: Vec::new(),
1987 tp_kv: vec![None],
1988 pos: 0,
1989 max_ctx: 10_000,
1990 dflash_taps: None,
1991 last_logits_dev: None,
1992 };
1993 assert!(!cache.has_swa_ring());
1994 cache.tp_kv[0] = Some(ResidentTpKvCache::new_swa(
1995 Vec::new(),
1996 128,
1997 128,
1998 136,
1999 96,
2000 10_000,
2001 512,
2002 ));
2003 assert!(cache.has_swa_ring());
2004 }
2005}
2006
2007#[cfg(test)]
2008mod swa_ring_tests {
2009 use super::{KvRing, KvRingAppend, kv_plane_allocation_bytes, swa_ring_rows};
2010
2011 #[test]
2012 fn allocation_rows_cover_window_max_prime_and_alignment_slack() {
2013 assert_eq!(swa_ring_rows(512, 262_144), 512 + 4096 + 31);
2014 assert_eq!(swa_ring_rows(512, 4096), 4096);
2015 assert_eq!(
2016 kv_plane_allocation_bytes(4639, 1088),
2017 4639 * 1088 + 8,
2018 "the Step35 session plane allocates ring rows plus the existing tail pad",
2019 );
2020 }
2021
2022 #[test]
2023 fn ring_matches_flat_bytes_before_wrap() {
2024 let ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
2025 let flat: Vec<u32> = (0..1024).collect();
2026 let mut physical = vec![u32::MAX; ring.rows()];
2027 let KvRingAppend::Contiguous { write_row } = ring.append_plan(0, 0, flat.len()).unwrap()
2028 else {
2029 panic!("first append unexpectedly wrapped")
2030 };
2031 physical[write_row..write_row + flat.len()].copy_from_slice(&flat);
2032 let view = ring.physical_range(0, flat.len()).unwrap();
2033 assert_eq!(&physical[view], flat.as_slice());
2034 }
2035
2036 #[test]
2037 fn wrap_rebases_the_exact_aligned_prime_view() {
2038 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
2039 let flat: Vec<u32> = (0..8192).collect();
2040 let mut physical = vec![u32::MAX; ring.rows()];
2041 let KvRingAppend::Contiguous { write_row } = ring.append_plan(0, 0, 4096).unwrap() else {
2042 panic!("first prime chunk unexpectedly wrapped")
2043 };
2044 physical[write_row..write_row + 4096].copy_from_slice(&flat[..4096]);
2045
2046 let off = (4096usize - (512 - 1)) & !31usize;
2047 let KvRingAppend::Rebase {
2048 src_row,
2049 keep_rows,
2050 new_base,
2051 write_row,
2052 } = ring.append_plan(4096, off, 4096).unwrap()
2053 else {
2054 panic!("second prime chunk did not wrap")
2055 };
2056 let retained = physical[src_row..src_row + keep_rows].to_vec();
2057 physical[..keep_rows].copy_from_slice(&retained);
2058 ring.apply_rebase(new_base);
2059 physical[write_row..write_row + 4096].copy_from_slice(&flat[4096..8192]);
2060
2061 let view = ring.physical_range(off, 8192).unwrap();
2062 assert_eq!(&physical[view], &flat[off..8192]);
2063 assert_eq!(ring.base(), off);
2064 }
2065
2066 #[test]
2067 fn rewind_declines_once_the_required_window_was_lapped() {
2068 let mut ring = KvRing::new(swa_ring_rows(512, 262_144), 512);
2069 let KvRingAppend::Rebase { new_base, .. } = ring.append_plan(4096, 3584, 4096).unwrap()
2070 else {
2071 panic!("expected wrap")
2072 };
2073 ring.apply_rebase(new_base);
2074 assert!(ring.can_rewind_to(4095));
2075 assert!(!ring.can_rewind_to(4094));
2076 assert!(!ring.can_rewind_to(0));
2077 }
2078}