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
145#[allow(clippy::module_name_repetitions)]
147pub struct LlamaModelParams {
148 pub(crate) params: llama_cpp_sys_2::llama_model_params,
149 kv_overrides: Vec<llama_cpp_sys_2::llama_model_kv_override>,
150 buft_overrides: Vec<llama_cpp_sys_2::llama_model_tensor_buft_override>,
151 devices: Pin<Box<[llama_cpp_sys_2::ggml_backend_dev_t; LLAMA_CPP_MAX_DEVICES]>>,
152 tensor_split: Vec<f32>,
153 progress_callback: Option<Box<dyn FnMut(f32) -> bool>>,
154}
155
156impl Debug for LlamaModelParams {
157 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
158 f.debug_struct("LlamaModelParams")
159 .field("n_gpu_layers", &self.params.n_gpu_layers)
160 .field("main_gpu", &self.params.main_gpu)
161 .field("vocab_only", &self.params.vocab_only)
162 .field("use_mmap", &self.params.use_mmap)
163 .field("use_mlock", &self.params.use_mlock)
164 .field("split_mode", &self.split_mode())
165 .field("devices", &self.devices)
166 .field("kv_overrides", &"vec of kv_overrides")
167 .finish()
168 }
169}
170
171impl LlamaModelParams {
172 #[must_use]
184 pub fn kv_overrides<'a>(&'a self) -> KvOverrides<'a> {
185 KvOverrides::new(self)
186 }
187
188 #[allow(clippy::missing_panics_doc)] pub fn append_kv_override(
211 mut self: Pin<&mut Self>,
212 key: &CStr,
213 value: kv_overrides::ParamOverrideValue,
214 ) {
215 let kv_override = self
216 .kv_overrides
217 .get_mut(0)
218 .expect("kv_overrides did not have a next allocated");
219
220 assert_eq!(kv_override.key[0], 0, "last kv_override was not empty");
221
222 for (i, &c) in key.to_bytes_with_nul().iter().enumerate() {
224 kv_override.key[i] = c_char::try_from(c).expect("invalid character in key");
225 }
226
227 kv_override.tag = value.tag();
228 kv_override.__bindgen_anon_1 = value.value();
229
230 self.params.kv_overrides = null();
232
233 self.kv_overrides
235 .push(llama_cpp_sys_2::llama_model_kv_override {
236 key: [0; 128],
237 tag: 0,
238 __bindgen_anon_1: llama_cpp_sys_2::llama_model_kv_override__bindgen_ty_1 {
239 val_i64: 0,
240 },
241 });
242
243 self.params.kv_overrides = self.kv_overrides.as_ptr();
245
246 eprintln!("saved ptr: {:?}", self.params.kv_overrides);
247 }
248}
249
250impl LlamaModelParams {
251 pub fn add_cpu_moe_override(self: Pin<&mut Self>) {
253 self.add_cpu_buft_override(c"\\.ffn_(up|down|gate)_(ch|)exps");
254 }
255
256 pub fn add_cpu_buft_override(mut self: Pin<&mut Self>, key: &CStr) {
259 let buft_override = self
260 .buft_overrides
261 .get_mut(0)
262 .expect("buft_overrides did not have a next allocated");
263
264 assert!(
265 buft_override.pattern.is_null(),
266 "last buft_override was not empty"
267 );
268
269 for &c in key.to_bytes_with_nul().iter() {
271 c_char::try_from(c).expect("invalid character in key");
272 }
273
274 buft_override.pattern = key.as_ptr();
275 buft_override.buft = unsafe { llama_cpp_sys_2::ggml_backend_cpu_buffer_type() };
276
277 self.params.tensor_buft_overrides = null();
279
280 self.buft_overrides
282 .push(llama_cpp_sys_2::llama_model_tensor_buft_override {
283 pattern: std::ptr::null(),
284 buft: std::ptr::null_mut(),
285 });
286
287 self.params.tensor_buft_overrides = self.buft_overrides.as_ptr();
289 }
290
291 #[must_use]
302 pub fn tensor_buft_override_patterns(&self) -> Vec<String> {
303 self.buft_overrides
304 .iter()
305 .filter(|o| !o.pattern.is_null())
306 .map(|o| {
307 unsafe { CStr::from_ptr(o.pattern) }
315 .to_string_lossy()
316 .into_owned()
317 })
318 .collect()
319 }
320}
321
322#[cfg(feature = "common")]
323impl LlamaModelParams {
324 pub fn fit_params(
361 mut self: Pin<&mut Self>,
362 model_path: &CStr,
363 cparams: &mut LlamaContextParams,
364 margins: &mut [usize],
365 n_ctx_min: u32,
366 log_level: llama_cpp_sys_2::ggml_log_level,
367 ) -> Result<FitResult, FitError> {
368 let max_devices = unsafe { llama_cpp_sys_2::llama_max_devices() };
369 let max_buft = unsafe { llama_cpp_sys_2::llama_max_tensor_buft_overrides() };
370
371 self.tensor_split.clear();
373 self.tensor_split.resize(max_devices, 0.0);
374
375 self.buft_overrides.clear();
377 self.buft_overrides.resize(
378 max_buft + 1,
379 llama_cpp_sys_2::llama_model_tensor_buft_override {
380 pattern: std::ptr::null(),
381 buft: std::ptr::null_mut(),
382 },
383 );
384
385 self.params.tensor_split = null::<f32>();
387 self.params.tensor_buft_overrides = null();
388
389 let status = unsafe {
390 llama_cpp_sys_2::llama_rs_fit_params(
391 model_path.as_ptr(),
392 &raw mut self.params,
393 &raw mut cparams.context_params,
394 self.tensor_split.as_mut_ptr(),
395 self.buft_overrides.as_mut_ptr(),
396 margins.as_mut_ptr(),
397 n_ctx_min,
398 log_level,
399 )
400 };
401
402 match status {
404 0 => {}
405 1 => return Err(FitError::Failure),
406 _ => return Err(FitError::Error),
407 }
408
409 self.params.tensor_split = self.tensor_split.as_ptr();
411 self.params.tensor_buft_overrides = self.buft_overrides.as_ptr();
412
413 Ok(FitResult {
414 n_ctx: cparams.context_params.n_ctx,
415 })
416 }
417}
418
419impl LlamaModelParams {
420 #[must_use]
422 pub fn n_gpu_layers(&self) -> i32 {
423 self.params.n_gpu_layers
424 }
425
426 #[must_use]
428 pub fn main_gpu(&self) -> i32 {
429 self.params.main_gpu
430 }
431
432 #[must_use]
434 pub fn vocab_only(&self) -> bool {
435 self.params.vocab_only
436 }
437
438 #[must_use]
440 pub fn use_mmap(&self) -> bool {
441 self.params.use_mmap
442 }
443
444 #[must_use]
446 pub fn use_mlock(&self) -> bool {
447 self.params.use_mlock
448 }
449
450 pub fn split_mode(&self) -> Result<LlamaSplitMode, LlamaSplitModeParseError> {
455 LlamaSplitMode::try_from(self.params.split_mode)
456 }
457
458 #[must_use]
460 pub fn devices(&self) -> Vec<usize> {
461 let mut backend_devices = Vec::new();
462 for i in 0..unsafe { llama_cpp_sys_2::ggml_backend_dev_count() } {
463 let dev = unsafe { llama_cpp_sys_2::ggml_backend_dev_get(i) };
464 backend_devices.push(dev);
465 }
466 let mut devices = Vec::new();
467 for &dev in self.devices.iter() {
468 if dev.is_null() {
469 break;
470 }
471 if let Some((index, _)) = backend_devices
472 .iter()
473 .enumerate()
474 .find(|&(_i, &d)| d == dev)
475 {
476 devices.push(index);
477 }
478 }
479 devices
480 }
481
482 #[must_use]
490 pub fn with_n_gpu_layers(mut self, n_gpu_layers: u32) -> Self {
491 let n_gpu_layers = i32::try_from(n_gpu_layers).unwrap_or(i32::MAX);
494 self.params.n_gpu_layers = n_gpu_layers;
495 self
496 }
497
498 #[must_use]
502 pub fn with_main_gpu(mut self, main_gpu: i32) -> Self {
503 self.params.main_gpu = main_gpu;
504 self
505 }
506
507 #[must_use]
509 pub fn with_vocab_only(mut self, vocab_only: bool) -> Self {
510 self.params.vocab_only = vocab_only;
511 self
512 }
513
514 #[must_use]
516 pub fn with_use_mmap(mut self, use_mmap: bool) -> Self {
517 self.params.use_mmap = use_mmap;
518 self
519 }
520
521 #[must_use]
523 pub fn with_use_mlock(mut self, use_mlock: bool) -> Self {
524 self.params.use_mlock = use_mlock;
525 self
526 }
527
528 #[must_use]
530 pub fn with_split_mode(mut self, split_mode: LlamaSplitMode) -> Self {
531 self.params.split_mode = split_mode.into();
532 self
533 }
534
535 pub fn with_devices(mut self, devices: &[usize]) -> Result<Self, LlamaCppError> {
546 for dev in self.devices.iter_mut() {
547 *dev = std::ptr::null_mut();
548 }
549 let max_devices = crate::max_devices().min(LLAMA_CPP_MAX_DEVICES);
551 if devices.len() > max_devices {
552 return Err(LlamaCppError::MaxDevicesExceeded(max_devices));
553 }
554 for (i, &dev) in devices.iter().enumerate() {
555 if dev >= unsafe { llama_cpp_sys_2::ggml_backend_dev_count() } {
556 return Err(LlamaCppError::BackendDeviceNotFound(dev));
557 }
558 let backend_dev = unsafe { llama_cpp_sys_2::ggml_backend_dev_get(dev) };
559 self.devices[i] = backend_dev;
560 }
561 if self.devices.is_empty() {
562 self.params.devices = std::ptr::null_mut();
563 } else {
564 self.params.devices = self.devices.as_mut_ptr();
565 }
566 Ok(self)
567 }
568
569 #[must_use]
575 pub fn with_no_alloc(mut self, no_alloc: bool) -> Self {
576 self.params.no_alloc = no_alloc;
577 if no_alloc {
578 self = self.with_use_mmap(false);
579 }
580 self
581 }
582
583 #[must_use]
587 pub fn no_alloc(&self) -> bool {
588 self.params.no_alloc
589 }
590
591 #[must_use]
594 pub fn with_progress_callback<F: FnMut(f32) -> bool + 'static>(mut self, callback: F) -> Self {
595 unsafe extern "C" fn trampoline<F: FnMut(f32) -> bool>(
596 progress: f32,
597 user_data: *mut c_void,
598 ) -> bool {
599 let callback = unsafe { &mut *user_data.cast::<F>() };
600 callback(progress)
601 }
602
603 let mut callback = Box::new(callback);
604 self.params.progress_callback_user_data =
605 std::ptr::from_mut(&mut *callback).cast::<c_void>();
606 self.params.progress_callback = Some(trampoline::<F>);
607 self.progress_callback = Some(callback);
608 self
609 }
610}
611
612impl Default for LlamaModelParams {
627 fn default() -> Self {
628 let default_params = unsafe { llama_cpp_sys_2::llama_model_default_params() };
629 LlamaModelParams {
630 params: default_params,
631 kv_overrides: vec![llama_cpp_sys_2::llama_model_kv_override {
633 key: [0; 128],
634 tag: 0,
635 __bindgen_anon_1: llama_cpp_sys_2::llama_model_kv_override__bindgen_ty_1 {
636 val_i64: 0,
637 },
638 }],
639 buft_overrides: vec![llama_cpp_sys_2::llama_model_tensor_buft_override {
640 pattern: std::ptr::null(),
641 buft: std::ptr::null_mut(),
642 }],
643 devices: Box::pin([std::ptr::null_mut(); 16]),
644 tensor_split: Vec::new(),
645 progress_callback: None,
646 }
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 use super::{LlamaModelParams, LlamaSplitMode};
653 use std::pin::pin;
654
655 #[test]
656 fn tensor_buft_override_patterns_empty_by_default() {
657 assert!(LlamaModelParams::default()
659 .tensor_buft_override_patterns()
660 .is_empty());
661 }
662
663 #[test]
664 fn tensor_buft_override_patterns_reads_back_added_override() {
665 let mut params = pin!(LlamaModelParams::default());
670 params.as_mut().add_cpu_moe_override();
671 assert_eq!(
672 params.tensor_buft_override_patterns(),
673 vec!["\\.ffn_(up|down|gate)_(ch|)exps".to_owned()],
674 );
675 }
676
677 #[test]
678 fn tensor_split_mode_round_trips() {
679 assert_eq!(
680 LlamaSplitMode::try_from(llama_cpp_sys_2::LLAMA_SPLIT_MODE_TENSOR),
681 Ok(LlamaSplitMode::Tensor)
682 );
683 assert_eq!(
684 u32::from(LlamaSplitMode::Tensor),
685 llama_cpp_sys_2::LLAMA_SPLIT_MODE_TENSOR as u32
686 );
687 assert_eq!(
688 i32::from(LlamaSplitMode::Tensor),
689 llama_cpp_sys_2::LLAMA_SPLIT_MODE_TENSOR as i32
690 );
691 }
692
693 #[test]
694 fn progress_callback_round_trips_and_can_abort() {
695 use super::LlamaModelParams;
696 use std::cell::Cell;
697 use std::rc::Rc;
698
699 let calls = Rc::new(Cell::new(0_u32));
700 let counter = Rc::clone(&calls);
701 let params = LlamaModelParams::default().with_progress_callback(move |_progress| {
702 counter.set(counter.get() + 1);
703 false
704 });
705
706 assert!(params.params.progress_callback.is_some());
707 assert!(!params.params.progress_callback_user_data.is_null());
708
709 let trampoline = params.params.progress_callback.unwrap();
710 let user_data = params.params.progress_callback_user_data;
711 let first = unsafe { trampoline(0.5, user_data) };
712 let second = unsafe { trampoline(1.0, user_data) };
713
714 assert!(!first && !second, "returning false signals an abort");
715 assert_eq!(calls.get(), 2);
716 }
717}