llama_cpp_4/context.rs
1//! Safe wrapper around `llama_context`.
2//!
3//! Submodules:
4//!
5//! - [`tensor_capture`] — hook `cb_eval` during [`LlamaContext::decode`] to copy
6//! intermediate tensors (per-layer hidden states, norms, …).
7//! - [`memory_breakdown`] — per-buffer memory usage after load/decode.
8//! - [`kv_cache`] — sequence copy, shift, and clear helpers.
9
10use std::fmt::{Debug, Formatter};
11use std::num::NonZeroI32;
12use std::pin::Pin;
13use std::ptr::NonNull;
14use std::slice;
15
16use llama_cpp_sys_4::llama_pooling_type;
17use params::{LlamaContextType, LlamaPoolingType};
18use perf::PerfContextData;
19
20use crate::llama_batch::LlamaBatch;
21use crate::model::{LlamaLoraAdapter, LlamaModel};
22use crate::token::data::LlamaTokenData;
23use crate::token::data_array::LlamaTokenDataArray;
24use crate::token::LlamaToken;
25use crate::{
26 DecodeError, EmbeddingsError, EncodeError, LlamaLoraAdapterRemoveError,
27 LlamaLoraAdapterSetError,
28};
29
30pub mod kv_cache;
31pub mod memory_breakdown;
32pub mod params;
33pub mod perf;
34pub mod session;
35pub mod tensor_capture;
36pub mod tensor_transaction;
37
38pub use memory_breakdown::MemoryBreakdownEntry;
39pub use tensor_capture::{CapturedTensor, TensorCapture};
40pub use tensor_transaction::{
41 CapturedTensorData, TensorAccess, TensorBatchRow, TensorCallbackFailure, TensorDataMut,
42 TensorElementType, TensorFiniteValidation, TensorRowMapping, TensorSelector, TensorShape,
43 TensorTransaction, TensorTransactionError, TensorTransactionHandler, TensorTransactions,
44 TensorWriteback, TransactionalTensorCapture,
45};
46
47/// A safe wrapper around the `llama_context` C++ context.
48///
49/// This struct provides a safe interface to interact with the `llama_context` used by the `LlamaModel`.
50/// It encapsulates the raw C++ context pointer and provides additional fields for managing the model and
51/// context-specific settings like embeddings and logits.
52///
53/// The `LlamaContext` struct ensures that the C++ context is always valid by using the `NonNull` type for
54/// the context pointer, preventing it from being null. The struct also holds a reference to the model
55/// (`LlamaModel`) that the context is tied to, along with some internal state like whether embeddings are enabled
56/// and the initialized logits for the context.
57///
58/// # Fields
59///
60/// - `context`: A non-null pointer to the raw C++ `llama_context`. This is the main context used for interacting with the model.
61/// - `model`: A reference to the `LlamaModel` associated with this context. This model provides the data and parameters
62/// that the context interacts with.
63/// - `initialized_logits`: A vector used to store the initialized logits. These are used in the model's processing and
64/// are kept separate from the context data.
65/// - `embeddings_enabled`: A boolean flag indicating whether embeddings are enabled in the context. This is useful for
66/// controlling whether embedding data is generated during the interaction with the model.
67#[allow(clippy::module_name_repetitions)]
68pub struct LlamaContext<'a> {
69 pub(crate) context: NonNull<llama_cpp_sys_4::llama_context>,
70 /// a reference to the contexts model.
71 pub model: &'a LlamaModel,
72 initialized_logits: Vec<i32>,
73 embeddings_enabled: bool,
74 context_type: LlamaContextType,
75 tensor_transactions: Option<Pin<Box<TensorTransactions>>>,
76}
77
78impl Debug for LlamaContext<'_> {
79 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80 f.debug_struct("LlamaContext")
81 .field("context", &self.context)
82 .finish()
83 }
84}
85
86impl<'model> LlamaContext<'model> {
87 /// Creates a new instance of `LlamaContext` with the provided model, context, and embeddings flag.
88 ///
89 /// This function initializes a new `LlamaContext` object, which is used to interact with the
90 /// `LlamaModel`. The context is created from a pointer to a C++ context and the embeddings flag
91 /// determines whether embeddings are enabled in the context.
92 ///
93 /// # Parameters
94 ///
95 /// - `llama_model`: A reference to an existing `LlamaModel` that will be used with the new context.
96 /// - `llama_context`: A non-null pointer to an existing `llama_cpp_sys_4::llama_context` representing
97 /// the context created in previous steps. This context is necessary for interacting with the model.
98 /// - `embeddings_enabled`: A boolean flag indicating whether embeddings are enabled in this context.
99 /// - `context_type`: The exact graph/context mode selected at allocation.
100 ///
101 /// # Returns
102 ///
103 /// This function returns a new instance of `LlamaContext` initialized with the given parameters:
104 /// - The model reference (`llama_model`) is stored in the context.
105 /// - The raw context pointer (`llama_context`) is wrapped in a `NonNull` to ensure safety.
106 /// - The `embeddings_enabled` flag is used to determine if embeddings are enabled for the context.
107 ///
108 /// # Example
109 /// ```ignore
110 /// let llama_model = LlamaModel::load_from_file(&backend, "path/to/model", ¶ms).unwrap();
111 /// let context_ptr = NonNull::new(some_llama_context_ptr).unwrap();
112 /// let context = LlamaContext::new(
113 /// &llama_model,
114 /// context_ptr,
115 /// true,
116 /// LlamaContextType::Default,
117 /// None,
118 /// );
119 /// // Now you can use the context
120 /// ```
121 pub(crate) fn new(
122 llama_model: &'model LlamaModel,
123 llama_context: NonNull<llama_cpp_sys_4::llama_context>,
124 embeddings_enabled: bool,
125 context_type: LlamaContextType,
126 tensor_transactions: Option<Pin<Box<TensorTransactions>>>,
127 ) -> Self {
128 Self {
129 context: llama_context,
130 model: llama_model,
131 initialized_logits: Vec::new(),
132 embeddings_enabled,
133 context_type,
134 tensor_transactions,
135 }
136 }
137
138 /// Returns the context type selected at native allocation.
139 #[must_use]
140 pub fn context_type(&self) -> LlamaContextType {
141 self.context_type
142 }
143
144 /// Gets the max number of logical tokens that can be submitted to decode. Must be greater than or equal to `n_ubatch`.
145 #[must_use]
146 pub fn n_batch(&self) -> u32 {
147 unsafe { llama_cpp_sys_4::llama_n_batch(self.context.as_ptr()) }
148 }
149
150 /// Gets the max number of physical tokens (hardware level) to decode in batch. Must be less than or equal to `n_batch`.
151 #[must_use]
152 pub fn n_ubatch(&self) -> u32 {
153 unsafe { llama_cpp_sys_4::llama_n_ubatch(self.context.as_ptr()) }
154 }
155
156 /// Gets the size of the context.
157 #[must_use]
158 pub fn n_ctx(&self) -> u32 {
159 unsafe { llama_cpp_sys_4::llama_n_ctx(self.context.as_ptr()) }
160 }
161
162 /// Decodes the batch.
163 ///
164 /// # Errors
165 ///
166 /// - `DecodeError` if the decoding failed.
167 ///
168 /// # Panics
169 ///
170 /// - the returned [`std::ffi::c_int`] from llama-cpp does not fit into a i32 (this should never happen on most systems)
171 pub fn decode(&mut self, batch: &mut LlamaBatch) -> Result<(), DecodeError> {
172 let result =
173 unsafe { llama_cpp_sys_4::llama_decode(self.context.as_ptr(), batch.llama_batch) };
174 if let Some(failure) = self
175 .tensor_transactions
176 .as_ref()
177 .and_then(|transactions| transactions.failure())
178 .cloned()
179 {
180 return Err(DecodeError::TensorCallback(failure));
181 }
182
183 match NonZeroI32::new(result) {
184 None => {
185 self.initialized_logits
186 .clone_from(&batch.initialized_logits);
187 Ok(())
188 }
189 Some(error) => Err(DecodeError::from(error)),
190 }
191 }
192
193 /// Returns the owned tensor transaction program attached to this context.
194 #[must_use]
195 pub fn tensor_transactions(&self) -> Option<&TensorTransactions> {
196 self.tensor_transactions.as_deref()
197 }
198
199 /// Returns the owned tensor transaction program attached to this context.
200 ///
201 /// Retained captures can be removed with [`TensorTransactions::take_captures`].
202 pub fn tensor_transactions_mut(&mut self) -> Option<&mut TensorTransactions> {
203 self.tensor_transactions
204 .as_mut()
205 .map(|transactions| transactions.as_mut().get_mut())
206 }
207
208 /// Encodes the batch.
209 ///
210 /// # Errors
211 ///
212 /// - `EncodeError` if the decoding failed.
213 ///
214 /// # Panics
215 ///
216 /// - the returned [`std::ffi::c_int`] from llama-cpp does not fit into a i32 (this should never happen on most systems)
217 pub fn encode(&mut self, batch: &mut LlamaBatch) -> Result<(), EncodeError> {
218 let result =
219 unsafe { llama_cpp_sys_4::llama_encode(self.context.as_ptr(), batch.llama_batch) };
220
221 match NonZeroI32::new(result) {
222 None => {
223 self.initialized_logits
224 .clone_from(&batch.initialized_logits);
225 Ok(())
226 }
227 Some(error) => Err(EncodeError::from(error)),
228 }
229 }
230
231 /// Return Pooling type for Llama's Context
232 #[must_use]
233 pub fn pooling_type(&self) -> LlamaPoolingType {
234 let pooling_type = unsafe { llama_pooling_type(self.context.as_ptr()) };
235
236 LlamaPoolingType::from(pooling_type)
237 }
238
239 /// Get the embeddings for the `i`th sequence in the current context.
240 ///
241 /// # Returns
242 ///
243 /// A slice containing the embeddings for the last decoded batch.
244 /// The size corresponds to the `n_embd` parameter of the context's model.
245 ///
246 /// # Errors
247 ///
248 /// - When the current context was constructed without enabling embeddings.
249 /// - If the current model had a pooling type of [`llama_cpp_sys_4::LLAMA_POOLING_TYPE_NONE`]
250 /// - If the given sequence index exceeds the max sequence id.
251 ///
252 /// # Panics
253 ///
254 /// * `n_embd` does not fit into a usize
255 pub fn embeddings_seq_ith(&self, i: i32) -> Result<&[f32], EmbeddingsError> {
256 if !self.embeddings_enabled {
257 return Err(EmbeddingsError::NotEnabled);
258 }
259
260 let n_embd =
261 usize::try_from(self.model.n_embd()).expect("n_embd does not fit into a usize");
262
263 unsafe {
264 let embedding = llama_cpp_sys_4::llama_get_embeddings_seq(self.context.as_ptr(), i);
265
266 // Technically also possible whenever `i >= max(batch.n_seq)`, but can't check that here.
267 if embedding.is_null() {
268 Err(EmbeddingsError::NonePoolType)
269 } else {
270 Ok(slice::from_raw_parts(embedding, n_embd))
271 }
272 }
273 }
274
275 /// Get the embeddings for the `i`th token in the current context.
276 ///
277 /// # Returns
278 ///
279 /// A slice containing the embeddings for the last decoded batch of the given token.
280 /// The size corresponds to the `n_embd` parameter of the context's model.
281 ///
282 /// # Errors
283 ///
284 /// - When the current context was constructed without enabling embeddings.
285 /// - When the given token didn't have logits enabled when it was passed.
286 /// - If the given token index exceeds the max token id.
287 ///
288 /// # Panics
289 ///
290 /// * `n_embd` does not fit into a usize
291 pub fn embeddings_ith(&self, i: i32) -> Result<&[f32], EmbeddingsError> {
292 if !self.embeddings_enabled {
293 return Err(EmbeddingsError::NotEnabled);
294 }
295
296 let n_embd =
297 usize::try_from(self.model.n_embd()).expect("n_embd does not fit into a usize");
298
299 unsafe {
300 let embedding = llama_cpp_sys_4::llama_get_embeddings_ith(self.context.as_ptr(), i);
301 // Technically also possible whenever `i >= batch.n_tokens`, but no good way of checking `n_tokens` here.
302 if embedding.is_null() {
303 Err(EmbeddingsError::LogitsNotEnabled)
304 } else {
305 Ok(slice::from_raw_parts(embedding, n_embd))
306 }
307 }
308 }
309
310 /// Get the logits for the last token in the context.
311 ///
312 /// # Returns
313 /// An iterator over unsorted `LlamaTokenData` containing the
314 /// logits for the last token in the context.
315 ///
316 /// # Panics
317 ///
318 /// - underlying logits data is null
319 pub fn candidates(&self) -> impl Iterator<Item = LlamaTokenData> + '_ {
320 (0_i32..).zip(self.get_logits()).map(|(i, logit)| {
321 let token = LlamaToken::new(i);
322 LlamaTokenData::new(token, *logit, 0_f32)
323 })
324 }
325
326 /// Get the token data array for the last token in the context.
327 ///
328 /// This is a convience method that implements:
329 /// ```ignore
330 /// LlamaTokenDataArray::from_iter(ctx.candidates(), false)
331 /// ```
332 ///
333 /// # Panics
334 ///
335 /// - underlying logits data is null
336 #[must_use]
337 pub fn token_data_array(&self) -> LlamaTokenDataArray {
338 LlamaTokenDataArray::from_iter(self.candidates(), false)
339 }
340
341 /// Token logits obtained from the last call to `decode()`.
342 /// The logits for which `batch.logits[i] != 0` are stored contiguously
343 /// in the order they have appeared in the batch.
344 /// Rows: number of tokens for which `batch.logits[i] != 0`
345 /// Cols: `n_vocab`
346 ///
347 /// # Returns
348 ///
349 /// A slice containing the logits for the last decoded token.
350 /// The size corresponds to the `n_vocab` parameter of the context's model.
351 ///
352 /// # Panics
353 ///
354 /// - `n_vocab` does not fit into a usize
355 /// - token data returned is null
356 #[must_use]
357 pub fn get_logits(&self) -> &[f32] {
358 let data = unsafe { llama_cpp_sys_4::llama_get_logits(self.context.as_ptr()) };
359 assert!(!data.is_null(), "logits data for last token is null");
360 let len = usize::try_from(self.model.n_vocab()).expect("n_vocab does not fit into a usize");
361
362 unsafe { slice::from_raw_parts(data, len) }
363 }
364
365 /// Get the logits for the ith token in the context.
366 ///
367 /// # Panics
368 ///
369 /// - logit `i` is not initialized.
370 pub fn candidates_ith(&self, i: i32) -> impl Iterator<Item = LlamaTokenData> + '_ {
371 (0_i32..).zip(self.get_logits_ith(i)).map(|(i, logit)| {
372 let token = LlamaToken::new(i);
373 LlamaTokenData::new(token, *logit, 0_f32)
374 })
375 }
376
377 /// Get the logits for the ith token in the context.
378 ///
379 /// # Panics
380 ///
381 /// - `i` is greater than `n_ctx`
382 /// - `n_vocab` does not fit into a usize
383 /// - logit `i` is not initialized.
384 #[must_use]
385 pub fn get_logits_ith(&self, i: i32) -> &[f32] {
386 assert!(
387 self.initialized_logits.contains(&i),
388 "logit {i} is not initialized. only {:?} is",
389 self.initialized_logits
390 );
391 assert!(
392 self.n_ctx() > u32::try_from(i).expect("i does not fit into a u32"),
393 "n_ctx ({}) must be greater than i ({})",
394 self.n_ctx(),
395 i
396 );
397
398 let data = unsafe { llama_cpp_sys_4::llama_get_logits_ith(self.context.as_ptr(), i) };
399 let len = usize::try_from(self.model.n_vocab()).expect("n_vocab does not fit into a usize");
400
401 unsafe { slice::from_raw_parts(data, len) }
402 }
403
404 /// Get the number of context tokens per sequence.
405 #[must_use]
406 pub fn n_ctx_seq(&self) -> u32 {
407 unsafe { llama_cpp_sys_4::llama_n_ctx_seq(self.context.as_ptr()) }
408 }
409
410 /// Get the maximum number of sequences.
411 #[must_use]
412 pub fn n_seq_max(&self) -> u32 {
413 unsafe { llama_cpp_sys_4::llama_n_seq_max(self.context.as_ptr()) }
414 }
415
416 /// Get the number of recurrent-state snapshots per sequence.
417 #[must_use]
418 pub fn n_rs_seq(&self) -> u32 {
419 unsafe { llama_cpp_sys_4::llama_n_rs_seq(self.context.as_ptr()) }
420 }
421
422 /// Get the number of threads used for generation.
423 #[must_use]
424 pub fn n_threads(&self) -> i32 {
425 unsafe { llama_cpp_sys_4::llama_n_threads(self.context.as_ptr()) }
426 }
427
428 /// Get the number of threads used for batch processing.
429 #[must_use]
430 pub fn n_threads_batch(&self) -> i32 {
431 unsafe { llama_cpp_sys_4::llama_n_threads_batch(self.context.as_ptr()) }
432 }
433
434 /// Set the number of threads used for generation and batch processing.
435 pub fn set_n_threads(&mut self, n_threads: i32, n_threads_batch: i32) {
436 unsafe {
437 llama_cpp_sys_4::llama_set_n_threads(self.context.as_ptr(), n_threads, n_threads_batch);
438 }
439 }
440
441 /// Set whether to use causal attention.
442 ///
443 /// If set to `false`, the model will use non-causal attention, which is
444 /// needed for embedding models.
445 pub fn set_causal_attn(&mut self, causal_attn: bool) {
446 unsafe {
447 llama_cpp_sys_4::llama_set_causal_attn(self.context.as_ptr(), causal_attn);
448 }
449 }
450
451 /// Set whether to compute embeddings.
452 ///
453 /// This allows toggling embedding mode at runtime (as opposed to only at
454 /// context creation time).
455 pub fn set_embeddings(&mut self, embeddings: bool) {
456 self.embeddings_enabled = embeddings;
457 unsafe {
458 llama_cpp_sys_4::llama_set_embeddings(self.context.as_ptr(), embeddings);
459 }
460 }
461
462 /// Mark the next computation as a warmup run.
463 ///
464 /// Warmup runs are useful for GPU backends to compile kernels before
465 /// actual inference begins.
466 pub fn set_warmup(&mut self, warmup: bool) {
467 unsafe {
468 llama_cpp_sys_4::llama_set_warmup(self.context.as_ptr(), warmup);
469 }
470 }
471
472 /// Wait for all pending async computations to finish.
473 pub fn synchronize(&mut self) {
474 unsafe {
475 llama_cpp_sys_4::llama_synchronize(self.context.as_ptr());
476 }
477 }
478
479 /// Get all embeddings for the current context.
480 ///
481 /// Returns a slice of all embeddings from the last decoded batch.
482 /// For pooled embeddings use [`embeddings_seq_ith`](Self::embeddings_seq_ith) instead.
483 ///
484 /// # Errors
485 ///
486 /// - When the current context was constructed without enabling embeddings.
487 /// - If the embeddings pointer is null.
488 ///
489 /// # Panics
490 ///
491 /// * `n_embd` does not fit into a usize
492 pub fn get_embeddings(&self) -> Result<&[f32], EmbeddingsError> {
493 if !self.embeddings_enabled {
494 return Err(EmbeddingsError::NotEnabled);
495 }
496
497 let n_embd =
498 usize::try_from(self.model.n_embd()).expect("n_embd does not fit into a usize");
499
500 unsafe {
501 let embedding = llama_cpp_sys_4::llama_get_embeddings(self.context.as_ptr());
502 if embedding.is_null() {
503 Err(EmbeddingsError::NonePoolType)
504 } else {
505 Ok(slice::from_raw_parts(embedding, n_embd))
506 }
507 }
508 }
509
510 /// Toggle extraction of next-n embeddings (Rust name: pre-norm) — hidden
511 /// states used by MTP draft heads. Upstream C API: `llama_set_embeddings_nextn`
512 /// (llama.cpp PR #23198 and later renames).
513 ///
514 /// If `masked` is `true`, pre-norm rows are extracted only for tokens
515 /// whose `batch.logits[i]` is non-zero. If `masked` is `false`, rows are
516 /// extracted for every token in the batch regardless of `batch.logits` —
517 /// callers can then leave `batch.logits[i] = false` on prompt-fill
518 /// positions and avoid copying the full logits row for each one.
519 ///
520 /// Upstream's MTP session init configures pre-norm extraction on the target
521 /// and draft contexts automatically. Call this manually only for custom
522 /// speculative setups.
523 pub fn set_embeddings_pre_norm(&mut self, value: bool, masked: bool) {
524 unsafe {
525 llama_cpp_sys_4::llama_set_embeddings_nextn(self.context.as_ptr(), value, masked);
526 }
527 }
528
529 /// Get the full pre-norm embeddings buffer for the last decoded batch.
530 ///
531 /// Returns `None` when pre-norm embeddings are disabled or the buffer
532 /// hasn't been populated. The length of the returned slice is
533 /// `n_embd * <number of pre-norm rows>` — interpretation of the row
534 /// count depends on whether the setter was called with `masked=true`
535 /// (one row per sampled token) or `masked=false` (one row per batch
536 /// token). Use [`get_embeddings_pre_norm_ith`](Self::get_embeddings_pre_norm_ith)
537 /// when you only need a single row.
538 ///
539 /// # Panics
540 ///
541 /// Panics if `n_embd` does not fit in `usize`.
542 #[must_use]
543 pub fn get_embeddings_pre_norm(&self) -> Option<&[f32]> {
544 let n_embd =
545 usize::try_from(self.model.n_embd()).expect("n_embd does not fit into a usize");
546 unsafe {
547 let p = llama_cpp_sys_4::llama_get_embeddings_nextn(self.context.as_ptr());
548 if p.is_null() {
549 None
550 } else {
551 Some(slice::from_raw_parts(p, n_embd))
552 }
553 }
554 }
555
556 /// Get the pre-norm embedding row for the `i`th output position of the
557 /// last decoded batch. Returns `None` if upstream rejects the index
558 /// (e.g. masked mode with `batch.logits[i] == 0`, or out of range).
559 ///
560 /// # Panics
561 ///
562 /// Panics if `n_embd` does not fit in `usize`.
563 #[must_use]
564 pub fn get_embeddings_pre_norm_ith(&self, i: i32) -> Option<&[f32]> {
565 let n_embd =
566 usize::try_from(self.model.n_embd()).expect("n_embd does not fit into a usize");
567 unsafe {
568 let p = llama_cpp_sys_4::llama_get_embeddings_nextn_ith(self.context.as_ptr(), i);
569 if p.is_null() {
570 None
571 } else {
572 Some(slice::from_raw_parts(p, n_embd))
573 }
574 }
575 }
576
577 /// Select which `NextN` block the MTP draft graph runs.
578 ///
579 /// `offset` indexes past the trunk transformer layers (`0` = first `NextN`
580 /// head). Required for multi-head MTP models such as Step3.5; restore to
581 /// `0` after drafting. See [`crate::mtp`] for the full speculative loop.
582 ///
583 /// # Examples
584 ///
585 /// ```ignore
586 /// for head in 0..model.n_layer_nextn() {
587 /// draft.set_nextn_layer_offset(head);
588 /// let drafts = session.draft(0, n_past, last_token)?;
589 /// }
590 /// draft.set_nextn_layer_offset(0);
591 /// ```
592 pub fn set_nextn_layer_offset(&mut self, offset: i32) {
593 unsafe {
594 llama_cpp_sys_4::llama_set_nextn_layer_offset(self.context.as_ptr(), offset);
595 }
596 }
597
598 /// Return the paired context set via
599 /// [`crate::context::params::LlamaContextParams::with_ctx_other`].
600 ///
601 /// The pointer refers to the other live context created during
602 /// [`crate::model::LlamaModel::new_context`]; it is `None` when no pairing
603 /// was configured.
604 #[must_use]
605 pub fn ctx_other(&self) -> Option<NonNull<llama_cpp_sys_4::llama_context>> {
606 NonNull::new(unsafe { llama_cpp_sys_4::llama_get_ctx_other(self.context.as_ptr()) })
607 }
608
609 /// Reset the timings for the context.
610 pub fn reset_timings(&mut self) {
611 unsafe { llama_cpp_sys_4::ggml_time_init() }
612 }
613
614 /// Returns the timings for the context.
615 pub fn timings(&mut self) -> PerfContextData {
616 let perf_context_data =
617 unsafe { llama_cpp_sys_4::llama_perf_context(self.context.as_ptr()) };
618 PerfContextData { perf_context_data }
619 }
620
621 /// Reset the performance counters for the context.
622 pub fn perf_context_reset(&mut self) {
623 unsafe { llama_cpp_sys_4::llama_perf_context_reset(self.context.as_ptr()) }
624 }
625
626 /// Check if the KV cache memory supports shifting.
627 #[must_use]
628 pub fn memory_can_shift(&self) -> bool {
629 unsafe {
630 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
631 llama_cpp_sys_4::llama_memory_can_shift(mem)
632 }
633 }
634
635 /// Get the minimum position in a sequence's KV cache.
636 #[must_use]
637 pub fn memory_seq_pos_min(&self, seq_id: i32) -> i32 {
638 unsafe {
639 let mem = llama_cpp_sys_4::llama_get_memory(self.context.as_ptr());
640 llama_cpp_sys_4::llama_memory_seq_pos_min(mem, seq_id)
641 }
642 }
643
644 /// Print a human-readable memory breakdown to stderr via llama.cpp.
645 ///
646 /// For structured access use [`Self::memory_breakdown`].
647 pub fn memory_breakdown_print(&self) {
648 unsafe {
649 llama_cpp_sys_4::common_memory_breakdown_print(self.context.as_ptr());
650 }
651 }
652
653 /// Return structured per-buffer memory usage for this context.
654 ///
655 /// Each [`memory_breakdown::MemoryBreakdownEntry`] reports model weights,
656 /// KV / recurrent cache, and compute scratch bytes for one ggml buffer
657 /// type. Returns an empty vector when no buffers are registered.
658 ///
659 /// # Examples
660 ///
661 /// ```no_run
662 /// use llama_cpp_4::prelude::*;
663 ///
664 /// fn main() {
665 /// let backend = LlamaBackend::init().unwrap();
666 /// let model = LlamaModel::load_from_file(&backend, "model.gguf", &LlamaModelParams::default()).unwrap();
667 /// let ctx = model.new_context(&backend, LlamaContextParams::default()).unwrap();
668 /// let total: usize = ctx.memory_breakdown().iter().map(|e| e.total()).sum();
669 /// println!("context uses {total} bytes across all buffer types");
670 /// }
671 /// ```
672 #[must_use]
673 pub fn memory_breakdown(&self) -> Vec<memory_breakdown::MemoryBreakdownEntry> {
674 memory_breakdown::collect_memory_breakdown(self.context.as_ptr())
675 }
676
677 /// Enable or disable extraction of input embeddings for a transformer layer.
678 ///
679 /// Maps to `llama_set_embeddings_layer_inp`. After a successful
680 /// [`Self::decode`], read the vector with [`Self::get_embeddings_layer_inp`].
681 pub fn set_embeddings_layer_inp(&mut self, layer_id: u32, value: bool) {
682 unsafe {
683 llama_cpp_sys_4::llama_set_embeddings_layer_inp(self.context.as_ptr(), layer_id, value);
684 }
685 }
686
687 /// Get input embeddings for `layer_id` from the last decoded batch.
688 ///
689 /// Returns `None` when the layer was not enabled via
690 /// [`Self::set_embeddings_layer_inp`] or when upstream has no data for
691 /// `layer_id`. The slice length is [`LlamaModel::n_embd`].
692 ///
693 /// # Panics
694 ///
695 /// Panics if `n_embd` does not fit in `usize`.
696 #[must_use]
697 pub fn get_embeddings_layer_inp(&self, layer_id: u32) -> Option<&[f32]> {
698 let n_embd =
699 usize::try_from(self.model.n_embd()).expect("n_embd does not fit into a usize");
700 unsafe {
701 let p =
702 llama_cpp_sys_4::llama_get_embeddings_layer_inp(self.context.as_ptr(), layer_id);
703 if p.is_null() {
704 None
705 } else {
706 Some(slice::from_raw_parts(p, n_embd))
707 }
708 }
709 }
710
711 /// Get the size of the full context state in bytes.
712 ///
713 /// This is the size needed for [`state_get_data`](Self::state_get_data) and
714 /// [`state_set_data`](Self::state_set_data).
715 #[must_use]
716 pub fn state_get_size(&mut self) -> usize {
717 unsafe { llama_cpp_sys_4::llama_state_get_size(self.context.as_ptr()) }
718 }
719
720 /// Copy the full context state into a byte buffer.
721 ///
722 /// The buffer must be at least [`state_get_size`](Self::state_get_size) bytes.
723 ///
724 /// Returns the number of bytes written.
725 pub fn state_get_data(&mut self, dst: &mut [u8]) -> usize {
726 unsafe {
727 llama_cpp_sys_4::llama_state_get_data(
728 self.context.as_ptr(),
729 dst.as_mut_ptr(),
730 dst.len(),
731 )
732 }
733 }
734
735 /// Restore the full context state from a byte buffer.
736 ///
737 /// Returns the number of bytes read.
738 pub fn state_set_data(&mut self, src: &[u8]) -> usize {
739 unsafe {
740 llama_cpp_sys_4::llama_state_set_data(self.context.as_ptr(), src.as_ptr(), src.len())
741 }
742 }
743
744 /// Save the context state to a file along with the given tokens.
745 ///
746 /// Returns `true` on success.
747 ///
748 /// # Panics
749 ///
750 /// Panics if the path contains null bytes.
751 pub fn state_save_file(
752 &mut self,
753 path: impl AsRef<std::path::Path>,
754 tokens: &[LlamaToken],
755 ) -> bool {
756 let path_str = path.as_ref().to_str().expect("path is not valid UTF-8");
757 let c_path = std::ffi::CString::new(path_str).expect("path contains null bytes");
758 unsafe {
759 llama_cpp_sys_4::llama_state_save_file(
760 self.context.as_ptr(),
761 c_path.as_ptr(),
762 tokens.as_ptr().cast(),
763 tokens.len(),
764 )
765 }
766 }
767
768 /// Load a context state from a file.
769 ///
770 /// Returns `true` on success and fills `tokens_out` with the saved tokens.
771 ///
772 /// # Panics
773 ///
774 /// Panics if the path contains null bytes.
775 pub fn state_load_file(
776 &mut self,
777 path: impl AsRef<std::path::Path>,
778 tokens_out: &mut Vec<LlamaToken>,
779 n_token_capacity: usize,
780 ) -> bool {
781 tokens_out.resize(n_token_capacity, LlamaToken(0));
782 let mut n_token_count: usize = 0;
783 let path_str = path.as_ref().to_str().expect("path is not valid UTF-8");
784 let c_path = std::ffi::CString::new(path_str).expect("path contains null bytes");
785 let ok = unsafe {
786 llama_cpp_sys_4::llama_state_load_file(
787 self.context.as_ptr(),
788 c_path.as_ptr(),
789 tokens_out.as_mut_ptr().cast(),
790 n_token_capacity,
791 std::ptr::addr_of_mut!(n_token_count),
792 )
793 };
794 if ok {
795 tokens_out.truncate(n_token_count);
796 }
797 ok
798 }
799
800 /// Get the size of a single sequence's state in bytes.
801 #[must_use]
802 pub fn state_seq_get_size(&mut self, seq_id: i32) -> usize {
803 unsafe { llama_cpp_sys_4::llama_state_seq_get_size(self.context.as_ptr(), seq_id) }
804 }
805
806 /// Copy a single sequence's state into a byte buffer.
807 ///
808 /// Returns the number of bytes written.
809 pub fn state_seq_get_data(&mut self, dst: &mut [u8], seq_id: i32) -> usize {
810 unsafe {
811 llama_cpp_sys_4::llama_state_seq_get_data(
812 self.context.as_ptr(),
813 dst.as_mut_ptr(),
814 dst.len(),
815 seq_id,
816 )
817 }
818 }
819
820 /// Restore a single sequence's state from a byte buffer.
821 ///
822 /// Returns the number of bytes read.
823 pub fn state_seq_set_data(&mut self, src: &[u8], dest_seq_id: i32) -> usize {
824 unsafe {
825 llama_cpp_sys_4::llama_state_seq_set_data(
826 self.context.as_ptr(),
827 src.as_ptr(),
828 src.len(),
829 dest_seq_id,
830 )
831 }
832 }
833
834 /// Save a single sequence's state to a file.
835 ///
836 /// Returns the number of bytes written (0 on failure).
837 ///
838 /// # Panics
839 ///
840 /// Panics if the path contains null bytes.
841 pub fn state_seq_save_file(
842 &mut self,
843 path: impl AsRef<std::path::Path>,
844 seq_id: i32,
845 tokens: &[LlamaToken],
846 ) -> usize {
847 let path_str = path.as_ref().to_str().expect("path is not valid UTF-8");
848 let c_path = std::ffi::CString::new(path_str).expect("path contains null bytes");
849 unsafe {
850 llama_cpp_sys_4::llama_state_seq_save_file(
851 self.context.as_ptr(),
852 c_path.as_ptr(),
853 seq_id,
854 tokens.as_ptr().cast(),
855 tokens.len(),
856 )
857 }
858 }
859
860 /// Load a single sequence's state from a file.
861 ///
862 /// Returns the number of bytes read (0 on failure).
863 ///
864 /// # Panics
865 ///
866 /// Panics if the path contains null bytes.
867 pub fn state_seq_load_file(
868 &mut self,
869 path: impl AsRef<std::path::Path>,
870 dest_seq_id: i32,
871 tokens_out: &mut Vec<LlamaToken>,
872 n_token_capacity: usize,
873 ) -> usize {
874 tokens_out.resize(n_token_capacity, LlamaToken(0));
875 let mut n_token_count: usize = 0;
876 let path_str = path.as_ref().to_str().expect("path is not valid UTF-8");
877 let c_path = std::ffi::CString::new(path_str).expect("path contains null bytes");
878 let ret = unsafe {
879 llama_cpp_sys_4::llama_state_seq_load_file(
880 self.context.as_ptr(),
881 c_path.as_ptr(),
882 dest_seq_id,
883 tokens_out.as_mut_ptr().cast(),
884 n_token_capacity,
885 std::ptr::addr_of_mut!(n_token_count),
886 )
887 };
888 if ret > 0 {
889 tokens_out.truncate(n_token_count);
890 }
891 ret
892 }
893
894 /// Set a control vector on the context.
895 ///
896 /// # Parameters
897 ///
898 /// - `data`: The control vector data (embedding values). Pass an empty slice to clear.
899 /// - `n_embd`: The embedding dimension.
900 /// - `il_start`: The starting layer index (inclusive).
901 /// - `il_end`: The ending layer index (exclusive).
902 ///
903 /// # Errors
904 ///
905 /// Returns `Err` with the error code if the operation fails.
906 pub fn set_adapter_cvec(
907 &mut self,
908 data: &[f32],
909 n_embd: i32,
910 il_start: i32,
911 il_end: i32,
912 ) -> Result<(), i32> {
913 let ret = unsafe {
914 llama_cpp_sys_4::llama_set_adapter_cvec(
915 self.context.as_ptr(),
916 data.as_ptr(),
917 data.len(),
918 n_embd,
919 il_start,
920 il_end,
921 )
922 };
923 if ret != 0 {
924 Err(ret)
925 } else {
926 Ok(())
927 }
928 }
929
930 /// Get sampled token debug info for the `i`th position.
931 ///
932 /// Returns the sampled token at position `i` from the last decode call.
933 #[must_use]
934 pub fn get_sampled_token_ith(&self, i: i32) -> LlamaToken {
935 let token =
936 unsafe { llama_cpp_sys_4::llama_get_sampled_token_ith(self.context.as_ptr(), i) };
937 LlamaToken(token)
938 }
939
940 /// Get sampled candidate tokens for the `i`th position.
941 ///
942 /// Returns a slice of candidate tokens from the last decode call.
943 #[must_use]
944 pub fn get_sampled_candidates_ith(&self, i: i32) -> &[LlamaToken] {
945 let count = unsafe {
946 llama_cpp_sys_4::llama_get_sampled_candidates_count_ith(self.context.as_ptr(), i)
947 } as usize;
948 if count == 0 {
949 return &[];
950 }
951 let ptr =
952 unsafe { llama_cpp_sys_4::llama_get_sampled_candidates_ith(self.context.as_ptr(), i) };
953 if ptr.is_null() {
954 return &[];
955 }
956 unsafe { slice::from_raw_parts(ptr.cast::<LlamaToken>(), count) }
957 }
958
959 /// Get the number of sampled logits for the `i`th position.
960 #[must_use]
961 pub fn get_sampled_logits_count_ith(&self, i: i32) -> u32 {
962 unsafe { llama_cpp_sys_4::llama_get_sampled_logits_count_ith(self.context.as_ptr(), i) }
963 }
964
965 /// Get sampled logits for the `i`th position.
966 ///
967 /// Returns a slice of logit values from the last decode call.
968 #[must_use]
969 pub fn get_sampled_logits_ith(&self, i: i32) -> &[f32] {
970 let count = self.get_sampled_logits_count_ith(i) as usize;
971 if count == 0 {
972 return &[];
973 }
974 let ptr =
975 unsafe { llama_cpp_sys_4::llama_get_sampled_logits_ith(self.context.as_ptr(), i) };
976 if ptr.is_null() {
977 return &[];
978 }
979 unsafe { slice::from_raw_parts(ptr, count) }
980 }
981
982 /// Get the number of sampled probabilities for the `i`th position.
983 #[must_use]
984 pub fn get_sampled_probs_count_ith(&self, i: i32) -> u32 {
985 unsafe { llama_cpp_sys_4::llama_get_sampled_probs_count_ith(self.context.as_ptr(), i) }
986 }
987
988 /// Get sampled probabilities for the `i`th position.
989 ///
990 /// Returns a slice of probability values from the last decode call.
991 #[must_use]
992 pub fn get_sampled_probs_ith(&self, i: i32) -> &[f32] {
993 let count = self.get_sampled_probs_count_ith(i) as usize;
994 if count == 0 {
995 return &[];
996 }
997 let ptr = unsafe { llama_cpp_sys_4::llama_get_sampled_probs_ith(self.context.as_ptr(), i) };
998 if ptr.is_null() {
999 return &[];
1000 }
1001 unsafe { slice::from_raw_parts(ptr, count) }
1002 }
1003
1004 /// Get the size of a single sequence's state with flags.
1005 #[must_use]
1006 pub fn state_seq_get_size_ext(&mut self, seq_id: i32, flags: u32) -> usize {
1007 unsafe {
1008 llama_cpp_sys_4::llama_state_seq_get_size_ext(self.context.as_ptr(), seq_id, flags)
1009 }
1010 }
1011
1012 /// Copy a single sequence's state into a byte buffer with flags.
1013 ///
1014 /// Returns the number of bytes written.
1015 pub fn state_seq_get_data_ext(&mut self, dst: &mut [u8], seq_id: i32, flags: u32) -> usize {
1016 unsafe {
1017 llama_cpp_sys_4::llama_state_seq_get_data_ext(
1018 self.context.as_ptr(),
1019 dst.as_mut_ptr(),
1020 dst.len(),
1021 seq_id,
1022 flags,
1023 )
1024 }
1025 }
1026
1027 /// Restore a single sequence's state from a byte buffer with flags.
1028 ///
1029 /// Returns the number of bytes read.
1030 pub fn state_seq_set_data_ext(&mut self, src: &[u8], dest_seq_id: i32, flags: u32) -> usize {
1031 unsafe {
1032 llama_cpp_sys_4::llama_state_seq_set_data_ext(
1033 self.context.as_ptr(),
1034 src.as_ptr(),
1035 src.len(),
1036 dest_seq_id,
1037 flags,
1038 )
1039 }
1040 }
1041
1042 /// Set an abort callback for the context.
1043 ///
1044 /// The callback is called periodically during computation. If it returns `true`,
1045 /// the computation is aborted.
1046 ///
1047 /// # Safety
1048 ///
1049 /// The callback data must remain valid for the lifetime of the context or until
1050 /// the callback is replaced.
1051 pub unsafe fn set_abort_callback(
1052 &mut self,
1053 callback: llama_cpp_sys_4::ggml_abort_callback,
1054 data: *mut std::ffi::c_void,
1055 ) {
1056 llama_cpp_sys_4::llama_set_abort_callback(self.context.as_ptr(), callback, data);
1057 }
1058
1059 /// Attach a thread pool to the context.
1060 ///
1061 /// # Safety
1062 ///
1063 /// The thread pools must remain valid for the lifetime of the context or until
1064 /// they are detached.
1065 pub unsafe fn attach_threadpool(
1066 &mut self,
1067 threadpool: llama_cpp_sys_4::ggml_threadpool_t,
1068 threadpool_batch: llama_cpp_sys_4::ggml_threadpool_t,
1069 ) {
1070 llama_cpp_sys_4::llama_attach_threadpool(
1071 self.context.as_ptr(),
1072 threadpool,
1073 threadpool_batch,
1074 );
1075 }
1076
1077 /// Detach the thread pool from the context.
1078 pub fn detach_threadpool(&mut self) {
1079 unsafe {
1080 llama_cpp_sys_4::llama_detach_threadpool(self.context.as_ptr());
1081 }
1082 }
1083
1084 /// Set a sampler for a specific sequence.
1085 ///
1086 /// Returns `true` on success.
1087 pub fn set_sampler(
1088 &mut self,
1089 seq_id: i32,
1090 sampler: &mut crate::sampling::LlamaSampler,
1091 ) -> bool {
1092 unsafe {
1093 llama_cpp_sys_4::llama_set_sampler(
1094 self.context.as_ptr(),
1095 seq_id,
1096 sampler.sampler.as_ptr(),
1097 )
1098 }
1099 }
1100
1101 /// Get the raw model pointer from this context.
1102 ///
1103 /// This is mainly useful for FFI interop. In normal usage, access
1104 /// the model via the `model` field instead.
1105 #[must_use]
1106 pub fn get_model_ptr(&self) -> *const llama_cpp_sys_4::llama_model {
1107 unsafe { llama_cpp_sys_4::llama_get_model(self.context.as_ptr()) }
1108 }
1109
1110 /// Sets a lora adapter.
1111 ///
1112 /// # Errors
1113 ///
1114 /// See [`LlamaLoraAdapterSetError`] for more information.
1115 pub fn lora_adapter_set(
1116 &self,
1117 adapter: &mut LlamaLoraAdapter,
1118 scale: f32,
1119 ) -> Result<(), LlamaLoraAdapterSetError> {
1120 let err_code = unsafe {
1121 // llama_set_adapter_lora / llama_rm_adapter_lora were replaced by llama_set_adapters_lora
1122 // which takes a full list of adapters + scales at once (b8249+)
1123 let mut adapter_ptr = adapter.lora_adapter.as_ptr();
1124 let mut scale_val = scale;
1125 llama_cpp_sys_4::llama_set_adapters_lora(
1126 self.context.as_ptr(),
1127 &raw mut adapter_ptr,
1128 1,
1129 &raw mut scale_val,
1130 )
1131 };
1132 if err_code != 0 {
1133 return Err(LlamaLoraAdapterSetError::ErrorResult(err_code));
1134 }
1135
1136 tracing::debug!("Set lora adapter");
1137 Ok(())
1138 }
1139
1140 /// Remove all lora adapters from the context.
1141 ///
1142 /// Note: as of llama.cpp b8249 the per-adapter remove API was replaced by
1143 /// `llama_set_adapters_lora` which operates on the full adapter list at once.
1144 /// Calling this function clears **all** adapters currently set on the context.
1145 ///
1146 /// # Errors
1147 ///
1148 /// See [`LlamaLoraAdapterRemoveError`] for more information.
1149 pub fn lora_adapter_remove(
1150 &self,
1151 _adapter: &mut LlamaLoraAdapter,
1152 ) -> Result<(), LlamaLoraAdapterRemoveError> {
1153 let err_code = unsafe {
1154 llama_cpp_sys_4::llama_set_adapters_lora(
1155 self.context.as_ptr(),
1156 std::ptr::null_mut(),
1157 0,
1158 std::ptr::null_mut(),
1159 )
1160 };
1161 if err_code != 0 {
1162 return Err(LlamaLoraAdapterRemoveError::ErrorResult(err_code));
1163 }
1164
1165 tracing::debug!("Remove lora adapter");
1166 Ok(())
1167 }
1168}
1169
1170impl Drop for LlamaContext<'_> {
1171 fn drop(&mut self) {
1172 unsafe { llama_cpp_sys_4::llama_free(self.context.as_ptr()) }
1173 }
1174}