Skip to main content

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", &params).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}