Skip to main content

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}