llama-cpp-4 0.6.0

llama.cpp bindings for Rust
Documentation
//! A safe wrapper around `llama_model_params`.

use crate::model::params::kv_overrides::KvOverrides;
use std::ffi::{c_char, CStr};
use std::fmt::{Debug, Formatter};
use std::pin::Pin;
use std::ptr::null;

pub mod kv_overrides;

/// Exact model-file loading strategy exposed by llama.cpp.
///
/// `llama_load_mode` is a signed enum on every target because of the negative
/// `LLAMA_LOAD_MODE_AUTO` discriminant, so each variant uses `as _` to coerce to
/// the `#[repr(i32)]` type (matching [`token_type`](crate::token_type)).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum LlamaLoadMode {
    /// Pick the strategy from the backend devices' capabilities: memory-map when
    /// every device supports it, otherwise fall back to a plain read. This is
    /// llama.cpp's default.
    Auto = llama_cpp_sys_4::LLAMA_LOAD_MODE_AUTO as _,
    /// No memory mapping, locking, or direct I/O.
    None = llama_cpp_sys_4::LLAMA_LOAD_MODE_NONE as _,
    /// Memory-map model files when supported.
    Mmap = llama_cpp_sys_4::LLAMA_LOAD_MODE_MMAP as _,
    /// Read model files normally and lock loaded pages in memory.
    Mlock = llama_cpp_sys_4::LLAMA_LOAD_MODE_MLOCK as _,
    /// Memory-map model files and lock mapped pages in memory.
    MmapMlock = llama_cpp_sys_4::LLAMA_LOAD_MODE_MMAP_MLOCK as _,
    /// Use direct I/O when supported.
    DirectIo = llama_cpp_sys_4::LLAMA_LOAD_MODE_DIRECT_IO as _,
}

/// A safe wrapper around `llama_model_params`.
#[allow(clippy::module_name_repetitions)]
pub struct LlamaModelParams {
    pub(crate) params: llama_cpp_sys_4::llama_model_params,
    kv_overrides: Vec<llama_cpp_sys_4::llama_model_kv_override>,
}

impl Debug for LlamaModelParams {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("LlamaModelParams")
            .field("n_gpu_layers", &self.params.n_gpu_layers)
            .field("main_gpu", &self.params.main_gpu)
            .field("vocab_only", &self.params.vocab_only)
            .field("load_mode", &self.load_mode())
            .field("load_mtp", &self.load_mtp())
            .field("kv_overrides", &"vec of kv_overrides")
            .finish()
    }
}

impl LlamaModelParams {
    /// See [`KvOverrides`]
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use llama_cpp_4::model::params::LlamaModelParams;
    /// let params = Box::pin(LlamaModelParams::default());
    /// let kv_overrides = params.kv_overrides();
    /// let count = kv_overrides.into_iter().count();
    /// assert_eq!(count, 0);
    /// ```
    #[must_use]
    pub fn kv_overrides(&self) -> KvOverrides<'_> {
        KvOverrides::new(self)
    }

    /// Appends a key-value override to the model parameters. It must be pinned as this creates a self-referential struct.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use std::ffi::{CStr, CString};
    /// use std::pin::pin;
    /// # use llama_cpp_4::model::params::LlamaModelParams;
    /// # use llama_cpp_4::model::params::kv_overrides::ParamOverrideValue;
    /// let mut params = pin!(LlamaModelParams::default());
    /// let key = CString::new("key").expect("CString::new failed");
    /// params.as_mut().append_kv_override(&key, ParamOverrideValue::Int(50));
    ///
    /// let kv_overrides = params.kv_overrides().into_iter().collect::<Vec<_>>();
    /// assert_eq!(kv_overrides.len(), 1);
    ///
    /// let (k, v) = &kv_overrides[0];
    /// assert_eq!(v, &ParamOverrideValue::Int(50));
    ///
    /// assert_eq!(k.to_bytes(), b"key", "expected key to be 'key', was {:?}", k);
    /// ```
    #[allow(clippy::missing_panics_doc)] // panics are just to enforce internal invariants, not user errors
    pub fn append_kv_override(
        mut self: Pin<&mut Self>,
        key: &CStr,
        value: kv_overrides::ParamOverrideValue,
    ) {
        let kv_override = self
            .kv_overrides
            .get_mut(0)
            .expect("kv_overrides did not have a next allocated");

        assert_eq!(kv_override.key[0], 0, "last kv_override was not empty");

        // There should be some way to do this without iterating over everything.
        for (i, &c) in key.to_bytes_with_nul().iter().enumerate() {
            kv_override.key[i] = c_char::try_from(c).expect("invalid character in key");
        }

        kv_override.tag = value.tag();
        kv_override.__bindgen_anon_1 = value.value();

        // set to null pointer for panic safety (as push may move the vector, invalidating the pointer)
        self.params.kv_overrides = null();

        // push the next one to ensure we maintain the iterator invariant of ending with a 0
        self.kv_overrides
            .push(llama_cpp_sys_4::llama_model_kv_override {
                key: [0; 128],
                tag: 0,
                __bindgen_anon_1: llama_cpp_sys_4::llama_model_kv_override__bindgen_ty_1 {
                    val_i64: 0,
                },
            });

        // set the pointer to the (potentially) new vector
        self.params.kv_overrides = self.kv_overrides.as_ptr();

        eprintln!("saved ptr: {:?}", self.params.kv_overrides);
    }
}

