1use crate::context::params::LlamaContextParams;
4use crate::model::params::kv_overrides::KvOverrides;
5use crate::LlamaCppError;
6use std::ffi::{c_char, c_void, CStr};
7use std::fmt::{Debug, Formatter};
8use std::pin::Pin;
9use std::ptr::null;
10
11pub mod kv_overrides;
12
13#[cfg(feature = "common")]
15#[derive(Debug, Clone)]
16pub struct FitResult {
17 pub n_ctx: u32,
19}
20
21#[cfg(feature = "common")]
23#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
24pub enum FitError {
25 #[error("could not find allocations that fit available memory")]
27 Failure,
28 #[error("hard error during parameter fitting")]
30 Error,
31}
32
33#[allow(clippy::cast_possible_wrap)]
34#[allow(clippy::cast_possible_truncation)]
35const LLAMA_SPLIT_MODE_NONE: i8 = llama_cpp_sys_2::LLAMA_SPLIT_MODE_NONE as i8;
36#[allow(clippy::cast_possible_wrap)]
37#[allow(clippy::cast_possible_truncation)]
38const LLAMA_SPLIT_MODE_LAYER: i8 = llama_cpp_sys_2::LLAMA_SPLIT_MODE_LAYER as i8;
39#[allow(clippy::cast_possible_wrap)]
40#[allow(clippy::cast_possible_truncation)]
41const LLAMA_SPLIT_MODE_ROW: i8 = llama_cpp_sys_2::LLAMA_SPLIT_MODE_ROW as i8;
42#[allow(clippy::cast_possible_wrap)]
43#[allow(clippy::cast_possible_truncation)]
44const LLAMA_SPLIT_MODE_TENSOR: i8 = llama_cpp_sys_2::LLAMA_SPLIT_MODE_TENSOR as i8;
45
46#[repr(i8)]
48#[derive(Copy, Clone, Debug, PartialEq, Eq)]
49pub enum LlamaSplitMode {
50 None = LLAMA_SPLIT_MODE_NONE,
52 Layer = LLAMA_SPLIT_MODE_LAYER,
54 Row = LLAMA_SPLIT_MODE_ROW,
56 Tensor = LLAMA_SPLIT_MODE_TENSOR,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct LlamaSplitModeParseError(pub i32);
63
64impl TryFrom<i32> for LlamaSplitMode {
69 type Error = LlamaSplitModeParseError;
70
71 fn try_from(value: i32) -> Result<Self, Self::Error> {
72 let i8_value = value
73 .try_into()
74 .map_err(|_| LlamaSplitModeParseError(value))?;
75 match i8_value {
76 LLAMA_SPLIT_MODE_NONE => Ok(Self::None),
77 LLAMA_SPLIT_MODE_LAYER => Ok(Self::Layer),
78 LLAMA_SPLIT_MODE_ROW => Ok(Self::Row),
79 LLAMA_SPLIT_MODE_TENSOR => Ok(Self::Tensor),
80 _ => Err(LlamaSplitModeParseError(value)),
81 }
82 }
83}
84
85impl TryFrom<u32> for LlamaSplitMode {
90 type Error = LlamaSplitModeParseError;
91
92 fn try_from(value: u32) -> Result<Self, Self::Error> {
93 let i8_value = value
94 .try_into()
95 .map_err(|_| LlamaSplitModeParseError(value.try_into().unwrap_or(i32::MAX)))?;
96 match i8_value {
97 LLAMA_SPLIT_MODE_NONE => Ok(Self::None),
98 LLAMA_SPLIT_MODE_LAYER => Ok(Self::Layer),
99 LLAMA_SPLIT_MODE_ROW => Ok(Self::Row),
100 LLAMA_SPLIT_MODE_TENSOR => Ok(Self::Tensor),
101 _ => Err(LlamaSplitModeParseError(
102 value.try_into().unwrap_or(i32::MAX),
103 )),
104 }
105 }
106}
107
108impl From<LlamaSplitMode> for i32 {
110 fn from(value: LlamaSplitMode) -> Self {
111 match value {
112 LlamaSplitMode::None => LLAMA_SPLIT_MODE_NONE.into(),
113 LlamaSplitMode::Layer => LLAMA_SPLIT_MODE_LAYER.into(),
114 LlamaSplitMode::Row => LLAMA_SPLIT_MODE_ROW.into(),
115 LlamaSplitMode::Tensor => LLAMA_SPLIT_MODE_TENSOR.into(),
116 }
117 }
118}
119
120impl From<LlamaSplitMode> for u32 {
122 fn from(value: LlamaSplitMode) -> Self {
123 match value {
124 LlamaSplitMode::None => LLAMA_SPLIT_MODE_NONE as u32,
125 LlamaSplitMode::Layer => LLAMA_SPLIT_MODE_LAYER as u32,
126 LlamaSplitMode::Row => LLAMA_SPLIT_MODE_ROW as u32,
127 LlamaSplitMode::Tensor => LLAMA_SPLIT_MODE_TENSOR as u32,
128 }
129 }
130}
131
132impl Default for LlamaSplitMode {
134 fn default() -> Self {
135 LlamaSplitMode::Layer
136 }
137}
138
139pub const LLAMA_CPP_MAX_DEVICES: usize = 16;
144
145fn load_mode_from_flags(use_mmap: bool, use_mlock: bool) -> llama_cpp_sys_2::llama_load_mode {
148 match (use_mmap, use_mlock) {
149 (false, false) => llama_cpp_sys_2::LLAMA_LOAD_MODE_NONE,
150 (true, false) => llama_cpp_sys_2::LLAMA_LOAD_MODE_MMAP,
151 (false, true) => llama_cpp_sys_2::LLAMA_LOAD_MODE_MLOCK,
152 (true, true) => llama_cpp_sys_2::LLAMA_LOAD_MODE_MMAP_MLOCK,
153 }
154}
155
156#[allow(clippy::module_name_repetitions)]
158pub struct LlamaModelParams {
159 pub(crate) params: llama_cpp_sys_2::llama_model_params,
160 kv_overrides: Vec<llama_cpp_sys_2::llama_model_kv_override>,
161 buft_overrides: Vec<llama_cpp_sys_2::llama_model_tensor_buft_override>,
162 devices: Pin<Box<[llama_cpp_sys_2::ggml_backend_dev_t; LLAMA_CPP_MAX_DEVICES]>>,
163 tensor_split: Vec<f32>,
164 progress_callback: Option<Box<dyn FnMut(f32) -> bool>>,
165}
166
167impl Debug for LlamaModelParams {
168 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
169 f.debug_struct("LlamaModelParams")
170 .field("n_gpu_layers", &self.params.n_gpu_layers)
171 .field("main_gpu", &self.params.main_gpu)
172 .field("vocab_only", &self.params.vocab_only)
173 .field("use_mmap", &self.use_mmap())
174 .field("use_mlock", &self.use_mlock())
175 .field("split_mode", &self.split_mode())
176 .field("devices", &self.devices)
177 .field("kv_overrides", &"vec of kv_overrides")
178 .finish()
179 }
180}
181
182impl LlamaModelParams {
183 #[must_use]
195 pub fn kv_overrides<'a>(&'a self) -> KvOverrides<'a> {
196 KvOverrides::new(self)
197 }
198
199 #[allow(clippy::missing_panics_doc)] pub fn append_kv_override(
222 mut self: Pin<&mut Self>,
223 key: &CStr,
224 value: kv_overrides::ParamOverrideValue,
225 ) {
226 let kv_override = self
227 .kv_overrides
228 .last_mut()
229 .expect("kv_overrides did not have a next allocated");
230
231 assert_eq!(kv_override.key[0], 0, "last kv_override was not empty");
232
233 for (i, &c) in key.to_bytes_with_nul().iter().enumerate() {
235 kv_override.key[i] = c_char::try_from(c).expect("invalid character in key");
236 }
237
238 kv_override.tag = value.tag();
239 kv_override.__bindgen_anon_1 = value.value();
240
241 self.params.kv_overrides = null();
243
244 self.kv_overrides
246 .push(llama_cpp_sys_2::llama_model_kv_override {
247 key: [0; 128],
248 tag: 0,
249 __bindgen_anon_1: llama_cpp_sys_2::llama_model_kv_override__bindgen_ty_1 {
250 val_i64: 0,
251 },
252 });
253
254 self.params.kv_overrides = self.kv_overrides.as_ptr();
256
257 eprintln!("saved ptr: {:?}", self.params.kv_overrides);
258 }
259}
260
261impl LlamaModelParams {
262 pub fn add_cpu_moe_override(self: Pin<&mut Self>) {
264 self.add_cpu_buft_override(c"\\.ffn_(up|down|gate)_(ch|)exps");
265 }
266
267 pub fn add_cpu_buft_override(mut self: Pin<&mut Self>, key: &CStr) {
270 let buft_override = self
271 .buft_overrides
272 .last_mut()
273 .expect("buft_overrides did not have a next allocated");
274
275 assert!(
276 buft_override.pattern.is_null(),
277 "last buft_override was not empty"
278 );
279
280 for &c in key.to_bytes_with_nul().iter() {
282 c_char::try_from(c).expect("invalid character in key");
283 }
284
285 buft_override.pattern = key.as_ptr();
286 buft_override.buft = unsafe { llama_cpp_sys_2::ggml_backend_cpu_buffer_type() };
287
288 self.params.tensor_buft_overrides = null();
290
291 self.buft_overrides
293 .push(llama_cpp_sys_2::llama_model_tensor_buft_override {
294 pattern: std::ptr::null(),
295 buft: std::ptr::null_mut(),
296 });
297
298 self.params.tensor_buft_overrides = self.buft_overrides.as_ptr();
300 }
301
302 #[must_use]
313 pub fn tensor_buft_override_patterns(&self) -> Vec<String> {
314 self.buft_overrides
315 .iter()
316 .filter(|o| !o.pattern.is_null())
317 .map(|o| {
318 unsafe { CStr::from_ptr(o.pattern) }
326 .to_string_lossy()
327 .into_owned()
328 })
329 .collect()
330 }
331}
332
333#[cfg(feature = "common")]
334impl LlamaModelParams {
335 pub fn fit_params(
374 mut self: Pin<&mut Self>,
375 model_path: &CStr,
376 cparams: &mut LlamaContextParams,
377 margins: &mut [usize],
378 n_ctx_min: u32,
379 log_level: llama_cpp_sys_2::ggml_log_level,
380 ) -> Result<FitResult, FitError> {
381 let max_devices = unsafe { llama_cpp_sys_2::llama_max_devices() };
382 let max_buft = unsafe { llama_cpp_sys_2::llama_max_tensor_buft_overrides() };
383
384 self.tensor_split.clear();
386 self.tensor_split.resize(max_devices, 0.0);
387
388 self.buft_overrides.clear();
390 self.buft_overrides.resize(
391 max_buft + 1,
392 llama_cpp_sys_2::llama_model_tensor_buft_override {
393 pattern: std::ptr::null(),
394 buft: std::ptr::null_mut(),
395 },
396 );
397
398 self.params.tensor_split = null::<f32>();
400 self.params.tensor_buft_overrides = null();
401
402 let status = unsafe {
403 llama_cpp_sys_2::llama_rs_fit_params(
404 model_path.as_ptr(),
405 &raw mut self.params,
406 &raw mut cparams.context_params,
407 self.tensor_split.as_mut_ptr(),
408 self.buft_overrides.as_mut_ptr(),
409 margins.as_mut_ptr(),
410 n_ctx_min,
411 std::ptr::null_mut(),
412 log_level,
413 )
414 };
415
416 match status {
418 0 => {}
419 1 => return Err(FitError::Failure),
420 _ => return Err(FitError::Error),
421 }
422
423 self.params.tensor_split = self.tensor_split.as_ptr();
425 self.params.tensor_buft_overrides = self.buft_overrides.as_ptr();
426
427 Ok(FitResult {
428 n_ctx: cparams.context_params.n_ctx,
429 })
430 }
431}
432
433impl LlamaModelParams {
434 #[must_use]
436 pub fn n_gpu_layers(&self) -> i32 {
437 self.params.n_gpu_layers
438 }
439
440 #[must_use]
442 pub fn main_gpu(&self) -> i32 {
443 self.params.main_gpu
444 }
445
446 #[must_use]
448 pub fn vocab_only(&self) -> bool {
449 self.params.vocab_only
450 }
451
452 #[must_use]
459 pub fn use_mmap(&self) -> bool {
460 matches!(
464 self.params.load_mode,
465 llama_cpp_sys_2::LLAMA_LOAD_MODE_AUTO
466 | llama_cpp_sys_2::LLAMA_LOAD_MODE_MMAP
467 | llama_cpp_sys_2::LLAMA_LOAD_MODE_MMAP_MLOCK
468 )
469 }
470
471 #[must_use]
473 pub fn use_mlock(&self) -> bool {
474 matches!(
475 self.params.load_mode,
476 llama_cpp_sys_2::LLAMA_LOAD_MODE_MLOCK | llama_cpp_sys_2::LLAMA_LOAD_MODE_MMAP_MLOCK
477 )
478 }
479
480 pub fn split_mode(&self) -> Result<LlamaSplitMode, LlamaSplitModeParseError> {
485 LlamaSplitMode::try_from(self.params.split_mode)
486 }
487
488 #[must_use]
490 pub fn devices(&self) -> Vec<usize> {
491 let mut backend_devices = Vec::new();
492 for i in 0..unsafe { llama_cpp_sys_2::ggml_backend_dev_count() } {
493 let dev = unsafe { llama_cpp_sys_2::ggml_backend_dev_get(i) };
494 backend_devices.push(dev);
495 }
496 let mut devices = Vec::new();
497 for &dev in self.devices.iter() {
498 if dev.is_null() {
499 break;
500 }
501 if let Some((index, _)) = backend_devices
502 .iter()
503 .enumerate()
504 .find(|&(_i, &d)| d == dev)
505 {
506 devices.push(index);
507 }
508 }
509 devices
510 }
511
512 #[must_use]
520 pub fn with_n_gpu_layers(mut self, n_gpu_layers: u32) -> Self {
521 let n_gpu_layers = i32::try_from(n_gpu_layers).unwrap_or(i32::MAX);
524 self.params.n_gpu_layers = n_gpu_layers;
525 self
526 }
527
528 #[must_use]
532 pub fn with_main_gpu(mut self, main_gpu: i32) -> Self {
533 self.params.main_gpu = main_gpu;
534 self
535 }
536
537 #[must_use]
539 pub fn with_vocab_only(mut self, vocab_only: bool) -> Self {
540 self.params.vocab_only = vocab_only;
541 self
542 }
543
544 #[must_use]
546 pub fn with_use_mmap(mut self, use_mmap: bool) -> Self {
547 self.params.load_mode = load_mode_from_flags(use_mmap, self.use_mlock());
548 self
549 }
550
551 #[must_use]
553 pub fn with_use_mlock(mut self, use_mlock: bool) -> Self {
554 self.params.load_mode = load_mode_from_flags(self.use_mmap(), use_mlock);
555 self
556 }
557
558 #[must_use]
560 pub fn with_split_mode(mut self, split_mode: LlamaSplitMode) -> Self {
561 self.params.split_mode = split_mode.into();
562 self
563 }
564
565 pub fn with_tensor_split(mut self, split: &[f32]) -> Result<Self, LlamaCppError> {
579 if split.is_empty() {
580 self.tensor_split.clear();
581 self.params.tensor_split = null::<f32>();
582 return Ok(self);
583 }
584 let max_devices = crate::max_devices();
585 if split.len() > max_devices {
586 return Err(LlamaCppError::MaxDevicesExceeded(max_devices));
587 }
588 self.tensor_split.clear();
589 self.tensor_split.extend_from_slice(split);
590 self.tensor_split.resize(max_devices, 0.0);
591 self.params.tensor_split = self.tensor_split.as_ptr();
592 Ok(self)
593 }
594
595 #[must_use]
599 pub fn tensor_split(&self) -> &[f32] {
600 &self.tensor_split
601 }
602
603 pub fn with_devices(mut self, devices: &[usize]) -> Result<Self, LlamaCppError> {
614 for dev in self.devices.iter_mut() {
615 *dev = std::ptr::null_mut();
616 }
617 let max_devices = crate::max_devices().min(LLAMA_CPP_MAX_DEVICES);
619 if devices.len() > max_devices {
620 return Err(LlamaCppError::MaxDevicesExceeded(max_devices));
621 }
622 for (i, &dev) in devices.iter().enumerate() {
623 if dev >= unsafe { llama_cpp_sys_2::ggml_backend_dev_count() } {
624 return Err(LlamaCppError::BackendDeviceNotFound(dev));
625 }
626 let backend_dev = unsafe { llama_cpp_sys_2::ggml_backend_dev_get(dev) };
627 self.devices[i] = backend_dev;
628 }
629 if self.devices.is_empty() {
630 self.params.devices = std::ptr::null_mut();
631 } else {
632 self.params.devices = self.devices.as_mut_ptr();
633 }
634 Ok(self)
635 }
636
637 #[must_use]
643 pub fn with_no_alloc(mut self, no_alloc: bool) -> Self {
644 self.params.no_alloc = no_alloc;
645 if no_alloc {
646 self = self.with_use_mmap(false);
647 }
648 self
649 }
650
651 #[must_use]
655 pub fn no_alloc(&self) -> bool {
656 self.params.no_alloc
657 }
658
659 #[must_use]
661 pub fn with_load_mtp(mut self, load_mtp: bool) -> Self {
662 self.params.load_mtp = load_mtp;
663 self
664 }
665
666 #[must_use]
668 pub fn load_mtp(&self) -> bool {
669 self.params.load_mtp
670 }
671
672 #[must_use]
675 pub fn with_progress_callback<F: FnMut(f32) -> bool + 'static>(mut self, callback: F) -> Self {
676 unsafe extern "C" fn trampoline<F: FnMut(f32) -> bool>(
677 progress: f32,
678 user_data: *mut c_void,
679 ) -> bool {
680 let callback = unsafe { &mut *user_data.cast::<F>() };
681 callback(progress)
682 }
683
684 let mut callback = Box::new(callback);
685 self.params.progress_callback_user_data =
686 std::ptr::from_mut(&mut *callback).cast::<c_void>();
687 self.params.progress_callback = Some(trampoline::<F>);
688 self.progress_callback = Some(callback);
689 self
690 }
691}
692
693impl Default for LlamaModelParams {
709 fn default() -> Self {
710 let default_params = unsafe { llama_cpp_sys_2::llama_model_default_params() };
711 LlamaModelParams {
712 params: default_params,
713 kv_overrides: vec![llama_cpp_sys_2::llama_model_kv_override {
715 key: [0; 128],
716 tag: 0,
717 __bindgen_anon_1: llama_cpp_sys_2::llama_model_kv_override__bindgen_ty_1 {
718 val_i64: 0,
719 },
720 }],
721 buft_overrides: vec![llama_cpp_sys_2::llama_model_tensor_buft_override {
722 pattern: std::ptr::null(),
723 buft: std::ptr::null_mut(),
724 }],
725 devices: Box::pin([std::ptr::null_mut(); 16]),
726 tensor_split: Vec::new(),
727 progress_callback: None,
728 }
729 }
730}
731
732#[cfg(test)]
733mod tests {
734 use super::kv_overrides::ParamOverrideValue;
735 use super::{LlamaModelParams, LlamaSplitMode};
736 use std::pin::pin;
737
738 #[test]
739 fn tensor_buft_override_patterns_empty_by_default() {
740 assert!(LlamaModelParams::default()
742 .tensor_buft_override_patterns()
743 .is_empty());
744 }
745
746 #[test]
747 fn tensor_buft_override_patterns_reads_back_added_override() {
748 let mut params = pin!(LlamaModelParams::default());
753 params.as_mut().add_cpu_moe_override();
754 assert_eq!(
755 params.tensor_buft_override_patterns(),
756 vec!["\\.ffn_(up|down|gate)_(ch|)exps".to_owned()],
757 );
758 }
759
760 #[test]
761 fn tensor_buft_override_appends_on_second_call() {
762 let mut params = pin!(LlamaModelParams::default());
766 params
767 .as_mut()
768 .add_cpu_buft_override(c"blk\\.0\\.ffn_(up|down|gate)_(ch|)exps");
769 params
770 .as_mut()
771 .add_cpu_buft_override(c"blk\\.1\\.ffn_(up|down|gate)_(ch|)exps");
772
773 assert_eq!(
774 params.tensor_buft_override_patterns(),
775 vec![
776 "blk\\.0\\.ffn_(up|down|gate)_(ch|)exps".to_owned(),
777 "blk\\.1\\.ffn_(up|down|gate)_(ch|)exps".to_owned(),
778 ],
779 );
780 }
781
782 #[test]
783 fn kv_override_appends_on_second_call() {
784 let mut params = pin!(LlamaModelParams::default());
787 params
788 .as_mut()
789 .append_kv_override(c"first", ParamOverrideValue::Int(1));
790 params
791 .as_mut()
792 .append_kv_override(c"second", ParamOverrideValue::Int(2));
793
794 let kv_overrides = params.kv_overrides().into_iter().collect::<Vec<_>>();
795 assert_eq!(kv_overrides.len(), 2);
796 assert_eq!(kv_overrides[0].0.to_bytes(), b"first");
797 assert_eq!(kv_overrides[1].0.to_bytes(), b"second");
798 assert_eq!(kv_overrides[1].1, ParamOverrideValue::Int(2));
799 }
800
801 #[test]
802 fn tensor_split_round_trips_and_pads() {
803 let max = crate::max_devices();
804 let params = LlamaModelParams::default()
805 .with_tensor_split(&[0.25, 0.75])
806 .expect("two devices is under any max");
807 assert_eq!(params.tensor_split().len(), max);
808 assert_eq!(¶ms.tensor_split()[..2], &[0.25, 0.75]);
809 assert!(params.tensor_split()[2..].iter().all(|share| *share == 0.0));
810
811 let params = params.with_n_gpu_layers(99);
813 assert_eq!(¶ms.tensor_split()[..2], &[0.25, 0.75]);
814
815 let cleared = params.with_tensor_split(&[]).expect("empty clears");
816 assert!(cleared.tensor_split().is_empty());
817 }
818
819 #[test]
820 fn tensor_split_rejects_more_devices_than_llama_supports() {
821 let too_many = vec![1.0_f32; crate::max_devices() + 1];
822 assert!(LlamaModelParams::default()
823 .with_tensor_split(&too_many)
824 .is_err());
825 }
826
827 #[test]
828 fn tensor_split_mode_round_trips() {
829 assert_eq!(
830 LlamaSplitMode::try_from(llama_cpp_sys_2::LLAMA_SPLIT_MODE_TENSOR),
831 Ok(LlamaSplitMode::Tensor)
832 );
833 assert_eq!(
834 u32::from(LlamaSplitMode::Tensor),
835 llama_cpp_sys_2::LLAMA_SPLIT_MODE_TENSOR as u32
836 );
837 assert_eq!(
838 i32::from(LlamaSplitMode::Tensor),
839 llama_cpp_sys_2::LLAMA_SPLIT_MODE_TENSOR as i32
840 );
841 }
842
843 #[test]
844 fn load_mtp_round_trips() {
845 let params = LlamaModelParams::default();
846 assert!(!params.load_mtp());
847
848 let params = params.with_load_mtp(true);
849 assert!(params.load_mtp());
850
851 let params = params.with_load_mtp(false);
852 assert!(!params.load_mtp());
853 }
854
855 #[test]
856 fn progress_callback_round_trips_and_can_abort() {
857 use super::LlamaModelParams;
858 use std::cell::Cell;
859 use std::rc::Rc;
860
861 let calls = Rc::new(Cell::new(0_u32));
862 let counter = Rc::clone(&calls);
863 let params = LlamaModelParams::default().with_progress_callback(move |_progress| {
864 counter.set(counter.get() + 1);
865 false
866 });
867
868 assert!(params.params.progress_callback.is_some());
869 assert!(!params.params.progress_callback_user_data.is_null());
870
871 let trampoline = params.params.progress_callback.unwrap();
872 let user_data = params.params.progress_callback_user_data;
873 let first = unsafe { trampoline(0.5, user_data) };
874 let second = unsafe { trampoline(1.0, user_data) };
875
876 assert!(!first && !second, "returning false signals an abort");
877 assert_eq!(calls.get(), 2);
878 }
879}