Skip to main content

rig_llama_cpp/
types.rs

1use rig_core::completion::{CompletionError, GetTokenUsage, Usage};
2use rig_core::message::AssistantContent;
3use rig_core::one_or_many::OneOrMany;
4use rig_core::streaming::RawStreamingChoice;
5use serde::{Deserialize, Serialize};
6use tokio::sync::{mpsc, oneshot};
7
8/// Raw completion response returned by the model.
9///
10/// Marked `#[non_exhaustive]` because new fields may be added in future
11/// minor releases.
12#[derive(Clone, Debug, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct RawResponse {
15    /// The full generated text.
16    pub text: String,
17}
18
19/// A single chunk emitted during streaming inference.
20///
21/// The final chunk in a stream includes token usage counts. Marked
22/// `#[non_exhaustive]` because new fields may be added in future minor
23/// releases.
24#[derive(Clone, Debug, Serialize, Deserialize)]
25#[non_exhaustive]
26pub struct StreamChunk {
27    /// The text fragment for this chunk.
28    pub text: String,
29    /// Number of prompt tokens (only set on the final chunk).
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub prompt_tokens: Option<u64>,
32    /// Number of completion tokens (only set on the final chunk).
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub completion_tokens: Option<u64>,
35    /// Number of prompt tokens that were served from the persistent KV-cache prefix
36    /// (only set on the final chunk).
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub cached_input_tokens: Option<u64>,
39}
40
41impl GetTokenUsage for StreamChunk {
42    fn token_usage(&self) -> Usage {
43        let Some((input, output)) = self.prompt_tokens.zip(self.completion_tokens) else {
44            return Usage::new();
45        };
46        Usage {
47            input_tokens: input,
48            output_tokens: output,
49            total_tokens: input + output,
50            cached_input_tokens: self.cached_input_tokens.unwrap_or(0),
51            cache_creation_input_tokens: 0,
52            tool_use_prompt_tokens: 0,
53            reasoning_tokens: 0,
54        }
55    }
56}
57
58pub(crate) type StreamSender =
59    mpsc::UnboundedSender<Result<RawStreamingChoice<StreamChunk>, CompletionError>>;
60
61pub(crate) enum ResponseChannel {
62    Completion(oneshot::Sender<Result<InferenceResult, String>>),
63    Streaming(StreamSender),
64}
65
66pub(crate) enum InferenceCommand {
67    Request(InferenceRequest),
68    Reload(ReloadRequest),
69    Shutdown,
70}
71
72pub(crate) struct ReloadRequest {
73    pub model_path: String,
74    pub mmproj_path: Option<String>,
75    pub n_ctx: u32,
76    pub fit_params: FitParams,
77    pub kv_cache_params: KvCacheParams,
78    pub checkpoint_params: CheckpointParams,
79    pub result_tx: std::sync::mpsc::Sender<Result<(), crate::error::LoadError>>,
80}
81
82pub(crate) struct InferenceRequest {
83    pub params: InferenceParams,
84    pub response_channel: ResponseChannel,
85}
86
87pub(crate) struct InferenceParams {
88    pub prepared_request: PreparedRequest,
89    pub max_tokens: u32,
90    pub temperature: f32,
91    pub top_p: f32,
92    pub top_k: i32,
93    pub min_p: f32,
94    pub presence_penalty: f32,
95    pub repetition_penalty: f32,
96}
97
98pub(crate) struct InferenceResult {
99    pub text: String,
100    pub choice: OneOrMany<AssistantContent>,
101    pub prompt_tokens: u64,
102    pub completion_tokens: u64,
103    /// Tokens of the prompt that were already present in the persistent KV cache
104    /// (i.e. the longest common prefix shared with the previous request).
105    pub cached_input_tokens: u64,
106}
107
108pub(crate) struct PreparedRequest {
109    pub messages_json: String,
110    pub tools_json: Option<String>,
111    pub tool_choice: Option<String>,
112    pub json_schema: Option<String>,
113    /// Parsed from the request's `additional_params` (`{ "thinking": bool }`).
114    ///
115    /// `None` means the caller expressed no preference, and is distinct from
116    /// `Some(false)`: templates commonly gate reasoning on
117    /// `enable_thinking is defined` or `| default(true)`, so passing an
118    /// explicit `false` for an unasked question turns thinking *off* for a
119    /// model that reasons by default. `None` reaches the template as undefined,
120    /// leaving its own default in charge.
121    ///
122    /// Forwarded as the `enable_thinking` variable, but only on the minijinja
123    /// paths (`src/jinja.rs`). llama.cpp's own applier takes just
124    /// `(role, content)` pairs — `llama-cpp-2` 0.1.147 dropped the
125    /// `chat_template_kwargs` plumbing the old oaicompat path used — so for a
126    /// model llama.cpp can template natively this flag stays advisory and the
127    /// template's own default decides.
128    pub enable_thinking: Option<bool>,
129    #[cfg(feature = "mtmd")]
130    pub images: Vec<PreparedImage>,
131}
132
133/// One image extracted from the chat history with its FNV-1a hash precomputed.
134/// The hash is propagated into the underlying `MtmdBitmap` via `set_id` so
135/// that `MtmdInputChunk::id()` round-trips it for the prefix-cache diff.
136#[cfg(feature = "mtmd")]
137#[derive(Clone, Debug)]
138pub(crate) struct PreparedImage {
139    pub bytes: Vec<u8>,
140    pub hash: u64,
141}
142
143pub(crate) struct PromptBuildResult {
144    pub prompt: String,
145}
146
147/// Sampling parameters that control token generation.
148///
149/// Marked `#[non_exhaustive]` so future sampling knobs can be added without
150/// a breaking release. Start from [`SamplingParams::default`] and chain
151/// `with_*` setters:
152///
153/// ```
154/// let params = rig_llama_cpp::SamplingParams::default()
155///     .with_top_k(40)
156///     .with_presence_penalty(1.5);
157/// ```
158#[derive(Clone, Copy, Debug)]
159#[non_exhaustive]
160pub struct SamplingParams {
161    /// Nucleus sampling threshold (default: `0.95`).
162    pub top_p: f32,
163    /// Top-k sampling parameter (default: `40`).
164    pub top_k: i32,
165    /// Minimum probability threshold (default: `0.0`).
166    pub min_p: f32,
167    /// Penalty for token presence (default: `0.0`).
168    pub presence_penalty: f32,
169    /// Penalty for token repetition (default: `1.0`).
170    pub repetition_penalty: f32,
171}
172
173impl Default for SamplingParams {
174    fn default() -> Self {
175        Self {
176            top_p: 0.95,
177            top_k: 40,
178            min_p: 0.0,
179            presence_penalty: 0.0,
180            repetition_penalty: 1.0,
181        }
182    }
183}
184
185impl SamplingParams {
186    /// Set the nucleus sampling threshold.
187    #[must_use]
188    pub fn with_top_p(mut self, top_p: f32) -> Self {
189        self.top_p = top_p;
190        self
191    }
192
193    /// Set the top-k sampling parameter.
194    #[must_use]
195    pub fn with_top_k(mut self, top_k: i32) -> Self {
196        self.top_k = top_k;
197        self
198    }
199
200    /// Set the minimum probability threshold.
201    #[must_use]
202    pub fn with_min_p(mut self, min_p: f32) -> Self {
203        self.min_p = min_p;
204        self
205    }
206
207    /// Set the presence penalty.
208    #[must_use]
209    pub fn with_presence_penalty(mut self, presence_penalty: f32) -> Self {
210        self.presence_penalty = presence_penalty;
211        self
212    }
213
214    /// Set the repetition penalty.
215    #[must_use]
216    pub fn with_repetition_penalty(mut self, repetition_penalty: f32) -> Self {
217        self.repetition_penalty = repetition_penalty;
218        self
219    }
220}
221
222/// Configuration for automatic GPU/CPU layer fitting.
223///
224/// Passed to [`crate::Client::builder`] (or [`crate::Client::from_gguf`]) so
225/// llama.cpp can probe available device memory and pick the optimal number
226/// of layers to offload to GPU automatically, instead of requiring a manual
227/// `n_gpu_layers` value.
228///
229/// Marked `#[non_exhaustive]`; build via `Default::default()` and chain the
230/// `with_*` setters.
231#[derive(Clone, Debug)]
232#[non_exhaustive]
233pub struct FitParams {
234    /// Memory margin per device in bytes. If `None`, defaults to 1 GiB per device.
235    pub margins: Option<Vec<usize>>,
236    /// Minimum context size to preserve during fitting (default: `4096`).
237    pub n_ctx_min: u32,
238}
239
240impl Default for FitParams {
241    fn default() -> Self {
242        Self {
243            margins: None,
244            n_ctx_min: 4096,
245        }
246    }
247}
248
249impl FitParams {
250    /// Override the per-device memory margin in bytes.
251    #[must_use]
252    pub fn with_margins(mut self, margins: Option<Vec<usize>>) -> Self {
253        self.margins = margins;
254        self
255    }
256
257    /// Override the minimum context size to preserve during fitting.
258    #[must_use]
259    pub fn with_n_ctx_min(mut self, n_ctx_min: u32) -> Self {
260        self.n_ctx_min = n_ctx_min;
261        self
262    }
263}
264
265/// Tunable parameters for the in-memory state-checkpoint cache used to
266/// preserve KV/recurrent state across chat turns for hybrid models.
267///
268/// Hybrid architectures (Qwen 3.5, Jamba, etc.) interleave Mamba-style
269/// recurrent layers with transformer layers. The recurrent state can't be
270/// rolled back to an arbitrary earlier position, so a partial KV trim
271/// fails whenever the next prompt diverges deep into the conversation.
272/// To work around this, we periodically snapshot the partial seq state
273/// (recurrent + SWA, via `LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY`) during
274/// prompt prefill and restore the closest snapshot when the next prompt
275/// arrives. Mirrors the mechanism used by upstream `llama-server`.
276///
277/// For non-hybrid models (Qwen 2.5, Llama 3, Gemma, ...) checkpoints are
278/// created but never used because the cheaper partial-trim path
279/// succeeds.
280///
281/// Marked `#[non_exhaustive]`; build via `Default::default()` and chain the
282/// `with_*` setters.
283#[derive(Clone, Copy, Debug)]
284#[non_exhaustive]
285pub struct CheckpointParams {
286    /// Maximum number of checkpoints retained per persistent context.
287    /// `0` disables checkpointing entirely. Each checkpoint is a few MB
288    /// for typical hybrid models.
289    pub max_checkpoints: u32,
290    /// Approximate spacing between checkpoints during prompt prefill, in
291    /// tokens. The last `4..=4 + n_ubatch` tokens always get a
292    /// checkpoint regardless. `<= 0` means "only checkpoint near the end
293    /// of the prompt".
294    pub every_n_tokens: i32,
295    /// Don't checkpoint the very start of a prompt — saves space for
296    /// no benefit because we'd have to re-decode that prefix anyway if
297    /// it's the entire reuse window.
298    pub min_tokens: u32,
299    /// Don't take two checkpoints closer than this many tokens apart.
300    pub min_gap: u32,
301}
302
303impl Default for CheckpointParams {
304    fn default() -> Self {
305        Self {
306            // llama-server uses 32; cap lower because each checkpoint is
307            // a few MB and we'd rather not balloon RSS.
308            max_checkpoints: 8,
309            every_n_tokens: 8192,
310            min_tokens: 64,
311            min_gap: 64,
312        }
313    }
314}
315
316impl CheckpointParams {
317    /// Override the maximum number of checkpoints retained per context.
318    #[must_use]
319    pub fn with_max_checkpoints(mut self, max_checkpoints: u32) -> Self {
320        self.max_checkpoints = max_checkpoints;
321        self
322    }
323
324    /// Override the approximate spacing between checkpoints (in tokens).
325    #[must_use]
326    pub fn with_every_n_tokens(mut self, every_n_tokens: i32) -> Self {
327        self.every_n_tokens = every_n_tokens;
328        self
329    }
330
331    /// Override the minimum prompt length before checkpoints are taken.
332    #[must_use]
333    pub fn with_min_tokens(mut self, min_tokens: u32) -> Self {
334        self.min_tokens = min_tokens;
335        self
336    }
337
338    /// Override the minimum spacing between two consecutive checkpoints.
339    #[must_use]
340    pub fn with_min_gap(mut self, min_gap: u32) -> Self {
341        self.min_gap = min_gap;
342        self
343    }
344}
345
346/// Data type used for an entry in the attention KV cache.
347///
348/// Mirrors the subset of `ggml_type` values that `llama.cpp` accepts as KV
349/// cache element types. The `F16` default preserves full attention quality;
350/// quantizing (e.g. `Q8_0` ≈ ½ size, `Q4_0` ≈ ¼ size) trades a small amount
351/// of accuracy for a large VRAM reduction at long `n_ctx`.
352///
353/// This is a local shim around `llama_cpp_2::context::params::KvCacheType`
354/// so a future `llama-cpp-2` update doesn't force a breaking release of
355/// `rig-llama-cpp`. Marked `#[non_exhaustive]`: when llama.cpp adds a new
356/// `ggml_type`, we add a corresponding variant in a minor (`0.1.x`) release.
357#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
358#[allow(non_camel_case_types)]
359#[non_exhaustive]
360pub enum KvCacheType {
361    /// IEEE 754 single precision.
362    F32,
363    /// IEEE 754 half precision (llama.cpp's default for both K and V).
364    F16,
365    /// Brain floating-point 16, common on newer NVIDIA / AMD GPUs.
366    BF16,
367    /// IEEE 754 double precision.
368    F64,
369    /// 4-bit block quantization, type 0.
370    Q4_0,
371    /// 4-bit block quantization, type 1.
372    Q4_1,
373    /// 5-bit block quantization, type 0.
374    Q5_0,
375    /// 5-bit block quantization, type 1.
376    Q5_1,
377    /// 8-bit block quantization, type 0.
378    Q8_0,
379    /// 8-bit block quantization, type 1.
380    Q8_1,
381    /// 2-bit K-quant.
382    Q2_K,
383    /// 3-bit K-quant.
384    Q3_K,
385    /// 4-bit K-quant.
386    Q4_K,
387    /// 5-bit K-quant.
388    Q5_K,
389    /// 6-bit K-quant.
390    Q6_K,
391    /// 8-bit K-quant.
392    Q8_K,
393    /// Importance-weighted 2-bit, extra-extra-small.
394    IQ2_XXS,
395    /// Importance-weighted 2-bit, extra-small.
396    IQ2_XS,
397    /// Importance-weighted 2-bit, small.
398    IQ2_S,
399    /// Importance-weighted 3-bit, extra-extra-small.
400    IQ3_XXS,
401    /// Importance-weighted 3-bit, small.
402    IQ3_S,
403    /// Importance-weighted 1-bit, small.
404    IQ1_S,
405    /// Importance-weighted 1-bit, medium.
406    IQ1_M,
407    /// Importance-weighted 4-bit, extra-small.
408    IQ4_XS,
409    /// Importance-weighted 4-bit, non-linear.
410    IQ4_NL,
411    /// Signed 8-bit integer.
412    I8,
413    /// Signed 16-bit integer.
414    I16,
415    /// Signed 32-bit integer.
416    I32,
417    /// Signed 64-bit integer.
418    I64,
419    /// Ternary 1-bit, type 0.
420    TQ1_0,
421    /// Ternary 2-bit, type 0.
422    TQ2_0,
423    /// Microscaling FP4.
424    MXFP4,
425}
426
427impl From<KvCacheType> for llama_cpp_2::context::params::KvCacheType {
428    fn from(value: KvCacheType) -> Self {
429        use llama_cpp_2::context::params::KvCacheType as Upstream;
430        match value {
431            KvCacheType::F32 => Upstream::F32,
432            KvCacheType::F16 => Upstream::F16,
433            KvCacheType::BF16 => Upstream::BF16,
434            KvCacheType::F64 => Upstream::F64,
435            KvCacheType::Q4_0 => Upstream::Q4_0,
436            KvCacheType::Q4_1 => Upstream::Q4_1,
437            KvCacheType::Q5_0 => Upstream::Q5_0,
438            KvCacheType::Q5_1 => Upstream::Q5_1,
439            KvCacheType::Q8_0 => Upstream::Q8_0,
440            KvCacheType::Q8_1 => Upstream::Q8_1,
441            KvCacheType::Q2_K => Upstream::Q2_K,
442            KvCacheType::Q3_K => Upstream::Q3_K,
443            KvCacheType::Q4_K => Upstream::Q4_K,
444            KvCacheType::Q5_K => Upstream::Q5_K,
445            KvCacheType::Q6_K => Upstream::Q6_K,
446            KvCacheType::Q8_K => Upstream::Q8_K,
447            KvCacheType::IQ2_XXS => Upstream::IQ2_XXS,
448            KvCacheType::IQ2_XS => Upstream::IQ2_XS,
449            KvCacheType::IQ2_S => Upstream::IQ2_S,
450            KvCacheType::IQ3_XXS => Upstream::IQ3_XXS,
451            KvCacheType::IQ3_S => Upstream::IQ3_S,
452            KvCacheType::IQ1_S => Upstream::IQ1_S,
453            KvCacheType::IQ1_M => Upstream::IQ1_M,
454            KvCacheType::IQ4_XS => Upstream::IQ4_XS,
455            KvCacheType::IQ4_NL => Upstream::IQ4_NL,
456            KvCacheType::I8 => Upstream::I8,
457            KvCacheType::I16 => Upstream::I16,
458            KvCacheType::I32 => Upstream::I32,
459            KvCacheType::I64 => Upstream::I64,
460            KvCacheType::TQ1_0 => Upstream::TQ1_0,
461            KvCacheType::TQ2_0 => Upstream::TQ2_0,
462            KvCacheType::MXFP4 => Upstream::MXFP4,
463        }
464    }
465}
466
467/// KV cache quantization configuration.
468///
469/// Controls the data type used for the attention K and V caches. llama.cpp defaults
470/// both to `F16` (`GGML_TYPE_F16`), which is what `KvCacheParams::default()` preserves.
471/// Quantizing the KV cache (e.g. `Q8_0` → ~½ size, `Q4_0` → ~¼ size) trades a small
472/// amount of accuracy for a large reduction in VRAM usage, which is often the dominant
473/// cost at long `n_ctx`.
474///
475/// Marked `#[non_exhaustive]`; build via `Default::default()` and chain the
476/// `with_*` setters:
477///
478/// ```
479/// use rig_llama_cpp::{KvCacheParams, KvCacheType};
480///
481/// let kv = KvCacheParams::default()
482///     .with_type_k(KvCacheType::Q8_0)
483///     .with_type_v(KvCacheType::Q8_0);
484/// ```
485#[derive(Clone, Copy, Debug)]
486#[non_exhaustive]
487pub struct KvCacheParams {
488    /// Data type for the K cache (default: [`KvCacheType::F16`]).
489    pub type_k: KvCacheType,
490    /// Data type for the V cache (default: [`KvCacheType::F16`]).
491    pub type_v: KvCacheType,
492}
493
494impl Default for KvCacheParams {
495    fn default() -> Self {
496        Self {
497            type_k: KvCacheType::F16,
498            type_v: KvCacheType::F16,
499        }
500    }
501}
502
503impl KvCacheParams {
504    /// Override the K cache data type.
505    #[must_use]
506    pub fn with_type_k(mut self, type_k: KvCacheType) -> Self {
507        self.type_k = type_k;
508        self
509    }
510
511    /// Override the V cache data type.
512    #[must_use]
513    pub fn with_type_v(mut self, type_v: KvCacheType) -> Self {
514        self.type_v = type_v;
515        self
516    }
517}