Skip to main content

llama_cpp_bindings/model/
params.rs

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