llama_cpp_4/context/kv_cache.rs
1//! utilities for working with the kv cache
2
3use crate::context::LlamaContext;
4use std::num::{NonZeroU8, TryFromIntError};
5
6/// Errors that can occur when attempting to prepare values for the kv cache
7#[derive(Debug, Eq, PartialEq, thiserror::Error)]
8pub enum KvCacheConversionError {
9 /// Sequence id conversion to i32 failed
10 #[error("Provided sequence id is too large for a i32")]
11 SeqIdTooLarge(#[source] TryFromIntError),
12 /// Position 0 conversion to i32 failed
13 #[error("Provided start position is too large for a i32")]
14 P0TooLarge(#[source] TryFromIntError),
15 /// Position 1 conversion to i32 failed
16 #[error("Provided end position is too large for a i32")]
17 P1TooLarge(#[source] TryFromIntError),
18}
19
20impl LlamaContext<'_> {
21 /// How many backend buffers a state write of this context's memory would
22 /// touch.
23 ///
24 /// `0` for an empty cache. Useful before
25 /// [`state_seq_save_file`](crate::context::LlamaContext::state_seq_save_file)
26 /// and friends: a KV cache spread across several backends is written buffer
27 /// by buffer, so the count is a proxy for how much work a save will be, and
28 /// a cache reporting zero has nothing to save yet.
29 ///
30 /// Wraps `llama_memory_get_buffers` through the `ext_shim`, because
31 /// upstream returns a `std::vector` by value and bindgen cannot express
32 /// that — see the note in `build.rs`.
33 #[must_use]
34 pub fn memory_buffer_count(&self) -> usize {
35 // SAFETY: the memory handle is borrowed from a live context, and a null
36 // `out` asks the shim for the count without writing anything.
37 unsafe {
38 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
39 llama_cpp_sys_4::llama_memory_buffers_collect(mem, std::ptr::null_mut(), 0)
40 }
41 }
42
43 /// Copy the cache from one sequence to another.
44 ///
45 /// # Parameters
46 ///
47 /// * `src` - The sequence id to copy the cache from.
48 /// * `dest` - The sequence id to copy the cache to.
49 /// * `size` - The size of the cache to copy.
50 pub fn copy_cache(&mut self, src: i32, dest: i32, size: i32) {
51 // SAFETY: `self.context` is non-null and kept alive by this borrow, so the memory handle
52 // `llama_get_memory` returns is borrowed from it and valid for the rest of
53 // this block. The sequence and position arguments are scalars llama.cpp
54 // range-checks itself.
55 unsafe {
56 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
57 llama_cpp_sys_4::llama_memory_seq_cp(mem, src, dest, 0, size);
58 }
59 }
60
61 /// Copy the cache from one sequence to another.
62 ///
63 /// # Parameters
64 ///
65 /// * `src` - The sequence id to copy the cache from.
66 /// * `dest` - The sequence id to copy the cache to.
67 /// * `p0` - The start position of the cache to clear. If `None`, the entire cache is copied up to `p1`.
68 /// * `p1` - The end position of the cache to clear. If `None`, the entire cache is copied starting from `p0`.
69 ///
70 /// # Errors
71 ///
72 /// Returns [`KvCacheConversionError`] if either position exceeds the maximum `i32` value.
73 pub fn copy_kv_cache_seq(
74 &mut self,
75 src: i32,
76 dest: i32,
77 p0: Option<u32>,
78 p1: Option<u32>,
79 ) -> Result<(), KvCacheConversionError> {
80 let p0 = p0
81 .map_or(Ok(-1), i32::try_from)
82 .map_err(KvCacheConversionError::P0TooLarge)?;
83 let p1 = p1
84 .map_or(Ok(-1), i32::try_from)
85 .map_err(KvCacheConversionError::P1TooLarge)?;
86 // SAFETY: `self.context` is non-null and kept alive by this borrow, so the memory handle
87 // `llama_get_memory` returns is borrowed from it and valid for the rest of
88 // this block. The sequence and position arguments are scalars llama.cpp
89 // range-checks itself.
90 unsafe {
91 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
92 llama_cpp_sys_4::llama_memory_seq_cp(mem, src, dest, p0, p1);
93 }
94 Ok(())
95 }
96
97 /// Clear the kv cache for the given sequence within the specified range `[p0, p1)`.
98 ///
99 /// Returns `false` only when partial sequence removals fail. Full sequence removals always succeed.
100 ///
101 /// # Parameters
102 ///
103 /// * `src` - The sequence id to clear the cache for. If `None`, matches all sequences
104 /// * `p0` - The start position of the cache to clear. If `None`, the entire cache is cleared up to `p1`.
105 /// * `p1` - The end position of the cache to clear. If `None`, the entire cache is cleared from `p0`.
106 ///
107 /// # Errors
108 ///
109 /// Returns [`KvCacheConversionError`] if the sequence id or either position exceeds the maximum `i32` value.
110 pub fn clear_kv_cache_seq(
111 &mut self,
112 src: Option<u32>,
113 p0: Option<u32>,
114 p1: Option<u32>,
115 ) -> Result<bool, KvCacheConversionError> {
116 let src = src
117 .map_or(Ok(-1), i32::try_from)
118 .map_err(KvCacheConversionError::SeqIdTooLarge)?;
119 let p0 = p0
120 .map_or(Ok(-1), i32::try_from)
121 .map_err(KvCacheConversionError::P0TooLarge)?;
122 let p1 = p1
123 .map_or(Ok(-1), i32::try_from)
124 .map_err(KvCacheConversionError::P1TooLarge)?;
125 // SAFETY: `self.context` is non-null and kept alive by this borrow, so the memory handle
126 // `llama_get_memory` returns is borrowed from it and valid for the rest of
127 // this block. The sequence and position arguments are scalars llama.cpp
128 // range-checks itself.
129 Ok(unsafe {
130 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
131 llama_cpp_sys_4::llama_memory_seq_rm(mem, src, p0, p1)
132 })
133 }
134
135 /// Clear the KV cache
136 pub fn clear_kv_cache(&mut self) {
137 // SAFETY: `self.context` is non-null and kept alive by this borrow, so the memory handle
138 // `llama_get_memory` returns is borrowed from it and valid for the rest of
139 // this block. The sequence and position arguments are scalars llama.cpp
140 // range-checks itself.
141 unsafe {
142 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
143 llama_cpp_sys_4::llama_memory_clear(mem, true);
144 }
145 }
146
147 /// Removes all tokens that do not belong to the specified sequence
148 ///
149 /// # Parameters
150 ///
151 /// * `seq_id` - The sequence id to keep
152 pub fn llama_kv_cache_seq_keep(&mut self, seq_id: i32) {
153 // SAFETY: `self.context` is non-null and kept alive by this borrow, so the memory handle
154 // `llama_get_memory` returns is borrowed from it and valid for the rest of
155 // this block. The sequence and position arguments are scalars llama.cpp
156 // range-checks itself.
157 unsafe {
158 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
159 llama_cpp_sys_4::llama_memory_seq_keep(mem, seq_id);
160 }
161 }
162
163 #[allow(clippy::doc_markdown)]
164 /// Adds relative position "delta" to all tokens that belong to the specified sequence and have positions in `[p0, p1)`.
165 ///
166 /// # Parameters
167 ///
168 /// * `seq_id` - The sequence id to update
169 /// * `p0` - The start position of the cache to update. If `None`, the entire cache is updated up to `p1`.
170 /// * `p1` - The end position of the cache to update. If `None`, the entire cache is updated starting from `p0`.
171 /// * `delta` - The relative position to add to the tokens
172 ///
173 /// # Errors
174 ///
175 /// Returns [`KvCacheConversionError`] if either position exceeds the maximum `i32` value.
176 pub fn kv_cache_seq_add(
177 &mut self,
178 seq_id: i32,
179 p0: Option<u32>,
180 p1: Option<u32>,
181 delta: i32,
182 ) -> Result<(), KvCacheConversionError> {
183 let p0 = p0
184 .map_or(Ok(-1), i32::try_from)
185 .map_err(KvCacheConversionError::P0TooLarge)?;
186 let p1 = p1
187 .map_or(Ok(-1), i32::try_from)
188 .map_err(KvCacheConversionError::P1TooLarge)?;
189 // SAFETY: `self.context` is non-null and kept alive by this borrow, so the memory handle
190 // `llama_get_memory` returns is borrowed from it and valid for the rest of
191 // this block. The sequence and position arguments are scalars llama.cpp
192 // range-checks itself.
193 unsafe {
194 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
195 llama_cpp_sys_4::llama_memory_seq_add(mem, seq_id, p0, p1, delta);
196 }
197 Ok(())
198 }
199
200 /// Integer division of the positions by factor of `d > 1`.
201 ///
202 /// # Parameters
203 ///
204 /// * `seq_id` - The sequence id to update
205 /// * `p0` - The start position of the cache to update. If `None`, the entire cache is updated up to `p1`.
206 /// * `p1` - The end position of the cache to update. If `None`, the entire cache is updated starting from `p0`.
207 /// * `d` - The factor to divide the positions by
208 ///
209 /// # Errors
210 ///
211 /// Returns [`KvCacheConversionError`] if either position exceeds the maximum `i32` value.
212 pub fn kv_cache_seq_div(
213 &mut self,
214 seq_id: i32,
215 p0: Option<u32>,
216 p1: Option<u32>,
217 d: NonZeroU8,
218 ) -> Result<(), KvCacheConversionError> {
219 let p0 = p0
220 .map_or(Ok(-1), i32::try_from)
221 .map_err(KvCacheConversionError::P0TooLarge)?;
222 let p1 = p1
223 .map_or(Ok(-1), i32::try_from)
224 .map_err(KvCacheConversionError::P1TooLarge)?;
225 let d = i32::from(d.get());
226 // SAFETY: `self.context` is non-null and kept alive by this borrow, so the memory handle
227 // `llama_get_memory` returns is borrowed from it and valid for the rest of
228 // this block. The sequence and position arguments are scalars llama.cpp
229 // range-checks itself.
230 unsafe {
231 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
232 llama_cpp_sys_4::llama_memory_seq_div(mem, seq_id, p0, p1, d);
233 }
234 Ok(())
235 }
236
237 /// Returns the largest position present in the KV cache for the specified sequence
238 ///
239 /// # Parameters
240 ///
241 /// * `seq_id` - The sequence id to get the max position for
242 #[must_use]
243 pub fn kv_cache_seq_pos_max(&self, seq_id: i32) -> i32 {
244 // SAFETY: `self.context` is non-null and kept alive by this borrow, so the memory handle
245 // `llama_get_memory` returns is borrowed from it and valid for the rest of
246 // this block. The sequence and position arguments are scalars llama.cpp
247 // range-checks itself.
248 unsafe {
249 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
250 llama_cpp_sys_4::llama_memory_seq_pos_max(mem, seq_id)
251 }
252 }
253}