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