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