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::LlamaModelParams;
401
402    #[test]
403    fn default_params_have_expected_values() {
404        let params = LlamaModelParams::default();
405
406        assert_eq!(params.n_gpu_layers(), -1);
407        assert_eq!(params.main_gpu(), 0);
408        assert!(!params.vocab_only());
409        assert!(params.use_mmap());
410        assert!(!params.use_mlock());
411        assert_eq!(params.split_mode(), Ok(LlamaSplitMode::Layer));
412        assert!(params.devices().is_empty());
413    }
414
415    #[test]
416    fn with_n_gpu_layers_sets_the_offload_count() {
417        let params = LlamaModelParams::default().with_n_gpu_layers(999);
418
419        assert_eq!(params.n_gpu_layers(), 999);
420    }
421
422    #[test]
423    fn with_n_gpu_layers_sets_value() {
424        let params = LlamaModelParams::default().with_n_gpu_layers(32);
425
426        assert_eq!(params.n_gpu_layers(), 32);
427    }
428
429    #[test]
430    fn with_main_gpu_sets_value() {
431        let params = LlamaModelParams::default().with_main_gpu(2);
432
433        assert_eq!(params.main_gpu(), 2);
434    }
435
436    #[test]
437    fn with_split_mode_none() {
438        let params = LlamaModelParams::default().with_split_mode(LlamaSplitMode::None);
439
440        assert_eq!(params.split_mode(), Ok(LlamaSplitMode::None));
441    }
442
443    #[test]
444    fn with_split_mode_row() {
445        let params = LlamaModelParams::default().with_split_mode(LlamaSplitMode::Row);
446
447        assert_eq!(params.split_mode(), Ok(LlamaSplitMode::Row));
448    }
449
450    #[test]
451    fn with_vocab_only_enables() {
452        let params = LlamaModelParams::default().with_vocab_only(true);
453
454        assert!(params.vocab_only());
455    }
456
457    #[test]
458    fn with_vocab_only_disables() {
459        let params = LlamaModelParams::default().with_vocab_only(false);
460
461        assert!(!params.vocab_only());
462    }
463
464    #[test]
465    fn with_use_mmap_enables() {
466        let params = LlamaModelParams::default().with_use_mmap(true);
467
468        assert!(params.use_mmap());
469    }
470
471    #[test]
472    fn with_use_mmap_disables() {
473        let params = LlamaModelParams::default().with_use_mmap(false);
474
475        assert!(!params.use_mmap());
476    }
477
478    #[test]
479    fn with_no_alloc_enables() {
480        let params = LlamaModelParams::default().with_no_alloc(true);
481
482        assert!(params.no_alloc());
483    }
484
485    #[test]
486    fn with_no_alloc_disables() {
487        let params = LlamaModelParams::default().with_no_alloc(false);
488
489        assert!(!params.no_alloc());
490    }
491
492    #[test]
493    fn with_no_alloc_true_disables_mmap() {
494        let params = LlamaModelParams::default()
495            .with_use_mmap(true)
496            .with_no_alloc(true);
497
498        assert!(params.no_alloc());
499        assert!(!params.use_mmap());
500    }
501
502    #[test]
503    fn default_no_alloc_is_false() {
504        let params = LlamaModelParams::default();
505
506        assert!(!params.no_alloc());
507    }
508
509    #[test]
510    fn with_use_mlock_enables() {
511        let params = LlamaModelParams::default().with_use_mlock(true);
512
513        assert!(params.use_mlock());
514    }
515
516    #[test]
517    fn with_use_mlock_disables() {
518        let params = LlamaModelParams::default().with_use_mlock(false);
519
520        assert!(!params.use_mlock());
521    }
522
523    #[test]
524    fn debug_format_contains_field_names() {
525        let params = LlamaModelParams::default();
526        let debug_output = format!("{params:?}");
527
528        assert!(debug_output.contains("n_gpu_layers"));
529        assert!(debug_output.contains("main_gpu"));
530        assert!(debug_output.contains("vocab_only"));
531        assert!(debug_output.contains("use_mmap"));
532        assert!(debug_output.contains("use_mlock"));
533        assert!(debug_output.contains("split_mode"));
534    }
535
536    #[test]
537    fn builder_chaining_preserves_all_values() {
538        let params = LlamaModelParams::default()
539            .with_n_gpu_layers(10)
540            .with_main_gpu(1)
541            .with_split_mode(LlamaSplitMode::Row)
542            .with_vocab_only(true)
543            .with_use_mlock(true);
544
545        assert_eq!(params.n_gpu_layers(), 10);
546        assert_eq!(params.main_gpu(), 1);
547        assert_eq!(params.split_mode(), Ok(LlamaSplitMode::Row));
548        assert!(params.vocab_only());
549        assert!(params.use_mlock());
550    }
551
552    #[test]
553    fn with_devices_empty_list_succeeds() {
554        let params = LlamaModelParams::default().with_devices(&[]);
555
556        assert!(params.is_ok());
557        assert!(params.unwrap().devices().is_empty());
558    }
559
560    #[test]
561    fn with_devices_invalid_index_returns_error() {
562        let result = LlamaModelParams::default().with_devices(&[999_999]);
563
564        assert_eq!(
565            std::mem::discriminant(&result.unwrap_err()),
566            std::mem::discriminant(&crate::LlamaCppError::BackendDeviceNotFound(0)),
567        );
568    }
569
570    #[test]
571    fn add_cpu_buft_override_succeeds() {
572        let mut params = std::pin::pin!(LlamaModelParams::default());
573        let result = params.as_mut().add_cpu_buft_override(c"test_pattern");
574
575        assert!(result.is_ok());
576    }
577
578    #[test]
579    fn add_cpu_buft_override_twice_fails_with_slot_not_empty() {
580        let mut params = std::pin::pin!(LlamaModelParams::default());
581        params
582            .as_mut()
583            .add_cpu_buft_override(c"first_pattern")
584            .unwrap();
585        let result = params.as_mut().add_cpu_buft_override(c"second_pattern");
586
587        assert_eq!(
588            result.unwrap_err(),
589            crate::error::ModelParamsError::SlotNotEmpty
590        );
591    }
592
593    #[test]
594    fn add_cpu_moe_override_succeeds() {
595        let mut params = std::pin::pin!(LlamaModelParams::default());
596        let result = params.as_mut().add_cpu_moe_override();
597
598        assert!(result.is_ok());
599    }
600
601    #[test]
602    fn append_kv_override_twice_fails_with_slot_not_empty() {
603        use crate::model::params::param_override_value::ParamOverrideValue;
604        use std::ffi::CString;
605
606        let mut params = std::pin::pin!(LlamaModelParams::default());
607        let key = CString::new("first_key").unwrap();
608        params
609            .as_mut()
610            .append_kv_override(&key, ParamOverrideValue::Int(1))
611            .unwrap();
612
613        let key2 = CString::new("second_key").unwrap();
614        let result = params
615            .as_mut()
616            .append_kv_override(&key2, ParamOverrideValue::Int(2));
617
618        assert_eq!(
619            result.unwrap_err(),
620            crate::error::ModelParamsError::SlotNotEmpty
621        );
622    }
623
624    #[test]
625    fn with_devices_too_many_returns_max_exceeded() {
626        let too_many: Vec<usize> = (0..17).collect();
627        let result = LlamaModelParams::default().with_devices(&too_many);
628
629        assert!(
630            result
631                .unwrap_err()
632                .to_string()
633                .contains("Max devices exceeded")
634        );
635    }
636
637    #[test]
638    fn with_devices_sets_devices_when_available() {
639        #[cfg(feature = "dynamic-backends")]
640        crate::load_backends::load_backends().unwrap();
641
642        let dev_count = unsafe { llama_cpp_bindings_sys::ggml_backend_dev_count() };
643        assert!(dev_count > 0, "Test requires at least one backend device");
644
645        let params = LlamaModelParams::default().with_devices(&[0]).unwrap();
646
647        assert_eq!(params.devices().len(), 1);
648        assert_eq!(params.devices()[0], 0);
649    }
650
651    #[test]
652    fn with_devices_invalid_index_returns_not_found() {
653        let invalid_index = usize::MAX;
654        let result = LlamaModelParams::default().with_devices(&[invalid_index]);
655
656        assert!(result.unwrap_err().to_string().contains("Backend device"));
657    }
658
659    #[test]
660    #[cfg(not(target_os = "windows"))]
661    fn append_kv_override_with_high_byte_returns_invalid_character_error() {
662        use crate::model::params::param_override_value::ParamOverrideValue;
663
664        let key_bytes: &[u8] = b"\xff\0";
665        let key = std::ffi::CStr::from_bytes_with_nul(key_bytes).unwrap();
666        let mut params = std::pin::pin!(LlamaModelParams::default());
667        let result = params
668            .as_mut()
669            .append_kv_override(key, ParamOverrideValue::Int(1));
670
671        assert_eq!(
672            std::mem::discriminant(&result.unwrap_err()),
673            std::mem::discriminant(&crate::error::ModelParamsError::InvalidCharacterInKey {
674                byte: 0,
675                reason: String::new(),
676            }),
677        );
678    }
679
680    #[test]
681    #[cfg(not(target_os = "windows"))]
682    fn add_cpu_buft_override_with_high_byte_returns_invalid_character_error() {
683        let key_bytes: &[u8] = b"\xff\0";
684        let key = std::ffi::CStr::from_bytes_with_nul(key_bytes).unwrap();
685        let mut params = std::pin::pin!(LlamaModelParams::default());
686        let result = params.as_mut().add_cpu_buft_override(key);
687
688        assert_eq!(
689            std::mem::discriminant(&result.unwrap_err()),
690            std::mem::discriminant(&crate::error::ModelParamsError::InvalidCharacterInKey {
691                byte: 0,
692                reason: String::new(),
693            }),
694        );
695    }
696
697    #[test]
698    fn append_kv_override_with_empty_slot_vector_returns_no_available_slot() {
699        use crate::model::params::param_override_value::ParamOverrideValue;
700
701        let mut params = LlamaModelParams::default();
702        params.kv_overrides.clear();
703        let mut pinned = std::pin::pin!(params);
704
705        let result = pinned
706            .as_mut()
707            .append_kv_override(c"any_key", ParamOverrideValue::Int(1));
708
709        assert_eq!(
710            result.unwrap_err(),
711            crate::error::ModelParamsError::NoAvailableSlot
712        );
713    }
714
715    #[test]
716    fn add_cpu_buft_override_with_empty_slot_vector_returns_no_available_slot() {
717        let mut params = LlamaModelParams::default();
718        params.buft_overrides.clear();
719        let mut pinned = std::pin::pin!(params);
720
721        let result = pinned.as_mut().add_cpu_buft_override(c"any_pattern");
722
723        assert_eq!(
724            result.unwrap_err(),
725            crate::error::ModelParamsError::NoAvailableSlot
726        );
727    }
728
729    #[test]
730    #[serial_test::serial]
731    fn fit_params_invalid_model_path_returns_error() {
732        use crate::context::params::LlamaContextParams;
733        use crate::error::FitError;
734        use crate::llama_backend::LlamaBackend;
735
736        let _backend = LlamaBackend::init();
737        let mut params = std::pin::pin!(LlamaModelParams::default());
738        let mut context_params = LlamaContextParams::default();
739        let mut margins = vec![0usize; crate::max_devices()];
740
741        let bogus_path = c"/nonexistent/path/to/model.gguf";
742        let result = params.as_mut().fit_params(
743            bogus_path,
744            &mut context_params,
745            &mut margins,
746            512,
747            llama_cpp_bindings_sys::GGML_LOG_LEVEL_NONE,
748        );
749
750        assert!(
751            matches!(result, Err(FitError::Aborted | FitError::Reported { .. })),
752            "expected Aborted or Reported, got {result:?}"
753        );
754    }
755
756    #[test]
757    fn fit_params_status_ok_returns_ok() {
758        let result = super::fit_params_status_to_result(
759            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_OK,
760            0,
761            std::ptr::null_mut(),
762        );
763
764        assert_eq!(result, Ok(()));
765    }
766
767    #[test]
768    fn fit_params_status_reported_failure_returns_no_fitting_memory_layout() {
769        let result = super::fit_params_status_to_result(
770            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_REPORTED_FAILURE,
771            0,
772            std::ptr::null_mut(),
773        );
774
775        assert_eq!(result, Err(crate::error::FitError::NoFittingMemoryLayout));
776    }
777
778    #[test]
779    fn fit_params_status_reported_error_returns_aborted() {
780        let result = super::fit_params_status_to_result(
781            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_REPORTED_ERROR,
782            0,
783            std::ptr::null_mut(),
784        );
785
786        assert_eq!(result, Err(crate::error::FitError::Aborted));
787    }
788
789    #[test]
790    fn fit_params_status_unrecognized_code_returns_unknown_status() {
791        let result = super::fit_params_status_to_result(
792            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_RETURNED_UNRECOGNIZED_STATUS_CODE,
793            42,
794            std::ptr::null_mut(),
795        );
796
797        assert_eq!(
798            result,
799            Err(crate::error::FitError::UnknownStatus { code: 42 })
800        );
801    }
802
803    #[test]
804    fn fit_params_status_allocation_failed_returns_not_enough_memory() {
805        let result = super::fit_params_status_to_result(
806            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_ERROR_STRING_ALLOCATION_FAILED,
807            0,
808            std::ptr::null_mut(),
809        );
810
811        assert_eq!(result, Err(crate::error::FitError::NotEnoughMemory));
812    }
813
814    #[test]
815    fn fit_params_status_cxx_exception_returns_reported_with_unknown_error() {
816        let result = super::fit_params_status_to_result(
817            llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_VENDORED_THREW_CXX_EXCEPTION,
818            0,
819            std::ptr::null_mut(),
820        );
821
822        assert_eq!(
823            result,
824            Err(crate::error::FitError::Reported {
825                message: "unknown error".to_owned()
826            })
827        );
828    }
829
830    #[test]
831    #[should_panic(expected = "unrecognized wrapper status")]
832    fn fit_params_status_out_of_range_panics() {
833        let _ = super::fit_params_status_to_result(
834            llama_cpp_bindings_sys::llama_rs_fit_params_status::MAX,
835            0,
836            std::ptr::null_mut(),
837        );
838    }
839}