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