Skip to main content

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("kv_overrides", &"vec of kv_overrides")
46            .finish()
47    }
48}
49
50impl LlamaModelParams {
51    /// See [`KvOverrides`]
52    ///
53    /// # Examples
54    ///
55    /// ```rust
56    /// # use llama_cpp_4::model::params::LlamaModelParams;
57    /// let params = Box::pin(LlamaModelParams::default());
58    /// let kv_overrides = params.kv_overrides();
59    /// let count = kv_overrides.into_iter().count();
60    /// assert_eq!(count, 0);
61    /// ```
62    #[must_use]
63    pub fn kv_overrides(&self) -> KvOverrides<'_> {
64        KvOverrides::new(self)
65    }
66
67    /// Appends a key-value override to the model parameters. It must be pinned as this creates a self-referential struct.
68    ///
69    /// # Examples
70    ///
71    /// ```rust
72    /// # use std::ffi::{CStr, CString};
73    /// use std::pin::pin;
74    /// # use llama_cpp_4::model::params::LlamaModelParams;
75    /// # use llama_cpp_4::model::params::kv_overrides::ParamOverrideValue;
76    /// let mut params = pin!(LlamaModelParams::default());
77    /// let key = CString::new("key").expect("CString::new failed");
78    /// params.as_mut().append_kv_override(&key, ParamOverrideValue::Int(50));
79    ///
80    /// let kv_overrides = params.kv_overrides().into_iter().collect::<Vec<_>>();
81    /// assert_eq!(kv_overrides.len(), 1);
82    ///
83    /// let (k, v) = &kv_overrides[0];
84    /// assert_eq!(v, &ParamOverrideValue::Int(50));
85    ///
86    /// assert_eq!(k.to_bytes(), b"key", "expected key to be 'key', was {:?}", k);
87    /// ```
88    #[allow(clippy::missing_panics_doc)] // panics are just to enforce internal invariants, not user errors
89    pub fn append_kv_override(
90        mut self: Pin<&mut Self>,
91        key: &CStr,
92        value: kv_overrides::ParamOverrideValue,
93    ) {
94        let kv_override = self
95            .kv_overrides
96            .get_mut(0)
97            .expect("kv_overrides did not have a next allocated");
98
99        assert_eq!(kv_override.key[0], 0, "last kv_override was not empty");
100
101        // There should be some way to do this without iterating over everything.
102        for (i, &c) in key.to_bytes_with_nul().iter().enumerate() {
103            kv_override.key[i] = c_char::try_from(c).expect("invalid character in key");
104        }
105
106        kv_override.tag = value.tag();
107        kv_override.__bindgen_anon_1 = value.value();
108
109        // set to null pointer for panic safety (as push may move the vector, invalidating the pointer)
110        self.params.kv_overrides = null();
111
112        // push the next one to ensure we maintain the iterator invariant of ending with a 0
113        self.kv_overrides
114            .push(llama_cpp_sys_4::llama_model_kv_override {
115                key: [0; 128],
116                tag: 0,
117                __bindgen_anon_1: llama_cpp_sys_4::llama_model_kv_override__bindgen_ty_1 {
118                    val_i64: 0,
119                },
120            });
121
122        // set the pointer to the (potentially) new vector
123        self.params.kv_overrides = self.kv_overrides.as_ptr();
124
125        eprintln!("saved ptr: {:?}", self.params.kv_overrides);
126    }
127}
128
129impl LlamaModelParams {
130    /// Get the number of layers to offload to the GPU.
131    #[must_use]
132    pub fn n_gpu_layers(&self) -> i32 {
133        self.params.n_gpu_layers
134    }
135
136    /// The GPU that is used for scratch and small tensors
137    #[must_use]
138    pub fn main_gpu(&self) -> i32 {
139        self.params.main_gpu
140    }
141
142    /// only load the vocabulary, no weights
143    #[must_use]
144    pub fn vocab_only(&self) -> bool {
145        self.params.vocab_only
146    }
147
148    /// Returns the exact model-file loading strategy.
149    #[must_use]
150    pub fn load_mode(&self) -> LlamaLoadMode {
151        match self.params.load_mode {
152            llama_cpp_sys_4::LLAMA_LOAD_MODE_MMAP => LlamaLoadMode::Mmap,
153            llama_cpp_sys_4::LLAMA_LOAD_MODE_MLOCK => LlamaLoadMode::Mlock,
154            llama_cpp_sys_4::LLAMA_LOAD_MODE_MMAP_MLOCK => LlamaLoadMode::MmapMlock,
155            llama_cpp_sys_4::LLAMA_LOAD_MODE_DIRECT_IO => LlamaLoadMode::DirectIo,
156            _ => LlamaLoadMode::None,
157        }
158    }
159
160    /// use mmap if possible
161    #[must_use]
162    pub fn use_mmap(&self) -> bool {
163        matches!(
164            self.load_mode(),
165            LlamaLoadMode::Mmap | LlamaLoadMode::MmapMlock
166        )
167    }
168
169    /// force system to keep model in RAM
170    #[must_use]
171    pub fn use_mlock(&self) -> bool {
172        matches!(
173            self.load_mode(),
174            LlamaLoadMode::Mlock | LlamaLoadMode::MmapMlock
175        )
176    }
177
178    /// sets the number of gpu layers to offload to the GPU.
179    /// ```
180    /// # use llama_cpp_4::model::params::LlamaModelParams;
181    /// let params = LlamaModelParams::default();
182    /// let params = params.with_n_gpu_layers(1);
183    /// assert_eq!(params.n_gpu_layers(), 1);
184    /// ```
185    #[must_use]
186    pub fn with_n_gpu_layers(mut self, n_gpu_layers: u32) -> Self {
187        // The only way this conversion can fail is if u32 overflows the i32 - in which case we set
188        // to MAX
189        let n_gpu_layers = i32::try_from(n_gpu_layers).unwrap_or(i32::MAX);
190        self.params.n_gpu_layers = n_gpu_layers;
191        self
192    }
193
194    /// sets the main GPU
195    #[must_use]
196    pub fn with_main_gpu(mut self, main_gpu: i32) -> Self {
197        self.params.main_gpu = main_gpu;
198        self
199    }
200
201    /// sets `vocab_only`
202    #[must_use]
203    pub fn with_vocab_only(mut self, vocab_only: bool) -> Self {
204        self.params.vocab_only = vocab_only;
205        self
206    }
207
208    /// Sets the exact model-file loading strategy.
209    #[must_use]
210    pub fn with_load_mode(mut self, load_mode: LlamaLoadMode) -> Self {
211        self.params.load_mode = load_mode as llama_cpp_sys_4::llama_load_mode;
212        self
213    }
214
215    /// sets `use_mlock`
216    #[must_use]
217    pub fn with_use_mlock(mut self, use_mlock: bool) -> Self {
218        let load_mode = match (self.use_mmap(), use_mlock) {
219            (true, true) => LlamaLoadMode::MmapMlock,
220            (true, false) => LlamaLoadMode::Mmap,
221            (false, true) => LlamaLoadMode::Mlock,
222            (false, false) => LlamaLoadMode::None,
223        };
224        self.params.load_mode = load_mode as llama_cpp_sys_4::llama_load_mode;
225        self
226    }
227}
228
229/// Default parameters for `LlamaModel`. (as defined in llama.cpp by `llama_model_default_params`)
230/// ```
231/// # use llama_cpp_4::model::params::LlamaModelParams;
232/// let params = LlamaModelParams::default();
233/// assert_eq!(params.n_gpu_layers(), -1, "n_gpu_layers should be -1 (all layers)");
234/// assert_eq!(params.main_gpu(), 0, "main_gpu should be 0");
235/// assert_eq!(params.vocab_only(), false, "vocab_only should be false");
236/// assert_eq!(params.use_mmap(), true, "use_mmap should be true");
237/// assert_eq!(params.use_mlock(), false, "use_mlock should be false");
238/// ```
239impl Default for LlamaModelParams {
240    fn default() -> Self {
241        let default_params = unsafe { llama_cpp_sys_4::llama_model_default_params() };
242        LlamaModelParams {
243            params: default_params,
244            // push the next one to ensure we maintain the iterator invariant of ending with a 0
245            kv_overrides: vec![llama_cpp_sys_4::llama_model_kv_override {
246                key: [0; 128],
247                tag: 0,
248                __bindgen_anon_1: llama_cpp_sys_4::llama_model_kv_override__bindgen_ty_1 {
249                    val_i64: 0,
250                },
251            }],
252        }
253    }
254}