impl LlamaModelParams {
    /// Get the number of layers to offload to the GPU.
    #[must_use]
    pub fn n_gpu_layers(&self) -> i32 {
        self.params.n_gpu_layers
    }

    /// The GPU that is used for scratch and small tensors
    #[must_use]
    pub fn main_gpu(&self) -> i32 {
        self.params.main_gpu
    }

    /// only load the vocabulary, no weights
    #[must_use]
    pub fn vocab_only(&self) -> bool {
        self.params.vocab_only
    }

    /// Returns the exact model-file loading strategy.
    #[must_use]
    pub fn load_mode(&self) -> LlamaLoadMode {
        match self.params.load_mode {
            llama_cpp_sys_4::LLAMA_LOAD_MODE_AUTO => LlamaLoadMode::Auto,
            llama_cpp_sys_4::LLAMA_LOAD_MODE_MMAP => LlamaLoadMode::Mmap,
            llama_cpp_sys_4::LLAMA_LOAD_MODE_MLOCK => LlamaLoadMode::Mlock,
            llama_cpp_sys_4::LLAMA_LOAD_MODE_MMAP_MLOCK => LlamaLoadMode::MmapMlock,
            llama_cpp_sys_4::LLAMA_LOAD_MODE_DIRECT_IO => LlamaLoadMode::DirectIo,
            _ => LlamaLoadMode::None,
        }
    }

    /// Whether the model's MTP (multi-token prediction) layers will be loaded.
    ///
    /// MTP layers drive multi-token-prediction speculative decoding for models
    /// that ship them (e.g. `DeepSeek V4`). Once loaded, the speculative state is
    /// captured and restored through [`crate::speculative`]. Defaults to `false`
    /// because most models carry no MTP weights.
    #[must_use]
    pub fn load_mtp(&self) -> bool {
        self.params.load_mtp
    }

    /// use mmap if possible
    ///
    /// [`LlamaLoadMode::Auto`] counts as "possible": llama.cpp memory-maps under
    /// `Auto` unless one of the backend devices lacks mmap support, which is only
    /// known once the model is loaded.
    #[must_use]
    pub fn use_mmap(&self) -> bool {
        matches!(
            self.load_mode(),
            LlamaLoadMode::Auto | LlamaLoadMode::Mmap | LlamaLoadMode::MmapMlock
        )
    }

    /// force system to keep model in RAM
    #[must_use]
    pub fn use_mlock(&self) -> bool {
        matches!(
            self.load_mode(),
            LlamaLoadMode::Mlock | LlamaLoadMode::MmapMlock
        )
    }

