1use anyhow::{Result, bail};
8
9use super::block_trace::BlockTrace;
10use super::{KvCacheConfig, KvCacheDtype, LayerPool, PagedKvCache};
11use crate::gpu::{DevicePtr, GpuBackend};
12
13impl PagedKvCache {
14 pub fn new(config: KvCacheConfig, num_blocks: usize, gpu: &dyn GpuBackend) -> Result<Self> {
16 let mut layers = Vec::with_capacity(config.num_layers);
17 let mut total_bytes: usize = 0;
18 for i in 0..config.num_layers {
19 let k_block_bytes = config.k_block_bytes_for_layer(i);
24 let v_block_bytes = config.v_block_bytes_for_layer(i);
25 let k_pool_bytes = num_blocks * k_block_bytes;
26 let v_pool_bytes = num_blocks * v_block_bytes;
27 let k_pool = gpu.alloc(k_pool_bytes)?;
28 let v_pool = gpu.alloc(v_pool_bytes)?;
29 total_bytes += k_pool_bytes + v_pool_bytes;
30 layers.push(LayerPool {
31 k_pool,
32 v_pool,
33 k_block_stride: k_block_bytes,
34 v_block_stride: v_block_bytes,
35 dtype: config.dtype_for_layer(i),
36 });
37 }
38
39 let free_blocks: Vec<u32> = (0..num_blocks as u32).rev().collect();
40 let block_ref_counts = vec![0u32; num_blocks];
41
42 let has_mixed = !config.layer_dtypes.is_empty()
43 && config.layer_dtypes.iter().any(|d| *d != config.dtype);
44 if has_mixed {
45 let hp_count = config
46 .layer_dtypes
47 .iter()
48 .filter(|d| **d != config.dtype)
49 .count();
50 tracing::info!(
51 "KV cache: {} blocks × {} layers ({} high-precision) = {:.1} GB total (mixed dtype)",
52 num_blocks,
53 config.num_layers,
54 hp_count,
55 total_bytes as f64 / (1024.0 * 1024.0 * 1024.0),
56 );
57 } else {
58 tracing::info!(
59 "KV cache: {} blocks × {} layers × {} bytes/block = {:.1} GB total",
60 num_blocks,
61 config.num_layers,
62 config.block_bytes_kv(),
63 (num_blocks * config.num_layers * config.block_bytes_kv()) as f64
64 / (1024.0 * 1024.0 * 1024.0),
65 );
66 }
67
68 Ok(Self {
69 layers,
70 num_blocks,
71 free_blocks,
72 block_ref_counts,
73 config,
74 trace: BlockTrace::new(num_blocks),
75 })
76 }
77
78 #[track_caller]
80 pub fn alloc_block(&mut self) -> Result<u32> {
81 let idx = self
82 .free_blocks
83 .pop()
84 .ok_or_else(|| anyhow::anyhow!("KV cache exhausted: no free blocks"))?;
85 self.block_ref_counts[idx as usize] = 1;
86 if self.trace.is_on() {
87 self.trace
88 .record(idx as usize, "alloc", 1, std::panic::Location::caller());
89 }
90 Ok(idx)
91 }
92
93 pub fn zero_block(
97 &self,
98 block_idx: u32,
99 gpu: &dyn crate::gpu::GpuBackend,
100 stream: u64,
101 ) -> anyhow::Result<()> {
102 for layer in &self.layers {
103 let k_offset = block_idx as usize * layer.k_block_stride;
104 let v_offset = block_idx as usize * layer.v_block_stride;
105 gpu.memset_async(
106 layer.k_pool.offset(k_offset),
107 0,
108 layer.k_block_stride,
109 stream,
110 )?;
111 gpu.memset_async(
112 layer.v_pool.offset(v_offset),
113 0,
114 layer.v_block_stride,
115 stream,
116 )?;
117 }
118 Ok(())
119 }
120
121 pub fn poison_block(
130 &self,
131 block_idx: u32,
132 gpu: &dyn crate::gpu::GpuBackend,
133 stream: u64,
134 ) -> anyhow::Result<()> {
135 for layer in &self.layers {
136 let k_offset = block_idx as usize * layer.k_block_stride;
137 let v_offset = block_idx as usize * layer.v_block_stride;
138 gpu.memset_async(
139 layer.k_pool.offset(k_offset),
140 0xFF,
141 layer.k_block_stride,
142 stream,
143 )?;
144 gpu.memset_async(
145 layer.v_pool.offset(v_offset),
146 0xFF,
147 layer.v_block_stride,
148 stream,
149 )?;
150 }
151 Ok(())
152 }
153
154 #[track_caller]
156 pub fn try_alloc_block(&mut self) -> Option<u32> {
157 let idx = self.free_blocks.pop()?;
158 self.block_ref_counts[idx as usize] = 1;
159 if self.trace.is_on() {
160 self.trace
161 .record(idx as usize, "try_alloc", 1, std::panic::Location::caller());
162 }
163 Some(idx)
164 }
165
166 #[track_caller]
168 pub fn inc_ref(&mut self, block_idx: u32) {
169 debug_assert!((block_idx as usize) < self.num_blocks);
170 self.block_ref_counts[block_idx as usize] += 1;
171 if self.trace.is_on() {
172 let after = self.block_ref_counts[block_idx as usize];
173 self.trace.record(
174 block_idx as usize,
175 "inc",
176 after,
177 std::panic::Location::caller(),
178 );
179 }
180 }
181
182 #[track_caller]
184 pub fn dec_ref(&mut self, block_idx: u32) -> bool {
185 let idx = block_idx as usize;
186 debug_assert!(idx < self.num_blocks);
187 debug_assert!(
193 self.block_ref_counts[idx] > 0,
194 "dec_ref on block with 0 refs"
195 );
196 if self.block_ref_counts[idx] == 0 {
197 let caller = std::panic::Location::caller();
198 tracing::error!(
199 "dec_ref on block {block_idx} with 0 refs (from {caller}) — refcount bug \
200 (ignoring; would otherwise wrap to u32::MAX and pin the block){}",
201 if self.trace.is_on() {
202 format!("\n history: {}", self.trace.dump(idx))
203 } else {
204 String::from(" [set ATLAS_KV_TRACE=1 for this block's ref history]")
205 }
206 );
207 return false;
208 }
209 self.block_ref_counts[idx] -= 1;
210 if self.trace.is_on() {
211 let after = self.block_ref_counts[idx];
212 self.trace
213 .record(idx, "dec", after, std::panic::Location::caller());
214 }
215 if self.block_ref_counts[idx] == 0 {
216 self.free_blocks.push(block_idx);
217 true
218 } else {
219 false
220 }
221 }
222
223 #[track_caller]
225 pub fn free_block(&mut self, block_idx: u32) {
226 self.dec_ref(block_idx);
227 }
228
229 #[track_caller]
231 pub fn free_blocks(&mut self, block_table: &[u32]) {
232 for &idx in block_table {
233 self.free_block(idx);
234 }
235 }
236
237 #[track_caller]
240 pub fn return_evicted_block(&mut self, block_idx: u32) {
241 let idx = block_idx as usize;
242 debug_assert!(idx < self.num_blocks);
243 if self.block_ref_counts[idx] == 0 {
263 tracing::warn!(
264 "return_evicted_block({block_idx}) with 0 refs (from {}) — the prefix cache \
265 returned a block it holds no reference on; ignoring (re-pushing it would \
266 duplicate a free-list entry and alias the block across sequences){}",
267 std::panic::Location::caller(),
268 if self.trace.is_on() {
269 format!("\n history: {}", self.trace.dump(idx))
270 } else {
271 String::new()
272 }
273 );
274 return;
275 }
276 self.block_ref_counts[idx] -= 1;
277 if self.trace.is_on() {
278 let after = self.block_ref_counts[idx];
279 self.trace
280 .record(idx, "evict_return", after, std::panic::Location::caller());
281 }
282 if self.block_ref_counts[idx] == 0 {
283 self.free_blocks.push(idx as u32);
284 }
285 }
286
287 pub fn ref_count(&self, block_idx: u32) -> u32 {
289 self.block_ref_counts[block_idx as usize]
290 }
291
292 pub fn num_free_blocks(&self) -> usize {
294 self.free_blocks.len()
295 }
296
297 pub fn k_cache_ptr(&self, layer_idx: usize, block_idx: u32) -> DevicePtr {
299 let layer = &self.layers[layer_idx];
300 layer
301 .k_pool
302 .offset(block_idx as usize * layer.k_block_stride)
303 }
304
305 pub fn v_cache_ptr(&self, layer_idx: usize, block_idx: u32) -> DevicePtr {
307 let layer = &self.layers[layer_idx];
308 layer
309 .v_pool
310 .offset(block_idx as usize * layer.v_block_stride)
311 }
312
313 fn bf16_reductions(buf: &[u8]) -> (f64, f64, f64) {
318 let (mut sum, mut ssq, mut sabs) = (0f64, 0f64, 0f64);
319 for c in buf.chunks_exact(2) {
320 let bits = u16::from_le_bytes([c[0], c[1]]);
321 let v = f32::from_bits((bits as u32) << 16) as f64;
322 sum += v;
323 ssq += v * v;
324 sabs += v.abs();
325 }
326 (sum, ssq, sabs)
327 }
328
329 pub fn debug_kv_checksum_per_layer(
339 &self,
340 blocks: &[u32],
341 boundary_idx: usize,
342 gpu: &dyn crate::gpu::GpuBackend,
343 stream: u64,
344 tag: &str,
345 ) {
346 gpu.synchronize(stream).ok();
347 let boundary = boundary_idx.min(blocks.len());
348 let regions: [(&str, &[u32]); 2] = [
349 ("prefix", &blocks[..boundary]),
350 ("suffix", &blocks[boundary..]),
351 ];
352 for (li, layer) in self.layers.iter().enumerate() {
353 if layer.dtype != super::KvCacheDtype::Bf16 {
354 if li == 0 {
355 tracing::warn!(
356 "ATLAS_KV_CKSUM[{tag}] layer 0 dtype={:?} != bf16 — probe \
357 only decodes BF16; skipping",
358 layer.dtype
359 );
360 }
361 continue;
362 }
363 let nbytes = layer.k_block_stride;
365 for (rname, rblocks) in ®ions {
366 let (mut k_sum, mut k_ssq, mut k_sabs) = (0f64, 0f64, 0f64);
367 let (mut v_sum, mut v_ssq, mut v_sabs) = (0f64, 0f64, 0f64);
368 for &blk in *rblocks {
369 let mut kb = vec![0u8; nbytes];
370 let mut vb = vec![0u8; nbytes];
371 if gpu.copy_d2h(self.k_cache_ptr(li, blk), &mut kb).is_err()
372 || gpu.copy_d2h(self.v_cache_ptr(li, blk), &mut vb).is_err()
373 {
374 continue;
375 }
376 let (ks, kq, ka) = Self::bf16_reductions(&kb);
377 let (vs, vq, va) = Self::bf16_reductions(&vb);
378 k_sum += ks;
379 k_ssq += kq;
380 k_sabs += ka;
381 v_sum += vs;
382 v_ssq += vq;
383 v_sabs += va;
384 }
385 tracing::warn!(
386 "ATLAS_KV_CKSUM[{tag}] L{li} {rname} nblk={} \
387 k_sum={k_sum:.4} k_ssq={k_ssq:.4} k_sabs={k_sabs:.4} \
388 v_sum={v_sum:.4} v_ssq={v_ssq:.4} v_sabs={v_sabs:.4}",
389 rblocks.len(),
390 );
391 }
392 }
393 }
394
395 pub fn debug_kv_per_block(
401 &self,
402 layer_idx: usize,
403 blocks: &[u32],
404 gpu: &dyn crate::gpu::GpuBackend,
405 stream: u64,
406 tag: &str,
407 ) {
408 gpu.synchronize(stream).ok();
409 let layer = &self.layers[layer_idx];
410 if layer.dtype != super::KvCacheDtype::Bf16 {
411 return;
412 }
413 let nbytes = layer.k_block_stride;
415 for (li, &blk) in blocks.iter().enumerate() {
416 let mut kb = vec![0u8; nbytes];
417 let mut vb = vec![0u8; nbytes];
418 if gpu
419 .copy_d2h(self.k_cache_ptr(layer_idx, blk), &mut kb)
420 .is_err()
421 || gpu
422 .copy_d2h(self.v_cache_ptr(layer_idx, blk), &mut vb)
423 .is_err()
424 {
425 continue;
426 }
427 let (_, k_ssq, _) = Self::bf16_reductions(&kb);
428 let (_, v_ssq, _) = Self::bf16_reductions(&vb);
429 tracing::warn!(
430 "ATLAS_KVBLK[{tag}] L{layer_idx} logical={li} phys={blk} \
431 k_ssq={k_ssq:.4} v_ssq={v_ssq:.4}"
432 );
433 }
434 }
435
436 pub fn k_pool_ptr(&self, layer_idx: usize) -> DevicePtr {
438 self.layers[layer_idx].k_pool
439 }
440
441 pub fn v_pool_ptr(&self, layer_idx: usize) -> DevicePtr {
443 self.layers[layer_idx].v_pool
444 }
445
446 pub fn cache_stride(&self) -> usize {
449 self.config.cache_stride_elements()
450 }
451
452 pub fn block_stride_bytes(&self) -> usize {
454 self.config.block_bytes()
455 }
456
457 pub fn block_stride_bytes_for_layer(&self, layer_idx: usize) -> usize {
462 self.layers[layer_idx].k_block_stride
463 }
464
465 pub fn k_block_stride_bytes_for_layer(&self, layer_idx: usize) -> usize {
468 self.layers[layer_idx].k_block_stride
469 }
470
471 pub fn v_block_stride_bytes_for_layer(&self, layer_idx: usize) -> usize {
474 self.layers[layer_idx].v_block_stride
475 }
476
477 pub fn nvfp4_data_bytes(&self) -> usize {
479 self.config.nvfp4_data_bytes()
480 }
481
482 pub fn turbo4_data_bytes(&self) -> usize {
484 self.config.turbo4_data_bytes()
485 }
486
487 pub fn turbo3_data_bytes(&self) -> usize {
489 self.config.turbo3_data_bytes()
490 }
491
492 pub fn turbo2_data_bytes(&self) -> usize {
494 self.config.turbo2_data_bytes()
495 }
496
497 pub fn turbo8_data_bytes(&self) -> usize {
499 self.config.turbo8_data_bytes()
500 }
501
502 pub fn turbo4_scale_bytes(&self) -> usize {
504 self.config.turbo4_scale_bytes()
505 }
506
507 pub fn config(&self) -> &KvCacheConfig {
510 &self.config
511 }
512
513 pub fn dtype_for_layer(&self, layer_idx: usize) -> KvCacheDtype {
515 self.layers[layer_idx].dtype
516 }
517
518 pub fn block_size(&self) -> usize {
519 self.config.block_size
520 }
521
522 pub fn num_blocks(&self) -> usize {
523 self.num_blocks
524 }
525
526 pub fn dtype(&self) -> KvCacheDtype {
527 self.config.dtype
528 }
529
530 pub fn num_layers(&self) -> usize {
532 self.config.num_layers
533 }
534
535 pub fn read_block(
540 &self,
541 layer_idx: usize,
542 block_idx: u32,
543 gpu: &dyn GpuBackend,
544 ) -> Result<(Vec<u8>, Vec<u8>)> {
545 let k_stride = self.layers[layer_idx].k_block_stride;
546 let v_stride = self.layers[layer_idx].v_block_stride;
547 let k_ptr = self.k_cache_ptr(layer_idx, block_idx);
548 let v_ptr = self.v_cache_ptr(layer_idx, block_idx);
549
550 let mut k_data = vec![0u8; k_stride];
551 let mut v_data = vec![0u8; v_stride];
552 gpu.copy_d2h(k_ptr, &mut k_data)?;
553 gpu.copy_d2h(v_ptr, &mut v_data)?;
554
555 Ok((k_data, v_data))
556 }
557
558 pub fn write_block(
560 &self,
561 layer_idx: usize,
562 block_idx: u32,
563 k_data: &[u8],
564 v_data: &[u8],
565 gpu: &dyn GpuBackend,
566 ) -> Result<()> {
567 let k_ptr = self.k_cache_ptr(layer_idx, block_idx);
568 let v_ptr = self.v_cache_ptr(layer_idx, block_idx);
569 gpu.copy_h2d(k_data, k_ptr)?;
570 gpu.copy_h2d(v_data, v_ptr)?;
571 Ok(())
572 }
573
574 pub fn compute_num_blocks(config: &KvCacheConfig, available_bytes: usize) -> Result<usize> {
577 let bytes_per_block = config.block_bytes_kv_all_layers();
578 if bytes_per_block == 0 {
579 bail!("KV cache block size is zero");
580 }
581 Ok(available_bytes / bytes_per_block)
582 }
583}