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", ¶ms).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}