    /// sets the number of gpu layers to offload to the GPU.
    /// ```
    /// # use llama_cpp_4::model::params::LlamaModelParams;
    /// let params = LlamaModelParams::default();
    /// let params = params.with_n_gpu_layers(1);
    /// assert_eq!(params.n_gpu_layers(), 1);
    /// ```
    #[must_use]
    pub fn with_n_gpu_layers(mut self, n_gpu_layers: u32) -> Self {
        // The only way this conversion can fail is if u32 overflows the i32 - in which case we set
        // to MAX
        let n_gpu_layers = i32::try_from(n_gpu_layers).unwrap_or(i32::MAX);
        self.params.n_gpu_layers = n_gpu_layers;
        self
    }

    /// sets the main GPU
    #[must_use]
    pub fn with_main_gpu(mut self, main_gpu: i32) -> Self {
        self.params.main_gpu = main_gpu;
        self
    }

    /// sets `vocab_only`
    #[must_use]
    pub fn with_vocab_only(mut self, vocab_only: bool) -> Self {
        self.params.vocab_only = vocab_only;
        self
    }

    /// Sets the exact model-file loading strategy.
    #[must_use]
    pub fn with_load_mode(mut self, load_mode: LlamaLoadMode) -> Self {
        self.params.load_mode = load_mode as llama_cpp_sys_4::llama_load_mode;
        self
    }

    /// Sets whether to load the model's MTP (multi-token prediction) layers.
    ///
    /// Enable this for models that ship MTP weights (e.g. `DeepSeek V4`) when you
    /// intend to use MTP-based speculative decoding, then drive the speculative
    /// state via [`crate::speculative`]. For models without MTP layers the flag
    /// has no effect. Corresponds to `llama_model_params.load_mtp`, added
    /// upstream in llama.cpp PR #25784 (`DeepSeek V4` MTP + `DSpark`).
    ///
    /// ```
    /// # use llama_cpp_4::model::params::LlamaModelParams;
    /// let params = LlamaModelParams::default().with_load_mtp(true);
    /// assert!(params.load_mtp());
    /// ```
    #[must_use]
    pub fn with_load_mtp(mut self, load_mtp: bool) -> Self {
        self.params.load_mtp = load_mtp;
        self
    }

    /// sets `use_mlock`
    #[must_use]
    pub fn with_use_mlock(mut self, use_mlock: bool) -> Self {
        let load_mode = match (self.use_mmap(), use_mlock) {
            (true, true) => LlamaLoadMode::MmapMlock,
            (true, false) => LlamaLoadMode::Mmap,
            (false, true) => LlamaLoadMode::Mlock,
            (false, false) => LlamaLoadMode::None,
        };
        self.params.load_mode = load_mode as llama_cpp_sys_4::llama_load_mode;
        self
    }
}

/// Default parameters for `LlamaModel`. (as defined in llama.cpp by `llama_model_default_params`)
/// ```
/// # use llama_cpp_4::model::params::LlamaModelParams;
/// let params = LlamaModelParams::default();
/// assert_eq!(params.n_gpu_layers(), -1, "n_gpu_layers should be -1 (all layers)");
/// assert_eq!(params.main_gpu(), 0, "main_gpu should be 0");
/// assert_eq!(params.vocab_only(), false, "vocab_only should be false");
/// assert_eq!(params.use_mmap(), true, "use_mmap should be true");
/// assert_eq!(params.use_mlock(), false, "use_mlock should be false");
/// ```
impl Default for LlamaModelParams {
    fn default() -> Self {
        let default_params = unsafe { llama_cpp_sys_4::llama_model_default_params() };
        LlamaModelParams {
            params: default_params,
            // push the next one to ensure we maintain the iterator invariant of ending with a 0
            kv_overrides: vec![llama_cpp_sys_4::llama_model_kv_override {
                key: [0; 128],
                tag: 0,
                __bindgen_anon_1: llama_cpp_sys_4::llama_model_kv_override__bindgen_ty_1 {
                    val_i64: 0,
                },
            }],
        }
    }
}