Skip to main content

llama_cpp_bindings/model/
params.rs

1use std::ffi::{CStr, c_char};
2use std::fmt::{Debug, Formatter};
3use std::pin::Pin;
4use std::ptr::null;
5
6use crate::LlamaCppError;
7use crate::context::params::LlamaContextParams;
8use crate::error::{FitError, ModelParamsError};
9use crate::model::llama_split_mode_parse_error::LlamaSplitModeParseError;
10use crate::model::params::fit_result::FitResult;
11use crate::model::params::kv_overrides::KvOverrides;
12use crate::model::split_mode::LlamaSplitMode;
13
14pub mod fit_result;
15pub mod kv_override_value_iterator;
16pub mod kv_overrides;
17pub mod param_override_value;
18pub mod unknown_kv_override_tag;
19
20pub const LLAMA_CPP_MAX_DEVICES: usize = 16;
21
22pub struct LlamaModelParams {
23    pub params: llama_cpp_bindings_sys::llama_model_params,
24    kv_overrides: Vec<llama_cpp_bindings_sys::llama_model_kv_override>,
25    buft_overrides: Vec<llama_cpp_bindings_sys::llama_model_tensor_buft_override>,
26    devices: Pin<Box<[llama_cpp_bindings_sys::ggml_backend_dev_t; LLAMA_CPP_MAX_DEVICES]>>,
27    tensor_split: Vec<f32>,
28}
29
30impl Debug for LlamaModelParams {
31    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32        f.debug_struct("LlamaModelParams")
33            .field("n_gpu_layers", &self.params.n_gpu_layers)
34            .field("main_gpu", &self.params.main_gpu)
35            .field("vocab_only", &self.params.vocab_only)
36            .field("use_mmap", &self.params.use_mmap)
37            .field("use_mlock", &self.params.use_mlock)
38            .field("split_mode", &self.split_mode())
39            .field("devices", &self.devices)
40            .field("kv_overrides", &"vec of kv_overrides")
41            .finish_non_exhaustive()
42    }
43}
44
45impl LlamaModelParams {
46    #[must_use]
47    pub const fn kv_overrides(&self) -> KvOverrides<'_> {
48        KvOverrides::new(self)
49    }
50
51    /// # Errors
52    /// Returns [`ModelParamsError`] if the internal override vector has no available slot,
53    /// the slot is not empty, or the key contains invalid characters.
54    ///
55    pub fn append_kv_override(
56        mut self: Pin<&mut Self>,
57        key: &CStr,
58        value: param_override_value::ParamOverrideValue,
59    ) -> Result<(), ModelParamsError> {
60        let kv_override = self
61            .kv_overrides
62            .get_mut(0)
63            .ok_or(ModelParamsError::NoAvailableSlot)?;
64
65        if kv_override.key[0] != 0 {
66            return Err(ModelParamsError::SlotNotEmpty);
67        }
68
69        for (i, &byte) in key.to_bytes_with_nul().iter().enumerate() {
70            kv_override.key[i] = c_char::try_from(byte).map_err(|convert_error| {
71                ModelParamsError::InvalidCharacterInKey {
72                    byte,
73                    reason: convert_error.to_string(),
74                }
75            })?;
76        }
77
78        kv_override.tag = value.tag();
79        kv_override.__bindgen_anon_1 = value.value();
80
81        self.push_kv_override_terminator();
82
83        Ok(())
84    }
85
86    fn push_kv_override_terminator(mut self: Pin<&mut Self>) {
87        self.params.kv_overrides = null();
88
89        self.kv_overrides
90            .push(llama_cpp_bindings_sys::llama_model_kv_override {
91                key: [0; 128],
92                tag: 0,
93                __bindgen_anon_1: llama_cpp_bindings_sys::llama_model_kv_override__bindgen_ty_1 {
94                    val_i64: 0,
95                },
96            });
97
98        self.params.kv_overrides = self.kv_overrides.as_ptr();
99    }
100}
101
102impl LlamaModelParams {
103    /// # Errors
104    /// Returns [`ModelParamsError`] if the internal override vector has no available slot,
105    /// the slot is not empty, or the key contains invalid characters.
106    pub fn add_cpu_moe_override(self: Pin<&mut Self>) -> Result<(), ModelParamsError> {
107        self.add_cpu_buft_override(c"\\.ffn_(up|down|gate)_(ch|)exps")
108    }
109
110    /// # Errors
111    /// Returns [`ModelParamsError`] if the internal override vector has no available slot,
112    /// the slot is not empty, or the key contains invalid characters.
113    pub fn add_cpu_buft_override(
114        mut self: Pin<&mut Self>,
115        key: &CStr,
116    ) -> Result<(), ModelParamsError> {
117        let buft_override = self
118            .buft_overrides
119            .get_mut(0)
120            .ok_or(ModelParamsError::NoAvailableSlot)?;
121
122        if !buft_override.pattern.is_null() {
123            return Err(ModelParamsError::SlotNotEmpty);
124        }
125
126        for &byte in key.to_bytes_with_nul() {
127            c_char::try_from(byte).map_err(|convert_error| {
128                ModelParamsError::InvalidCharacterInKey {
129                    byte,
130                    reason: convert_error.to_string(),
131                }
132            })?;
133        }
134
135        buft_override.pattern = key.as_ptr();
136        buft_override.buft = unsafe { llama_cpp_bindings_sys::ggml_backend_cpu_buffer_type() };
137
138        self.push_buft_override_terminator();
139
140        Ok(())
141    }
142
143    fn push_buft_override_terminator(mut self: Pin<&mut Self>) {
144        self.params.tensor_buft_overrides = null();
145
146        self.buft_overrides
147            .push(llama_cpp_bindings_sys::llama_model_tensor_buft_override {
148                pattern: null(),
149                buft: std::ptr::null_mut(),
150            });
151
152        self.params.tensor_buft_overrides = self.buft_overrides.as_ptr();
153    }
154}
155
156impl LlamaModelParams {
157    #[must_use]
158    pub const fn n_gpu_layers(&self) -> i32 {
159        self.params.n_gpu_layers
160    }
161
162    #[must_use]
163    pub const fn main_gpu(&self) -> i32 {
164        self.params.main_gpu
165    }
166
167    #[must_use]
168    pub const fn vocab_only(&self) -> bool {
169        self.params.vocab_only
170    }
171
172    #[must_use]
173    pub const fn use_mmap(&self) -> bool {
174        self.params.use_mmap
175    }
176
177    #[must_use]
178    pub const fn use_mlock(&self) -> bool {
179        self.params.use_mlock
180    }
181
182    /// # Errors
183    /// Returns `LlamaSplitModeParseError` if the unknown split mode is encountered.
184    pub fn split_mode(&self) -> Result<LlamaSplitMode, LlamaSplitModeParseError> {
185        LlamaSplitMode::try_from(self.params.split_mode)
186    }
187
188    #[must_use]
189    pub fn devices(&self) -> Vec<usize> {
190        let mut backend_devices = Vec::new();
191        for i in 0..unsafe { llama_cpp_bindings_sys::ggml_backend_dev_count() } {
192            let dev = unsafe { llama_cpp_bindings_sys::ggml_backend_dev_get(i) };
193            backend_devices.push(dev);
194        }
195        let mut devices = Vec::new();
196        for &dev in self.devices.iter() {
197            if dev.is_null() {
198                break;
199            }
200            let matched_index = backend_devices
201                .iter()
202                .enumerate()
203                .find(|&(_i, &d)| d == dev)
204                .map(|(index, _)| index);
205
206            if let Some(index) = matched_index {
207                devices.push(index);
208            }
209        }
210        devices
211    }
212
213    #[must_use]
214    pub const fn with_n_gpu_layers(mut self, n_gpu_layers: i32) -> Self {
215        self.params.n_gpu_layers = n_gpu_layers;
216        self
217    }
218
219    #[must_use]
220    pub const fn with_main_gpu(mut self, main_gpu: i32) -> Self {
221        self.params.main_gpu = main_gpu;
222        self
223    }
224
225    #[must_use]
226    pub const fn with_vocab_only(mut self, vocab_only: bool) -> Self {
227        self.params.vocab_only = vocab_only;
228        self
229    }
230
231    #[must_use]
232    pub const fn with_use_mmap(mut self, use_mmap: bool) -> Self {
233        self.params.use_mmap = use_mmap;
234        self
235    }
236
237    #[must_use]
238    pub const fn no_alloc(&self) -> bool {
239        self.params.no_alloc
240    }
241
242    #[must_use]
243    pub const fn with_no_alloc(mut self, no_alloc: bool) -> Self {
244        self.params.no_alloc = no_alloc;
245        if no_alloc {
246            self.params.use_mmap = false;
247        }
248        self
249    }
250
251    #[must_use]
252    pub const fn with_use_mlock(mut self, use_mlock: bool) -> Self {
253        self.params.use_mlock = use_mlock;
254        self
255    }
256
257    #[must_use]
258    pub fn with_split_mode(mut self, split_mode: LlamaSplitMode) -> Self {
259        self.params.split_mode = split_mode.into();
260        self
261    }
262
263    /// # Errors
264    /// Returns `LlamaCppError::BackendDeviceNotFound` if any device index is invalid.
265    pub fn with_devices(mut self, devices: &[usize]) -> Result<Self, LlamaCppError> {
266        for dev in self.devices.iter_mut() {
267            *dev = std::ptr::null_mut();
268        }
269        let max_devices = crate::max_devices().min(LLAMA_CPP_MAX_DEVICES);
270        if devices.len() > max_devices {
271            return Err(LlamaCppError::MaxDevicesExceeded(max_devices));
272        }
273        for (i, &dev) in devices.iter().enumerate() {
274            if dev >= unsafe { llama_cpp_bindings_sys::ggml_backend_dev_count() } {
275                return Err(LlamaCppError::BackendDeviceNotFound(dev));
276            }
277            let backend_dev = unsafe { llama_cpp_bindings_sys::ggml_backend_dev_get(dev) };
278            self.devices[i] = backend_dev;
279        }
280        self.params.devices = self.devices.as_mut_ptr();
281
282        Ok(self)
283    }
284}
285
286fn fit_params_status_to_result(
287    status: llama_cpp_bindings_sys::llama_rs_fit_params_status,
288    out_unrecognized_status_code: i32,
289    out_error: *mut c_char,
290) -> Result<(), FitError> {
291    match status {
292        llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_OK => Ok(()),
293        llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_REPORTED_FAILURE => {
294            Err(FitError::NoFittingMemoryLayout)
295        }
296        llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_REPORTED_ERROR => {
297            Err(FitError::Aborted)
298        }
299        llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_RETURNED_UNRECOGNIZED_STATUS_CODE => {
300            Err(FitError::UnknownStatus {
301                code: out_unrecognized_status_code,
302            })
303        }
304        llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_ERROR_STRING_ALLOCATION_FAILED => {
305            Err(FitError::NotEnoughMemory)
306        }
307        llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_THREW_CXX_EXCEPTION => {
308            let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
309            Err(FitError::Reported { message })
310        }
311        other => unreachable!("llama_rs_fit_params returned unrecognized wrapper status: {other}"),
312    }
313}
314
315impl LlamaModelParams {
316    /// # Errors
317    ///
318    /// Returns one of the [`FitError`] variants matching the vendored wrapper's status code.
319    pub fn fit_params(
320        mut self: Pin<&mut Self>,
321        model_path: &CStr,
322        context_params: &mut LlamaContextParams,
323        margins: &mut [usize],
324        n_ctx_min: u32,
325        log_level: llama_cpp_bindings_sys::ggml_log_level,
326    ) -> Result<FitResult, FitError> {
327        let max_devices = unsafe { llama_cpp_bindings_sys::llama_max_devices() };
328        let max_buft = unsafe { llama_cpp_bindings_sys::llama_max_tensor_buft_overrides() };
329
330        self.tensor_split.clear();
331        self.tensor_split.resize(max_devices, 0.0);
332
333        self.buft_overrides.clear();
334        self.buft_overrides.resize(
335            max_buft + 1,
336            llama_cpp_bindings_sys::llama_model_tensor_buft_override {
337                pattern: null(),
338                buft: std::ptr::null_mut(),
339            },
340        );
341
342        self.params.tensor_split = null::<f32>();
343        self.params.tensor_buft_overrides = null();
344
345        let mut out_unrecognized_status_code: i32 = 0;
346        let mut out_error: *mut c_char = std::ptr::null_mut();
347
348        let status = unsafe {
349            llama_cpp_bindings_sys::llama_rs_fit_params(
350                model_path.as_ptr(),
351                &raw mut self.params,
352                &raw mut context_params.context_params,
353                self.tensor_split.as_mut_ptr(),
354                self.buft_overrides.as_mut_ptr(),
355                margins.as_mut_ptr(),
356                n_ctx_min,
357                log_level,
358                &raw mut out_unrecognized_status_code,
359                &raw mut out_error,
360            )
361        };
362
363        fit_params_status_to_result(status, out_unrecognized_status_code, out_error)?;
364
365        self.params.tensor_split = self.tensor_split.as_ptr();
366        self.params.tensor_buft_overrides = self.buft_overrides.as_ptr();
367
368        Ok(FitResult {
369            n_ctx: context_params.context_params.n_ctx,
370        })
371    }
372}
373
374impl Default for LlamaModelParams {
375    fn default() -> Self {
376        let default_params = unsafe { llama_cpp_bindings_sys::llama_model_default_params() };
377        Self {
378            params: default_params,
379            kv_overrides: vec![llama_cpp_bindings_sys::llama_model_kv_override {
380                key: [0; 128],
381                tag: 0,
382                __bindgen_anon_1: llama_cpp_bindings_sys::llama_model_kv_override__bindgen_ty_1 {
383                    val_i64: 0,
384                },
385            }],
386            buft_overrides: vec![llama_cpp_bindings_sys::llama_model_tensor_buft_override {
387                pattern: null(),
388                buft: std::ptr::null_mut(),
389            }],
390            devices: Box::pin([std::ptr::null_mut(); 16]),
391            tensor_split: Vec::new(),
392        }
393    }
394}
395
396#[cfg(test)]
397mod tests {
398    use crate::model::split_mode::LlamaSplitMode;
399
400    use super::{LLAMA_CPP_MAX_DEVICES, LlamaModelParams};
401
402    #[test]
403    fn device_capacity_is_not_smaller_than_the_upstream_limit() {
404        let upstream_limit = crate::max_devices();
405        assert!(
406            LLAMA_CPP_MAX_DEVICES >= upstream_limit,
407            "LLAMA_CPP_MAX_DEVICES ({LLAMA_CPP_MAX_DEVICES}) backs a fixed-size device array and is \
408             clamped against llama_max_devices() ({upstream_limit}); once upstream reports more \
409             devices than the array can hold, the extra devices are dropped without any error"
410        );
411    }
412
413    #[test]
414    fn default_params_have_expected_values() {
415        let params = LlamaModelParams::default();
416
417        assert_eq!(params.n_gpu_layers(), -1);
418        assert_eq!(params.main_gpu(), 0);
419        assert!(!params.vocab_only());
420        assert!(params.use_mmap());
421        assert!(!params.use_mlock());
422        assert_eq!(params.split_mode(), Ok(LlamaSplitMode::Layer));
423        assert!(params.devices().is_empty());
424    }
425
426    #[test]
427    fn with_n_gpu_layers_sets_the_offload_count() {
428        let params = LlamaModelParams::default().with_n_gpu_layers(999);
429
430        assert_eq!(params.n_gpu_layers(), 999);
431    }
432
433    #[test]
434    fn with_n_gpu_layers_sets_value() {
435        let params = LlamaModelParams::default().with_n_gpu_layers(32);
436
437        assert_eq!(params.n_gpu_layers(), 32);
438    }
439
440    #[test]
441    fn with_main_gpu_sets_value() {
442        let params = LlamaModelParams::default().with_main_gpu(2);
443
444        assert_eq!(params.main_gpu(), 2);
445    }
446
447    #[test]
448    fn with_split_mode_none() {
449        let params = LlamaModelParams::default().with_split_mode(LlamaSplitMode::None);
450
451        assert_eq!(params.split_mode(), Ok(LlamaSplitMode::None));
452    }
453
454    #[test]
455    fn with_split_mode_row() {
456        let params = LlamaModelParams::default().with_split_mode(LlamaSplitMode::Row);
457
458        assert_eq!(params.split_mode(), Ok(LlamaSplitMode::Row));
459    }
460
461    #[test]
462    fn with_vocab_only_enables() {
463        let params = LlamaModelParams::default().with_vocab_only(true);
464
465        assert!(params.vocab_only());
466    }
467
468    #[test]
469    fn with_vocab_only_disables() {
470        let params = LlamaModelParams::default().with_vocab_only(false);
471
472        assert!(!params.vocab_only());
473    }
474
475    #[test]
476    fn with_use_mmap_enables() {
477        let params = LlamaModelParams::default().with_use_mmap(true);
478
479        assert!(params.use_mmap());
480    }
481
482    #[test]
483    fn with_use_mmap_disables() {
484        let params = LlamaModelParams::default().with_use_mmap(false);
485
486        assert!(!params.use_mmap());
487    }
488
489    #[test]
490    fn with_no_alloc_enables() {
491        let params = LlamaModelParams::default().with_no_alloc(true);
492
493        assert!(params.no_alloc());
494    }
495
496    #[test]
497    fn with_no_alloc_disables() {
498        let params = LlamaModelParams::default().with_no_alloc(false);
499
500        assert!(!params.no_alloc());
501    }
502
503    #[test]
504    fn with_no_alloc_true_disables_mmap() {
505        let params = LlamaModelParams::default()
506            .with_use_mmap(true)
507            .with_no_alloc(true);
508
509        assert!(params.no_alloc());
510        assert!(!params.use_mmap());
511    }
512
513    #[test]
514    fn default_no_alloc_is_false() {
515        let params = LlamaModelParams::default();
516
517        assert!(!params.no_alloc());
518    }
519
520    #[test]
521    fn with_use_mlock_enables() {
522        let params = LlamaModelParams::default().with_use_mlock(true);
523
524        assert!(params.use_mlock());
525    }
526
527    #[test]
528    fn with_use_mlock_disables() {
529        let params = LlamaModelParams::default().with_use_mlock(false);
530
531        assert!(!params.use_mlock());
532    }
533
534    #[test]
535    fn debug_format_contains_field_names() {
536        let params = LlamaModelParams::default();
537        let debug_output = format!("{params:?}");
538
539        assert!(debug_output.contains("n_gpu_layers"));
540        assert!(debug_output.contains("main_gpu"));
541        assert!(debug_output.contains("vocab_only"));
542        assert!(debug_output.contains("use_mmap"));
543        assert!(debug_output.contains("use_mlock"));
544        assert!(debug_output.contains("split_mode"));
545    }
546
547    #[test]
548    fn builder_chaining_preserves_all_values() {
549        let params = LlamaModelParams::default()
550            .with_n_gpu_layers(10)
551            .with_main_gpu(1)
552            .with_split_mode(LlamaSplitMode::Row)
553            .with_vocab_only(true)
554            .with_use_mlock(true);
555
556        assert_eq!(params.n_gpu_layers(), 10);
557        assert_eq!(params.main_gpu(), 1);
558        assert_eq!(params.split_mode(), Ok(LlamaSplitMode::Row));
559        assert!(params.vocab_only());
560        assert!(params.use_mlock());
561    }
562
563    #[test]
564    fn with_devices_empty_list_succeeds() {
565        let params = LlamaModelParams::default().with_devices(&[]);
566
567        assert!(params.is_ok());
568        assert!(params.unwrap().devices().is_empty());
569    }
570
571    #[test]
572    fn with_devices_invalid_index_returns_error() {
573        let result = LlamaModelParams::default().with_devices(&[999_999]);
574
575        assert_eq!(
576            std::mem::discriminant(&result.unwrap_err()),
577            std::mem::discriminant(&crate::LlamaCppError::BackendDeviceNotFound(0)),
578        );
579    }
580
581    #[test]
582    fn add_cpu_buft_override_succeeds() {
583        let mut params = std::pin::pin!(LlamaModelParams::default());
584        let result = params.as_mut().add_cpu_buft_override(c"test_pattern");
585
586        assert!(result.is_ok());
587    }
588
589    #[test]
590    fn add_cpu_buft_override_twice_fails_with_slot_not_empty() {
591        let mut params = std::pin::pin!(LlamaModelParams::default());
592        params
593            .as_mut()
594            .add_cpu_buft_override(c"first_pattern")
595            .unwrap();
596        let result = params.as_mut().add_cpu_buft_override(c"second_pattern");
597
598        assert_eq!(
599            result.unwrap_err(),
600            crate::error::ModelParamsError::SlotNotEmpty
601        );
602    }
603
604    #[test]
605    fn add_cpu_moe_override_succeeds() {
606        let mut params = std::pin::pin!(LlamaModelParams::default());
607        let result = params.as_mut().add_cpu_moe_override();
608
609        assert!(result.is_ok());
610    }
611
612    #[test]
613    fn append_kv_override_twice_fails_with_slot_not_empty() {
614        use crate::model::params::param_override_value::ParamOverrideValue;
615        use std::ffi::CString;
616
617        let mut params = std::pin::pin!(LlamaModelParams::default());
618        let key = CString::new("first_key").unwrap();
619        params
620            .as_mut()
621            .append_kv_override(&key, ParamOverrideValue::Int(1))
622            .unwrap();
623
624        let key2 = CString::new("second_key").unwrap();
625        let result = params
626            .as_mut()
627            .append_kv_override(&key2, ParamOverrideValue::Int(2));
628
629        assert_eq!(
630            result.unwrap_err(),
631            crate::error::ModelParamsError::SlotNotEmpty
632        );
633    }
634
635    #[test]
636    fn with_devices_too_many_returns_max_exceeded() {
637        let too_many: Vec<usize> = (0..17).collect();
638        let result = LlamaModelParams::default().with_devices(&too_many);
639
640        assert!(
641            result
642                .unwrap_err()
643                .to_string()
644                .contains("Max devices exceeded")
645        );
646    }
647
648    #[test]
649    fn with_devices_sets_devices_when_available() {
650        #[cfg(feature = "dynamic-backends")]
651        crate::load_backends::load_backends().unwrap();
652
653        let dev_count = unsafe { llama_cpp_bindings_sys::ggml_backend_dev_count() };
654        assert!(dev_count > 0, "Test requires at least one backend device");
655
656        let params = LlamaModelParams::default().with_devices(&[0]).unwrap();
657
658        assert_eq!(params.devices().len(), 1);
659        assert_eq!(params.devices()[0], 0);
660    }
661
662    #[test]
663    fn with_devices_invalid_index_returns_not_found() {
664        let invalid_index = usize::MAX;
665        let result = LlamaModelParams::default().with_devices(&[invalid_index]);
666
667        assert!(result.unwrap_err().to_string().contains("Backend device"));
668    }
669
670    #[test]
671    #[cfg(not(target_os = "windows"))]
672    fn append_kv_override_with_high_byte_returns_invalid_character_error() {
673        use crate::model::params::param_override_value::ParamOverrideValue;
674
675        let key_bytes: &[u8] = b"\xff\0";
676        let key = std::ffi::CStr::from_bytes_with_nul(key_bytes).unwrap();
677        let mut params = std::pin::pin!(LlamaModelParams::default());
678        let result = params
679            .as_mut()
680            .append_kv_override(key, ParamOverrideValue::Int(1));
681
682        assert_eq!(
683            std::mem::discriminant(&result.unwrap_err()),
684            std::mem::discriminant(&crate::error::ModelParamsError::InvalidCharacterInKey {
685                byte: 0,
686                reason: String::new(),
687            }),
688        );
689    }
690
691    #[test]
692    #[cfg(not(target_os = "windows"))]
693    fn add_cpu_buft_override_with_high_byte_returns_invalid_character_error() {
694        let key_bytes: &[u8] = b"\xff\0";
695        let key = std::ffi::CStr::from_bytes_with_nul(key_bytes).unwrap();
696        let mut params = std::pin::pin!(LlamaModelParams::default());
697        let result = params.as_mut().add_cpu_buft_override(key);
698
699        assert_eq!(
700            std::mem::discriminant(&result.unwrap_err()),
701            std::mem::discriminant(&crate::error::ModelParamsError::InvalidCharacterInKey {
702                byte: 0,
703                reason: String::new(),
704            }),
705        );
706    }
707
708    #[test]
709    fn append_kv_override_with_empty_slot_vector_returns_no_available_slot() {
710        use crate::model::params::param_override_value::ParamOverrideValue;
711
712        let mut params = LlamaModelParams::default();
713        params.kv_overrides.clear();
714        let mut pinned = std::pin::pin!(params);
715
716        let result = pinned
717            .as_mut()
718            .append_kv_override(c"any_key", ParamOverrideValue::Int(1));
719
720        assert_eq!(
721            result.unwrap_err(),
722            crate::error::ModelParamsError::NoAvailableSlot
723        );
724    }
725
726    #[test]
727    fn add_cpu_buft_override_with_empty_slot_vector_returns_no_available_slot() {
728        let mut params = LlamaModelParams::default();
729        params.buft_overrides.clear();
730        let mut pinned = std::pin::pin!(params);
731
732        let result = pinned.as_mut().add_cpu_buft_override(c"any_pattern");
733
734        assert_eq!(
735            result.unwrap_err(),
736            crate::error::ModelParamsError::NoAvailableSlot
737        );
738    }
739
740    #[test]
741    #[serial_test::serial]
742    fn fit_params_invalid_model_path_returns_error() {
743        use crate::context::params::LlamaContextParams;
744        use crate::error::FitError;
745        use crate::llama_backend::LlamaBackend;
746
747        let _backend = LlamaBackend::init();
748        let mut params = std::pin::pin!(LlamaModelParams::default());
749        let mut context_params = LlamaContextParams::default();
750        let mut margins = vec![0usize; crate::max_devices()];
751
752        let bogus_path = c"/nonexistent/path/to/model.gguf";
753        let result = params.as_mut().fit_params(
754            bogus_path,
755            &mut context_params,
756            &mut margins,
757            512,
758            llama_cpp_bindings_sys::GGML_LOG_LEVEL_NONE,
759        );
760
761        assert!(
762            matches!(result, Err(FitError::Aborted | FitError::Reported { .. })),
763            "expected Aborted or Reported, got {result:?}"
764        );
765    }
766
767    #[test]
768    fn fit_params_status_ok_returns_ok() {
769        let result = super::fit_params_status_to_result(
770            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_OK,
771            0,
772            std::ptr::null_mut(),
773        );
774
775        assert_eq!(result, Ok(()));
776    }
777
778    #[test]
779    fn fit_params_status_reported_failure_returns_no_fitting_memory_layout() {
780        let result = super::fit_params_status_to_result(
781            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_REPORTED_FAILURE,
782            0,
783            std::ptr::null_mut(),
784        );
785
786        assert_eq!(result, Err(crate::error::FitError::NoFittingMemoryLayout));
787    }
788
789    #[test]
790    fn fit_params_status_reported_error_returns_aborted() {
791        let result = super::fit_params_status_to_result(
792            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_REPORTED_ERROR,
793            0,
794            std::ptr::null_mut(),
795        );
796
797        assert_eq!(result, Err(crate::error::FitError::Aborted));
798    }
799
800    #[test]
801    fn fit_params_status_unrecognized_code_returns_unknown_status() {
802        let result = super::fit_params_status_to_result(
803            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_RETURNED_UNRECOGNIZED_STATUS_CODE,
804            42,
805            std::ptr::null_mut(),
806        );
807
808        assert_eq!(
809            result,
810            Err(crate::error::FitError::UnknownStatus { code: 42 })
811        );
812    }
813
814    #[test]
815    fn fit_params_status_allocation_failed_returns_not_enough_memory() {
816        let result = super::fit_params_status_to_result(
817            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_ERROR_STRING_ALLOCATION_FAILED,
818            0,
819            std::ptr::null_mut(),
820        );
821
822        assert_eq!(result, Err(crate::error::FitError::NotEnoughMemory));
823    }
824
825    #[test]
826    fn fit_params_status_cxx_exception_returns_reported_with_unknown_error() {
827        let result = super::fit_params_status_to_result(
828            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_THREW_CXX_EXCEPTION,
829            0,
830            std::ptr::null_mut(),
831        );
832
833        assert_eq!(
834            result,
835            Err(crate::error::FitError::Reported {
836                message: "unknown error".to_owned()
837            })
838        );
839    }
840
841    #[test]
842    #[should_panic(expected = "unrecognized wrapper status")]
843    fn fit_params_status_out_of_range_panics() {
844        let _ = super::fit_params_status_to_result(
845            llama_cpp_bindings_sys::llama_rs_fit_params_status::MAX,
846            0,
847            std::ptr::null_mut(),
848        );
849    }
850}