llama_cpp_2/context/session.rs
1//! utilities for working with session files
2
3use crate::context::LlamaContext;
4use crate::token::LlamaToken;
5use std::ffi::{CString, NulError};
6use std::fmt::{Debug, Formatter};
7use std::path::{Path, PathBuf};
8
9/// Flags for state sequence operations.
10///
11/// These flags control what parts of the state are included when saving/restoring
12/// sequence state.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct LlamaStateSeqFlags(pub(crate) llama_cpp_sys_2::llama_state_seq_flags);
15
16impl LlamaStateSeqFlags {
17 /// Work only with partial states, such as SWA KV cache or recurrent cache (e.g. Mamba).
18 ///
19 /// This flag is useful when you only want to save/restore the recurrent state
20 /// without affecting the KV cache.
21 pub const PARTIAL_ONLY: LlamaStateSeqFlags = LlamaStateSeqFlags(1);
22
23 /// Keep the copied data on device (GPU) memory rather than host.
24 ///
25 /// Getting the state for a `seq_id` with this flag invalidates all
26 /// prior states obtained for that `seq_id` with this flag set.
27 pub const ON_DEVICE: LlamaStateSeqFlags = LlamaStateSeqFlags(2);
28
29 /// Create an empty flags set.
30 pub const fn empty() -> LlamaStateSeqFlags {
31 LlamaStateSeqFlags(0)
32 }
33
34 /// Get the raw flags value.
35 pub const fn bits(&self) -> u32 {
36 self.0
37 }
38
39 /// Construct from raw bits.
40 pub const fn from_bits(bits: u32) -> LlamaStateSeqFlags {
41 LlamaStateSeqFlags(bits)
42 }
43
44 /// Check if a flag is set.
45 pub const fn contains(&self, other: LlamaStateSeqFlags) -> bool {
46 (self.0 & other.0) != 0
47 }
48}
49
50impl Default for LlamaStateSeqFlags {
51 fn default() -> Self {
52 Self::empty()
53 }
54}
55
56impl std::ops::BitOr for LlamaStateSeqFlags {
57 type Output = Self;
58 fn bitor(self, other: Self) -> Self {
59 Self(self.0 | other.0)
60 }
61}
62
63/// Failed to save a sequence state file
64#[derive(Debug, Eq, PartialEq, thiserror::Error)]
65pub enum SaveSeqStateError {
66 /// llama.cpp failed to save the sequence state file
67 #[error("Failed to save sequence state file")]
68 FailedToSave,
69
70 /// null byte in string
71 #[error("null byte in string {0}")]
72 NullError(#[from] NulError),
73
74 /// failed to convert path to str
75 #[error("failed to convert path {0} to str")]
76 PathToStrError(PathBuf),
77}
78
79/// Failed to load a sequence state file
80#[derive(Debug, Eq, PartialEq, thiserror::Error)]
81pub enum LoadSeqStateError {
82 /// llama.cpp failed to load the sequence state file
83 #[error("Failed to load sequence state file")]
84 FailedToLoad,
85
86 /// null byte in string
87 #[error("null byte in string {0}")]
88 NullError(#[from] NulError),
89
90 /// failed to convert path to str
91 #[error("failed to convert path {0} to str")]
92 PathToStrError(PathBuf),
93
94 /// Insufficient max length
95 #[error("max_length is not large enough to hold {n_out} (was {max_tokens})")]
96 InsufficientMaxLength {
97 /// The length of the loaded sequence
98 n_out: usize,
99 /// The maximum length
100 max_tokens: usize,
101 },
102}
103
104/// Failed to save a Session file
105#[derive(Debug, Eq, PartialEq, thiserror::Error)]
106pub enum SaveSessionError {
107 /// llama.cpp failed to save the session file
108 #[error("Failed to save session file")]
109 FailedToSave,
110
111 /// null byte in string
112 #[error("null byte in string {0}")]
113 NullError(#[from] NulError),
114
115 /// failed to convert path to str
116 #[error("failed to convert path {0} to str")]
117 PathToStrError(PathBuf),
118}
119
120/// Failed to load a Session file
121#[derive(Debug, Eq, PartialEq, thiserror::Error)]
122pub enum LoadSessionError {
123 /// llama.cpp failed to load the session file
124 #[error("Failed to load session file")]
125 FailedToLoad,
126
127 /// null byte in string
128 #[error("null byte in string {0}")]
129 NullError(#[from] NulError),
130
131 /// failed to convert path to str
132 #[error("failed to convert path {0} to str")]
133 PathToStrError(PathBuf),
134
135 /// Insufficient max length
136 #[error("max_length is not large enough to hold {n_out} (was {max_tokens})")]
137 InsufficientMaxLength {
138 /// The length of the session file
139 n_out: usize,
140 /// The maximum length
141 max_tokens: usize,
142 },
143}
144
145impl LlamaContext<'_> {
146 /// Save the current session to a file.
147 ///
148 /// # Parameters
149 ///
150 /// * `path_session` - The file to save to.
151 /// * `tokens` - The tokens to associate the session with. This should be a prefix of a sequence of tokens that the context has processed, so that the relevant KV caches are already filled.
152 ///
153 /// # Errors
154 ///
155 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to save the session file.
156 #[deprecated(since = "0.1.136", note = "Use `state_save_file` instead")]
157 pub fn save_session_file(
158 &self,
159 path_session: impl AsRef<Path>,
160 tokens: &[LlamaToken],
161 ) -> Result<(), SaveSessionError> {
162 let path = path_session.as_ref();
163 let path = path
164 .to_str()
165 .ok_or_else(|| SaveSessionError::PathToStrError(path.to_path_buf()))?;
166
167 let cstr = CString::new(path)?;
168
169 if unsafe {
170 llama_cpp_sys_2::llama_save_session_file(
171 self.context.as_ptr(),
172 cstr.as_ptr(),
173 tokens.as_ptr().cast::<llama_cpp_sys_2::llama_token>(),
174 tokens.len(),
175 )
176 } {
177 Ok(())
178 } else {
179 Err(SaveSessionError::FailedToSave)
180 }
181 }
182 /// Load a session file into the current context.
183 ///
184 /// You still need to pass the returned tokens to the context for inference to work. What this function buys you is that the KV caches are already filled with the relevant data.
185 ///
186 /// # Parameters
187 ///
188 /// * `path_session` - The file to load from. It must be a session file from a compatible context, otherwise the function will error.
189 /// * `max_tokens` - The maximum token length of the loaded session. If the session was saved with a longer length, the function will error.
190 ///
191 /// # Errors
192 ///
193 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to load the session file. (e.g. the file does not exist, is not a session file, etc.)
194 #[deprecated(since = "0.1.136", note = "Use `state_load_file` instead")]
195 pub fn load_session_file(
196 &mut self,
197 path_session: impl AsRef<Path>,
198 max_tokens: usize,
199 ) -> Result<Vec<LlamaToken>, LoadSessionError> {
200 let path = path_session.as_ref();
201 let path = path
202 .to_str()
203 .ok_or(LoadSessionError::PathToStrError(path.to_path_buf()))?;
204
205 let cstr = CString::new(path)?;
206 let mut tokens: Vec<LlamaToken> = Vec::with_capacity(max_tokens);
207 let mut n_out = 0;
208
209 // SAFETY: cast is valid as LlamaToken is repr(transparent)
210 let tokens_out = tokens.as_mut_ptr().cast::<llama_cpp_sys_2::llama_token>();
211
212 let load_session_success = unsafe {
213 llama_cpp_sys_2::llama_load_session_file(
214 self.context.as_ptr(),
215 cstr.as_ptr(),
216 tokens_out,
217 max_tokens,
218 &mut n_out,
219 )
220 };
221 if load_session_success {
222 if n_out > max_tokens {
223 return Err(LoadSessionError::InsufficientMaxLength { n_out, max_tokens });
224 }
225 // SAFETY: we checked that n_out <= max_tokens and llama.cpp promises that n_out tokens will be written
226 unsafe {
227 tokens.set_len(n_out);
228 }
229 Ok(tokens)
230 } else {
231 Err(LoadSessionError::FailedToLoad)
232 }
233 }
234
235 /// Save the full state to a file.
236 ///
237 /// This is the non-deprecated replacement for [`save_session_file`](Self::save_session_file).
238 ///
239 /// # Parameters
240 ///
241 /// * `path_session` - The file to save to.
242 /// * `tokens` - The tokens to associate the state with. This should be a prefix of a sequence
243 /// of tokens that the context has processed, so that the relevant KV caches are already filled.
244 ///
245 /// # Errors
246 ///
247 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to save
248 /// the state file.
249 pub fn state_save_file(
250 &self,
251 path_session: impl AsRef<Path>,
252 tokens: &[LlamaToken],
253 ) -> Result<(), SaveSessionError> {
254 let path = path_session.as_ref();
255 let path = path
256 .to_str()
257 .ok_or_else(|| SaveSessionError::PathToStrError(path.to_path_buf()))?;
258
259 let cstr = CString::new(path)?;
260
261 if unsafe {
262 llama_cpp_sys_2::llama_state_save_file(
263 self.context.as_ptr(),
264 cstr.as_ptr(),
265 tokens.as_ptr().cast::<llama_cpp_sys_2::llama_token>(),
266 tokens.len(),
267 )
268 } {
269 Ok(())
270 } else {
271 Err(SaveSessionError::FailedToSave)
272 }
273 }
274
275 /// Load a state file into the current context.
276 ///
277 /// This is the non-deprecated replacement for [`load_session_file`](Self::load_session_file).
278 ///
279 /// You still need to pass the returned tokens to the context for inference to work. What this
280 /// function buys you is that the KV caches are already filled with the relevant data.
281 ///
282 /// # Parameters
283 ///
284 /// * `path_session` - The file to load from. It must be a state file from a compatible context,
285 /// otherwise the function will error.
286 /// * `max_tokens` - The maximum token length of the loaded state. If the state was saved with a
287 /// longer length, the function will error.
288 ///
289 /// # Errors
290 ///
291 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to load
292 /// the state file.
293 pub fn state_load_file(
294 &mut self,
295 path_session: impl AsRef<Path>,
296 max_tokens: usize,
297 ) -> Result<Vec<LlamaToken>, LoadSessionError> {
298 let path = path_session.as_ref();
299 let path = path
300 .to_str()
301 .ok_or(LoadSessionError::PathToStrError(path.to_path_buf()))?;
302
303 let cstr = CString::new(path)?;
304 let mut tokens: Vec<LlamaToken> = Vec::with_capacity(max_tokens);
305 let mut n_out = 0;
306
307 // SAFETY: cast is valid as LlamaToken is repr(transparent)
308 let tokens_out = tokens.as_mut_ptr().cast::<llama_cpp_sys_2::llama_token>();
309
310 let success = unsafe {
311 llama_cpp_sys_2::llama_state_load_file(
312 self.context.as_ptr(),
313 cstr.as_ptr(),
314 tokens_out,
315 max_tokens,
316 &mut n_out,
317 )
318 };
319 if success {
320 if n_out > max_tokens {
321 return Err(LoadSessionError::InsufficientMaxLength { n_out, max_tokens });
322 }
323 // SAFETY: we checked that n_out <= max_tokens and llama.cpp promises that n_out tokens will be written
324 unsafe {
325 tokens.set_len(n_out);
326 }
327 Ok(tokens)
328 } else {
329 Err(LoadSessionError::FailedToLoad)
330 }
331 }
332
333 /// Save state for a single sequence to a file.
334 ///
335 /// This enables saving state for individual sequences, which is useful for multi-sequence
336 /// inference scenarios.
337 ///
338 /// # Parameters
339 ///
340 /// * `filepath` - The file to save to.
341 /// * `seq_id` - The sequence ID whose state to save.
342 /// * `tokens` - The tokens to associate with the saved state.
343 ///
344 /// # Errors
345 ///
346 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to save
347 /// the sequence state file.
348 ///
349 /// # Returns
350 ///
351 /// The number of bytes written on success.
352 pub fn state_seq_save_file(
353 &self,
354 filepath: impl AsRef<Path>,
355 seq_id: i32,
356 tokens: &[LlamaToken],
357 ) -> Result<usize, SaveSeqStateError> {
358 let path = filepath.as_ref();
359 let path = path
360 .to_str()
361 .ok_or_else(|| SaveSeqStateError::PathToStrError(path.to_path_buf()))?;
362
363 let cstr = CString::new(path)?;
364
365 let bytes_written = unsafe {
366 llama_cpp_sys_2::llama_state_seq_save_file(
367 self.context.as_ptr(),
368 cstr.as_ptr(),
369 seq_id,
370 tokens.as_ptr().cast::<llama_cpp_sys_2::llama_token>(),
371 tokens.len(),
372 )
373 };
374
375 if bytes_written == 0 {
376 Err(SaveSeqStateError::FailedToSave)
377 } else {
378 Ok(bytes_written)
379 }
380 }
381
382 /// Load state for a single sequence from a file.
383 ///
384 /// This enables loading state for individual sequences, which is useful for multi-sequence
385 /// inference scenarios.
386 ///
387 /// # Parameters
388 ///
389 /// * `filepath` - The file to load from.
390 /// * `dest_seq_id` - The destination sequence ID to load the state into.
391 /// * `max_tokens` - The maximum number of tokens to read.
392 ///
393 /// # Errors
394 ///
395 /// Fails if the path is not a valid utf8, is not a valid c string, or llama.cpp fails to load
396 /// the sequence state file.
397 ///
398 /// # Returns
399 ///
400 /// A tuple of `(tokens, bytes_read)` on success.
401 pub fn state_seq_load_file(
402 &mut self,
403 filepath: impl AsRef<Path>,
404 dest_seq_id: i32,
405 max_tokens: usize,
406 ) -> Result<(Vec<LlamaToken>, usize), LoadSeqStateError> {
407 let path = filepath.as_ref();
408 let path = path
409 .to_str()
410 .ok_or(LoadSeqStateError::PathToStrError(path.to_path_buf()))?;
411
412 let cstr = CString::new(path)?;
413 let mut tokens: Vec<LlamaToken> = Vec::with_capacity(max_tokens);
414 let mut n_out = 0;
415
416 // SAFETY: cast is valid as LlamaToken is repr(transparent)
417 let tokens_out = tokens.as_mut_ptr().cast::<llama_cpp_sys_2::llama_token>();
418
419 let bytes_read = unsafe {
420 llama_cpp_sys_2::llama_state_seq_load_file(
421 self.context.as_ptr(),
422 cstr.as_ptr(),
423 dest_seq_id,
424 tokens_out,
425 max_tokens,
426 &mut n_out,
427 )
428 };
429
430 if bytes_read == 0 {
431 return Err(LoadSeqStateError::FailedToLoad);
432 }
433
434 if n_out > max_tokens {
435 return Err(LoadSeqStateError::InsufficientMaxLength { n_out, max_tokens });
436 }
437
438 // SAFETY: we checked that n_out <= max_tokens and llama.cpp promises that n_out tokens will be written
439 unsafe {
440 tokens.set_len(n_out);
441 }
442
443 Ok((tokens, bytes_read))
444 }
445
446 /// Returns the maximum size in bytes of the state (rng, logits, embedding
447 /// and `kv_cache`) - will often be smaller after compacting tokens
448 #[must_use]
449 pub fn get_state_size(&self) -> usize {
450 unsafe { llama_cpp_sys_2::llama_get_state_size(self.context.as_ptr()) }
451 }
452
453 /// Copies the state to the specified destination address.
454 ///
455 /// Returns the number of bytes copied
456 ///
457 /// # Safety
458 ///
459 /// Destination needs to have allocated enough memory.
460 pub unsafe fn copy_state_data(&self, dest: *mut u8) -> usize {
461 unsafe { llama_cpp_sys_2::llama_copy_state_data(self.context.as_ptr(), dest) }
462 }
463
464 /// Set the state reading from the specified address
465 /// Returns the number of bytes read
466 ///
467 /// # Safety
468 ///
469 /// help wanted: not entirely sure what the safety requirements are here.
470 pub unsafe fn set_state_data(&mut self, src: &[u8]) -> usize {
471 unsafe { llama_cpp_sys_2::llama_set_state_data(self.context.as_ptr(), src.as_ptr()) }
472 }
473
474 /// Get the size of the state for a single sequence with optional flags.
475 ///
476 /// This is the extended version that supports flags for partial state operations.
477 ///
478 /// # Parameters
479 ///
480 /// * `seq_id` - The sequence ID to get the state size for.
481 /// * `flags` - Optional flags (e.g., [`LlamaStateSeqFlags::PARTIAL_ONLY`]).
482 ///
483 /// # Returns
484 ///
485 /// The size in bytes needed to store the sequence state.
486 #[must_use]
487 pub fn state_seq_get_size_ext(&self, seq_id: i32, flags: LlamaStateSeqFlags) -> usize {
488 unsafe {
489 llama_cpp_sys_2::llama_state_seq_get_size_ext(self.context.as_ptr(), seq_id, flags.0)
490 }
491 }
492
493 /// Copy the state of a single sequence into the specified buffer with optional flags.
494 ///
495 /// This is the extended version that supports flags for partial state operations.
496 ///
497 /// # Parameters
498 ///
499 /// * `dest` - Destination buffer to copy state into.
500 /// * `seq_id` - The sequence ID to get the state for.
501 /// * `flags` - Optional flags (e.g., [`LlamaStateSeqFlags::PARTIAL_ONLY`]).
502 ///
503 /// # Safety
504 ///
505 /// Destination needs to have allocated enough memory.
506 ///
507 /// # Returns
508 ///
509 /// The number of bytes copied.
510 pub unsafe fn state_seq_get_data_ext(
511 &self,
512 dest: *mut u8,
513 seq_id: i32,
514 flags: LlamaStateSeqFlags,
515 ) -> usize {
516 unsafe {
517 llama_cpp_sys_2::llama_state_seq_get_data_ext(
518 self.context.as_ptr(),
519 dest,
520 usize::MAX,
521 seq_id,
522 flags.0,
523 )
524 }
525 }
526
527 /// Set the state for a single sequence from the specified buffer with optional flags.
528 ///
529 /// This is the extended version that supports flags for partial state operations.
530 /// Useful for restoring only the recurrent/partial state without affecting the KV cache.
531 ///
532 /// # Parameters
533 ///
534 /// * `src` - Source buffer containing the state data.
535 /// * `dest_seq_id` - The destination sequence ID to load the state into.
536 /// * `flags` - Optional flags (e.g., [`LlamaStateSeqFlags::PARTIAL_ONLY`]).
537 ///
538 /// # Safety
539 ///
540 /// The source buffer must contain valid state data.
541 ///
542 /// # Returns
543 ///
544 /// Positive on success, zero on failure.
545 pub unsafe fn state_seq_set_data_ext(
546 &mut self,
547 src: &[u8],
548 dest_seq_id: i32,
549 flags: LlamaStateSeqFlags,
550 ) -> bool {
551 unsafe {
552 llama_cpp_sys_2::llama_state_seq_set_data_ext(
553 self.context.as_ptr(),
554 src.as_ptr(),
555 src.len(),
556 dest_seq_id,
557 flags.0,
558 ) > 0
559 }
560 }
561
562 /// Serialize sequence `seq_id`'s state into an opaque [`SeqState`].
563 ///
564 /// Enables save/restore of context state at arbitrary points in a
565 /// sequence. This is particularly useful on architectures where
566 /// `clear_kv_cache_seq` cannot roll back partial state (Mamba, RWKV,
567 /// Gated Delta Networks, and other recurrent / hybrid-recurrent
568 /// models): pair with [`LlamaStateSeqFlags::PARTIAL_ONLY`] to save
569 /// just the running recurrent and SWA state, then restore it via
570 /// [`Self::state_seq_set`] to effectively "rewind" the sequence.
571 ///
572 /// The returned [`SeqState`] is opaque — its bytes cannot be
573 /// inspected or forged from safe code, so [`Self::state_seq_set`]
574 /// only ever sees data produced by this method.
575 ///
576 /// Wraps `llama_state_seq_get_data_ext`.
577 ///
578 /// # Errors
579 ///
580 /// Returns [`StateSeqError::SizeMismatch`] if llama.cpp writes a
581 /// different number of bytes than the reported state size.
582 pub fn state_seq_get(
583 &self,
584 seq_id: i32,
585 flags: LlamaStateSeqFlags,
586 ) -> Result<SeqState, crate::StateSeqError> {
587 let size = unsafe {
588 llama_cpp_sys_2::llama_state_seq_get_size_ext(self.context.as_ptr(), seq_id, flags.0)
589 };
590 let mut bytes = vec![0u8; size];
591 let n = unsafe {
592 llama_cpp_sys_2::llama_state_seq_get_data_ext(
593 self.context.as_ptr(),
594 bytes.as_mut_ptr(),
595 size,
596 seq_id,
597 flags.0,
598 )
599 };
600 if n != size {
601 return Err(crate::StateSeqError::SizeMismatch {
602 expected: size,
603 actual: n,
604 });
605 }
606 Ok(SeqState { bytes, flags })
607 }
608
609 /// Restore sequence state previously captured by [`Self::state_seq_get`]
610 /// into `seq_id`.
611 ///
612 /// Cross-sequence restore (`seq_id` different from the sequence the
613 /// state was captured from) is supported — llama.cpp treats the
614 /// destination sequence id independently of the source.
615 ///
616 /// Wraps `llama_state_seq_set_data_ext`.
617 ///
618 /// # Errors
619 ///
620 /// Returns [`StateSeqError::SizeMismatch`] if llama.cpp reads a
621 /// different number of bytes than the state buffer contains — this
622 /// covers shape mismatches (different `n_ctx`, `n_layer`, quantization,
623 /// etc.) that llama.cpp's own deserializer detects and aborts on.
624 pub fn state_seq_set(
625 &mut self,
626 state: &SeqState,
627 seq_id: i32,
628 ) -> Result<(), crate::StateSeqError> {
629 let n = unsafe {
630 llama_cpp_sys_2::llama_state_seq_set_data_ext(
631 self.context.as_ptr(),
632 state.bytes.as_ptr(),
633 state.bytes.len(),
634 seq_id,
635 state.flags.0,
636 )
637 };
638 if n != state.bytes.len() {
639 return Err(crate::StateSeqError::SizeMismatch {
640 expected: state.bytes.len(),
641 actual: n,
642 });
643 }
644 Ok(())
645 }
646}
647
648/// Opaque, immutable snapshot of a single sequence's state.
649///
650/// Produced by [`LlamaContext::state_seq_get`] and consumed by
651/// [`LlamaContext::state_seq_set`]. Bytes cannot be constructed, forged,
652/// or mutated from safe code — the only way to obtain a `SeqState` is
653/// via a get call, which guarantees the payload came from llama.cpp
654/// itself. Combined with llama.cpp's own shape validation on the
655/// deserialize path, this closes the "arbitrary bytes into C parser"
656/// unsoundness that motivates the unsafe raw setters.
657///
658/// The state carries the flag set it was captured with; restore uses
659/// those same flags automatically so the byte layout always matches.
660#[derive(Clone)]
661pub struct SeqState {
662 bytes: Vec<u8>,
663 flags: LlamaStateSeqFlags,
664}
665
666impl SeqState {
667 /// The flag set that was used to capture this state.
668 #[must_use]
669 pub fn flags(&self) -> LlamaStateSeqFlags {
670 self.flags
671 }
672
673 /// Size in bytes of the serialized state.
674 #[must_use]
675 pub fn byte_len(&self) -> usize {
676 self.bytes.len()
677 }
678}
679
680impl Debug for SeqState {
681 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
682 f.debug_struct("SeqState")
683 .field("byte_len", &self.bytes.len())
684 .field("flags", &self.flags)
685 .finish()
686 }
687}