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