Skip to main content

llama_cpp_4/context/params/
mod.rs

1//! A safe wrapper around `llama_context_params`.
2//!
3//! Use [`LlamaContextParams`] to configure context size, batching, KV layout,
4//! `RoPE` / `YaRN` scaling, flash attention, per-sequence samplers, and pairing
5//! with another context (`ctx_other`).
6mod advanced;
7mod types;
8
9pub use types::*;
10
11use std::num::NonZeroU32;
12use std::pin::Pin;
13
14use thiserror::Error;
15
16use super::tensor_transaction::{
17    tensor_transaction_callback, tensor_transaction_decode_begin, tensor_transaction_decode_end,
18    TensorTransactions,
19};
20use crate::sampling::LlamaSampler;
21
22/// Error returned when [`LlamaContextParams::try_clone`] cannot duplicate state.
23#[derive(Debug, Error, PartialEq, Eq)]
24pub enum ParamsCloneError {
25    /// Per-sequence sampler chains cannot be duplicated.
26    #[error("cannot clone params that own per-sequence sampler chains")]
27    SamplerChains,
28    /// Owned tensor transaction handlers cannot be duplicated.
29    #[error("cannot clone params that own tensor transactions")]
30    TensorTransactions,
31}
32
33/// Builder for [`llama_context_params`](llama_cpp_sys_4::llama_context_params).
34///
35/// Construct with [`Default::default()`], chain `with_*` setters, then pass the
36/// value to [`crate::model::LlamaModel::new_context`]. Getter methods mirror
37/// the fields that exist on the underlying C struct.
38///
39/// # Sampler ownership
40///
41/// [`Self::with_sampler_seq_configs`] stores owned [`LlamaSampler`] chains inside
42/// this struct until the context is created. [`Clone`] clears sampler configs
43/// because the underlying chains cannot be duplicated safely.
44///
45/// # Examples
46///
47/// ```rust
48/// # use std::num::NonZeroU32;
49/// use llama_cpp_4::context::params::LlamaContextParams;
50///
51/// let ctx_params = LlamaContextParams::default()
52///     .with_n_ctx(NonZeroU32::new(2048));
53///
54/// assert_eq!(ctx_params.n_ctx(), NonZeroU32::new(2048));
55/// ```
56#[derive(Debug)]
57#[allow(
58    missing_docs,
59    clippy::struct_excessive_bools,
60    clippy::module_name_repetitions
61)]
62pub struct LlamaContextParams {
63    pub(crate) context_params: llama_cpp_sys_4::llama_context_params,
64    /// When `true`, the `TurboQuant` attention rotation (PR #21038) will be
65    /// disabled for any context created from these params.
66    pub(crate) attn_rot_disabled: bool,
67    /// Keeps sampler chains alive while `context_params.samplers` points at them.
68    owned_samplers: Vec<LlamaSampler>,
69    sampler_configs: Vec<llama_cpp_sys_4::llama_sampler_seq_config>,
70    pub(crate) tensor_transactions: Option<Pin<Box<TensorTransactions>>>,
71}
72
73impl LlamaContextParams {
74    /// Set the side of the context
75    ///
76    /// # Examples
77    ///
78    /// ```rust
79    /// # use std::num::NonZeroU32;
80    /// use llama_cpp_4::context::params::LlamaContextParams;
81    /// let params = LlamaContextParams::default();
82    /// let params = params.with_n_ctx(NonZeroU32::new(2048));
83    /// assert_eq!(params.n_ctx(), NonZeroU32::new(2048));
84    /// ```
85    #[must_use]
86    pub fn with_n_ctx(mut self, n_ctx: Option<NonZeroU32>) -> Self {
87        self.context_params.n_ctx = n_ctx.map_or(0, std::num::NonZeroU32::get);
88        self
89    }
90
91    /// Get the size of the context.
92    ///
93    /// [`None`] if the context size is specified by the model and not the context.
94    ///
95    /// # Examples
96    ///
97    /// ```rust
98    /// let params = llama_cpp_4::context::params::LlamaContextParams::default();
99    /// assert_eq!(params.n_ctx(), std::num::NonZeroU32::new(512));
100    #[must_use]
101    pub fn n_ctx(&self) -> Option<NonZeroU32> {
102        NonZeroU32::new(self.context_params.n_ctx)
103    }
104
105    /// Set the maximum number of independent sequence states in the context.
106    ///
107    /// This maps to llama.cpp's `llama_context_params.n_seq_max` and must match
108    /// the highest sequence id used by batched decoding.
109    ///
110    /// # Examples
111    ///
112    /// ```rust
113    /// use llama_cpp_4::context::params::LlamaContextParams;
114    /// let params = LlamaContextParams::default()
115    ///     .with_n_seq_max(16);
116    /// assert_eq!(params.n_seq_max(), 16);
117    /// ```
118    #[must_use]
119    pub fn with_n_seq_max(mut self, n_seq_max: u32) -> Self {
120        self.context_params.n_seq_max = n_seq_max.max(1);
121        self
122    }
123
124    /// Get the configured maximum number of independent sequence states.
125    #[must_use]
126    pub fn n_seq_max(&self) -> u32 {
127        self.context_params.n_seq_max
128    }
129
130    /// Set the `n_batch`
131    ///
132    /// # Examples
133    ///
134    /// ```rust
135    /// # use std::num::NonZeroU32;
136    /// use llama_cpp_4::context::params::LlamaContextParams;
137    /// let params = LlamaContextParams::default()
138    ///     .with_n_batch(2048);
139    /// assert_eq!(params.n_batch(), 2048);
140    /// ```
141    #[must_use]
142    pub fn with_n_batch(mut self, n_batch: u32) -> Self {
143        self.context_params.n_batch = n_batch;
144        self
145    }
146
147    /// Get the `n_batch`
148    ///
149    /// # Examples
150    ///
151    /// ```rust
152    /// use llama_cpp_4::context::params::LlamaContextParams;
153    /// let params = LlamaContextParams::default();
154    /// assert_eq!(params.n_batch(), 2048);
155    /// ```
156    #[must_use]
157    pub fn n_batch(&self) -> u32 {
158        self.context_params.n_batch
159    }
160
161    /// Set the `n_ubatch`
162    ///
163    /// # Examples
164    ///
165    /// ```rust
166    /// # use std::num::NonZeroU32;
167    /// use llama_cpp_4::context::params::LlamaContextParams;
168    /// let params = LlamaContextParams::default()
169    ///     .with_n_ubatch(512);
170    /// assert_eq!(params.n_ubatch(), 512);
171    /// ```
172    #[must_use]
173    pub fn with_n_ubatch(mut self, n_ubatch: u32) -> Self {
174        self.context_params.n_ubatch = n_ubatch;
175        self
176    }
177
178    /// Get the `n_ubatch`
179    ///
180    /// # Examples
181    ///
182    /// ```rust
183    /// use llama_cpp_4::context::params::LlamaContextParams;
184    /// let params = LlamaContextParams::default();
185    /// assert_eq!(params.n_ubatch(), 512);
186    /// ```
187    #[must_use]
188    pub fn n_ubatch(&self) -> u32 {
189        self.context_params.n_ubatch
190    }
191
192    /// Set the context type (e.g. [`LlamaContextType::Mtp`] for the draft context in
193    /// [`crate::mtp::MtpSession`]).
194    #[must_use]
195    pub fn with_ctx_type(mut self, ctx_type: LlamaContextType) -> Self {
196        self.context_params.ctx_type = ctx_type.into();
197        self
198    }
199
200    /// Get the configured context type.
201    #[must_use]
202    pub fn ctx_type(&self) -> LlamaContextType {
203        self.context_params.ctx_type.into()
204    }
205
206    /// Set the number of recurrent-state snapshots per sequence (MTP rollback).
207    ///
208    /// Must be `>=` [`MtpSessionConfig::n_draft_max`](crate::mtp::MtpSessionConfig::n_draft_max)
209    /// on the draft context. See [`crate::mtp`].
210    #[must_use]
211    pub fn with_n_rs_seq(mut self, n_rs_seq: u32) -> Self {
212        self.context_params.n_rs_seq = n_rs_seq;
213        self
214    }
215
216    /// Get the number of recurrent-state snapshots per sequence used for MTP rollback.
217    #[must_use]
218    pub fn n_rs_seq(&self) -> u32 {
219        self.context_params.n_rs_seq
220    }
221
222    /// Set the `offload_kqv` parameter to control offloading KV cache & KQV ops to GPU
223    ///
224    /// # Examples
225    ///
226    /// ```rust
227    /// use llama_cpp_4::context::params::LlamaContextParams;
228    /// let params = LlamaContextParams::default()
229    ///     .with_offload_kqv(false);
230    /// assert_eq!(params.offload_kqv(), false);
231    /// ```
232    #[must_use]
233    pub fn with_offload_kqv(mut self, enabled: bool) -> Self {
234        self.context_params.offload_kqv = enabled;
235        self
236    }
237
238    /// Get the `offload_kqv` parameter
239    ///
240    /// # Examples
241    ///
242    /// ```rust
243    /// use llama_cpp_4::context::params::LlamaContextParams;
244    /// let params = LlamaContextParams::default();
245    /// assert_eq!(params.offload_kqv(), true);
246    /// ```
247    #[must_use]
248    pub fn offload_kqv(&self) -> bool {
249        self.context_params.offload_kqv
250    }
251
252    /// Set the type of rope scaling.
253    ///
254    /// # Examples
255    ///
256    /// ```rust
257    /// use llama_cpp_4::context::params::{LlamaContextParams, RopeScalingType};
258    /// let params = LlamaContextParams::default()
259    ///     .with_rope_scaling_type(RopeScalingType::Linear);
260    /// assert_eq!(params.rope_scaling_type(), RopeScalingType::Linear);
261    /// ```
262    #[must_use]
263    pub fn with_rope_scaling_type(mut self, rope_scaling_type: RopeScalingType) -> Self {
264        self.context_params.rope_scaling_type = i32::from(rope_scaling_type);
265        self
266    }
267
268    /// Get the type of rope scaling.
269    ///
270    /// # Examples
271    ///
272    /// ```rust
273    /// let params = llama_cpp_4::context::params::LlamaContextParams::default();
274    /// assert_eq!(params.rope_scaling_type(), llama_cpp_4::context::params::RopeScalingType::Unspecified);
275    /// ```
276    #[must_use]
277    pub fn rope_scaling_type(&self) -> RopeScalingType {
278        RopeScalingType::from(self.context_params.rope_scaling_type)
279    }
280
281    /// Set the rope frequency base.
282    ///
283    /// # Examples
284    ///
285    /// ```rust
286    /// use llama_cpp_4::context::params::LlamaContextParams;
287    /// let params = LlamaContextParams::default()
288    ///    .with_rope_freq_base(0.5);
289    /// assert_eq!(params.rope_freq_base(), 0.5);
290    /// ```
291    #[must_use]
292    pub fn with_rope_freq_base(mut self, rope_freq_base: f32) -> Self {
293        self.context_params.rope_freq_base = rope_freq_base;
294        self
295    }
296
297    /// Get the rope frequency base.
298    ///
299    /// # Examples
300    ///
301    /// ```rust
302    /// let params = llama_cpp_4::context::params::LlamaContextParams::default();
303    /// assert_eq!(params.rope_freq_base(), 0.0);
304    /// ```
305    #[must_use]
306    pub fn rope_freq_base(&self) -> f32 {
307        self.context_params.rope_freq_base
308    }
309
310    /// Set the rope frequency scale.
311    ///
312    /// # Examples
313    ///
314    /// ```rust
315    /// use llama_cpp_4::context::params::LlamaContextParams;
316    /// let params = LlamaContextParams::default()
317    ///   .with_rope_freq_scale(0.5);
318    /// assert_eq!(params.rope_freq_scale(), 0.5);
319    /// ```
320    #[must_use]
321    pub fn with_rope_freq_scale(mut self, rope_freq_scale: f32) -> Self {
322        self.context_params.rope_freq_scale = rope_freq_scale;
323        self
324    }
325
326    /// Get the rope frequency scale.
327    ///
328    /// # Examples
329    ///
330    /// ```rust
331    /// let params = llama_cpp_4::context::params::LlamaContextParams::default();
332    /// assert_eq!(params.rope_freq_scale(), 0.0);
333    /// ```
334    #[must_use]
335    pub fn rope_freq_scale(&self) -> f32 {
336        self.context_params.rope_freq_scale
337    }
338
339    /// Get the number of threads.
340    ///
341    /// # Examples
342    ///
343    /// ```rust
344    /// let params = llama_cpp_4::context::params::LlamaContextParams::default();
345    /// assert_eq!(params.n_threads(), 4);
346    /// ```
347    #[must_use]
348    pub fn n_threads(&self) -> i32 {
349        self.context_params.n_threads
350    }
351
352    /// Get the number of threads allocated for batches.
353    ///
354    /// # Examples
355    ///
356    /// ```rust
357    /// let params = llama_cpp_4::context::params::LlamaContextParams::default();
358    /// assert_eq!(params.n_threads_batch(), 4);
359    /// ```
360    #[must_use]
361    pub fn n_threads_batch(&self) -> i32 {
362        self.context_params.n_threads_batch
363    }
364
365    /// Set the number of threads.
366    ///
367    /// # Examples
368    ///
369    /// ```rust
370    /// use llama_cpp_4::context::params::LlamaContextParams;
371    /// let params = LlamaContextParams::default()
372    ///    .with_n_threads(8);
373    /// assert_eq!(params.n_threads(), 8);
374    /// ```
375    #[must_use]
376    pub fn with_n_threads(mut self, n_threads: i32) -> Self {
377        self.context_params.n_threads = n_threads;
378        self
379    }
380
381    /// Set the number of threads allocated for batches.
382    ///
383    /// # Examples
384    ///
385    /// ```rust
386    /// use llama_cpp_4::context::params::LlamaContextParams;
387    /// let params = LlamaContextParams::default()
388    ///    .with_n_threads_batch(8);
389    /// assert_eq!(params.n_threads_batch(), 8);
390    /// ```
391    #[must_use]
392    pub fn with_n_threads_batch(mut self, n_threads: i32) -> Self {
393        self.context_params.n_threads_batch = n_threads;
394        self
395    }
396
397    /// Check whether embeddings are enabled
398    ///
399    /// # Examples
400    ///
401    /// ```rust
402    /// let params = llama_cpp_4::context::params::LlamaContextParams::default();
403    /// assert!(!params.embeddings());
404    /// ```
405    #[must_use]
406    pub fn embeddings(&self) -> bool {
407        self.context_params.embeddings
408    }
409
410    /// Enable the use of embeddings
411    ///
412    /// # Examples
413    ///
414    /// ```rust
415    /// use llama_cpp_4::context::params::LlamaContextParams;
416    /// let params = LlamaContextParams::default()
417    ///    .with_embeddings(true);
418    /// assert!(params.embeddings());
419    /// ```
420    #[must_use]
421    pub fn with_embeddings(mut self, embedding: bool) -> Self {
422        self.context_params.embeddings = embedding;
423        self
424    }
425
426    /// Set the evaluation callback.
427    ///
428    /// # Examples
429    ///
430    /// ```no_run
431    /// extern "C" fn cb_eval_fn(
432    ///     t: *mut llama_cpp_sys_4::ggml_tensor,
433    ///     ask: bool,
434    ///     user_data: *mut std::ffi::c_void,
435    /// ) -> bool {
436    ///     false
437    /// }
438    ///
439    /// use llama_cpp_4::context::params::LlamaContextParams;
440    /// let params = LlamaContextParams::default().with_cb_eval(Some(cb_eval_fn));
441    /// ```
442    #[must_use]
443    pub fn with_cb_eval(
444        mut self,
445        cb_eval: llama_cpp_sys_4::ggml_backend_sched_eval_callback,
446    ) -> Self {
447        self.context_params.cb_eval = cb_eval;
448        self
449    }
450
451    /// Set the evaluation callback user data.
452    ///
453    /// # Examples
454    ///
455    /// ```no_run
456    /// use llama_cpp_4::context::params::LlamaContextParams;
457    /// let params = LlamaContextParams::default();
458    /// let user_data = std::ptr::null_mut();
459    /// let params = params.with_cb_eval_user_data(user_data);
460    /// ```
461    #[must_use]
462    pub fn with_cb_eval_user_data(mut self, cb_eval_user_data: *mut std::ffi::c_void) -> Self {
463        self.context_params.cb_eval_user_data = cb_eval_user_data;
464        self
465    }
466
467    /// Attach a [`TensorCapture`](super::tensor_capture::TensorCapture) to
468    /// intercept intermediate tensor outputs during [`crate::LlamaContext::decode`].
469    ///
470    /// Sets `cb_eval` to copy tensors matching the capture filter (layer outputs,
471    /// named nodes, prefix, or all). After `decode()`, read results from the
472    /// capture — see [`crate::TensorCapture`] and [`crate::context::tensor_capture`].
473    ///
474    /// The capture must outlive the context. Call
475    /// [`TensorCapture::clear`](crate::TensorCapture::clear) before reusing it
476    /// on another batch. Prefer [`Self::with_tensor_transactions`], which
477    /// transfers owned pinned callback state into the context.
478    ///
479    /// # Safety
480    ///
481    /// The caller must keep `capture` at a stable address until after the
482    /// resulting context is dropped. It must not be accessed concurrently with
483    /// any context operation that can invoke the callback. The reference
484    /// accepted here does not remain borrowed by the returned params.
485    ///
486    /// # Example
487    ///
488    /// ```no_run
489    /// use llama_cpp_4::prelude::*;
490    ///
491    /// fn main() {
492    ///     let backend = LlamaBackend::init().unwrap();
493    ///     let model = LlamaModel::load_from_file(
494    ///         &backend,
495    ///         "model.gguf",
496    ///         &LlamaModelParams::default(),
497    ///     )
498    ///     .unwrap();
499    ///
500    ///     let mut capture = TensorCapture::for_layers(&[13, 20, 27]);
501    ///     let ctx_params = unsafe {
502    ///         LlamaContextParams::default().with_tensor_capture(&mut capture)
503    ///     };
504    ///     let _ctx = model.new_context(&backend, ctx_params).unwrap();
505    /// }
506    /// ```
507    #[must_use]
508    pub unsafe fn with_tensor_capture(
509        self,
510        capture: &mut super::tensor_capture::TensorCapture,
511    ) -> Self {
512        self.with_cb_eval(Some(super::tensor_capture::tensor_capture_callback))
513            .with_cb_eval_user_data(
514                std::ptr::from_mut::<super::tensor_capture::TensorCapture>(capture)
515                    .cast::<std::ffi::c_void>(),
516            )
517    }
518
519    /// Attaches bounded owned tensor transactions to the context.
520    ///
521    /// The transaction state is pinned before its address is installed in the
522    /// native callback parameters. On successful context creation ownership
523    /// moves into [`crate::LlamaContext`] and remains there until after
524    /// `llama_free`.
525    ///
526    /// # Panics
527    ///
528    /// Panics if the linked `libllama` was not built with the decode-lifecycle
529    /// hooks patch — an ABI mismatch that would otherwise silently disable the
530    /// hooks or misread the context-params struct.
531    #[must_use]
532    pub fn with_tensor_transactions(mut self, transactions: TensorTransactions) -> Self {
533        // ABI guard: this symbol is defined only in a libllama built with the
534        // decode-lifecycle-hooks patch (0004). Referencing it makes an
535        // unpatched or ABI-mismatched prebuilt fail at link time — and, if it
536        // somehow linked, this assert fails — rather than silently dropping the
537        // hooks or reading a struct with a different layout.
538        assert_eq!(
539            unsafe { llama_cpp_sys_4::llama_cpp_rs_decode_hooks_abi_v1() },
540            1,
541            "libllama is missing the decode-hook ABI patch (0004)",
542        );
543
544        let mut transactions = Box::pin(transactions);
545        let user_data = std::ptr::from_mut(transactions.as_mut().get_mut()).cast();
546        self.context_params.cb_eval = Some(tensor_transaction_callback);
547        self.context_params.cb_eval_user_data = user_data;
548        self.context_params.cb_decode_begin = Some(tensor_transaction_decode_begin);
549        self.context_params.cb_decode_end = Some(tensor_transaction_decode_end);
550        self.tensor_transactions = Some(transactions);
551        self
552    }
553
554    /// Set the storage type for the **K** (key) KV cache tensors.
555    ///
556    /// The default is `GgmlType::F16`.  Quantized types like `GgmlType::Q5_0`
557    /// or `GgmlType::Q4_0` reduce VRAM usage significantly; combining them with
558    /// `TurboQuant` attention rotation (the default) keeps quality high.
559    ///
560    /// # Examples
561    ///
562    /// ```rust
563    /// use llama_cpp_4::context::params::LlamaContextParams;
564    /// use llama_cpp_4::quantize::GgmlType;
565    /// let params = LlamaContextParams::default()
566    ///     .with_cache_type_k(GgmlType::Q5_0);
567    /// ```
568    #[must_use]
569    pub fn with_cache_type_k(mut self, ty: crate::quantize::GgmlType) -> Self {
570        self.context_params.type_k = ty as llama_cpp_sys_4::ggml_type;
571        self
572    }
573
574    /// Get the K-cache storage type.
575    #[must_use]
576    pub fn cache_type_k(&self) -> llama_cpp_sys_4::ggml_type {
577        self.context_params.type_k
578    }
579
580    /// Set the storage type for the **V** (value) KV cache tensors.
581    ///
582    /// See [`with_cache_type_k`](Self::with_cache_type_k) for details.
583    ///
584    /// # Examples
585    ///
586    /// ```rust
587    /// use llama_cpp_4::context::params::LlamaContextParams;
588    /// use llama_cpp_4::quantize::GgmlType;
589    /// let params = LlamaContextParams::default()
590    ///     .with_cache_type_v(GgmlType::Q5_0);
591    /// ```
592    #[must_use]
593    pub fn with_cache_type_v(mut self, ty: crate::quantize::GgmlType) -> Self {
594        self.context_params.type_v = ty as llama_cpp_sys_4::ggml_type;
595        self
596    }
597
598    /// Get the V-cache storage type.
599    #[must_use]
600    pub fn cache_type_v(&self) -> llama_cpp_sys_4::ggml_type {
601        self.context_params.type_v
602    }
603
604    /// Control the `TurboQuant` attention-rotation feature (llama.cpp PR #21038).
605    ///
606    /// By default, llama.cpp applies a Hadamard rotation to Q/K/V tensors
607    /// before writing them into the KV cache.  This significantly improves
608    /// quantized KV-cache quality at near-zero overhead, and is enabled
609    /// automatically for models whose head dimension is a power of two.
610    ///
611    /// Set `disabled = true` to opt out (equivalent to `LLAMA_ATTN_ROT_DISABLE=1`).
612    /// The env-var is applied just before the context is created and restored
613    /// afterwards, so this is safe to call from a single thread.
614    ///
615    /// # Examples
616    ///
617    /// ```rust
618    /// use llama_cpp_4::context::params::LlamaContextParams;
619    /// // Disable rotation for this context only:
620    /// let params = LlamaContextParams::default().with_attn_rot_disabled(true);
621    /// assert!(params.attn_rot_disabled());
622    /// ```
623    #[must_use]
624    pub fn with_attn_rot_disabled(mut self, disabled: bool) -> Self {
625        self.attn_rot_disabled = disabled;
626        self
627    }
628
629    /// Returns `true` if `TurboQuant` attention rotation is disabled for this context.
630    ///
631    /// ```rust
632    /// let params = llama_cpp_4::context::params::LlamaContextParams::default();
633    /// assert!(!params.attn_rot_disabled());
634    /// ```
635    #[must_use]
636    pub fn attn_rot_disabled(&self) -> bool {
637        self.attn_rot_disabled
638    }
639
640    /// Set the type of pooling.
641    ///
642    /// # Examples
643    ///
644    /// ```rust
645    /// use llama_cpp_4::context::params::{LlamaContextParams, LlamaPoolingType};
646    /// let params = LlamaContextParams::default()
647    ///     .with_pooling_type(LlamaPoolingType::Last);
648    /// assert_eq!(params.pooling_type(), LlamaPoolingType::Last);
649    /// ```
650    #[must_use]
651    pub fn with_pooling_type(mut self, pooling_type: LlamaPoolingType) -> Self {
652        self.context_params.pooling_type = i32::from(pooling_type);
653        self
654    }
655
656    /// Get the type of pooling.
657    ///
658    /// # Examples
659    ///
660    /// ```rust
661    /// let params = llama_cpp_4::context::params::LlamaContextParams::default();
662    /// assert_eq!(params.pooling_type(), llama_cpp_4::context::params::LlamaPoolingType::Unspecified);
663    /// ```
664    #[must_use]
665    pub fn pooling_type(&self) -> LlamaPoolingType {
666        LlamaPoolingType::from(self.context_params.pooling_type)
667    }
668
669    /// Clone these params, failing when sampler chains are attached.
670    ///
671    /// Prefer this over [`Clone::clone`] when you need to detect dropped sampler
672    /// configuration.
673    ///
674    /// # Errors
675    ///
676    /// Returns [`ParamsCloneError::SamplerChains`] when per-sequence sampler
677    /// chains are attached and cannot be duplicated, or
678    /// [`ParamsCloneError::TensorTransactions`] when an owned callback program
679    /// is attached.
680    pub fn try_clone(&self) -> Result<Self, ParamsCloneError> {
681        if !self.sampler_configs.is_empty() {
682            return Err(ParamsCloneError::SamplerChains);
683        }
684        if self.tensor_transactions.is_some() {
685            return Err(ParamsCloneError::TensorTransactions);
686        }
687        Ok(self.clone())
688    }
689}
690
691/// Default parameters for `LlamaContext`. (as defined in llama.cpp by `llama_context_default_params`)
692/// ```
693/// # use std::num::NonZeroU32;
694/// use llama_cpp_4::context::params::{LlamaContextParams, RopeScalingType};
695/// let params = LlamaContextParams::default();
696/// assert_eq!(params.n_ctx(), NonZeroU32::new(512), "n_ctx should be 512");
697/// assert_eq!(params.rope_scaling_type(), RopeScalingType::Unspecified);
698/// ```
699impl Default for LlamaContextParams {
700    fn default() -> Self {
701        let context_params = unsafe { llama_cpp_sys_4::llama_context_default_params() };
702        Self {
703            context_params,
704            attn_rot_disabled: false,
705            owned_samplers: Vec::new(),
706            sampler_configs: Vec::new(),
707            tensor_transactions: None,
708        }
709    }
710}
711
712/// Duplicate context params for reuse.
713///
714/// Sampler chains attached via [`LlamaContextParams::with_sampler_seq_configs`]
715/// are **not** cloned — the copy clears `samplers` / `n_samplers` because the
716/// underlying C chains cannot be duplicated safely.
717impl Clone for LlamaContextParams {
718    fn clone(&self) -> Self {
719        let mut context_params = self.context_params;
720        // Sampler chains cannot be duplicated here; cloned params omit them.
721        context_params.samplers = std::ptr::null_mut();
722        context_params.n_samplers = 0;
723        if self.tensor_transactions.is_some() {
724            context_params.cb_eval = None;
725            context_params.cb_eval_user_data = std::ptr::null_mut();
726            context_params.cb_decode_begin = None;
727            context_params.cb_decode_end = None;
728        }
729        Self {
730            context_params,
731            attn_rot_disabled: self.attn_rot_disabled,
732            owned_samplers: Vec::new(),
733            sampler_configs: Vec::new(),
734            tensor_transactions: None,
735        }
736    }
737}