llama_cpp_4/model/params.rs
1//! A safe wrapper around `llama_model_params`.
2
3use crate::model::params::kv_overrides::KvOverrides;
4use std::ffi::{c_char, CStr};
5use std::fmt::{Debug, Formatter};
6use std::pin::Pin;
7use std::ptr::null;
8
9pub mod kv_overrides;
10
11/// Exact model-file loading strategy exposed by llama.cpp.
12///
13/// `llama_load_mode` is a signed enum on every target because of the negative
14/// `LLAMA_LOAD_MODE_AUTO` discriminant, so each variant uses `as _` to coerce to
15/// the `#[repr(i32)]` type (matching [`token_type`](crate::token_type)).
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[repr(i32)]
18pub enum LlamaLoadMode {
19 /// Pick the strategy from the backend devices' capabilities: memory-map when
20 /// every device supports it, otherwise fall back to a plain read. This is
21 /// llama.cpp's default.
22 Auto = llama_cpp_sys_4::LLAMA_LOAD_MODE_AUTO as _,
23 /// No memory mapping, locking, or direct I/O.
24 None = llama_cpp_sys_4::LLAMA_LOAD_MODE_NONE as _,
25 /// Memory-map model files when supported.
26 Mmap = llama_cpp_sys_4::LLAMA_LOAD_MODE_MMAP as _,
27 /// Read model files normally and lock loaded pages in memory.
28 Mlock = llama_cpp_sys_4::LLAMA_LOAD_MODE_MLOCK as _,
29 /// Memory-map model files and lock mapped pages in memory.
30 MmapMlock = llama_cpp_sys_4::LLAMA_LOAD_MODE_MMAP_MLOCK as _,
31 /// Use direct I/O when supported.
32 DirectIo = llama_cpp_sys_4::LLAMA_LOAD_MODE_DIRECT_IO as _,
33}
34
35/// A safe wrapper around `llama_model_params`.
36#[allow(clippy::module_name_repetitions)]
37pub struct LlamaModelParams {
38 pub(crate) params: llama_cpp_sys_4::llama_model_params,
39 kv_overrides: Vec<llama_cpp_sys_4::llama_model_kv_override>,
40}
41
42impl Debug for LlamaModelParams {
43 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44 f.debug_struct("LlamaModelParams")
45 .field("n_gpu_layers", &self.params.n_gpu_layers)
46 .field("main_gpu", &self.params.main_gpu)
47 .field("vocab_only", &self.params.vocab_only)
48 .field("load_mode", &self.load_mode())
49 .field("load_mtp", &self.load_mtp())
50 .field("kv_overrides", &"vec of kv_overrides")
51 .finish()
52 }
53}
54
55impl LlamaModelParams {
56 /// See [`KvOverrides`]
57 ///
58 /// # Examples
59 ///
60 /// ```rust
61 /// # use llama_cpp_4::model::params::LlamaModelParams;
62 /// let params = Box::pin(LlamaModelParams::default());
63 /// let kv_overrides = params.kv_overrides();
64 /// let count = kv_overrides.into_iter().count();
65 /// assert_eq!(count, 0);
66 /// ```
67 #[must_use]
68 pub fn kv_overrides(&self) -> KvOverrides<'_> {
69 KvOverrides::new(self)
70 }
71
72 /// Appends a key-value override to the model parameters. It must be pinned as this creates a self-referential struct.
73 ///
74 /// # Examples
75 ///
76 /// ```rust
77 /// # use std::ffi::{CStr, CString};
78 /// use std::pin::pin;
79 /// # use llama_cpp_4::model::params::LlamaModelParams;
80 /// # use llama_cpp_4::model::params::kv_overrides::ParamOverrideValue;
81 /// let mut params = pin!(LlamaModelParams::default());
82 /// let key = CString::new("key").expect("CString::new failed");
83 /// params.as_mut().append_kv_override(&key, ParamOverrideValue::Int(50));
84 ///
85 /// let kv_overrides = params.kv_overrides().into_iter().collect::<Vec<_>>();
86 /// assert_eq!(kv_overrides.len(), 1);
87 ///
88 /// let (k, v) = &kv_overrides[0];
89 /// assert_eq!(v, &ParamOverrideValue::Int(50));
90 ///
91 /// assert_eq!(k.to_bytes(), b"key", "expected key to be 'key', was {:?}", k);
92 /// ```
93 #[allow(clippy::missing_panics_doc)] // panics are just to enforce internal invariants, not user errors
94 pub fn append_kv_override(
95 mut self: Pin<&mut Self>,
96 key: &CStr,
97 value: kv_overrides::ParamOverrideValue,
98 ) {
99 let kv_override = self
100 .kv_overrides
101 .get_mut(0)
102 .expect("kv_overrides did not have a next allocated");
103
104 assert_eq!(kv_override.key[0], 0, "last kv_override was not empty");
105
106 // There should be some way to do this without iterating over everything.
107 for (i, &c) in key.to_bytes_with_nul().iter().enumerate() {
108 kv_override.key[i] = c_char::try_from(c).expect("invalid character in key");
109 }
110
111 kv_override.tag = value.tag();
112 kv_override.__bindgen_anon_1 = value.value();
113
114 // set to null pointer for panic safety (as push may move the vector, invalidating the pointer)
115 self.params.kv_overrides = null();
116
117 // push the next one to ensure we maintain the iterator invariant of ending with a 0
118 self.kv_overrides
119 .push(llama_cpp_sys_4::llama_model_kv_override {
120 key: [0; 128],
121 tag: 0,
122 __bindgen_anon_1: llama_cpp_sys_4::llama_model_kv_override__bindgen_ty_1 {
123 val_i64: 0,
124 },
125 });
126
127 // set the pointer to the (potentially) new vector
128 self.params.kv_overrides = self.kv_overrides.as_ptr();
129
130 eprintln!("saved ptr: {:?}", self.params.kv_overrides);
131 }
132}
133
134impl LlamaModelParams {
135 /// Get the number of layers to offload to the GPU.
136 #[must_use]
137 pub fn n_gpu_layers(&self) -> i32 {
138 self.params.n_gpu_layers
139 }
140
141 /// The GPU that is used for scratch and small tensors
142 #[must_use]
143 pub fn main_gpu(&self) -> i32 {
144 self.params.main_gpu
145 }
146
147 /// only load the vocabulary, no weights
148 #[must_use]
149 pub fn vocab_only(&self) -> bool {
150 self.params.vocab_only
151 }
152
153 /// Returns the exact model-file loading strategy.
154 #[must_use]
155 pub fn load_mode(&self) -> LlamaLoadMode {
156 match self.params.load_mode {
157 llama_cpp_sys_4::LLAMA_LOAD_MODE_AUTO => LlamaLoadMode::Auto,
158 llama_cpp_sys_4::LLAMA_LOAD_MODE_MMAP => LlamaLoadMode::Mmap,
159 llama_cpp_sys_4::LLAMA_LOAD_MODE_MLOCK => LlamaLoadMode::Mlock,
160 llama_cpp_sys_4::LLAMA_LOAD_MODE_MMAP_MLOCK => LlamaLoadMode::MmapMlock,
161 llama_cpp_sys_4::LLAMA_LOAD_MODE_DIRECT_IO => LlamaLoadMode::DirectIo,
162 _ => LlamaLoadMode::None,
163 }
164 }
165
166 /// Whether the model's MTP (multi-token prediction) layers will be loaded.
167 ///
168 /// MTP layers drive multi-token-prediction speculative decoding for models
169 /// that ship them (e.g. `DeepSeek V4`). Once loaded, the speculative state is
170 /// captured and restored through [`crate::speculative`]. Defaults to `false`
171 /// because most models carry no MTP weights.
172 #[must_use]
173 pub fn load_mtp(&self) -> bool {
174 self.params.load_mtp
175 }
176
177 /// use mmap if possible
178 ///
179 /// [`LlamaLoadMode::Auto`] counts as "possible": llama.cpp memory-maps under
180 /// `Auto` unless one of the backend devices lacks mmap support, which is only
181 /// known once the model is loaded.
182 #[must_use]
183 pub fn use_mmap(&self) -> bool {
184 matches!(
185 self.load_mode(),
186 LlamaLoadMode::Auto | LlamaLoadMode::Mmap | LlamaLoadMode::MmapMlock
187 )
188 }
189
190 /// force system to keep model in RAM
191 #[must_use]
192 pub fn use_mlock(&self) -> bool {
193 matches!(
194 self.load_mode(),
195 LlamaLoadMode::Mlock | LlamaLoadMode::MmapMlock
196 )
197 }
198
199 /// sets the number of gpu layers to offload to the GPU.
200 /// ```
201 /// # use llama_cpp_4::model::params::LlamaModelParams;
202 /// let params = LlamaModelParams::default();
203 /// let params = params.with_n_gpu_layers(1);
204 /// assert_eq!(params.n_gpu_layers(), 1);
205 /// ```
206 #[must_use]
207 pub fn with_n_gpu_layers(mut self, n_gpu_layers: u32) -> Self {
208 // The only way this conversion can fail is if u32 overflows the i32 - in which case we set
209 // to MAX
210 let n_gpu_layers = i32::try_from(n_gpu_layers).unwrap_or(i32::MAX);
211 self.params.n_gpu_layers = n_gpu_layers;
212 self
213 }
214
215 /// sets the main GPU
216 #[must_use]
217 pub fn with_main_gpu(mut self, main_gpu: i32) -> Self {
218 self.params.main_gpu = main_gpu;
219 self
220 }
221
222 /// sets `vocab_only`
223 #[must_use]
224 pub fn with_vocab_only(mut self, vocab_only: bool) -> Self {
225 self.params.vocab_only = vocab_only;
226 self
227 }
228
229 /// Sets the exact model-file loading strategy.
230 #[must_use]
231 pub fn with_load_mode(mut self, load_mode: LlamaLoadMode) -> Self {
232 self.params.load_mode = load_mode as llama_cpp_sys_4::llama_load_mode;
233 self
234 }
235
236 /// Sets whether to load the model's MTP (multi-token prediction) layers.
237 ///
238 /// Enable this for models that ship MTP weights (e.g. `DeepSeek V4`) when you
239 /// intend to use MTP-based speculative decoding, then drive the speculative
240 /// state via [`crate::speculative`]. For models without MTP layers the flag
241 /// has no effect. Corresponds to `llama_model_params.load_mtp`, added
242 /// upstream in llama.cpp PR #25784 (`DeepSeek V4` MTP + `DSpark`).
243 ///
244 /// ```
245 /// # use llama_cpp_4::model::params::LlamaModelParams;
246 /// let params = LlamaModelParams::default().with_load_mtp(true);
247 /// assert!(params.load_mtp());
248 /// ```
249 #[must_use]
250 pub fn with_load_mtp(mut self, load_mtp: bool) -> Self {
251 self.params.load_mtp = load_mtp;
252 self
253 }
254
255 /// sets `use_mlock`
256 #[must_use]
257 pub fn with_use_mlock(mut self, use_mlock: bool) -> Self {
258 let load_mode = match (self.use_mmap(), use_mlock) {
259 (true, true) => LlamaLoadMode::MmapMlock,
260 (true, false) => LlamaLoadMode::Mmap,
261 (false, true) => LlamaLoadMode::Mlock,
262 (false, false) => LlamaLoadMode::None,
263 };
264 self.params.load_mode = load_mode as llama_cpp_sys_4::llama_load_mode;
265 self
266 }
267}
268
269/// Default parameters for `LlamaModel`. (as defined in llama.cpp by `llama_model_default_params`)
270/// ```
271/// # use llama_cpp_4::model::params::LlamaModelParams;
272/// let params = LlamaModelParams::default();
273/// assert_eq!(params.n_gpu_layers(), -1, "n_gpu_layers should be -1 (all layers)");
274/// assert_eq!(params.main_gpu(), 0, "main_gpu should be 0");
275/// assert_eq!(params.vocab_only(), false, "vocab_only should be false");
276/// assert_eq!(params.use_mmap(), true, "use_mmap should be true");
277/// assert_eq!(params.use_mlock(), false, "use_mlock should be false");
278/// ```
279impl Default for LlamaModelParams {
280 fn default() -> Self {
281 let default_params = unsafe { llama_cpp_sys_4::llama_model_default_params() };
282 LlamaModelParams {
283 params: default_params,
284 // push the next one to ensure we maintain the iterator invariant of ending with a 0
285 kv_overrides: vec![llama_cpp_sys_4::llama_model_kv_override {
286 key: [0; 128],
287 tag: 0,
288 __bindgen_anon_1: llama_cpp_sys_4::llama_model_kv_override__bindgen_ty_1 {
289 val_i64: 0,
290 },
291 }],
292 }
293 }
294}