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