mistralrs_core/paged_attention/
mod.rs1pub mod block_hash;
3pub mod block_pool;
5mod cache_engine;
9mod config;
10pub mod encoder_cache;
12pub mod kv_cache_manager;
14mod layers;
15mod scheduler;
16pub const _PAD_SLOT_ID: i64 = -1;
17
18pub use cache_engine::{CacheConfig, CacheEngine, PagedCacheType};
19use candle_core::{DType, Device};
20pub use config::{KvCacheLayout, ModelConfigLike, ModelConfigMetadata};
21pub use kv_cache_manager::KVCacheManager;
22pub use layers::PagedAttention;
23pub use scheduler::{
24 PagedAttentionScheduler, PagedAttentionSchedulerConfig, PagedAttentionSchedulerOutput,
25};
26
27use crate::MemoryUsage;
28use tracing::info;
29
30pub const DEFAULT_PAGED_ATTENTION_BLOCK_SIZE: usize = 32;
31
32#[derive(Clone, Copy)]
34pub struct PagedAttentionConfig {
35 pub(crate) block_size: Option<usize>,
36 pub(crate) mem_gpu: MemoryGpuConfig,
37 pub(crate) cache_type: PagedCacheType,
38}
39
40impl PagedAttentionConfig {
41 pub fn new(
42 block_size: Option<usize>,
43 mem_gpu: MemoryGpuConfig,
44 cache_type: PagedCacheType,
45 ) -> anyhow::Result<Self> {
46 Ok(Self {
47 block_size,
48 mem_gpu,
49 cache_type,
50 })
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq)]
55pub enum AttentionImplementation {
56 Eager,
57 PagedAttention,
58}
59
60#[derive(Clone, Copy)]
61#[cfg_attr(feature = "pyo3_macros", pyo3::pyclass)]
62pub enum MemoryGpuConfig {
63 MbAmount(usize),
64 Utilization(f32),
65 ContextSize(usize),
66}
67
68const SUPPORTED_BLOCK_SIZE: &[usize] = &[8, 16, 32];
70
71const SIZE_IN_MB: usize = 1024 * 1024;
72
73macro_rules! mb_to_blocks {
74 ($mb_size:expr, $dtype_size:expr, $block_size:expr, $config:expr) => {
75 $mb_size
76 / $dtype_size
77 / $block_size
78 / $config.num_layers()
79 / $config.kv_cache_elements_per_token()
80 };
81}
82
83macro_rules! ctxt_to_blocks {
84 ($context_len:expr, $dtype_size:expr, $block_size:expr, $config:expr) => {
85 $context_len * $dtype_size * $config.num_layers() * $config.kv_cache_elements_per_token()
86 };
87}
88
89#[allow(clippy::too_many_arguments)]
103pub fn calculate_cache_config(
104 mem_gpu: MemoryGpuConfig,
105 block_size: Option<usize>,
106 dtype: DType,
107 cache_type: PagedCacheType,
108 config: &dyn ModelConfigLike,
109 device: &Device,
110 layer_devices: &[Option<Device>],
111 silent: bool,
112 model_weight_size_in_bytes: Option<usize>,
113 max_num_tokens: Option<usize>,
114) -> anyhow::Result<CacheConfig> {
115 let block_size = block_size.unwrap_or(DEFAULT_PAGED_ATTENTION_BLOCK_SIZE);
116 if !SUPPORTED_BLOCK_SIZE.contains(&block_size) {
117 anyhow::bail!("Block size must be in {SUPPORTED_BLOCK_SIZE:?}, got {block_size}");
118 }
119 let dtype = cache_type.to_dtype(dtype);
120 let dtype_size = dtype.size_in_bytes();
121
122 let num_devices = layer_devices.len().max(1);
124 let model_weight_per_device_mb =
125 model_weight_size_in_bytes.unwrap_or(0) / num_devices / SIZE_IN_MB;
126
127 let mut min_mem_gpu = usize::MAX;
128 for dev in layer_devices {
129 let device = dev.as_ref().unwrap_or(device);
130
131 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
132 let mem_gpu = match mem_gpu {
133 MemoryGpuConfig::MbAmount(v) => v,
134 MemoryGpuConfig::Utilization(f) => {
135 let total = MemoryUsage.get_total_memory(device)? as f32 / SIZE_IN_MB as f32;
136 if model_weight_size_in_bytes.is_some() {
137 (total * f - model_weight_per_device_mb as f32).max(0.0) as usize
139 } else {
140 let free = MemoryUsage.get_memory_available(device)? as f32 / SIZE_IN_MB as f32;
141 #[allow(unused_mut)]
142 let mut used = total - free;
143 #[cfg(feature = "metal")]
148 if let Device::Metal(dev) = device {
149 used = dev.current_allocated_size() as f32 / SIZE_IN_MB as f32;
150 }
151 (total * f - used).max(0.0) as usize
152 }
153 }
154 MemoryGpuConfig::ContextSize(toks) => {
155 ctxt_to_blocks!(toks, dtype_size, block_size, config) / SIZE_IN_MB
157 }
158 };
159 min_mem_gpu = min_mem_gpu.min(mem_gpu);
160 }
161
162 #[allow(unused_mut, unused_variables)]
167 let mut mem_gpu = min_mem_gpu;
168 if device.is_metal() {
169 let max_tokens = max_num_tokens.unwrap_or(config.max_seq_len());
170 let mem_for_tokens =
171 ctxt_to_blocks!(max_tokens, dtype_size, block_size, config) / SIZE_IN_MB;
172 if mem_for_tokens < mem_gpu {
173 if !silent {
174 info!(
175 "Metal: capping KV cache from {} MB to {} MB ({} tokens).",
176 mem_gpu, mem_for_tokens, max_tokens
177 );
178 }
179 mem_gpu = mem_for_tokens;
180 }
181 }
182
183 let num_gpu_blocks = mb_to_blocks!(mem_gpu * SIZE_IN_MB, dtype_size, block_size, config);
184 if num_gpu_blocks == 0 {
185 anyhow::bail!("Num GPU blocks is 0. This means there is not enough memory. Either reduce the memory amount/utilization/context size or disable PagedAttention.");
186 }
187
188 if !silent {
189 info!("Allocating {mem_gpu} MB for PagedAttention KV cache per GPU");
190 info!("PagedAttention KV cache type is {dtype:?}");
191 info!("Using PagedAttention with block size {block_size} and {num_gpu_blocks} GPU blocks: available context length is {} tokens", num_gpu_blocks*block_size);
192 }
193 Ok(CacheConfig {
194 block_size,
195 num_gpu_blocks,
196 cache_type,
197 })
198}