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 /// Forwarded to the template as the `enable_thinking` variable, but only on
116 /// the minijinja path (`src/jinja.rs`). llama.cpp's own applier takes just
117 /// `(role, content)` pairs — `llama-cpp-2` 0.1.147 dropped the
118 /// `chat_template_kwargs` plumbing the old oaicompat path used — so for a
119 /// model llama.cpp can template natively this flag stays advisory and the
120 /// template's own default decides.
121 pub enable_thinking: bool,
122 #[cfg(feature = "mtmd")]
123 pub images: Vec<PreparedImage>,
124}
125
126/// One image extracted from the chat history with its FNV-1a hash precomputed.
127/// The hash is propagated into the underlying `MtmdBitmap` via `set_id` so
128/// that `MtmdInputChunk::id()` round-trips it for the prefix-cache diff.
129#[cfg(feature = "mtmd")]
130#[derive(Clone, Debug)]
131pub(crate) struct PreparedImage {
132 pub bytes: Vec<u8>,
133 pub hash: u64,
134}
135
136pub(crate) struct PromptBuildResult {
137 pub prompt: String,
138}
139
140/// Sampling parameters that control token generation.
141///
142/// Marked `#[non_exhaustive]` so future sampling knobs can be added without
143/// a breaking release. Start from [`SamplingParams::default`] and chain
144/// `with_*` setters:
145///
146/// ```
147/// let params = rig_llama_cpp::SamplingParams::default()
148/// .with_top_k(40)
149/// .with_presence_penalty(1.5);
150/// ```
151#[derive(Clone, Copy, Debug)]
152#[non_exhaustive]
153pub struct SamplingParams {
154 /// Nucleus sampling threshold (default: `0.95`).
155 pub top_p: f32,
156 /// Top-k sampling parameter (default: `40`).
157 pub top_k: i32,
158 /// Minimum probability threshold (default: `0.0`).
159 pub min_p: f32,
160 /// Penalty for token presence (default: `0.0`).
161 pub presence_penalty: f32,
162 /// Penalty for token repetition (default: `1.0`).
163 pub repetition_penalty: f32,
164}
165
166impl Default for SamplingParams {
167 fn default() -> Self {
168 Self {
169 top_p: 0.95,
170 top_k: 40,
171 min_p: 0.0,
172 presence_penalty: 0.0,
173 repetition_penalty: 1.0,
174 }
175 }
176}
177
178impl SamplingParams {
179 /// Set the nucleus sampling threshold.
180 #[must_use]
181 pub fn with_top_p(mut self, top_p: f32) -> Self {
182 self.top_p = top_p;
183 self
184 }
185
186 /// Set the top-k sampling parameter.
187 #[must_use]
188 pub fn with_top_k(mut self, top_k: i32) -> Self {
189 self.top_k = top_k;
190 self
191 }
192
193 /// Set the minimum probability threshold.
194 #[must_use]
195 pub fn with_min_p(mut self, min_p: f32) -> Self {
196 self.min_p = min_p;
197 self
198 }
199
200 /// Set the presence penalty.
201 #[must_use]
202 pub fn with_presence_penalty(mut self, presence_penalty: f32) -> Self {
203 self.presence_penalty = presence_penalty;
204 self
205 }
206
207 /// Set the repetition penalty.
208 #[must_use]
209 pub fn with_repetition_penalty(mut self, repetition_penalty: f32) -> Self {
210 self.repetition_penalty = repetition_penalty;
211 self
212 }
213}
214
215/// Configuration for automatic GPU/CPU layer fitting.
216///
217/// Passed to [`crate::Client::builder`] (or [`crate::Client::from_gguf`]) so
218/// llama.cpp can probe available device memory and pick the optimal number
219/// of layers to offload to GPU automatically, instead of requiring a manual
220/// `n_gpu_layers` value.
221///
222/// Marked `#[non_exhaustive]`; build via `Default::default()` and chain the
223/// `with_*` setters.
224#[derive(Clone, Debug)]
225#[non_exhaustive]
226pub struct FitParams {
227 /// Memory margin per device in bytes. If `None`, defaults to 1 GiB per device.
228 pub margins: Option<Vec<usize>>,
229 /// Minimum context size to preserve during fitting (default: `4096`).
230 pub n_ctx_min: u32,
231}
232
233impl Default for FitParams {
234 fn default() -> Self {
235 Self {
236 margins: None,
237 n_ctx_min: 4096,
238 }
239 }
240}
241
242impl FitParams {
243 /// Override the per-device memory margin in bytes.
244 #[must_use]
245 pub fn with_margins(mut self, margins: Option<Vec<usize>>) -> Self {
246 self.margins = margins;
247 self
248 }
249
250 /// Override the minimum context size to preserve during fitting.
251 #[must_use]
252 pub fn with_n_ctx_min(mut self, n_ctx_min: u32) -> Self {
253 self.n_ctx_min = n_ctx_min;
254 self
255 }
256}
257
258/// Tunable parameters for the in-memory state-checkpoint cache used to
259/// preserve KV/recurrent state across chat turns for hybrid models.
260///
261/// Hybrid architectures (Qwen 3.5, Jamba, etc.) interleave Mamba-style
262/// recurrent layers with transformer layers. The recurrent state can't be
263/// rolled back to an arbitrary earlier position, so a partial KV trim
264/// fails whenever the next prompt diverges deep into the conversation.
265/// To work around this, we periodically snapshot the partial seq state
266/// (recurrent + SWA, via `LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY`) during
267/// prompt prefill and restore the closest snapshot when the next prompt
268/// arrives. Mirrors the mechanism used by upstream `llama-server`.
269///
270/// For non-hybrid models (Qwen 2.5, Llama 3, Gemma, ...) checkpoints are
271/// created but never used because the cheaper partial-trim path
272/// succeeds.
273///
274/// Marked `#[non_exhaustive]`; build via `Default::default()` and chain the
275/// `with_*` setters.
276#[derive(Clone, Copy, Debug)]
277#[non_exhaustive]
278pub struct CheckpointParams {
279 /// Maximum number of checkpoints retained per persistent context.
280 /// `0` disables checkpointing entirely. Each checkpoint is a few MB
281 /// for typical hybrid models.
282 pub max_checkpoints: u32,
283 /// Approximate spacing between checkpoints during prompt prefill, in
284 /// tokens. The last `4..=4 + n_ubatch` tokens always get a
285 /// checkpoint regardless. `<= 0` means "only checkpoint near the end
286 /// of the prompt".
287 pub every_n_tokens: i32,
288 /// Don't checkpoint the very start of a prompt — saves space for
289 /// no benefit because we'd have to re-decode that prefix anyway if
290 /// it's the entire reuse window.
291 pub min_tokens: u32,
292 /// Don't take two checkpoints closer than this many tokens apart.
293 pub min_gap: u32,
294}
295
296impl Default for CheckpointParams {
297 fn default() -> Self {
298 Self {
299 // llama-server uses 32; cap lower because each checkpoint is
300 // a few MB and we'd rather not balloon RSS.
301 max_checkpoints: 8,
302 every_n_tokens: 8192,
303 min_tokens: 64,
304 min_gap: 64,
305 }
306 }
307}
308
309impl CheckpointParams {
310 /// Override the maximum number of checkpoints retained per context.
311 #[must_use]
312 pub fn with_max_checkpoints(mut self, max_checkpoints: u32) -> Self {
313 self.max_checkpoints = max_checkpoints;
314 self
315 }
316
317 /// Override the approximate spacing between checkpoints (in tokens).
318 #[must_use]
319 pub fn with_every_n_tokens(mut self, every_n_tokens: i32) -> Self {
320 self.every_n_tokens = every_n_tokens;
321 self
322 }
323
324 /// Override the minimum prompt length before checkpoints are taken.
325 #[must_use]
326 pub fn with_min_tokens(mut self, min_tokens: u32) -> Self {
327 self.min_tokens = min_tokens;
328 self
329 }
330
331 /// Override the minimum spacing between two consecutive checkpoints.
332 #[must_use]
333 pub fn with_min_gap(mut self, min_gap: u32) -> Self {
334 self.min_gap = min_gap;
335 self
336 }
337}
338
339/// Data type used for an entry in the attention KV cache.
340///
341/// Mirrors the subset of `ggml_type` values that `llama.cpp` accepts as KV
342/// cache element types. The `F16` default preserves full attention quality;
343/// quantizing (e.g. `Q8_0` ≈ ½ size, `Q4_0` ≈ ¼ size) trades a small amount
344/// of accuracy for a large VRAM reduction at long `n_ctx`.
345///
346/// This is a local shim around `llama_cpp_2::context::params::KvCacheType`
347/// so a future `llama-cpp-2` update doesn't force a breaking release of
348/// `rig-llama-cpp`. Marked `#[non_exhaustive]`: when llama.cpp adds a new
349/// `ggml_type`, we add a corresponding variant in a minor (`0.1.x`) release.
350#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
351#[allow(non_camel_case_types)]
352#[non_exhaustive]
353pub enum KvCacheType {
354 /// IEEE 754 single precision.
355 F32,
356 /// IEEE 754 half precision (llama.cpp's default for both K and V).
357 F16,
358 /// Brain floating-point 16, common on newer NVIDIA / AMD GPUs.
359 BF16,
360 /// IEEE 754 double precision.
361 F64,
362 /// 4-bit block quantization, type 0.
363 Q4_0,
364 /// 4-bit block quantization, type 1.
365 Q4_1,
366 /// 5-bit block quantization, type 0.
367 Q5_0,
368 /// 5-bit block quantization, type 1.
369 Q5_1,
370 /// 8-bit block quantization, type 0.
371 Q8_0,
372 /// 8-bit block quantization, type 1.
373 Q8_1,
374 /// 2-bit K-quant.
375 Q2_K,
376 /// 3-bit K-quant.
377 Q3_K,
378 /// 4-bit K-quant.
379 Q4_K,
380 /// 5-bit K-quant.
381 Q5_K,
382 /// 6-bit K-quant.
383 Q6_K,
384 /// 8-bit K-quant.
385 Q8_K,
386 /// Importance-weighted 2-bit, extra-extra-small.
387 IQ2_XXS,
388 /// Importance-weighted 2-bit, extra-small.
389 IQ2_XS,
390 /// Importance-weighted 2-bit, small.
391 IQ2_S,
392 /// Importance-weighted 3-bit, extra-extra-small.
393 IQ3_XXS,
394 /// Importance-weighted 3-bit, small.
395 IQ3_S,
396 /// Importance-weighted 1-bit, small.
397 IQ1_S,
398 /// Importance-weighted 1-bit, medium.
399 IQ1_M,
400 /// Importance-weighted 4-bit, extra-small.
401 IQ4_XS,
402 /// Importance-weighted 4-bit, non-linear.
403 IQ4_NL,
404 /// Signed 8-bit integer.
405 I8,
406 /// Signed 16-bit integer.
407 I16,
408 /// Signed 32-bit integer.
409 I32,
410 /// Signed 64-bit integer.
411 I64,
412 /// Ternary 1-bit, type 0.
413 TQ1_0,
414 /// Ternary 2-bit, type 0.
415 TQ2_0,
416 /// Microscaling FP4.
417 MXFP4,
418}
419
420impl From<KvCacheType> for llama_cpp_2::context::params::KvCacheType {
421 fn from(value: KvCacheType) -> Self {
422 use llama_cpp_2::context::params::KvCacheType as Upstream;
423 match value {
424 KvCacheType::F32 => Upstream::F32,
425 KvCacheType::F16 => Upstream::F16,
426 KvCacheType::BF16 => Upstream::BF16,
427 KvCacheType::F64 => Upstream::F64,
428 KvCacheType::Q4_0 => Upstream::Q4_0,
429 KvCacheType::Q4_1 => Upstream::Q4_1,
430 KvCacheType::Q5_0 => Upstream::Q5_0,
431 KvCacheType::Q5_1 => Upstream::Q5_1,
432 KvCacheType::Q8_0 => Upstream::Q8_0,
433 KvCacheType::Q8_1 => Upstream::Q8_1,
434 KvCacheType::Q2_K => Upstream::Q2_K,
435 KvCacheType::Q3_K => Upstream::Q3_K,
436 KvCacheType::Q4_K => Upstream::Q4_K,
437 KvCacheType::Q5_K => Upstream::Q5_K,
438 KvCacheType::Q6_K => Upstream::Q6_K,
439 KvCacheType::Q8_K => Upstream::Q8_K,
440 KvCacheType::IQ2_XXS => Upstream::IQ2_XXS,
441 KvCacheType::IQ2_XS => Upstream::IQ2_XS,
442 KvCacheType::IQ2_S => Upstream::IQ2_S,
443 KvCacheType::IQ3_XXS => Upstream::IQ3_XXS,
444 KvCacheType::IQ3_S => Upstream::IQ3_S,
445 KvCacheType::IQ1_S => Upstream::IQ1_S,
446 KvCacheType::IQ1_M => Upstream::IQ1_M,
447 KvCacheType::IQ4_XS => Upstream::IQ4_XS,
448 KvCacheType::IQ4_NL => Upstream::IQ4_NL,
449 KvCacheType::I8 => Upstream::I8,
450 KvCacheType::I16 => Upstream::I16,
451 KvCacheType::I32 => Upstream::I32,
452 KvCacheType::I64 => Upstream::I64,
453 KvCacheType::TQ1_0 => Upstream::TQ1_0,
454 KvCacheType::TQ2_0 => Upstream::TQ2_0,
455 KvCacheType::MXFP4 => Upstream::MXFP4,
456 }
457 }
458}
459
460/// KV cache quantization configuration.
461///
462/// Controls the data type used for the attention K and V caches. llama.cpp defaults
463/// both to `F16` (`GGML_TYPE_F16`), which is what `KvCacheParams::default()` preserves.
464/// Quantizing the KV cache (e.g. `Q8_0` → ~½ size, `Q4_0` → ~¼ size) trades a small
465/// amount of accuracy for a large reduction in VRAM usage, which is often the dominant
466/// cost at long `n_ctx`.
467///
468/// Marked `#[non_exhaustive]`; build via `Default::default()` and chain the
469/// `with_*` setters:
470///
471/// ```
472/// use rig_llama_cpp::{KvCacheParams, KvCacheType};
473///
474/// let kv = KvCacheParams::default()
475/// .with_type_k(KvCacheType::Q8_0)
476/// .with_type_v(KvCacheType::Q8_0);
477/// ```
478#[derive(Clone, Copy, Debug)]
479#[non_exhaustive]
480pub struct KvCacheParams {
481 /// Data type for the K cache (default: [`KvCacheType::F16`]).
482 pub type_k: KvCacheType,
483 /// Data type for the V cache (default: [`KvCacheType::F16`]).
484 pub type_v: KvCacheType,
485}
486
487impl Default for KvCacheParams {
488 fn default() -> Self {
489 Self {
490 type_k: KvCacheType::F16,
491 type_v: KvCacheType::F16,
492 }
493 }
494}
495
496impl KvCacheParams {
497 /// Override the K cache data type.
498 #[must_use]
499 pub fn with_type_k(mut self, type_k: KvCacheType) -> Self {
500 self.type_k = type_k;
501 self
502 }
503
504 /// Override the V cache data type.
505 #[must_use]
506 pub fn with_type_v(mut self, type_v: KvCacheType) -> Self {
507 self.type_v = type_v;
508 self
509 }
510}