Skip to main content

llama_cpp_4/
llama_backend.rs

1//! Representation of an initialized llama backend
2
3// Every `unsafe` block below is justified in a `SAFETY:` comment; keep it that
4// way. See the note on the lint in the workspace `Cargo.toml`.
5#![deny(clippy::undocumented_unsafe_blocks)]
6
7use crate::LLamaCppError;
8use llama_cpp_sys_4::ggml_log_level;
9use std::sync::atomic::AtomicBool;
10use std::sync::atomic::Ordering::SeqCst;
11
12/// Representation of an initialized llama backend
13/// This is required as a parameter for most llama functions as the backend must be initialized
14/// before any llama functions are called. This type is proof of initialization.
15#[derive(Eq, PartialEq, Debug)]
16pub struct LlamaBackend {}
17
18static LLAMA_BACKEND_INITIALIZED: AtomicBool = AtomicBool::new(false);
19
20impl LlamaBackend {
21    /// Mark the llama backend as initialized
22    fn mark_init() -> crate::Result<()> {
23        match LLAMA_BACKEND_INITIALIZED.compare_exchange(false, true, SeqCst, SeqCst) {
24            Ok(_) => Ok(()),
25            Err(_) => Err(LLamaCppError::BackendAlreadyInitialized),
26        }
27    }
28
29    /// Initialize the llama backend (without numa).
30    ///
31    /// # Examples
32    ///
33    /// ```
34    ///# use llama_cpp_4::llama_backend::LlamaBackend;
35    ///# use llama_cpp_4::LLamaCppError;
36    ///# use std::error::Error;
37    ///
38    ///# fn main() -> Result<(), Box<dyn Error>> {
39    ///
40    ///
41    /// let backend = LlamaBackend::init()?;
42    /// // the llama backend can only be initialized once
43    /// assert_eq!(Err(LLamaCppError::BackendAlreadyInitialized), LlamaBackend::init());
44    ///
45    ///# Ok(())
46    ///# }
47    /// ```
48    /// # Errors
49    ///
50    /// Returns [`LLamaCppError::BackendAlreadyInitialized`] if the backend has already been initialized.
51    #[tracing::instrument(skip_all)]
52    pub fn init() -> crate::Result<LlamaBackend> {
53        Self::mark_init()?;
54        // SAFETY: `llama_backend_init` must be called exactly once per process
55        // before any other llama.cpp entry point. `mark_init` is the gate: it
56        // compare-exchanges a global from false to true and has already
57        // returned `Err` above if anyone got here first, so this cannot run
58        // twice even from several threads.
59        unsafe { llama_cpp_sys_4::llama_backend_init() }
60        Ok(LlamaBackend {})
61    }
62
63    /// Initialize the llama backend (with numa).
64    /// ```
65    ///# use llama_cpp_4::llama_backend::LlamaBackend;
66    ///# use std::error::Error;
67    ///# use llama_cpp_4::llama_backend::NumaStrategy;
68    ///
69    ///# fn main() -> Result<(), Box<dyn Error>> {
70    ///
71    /// let llama_backend = LlamaBackend::init_numa(NumaStrategy::MIRROR)?;
72    ///
73    ///# Ok(())
74    ///# }
75    /// ```
76    /// # Errors
77    ///
78    /// Returns [`LLamaCppError::BackendAlreadyInitialized`] if the backend has already been initialized.
79    #[tracing::instrument(skip_all)]
80    pub fn init_numa(strategy: NumaStrategy) -> crate::Result<LlamaBackend> {
81        Self::mark_init()?;
82        // SAFETY: same once-only gate as `init`. The argument is a
83        // `ggml_numa_strategy` built by the `From` impl below, so it is always
84        // one of the discriminants llama.cpp matches on.
85        unsafe {
86            llama_cpp_sys_4::llama_numa_init(llama_cpp_sys_4::ggml_numa_strategy::from(strategy));
87        }
88        Ok(LlamaBackend {})
89    }
90
91    /// Change the output of llama.cpp's logging to be voided instead of pushed to `stderr`.
92    pub fn void_logs(&mut self) {
93        unsafe extern "C" fn void_log(
94            _level: ggml_log_level,
95            _text: *const ::std::os::raw::c_char,
96            _user_data: *mut ::std::os::raw::c_void,
97        ) {
98        }
99
100        // SAFETY: `void_log` is a `'static` fn item with the exact signature
101        // `ggml_log_callback` requires, and it touches none of its arguments —
102        // so it is sound for llama.cpp to call from any thread, at any time,
103        // for the rest of the process. The null user-data pointer is never
104        // dereferenced because `void_log` ignores it. `&mut self` is what keeps
105        // this from racing another `void_log`/`llama_log_set` caller.
106        unsafe {
107            llama_cpp_sys_4::llama_log_set(Some(void_log), std::ptr::null_mut());
108        }
109    }
110}
111
112/// A rusty wrapper around `numa_strategy`.
113///
114/// ## Description
115/// Represents different NUMA (Non-Uniform Memory Access) strategies for memory management
116/// in multi-core or multi-processor systems.
117///
118/// ## See more
119/// <https://github.com/ggerganov/llama.cpp/blob/master/ggml/include/ggml-cpu.h#L25-L32>
120#[derive(Debug, Eq, PartialEq, Copy, Clone)]
121pub enum NumaStrategy {
122    /// The NUMA strategy is disabled. No NUMA-aware optimizations are applied.
123    /// Memory allocation will not consider NUMA node locality.
124    DISABLED,
125
126    /// Distribute memory across NUMA nodes. This strategy aims to balance memory usage
127    /// across all available NUMA nodes, potentially improving load balancing and preventing
128    /// memory hotspots on a single node. It may use round-robin or another method to
129    /// distribute allocations.
130    DISTRIBUTE,
131
132    /// Isolate memory to specific NUMA nodes. Memory allocations will be restricted to
133    /// specific NUMA nodes, potentially reducing contention and improving locality for
134    /// processes or threads bound to a particular node.
135    ISOLATE,
136
137    /// Use `numactl` to manage memory and processor affinities. This strategy utilizes
138    /// the `numactl` command or library to bind processes or memory allocations to specific
139    /// NUMA nodes or CPUs, providing fine-grained control over memory placement.
140    NUMACTL,
141
142    /// Mirror memory across NUMA nodes. This strategy creates duplicate memory copies
143    /// on multiple NUMA nodes, which can help with fault tolerance and redundancy,
144    /// ensuring that each NUMA node has access to a copy of the memory.
145    MIRROR,
146
147    /// A placeholder representing the total number of strategies available.
148    /// Typically used for iteration or determining the number of strategies in the enum.
149    COUNT,
150}
151
152/// An invalid numa strategy was provided.
153#[derive(Debug, Eq, PartialEq, Copy, Clone)]
154pub struct InvalidNumaStrategy(
155    /// The invalid numa strategy that was provided.
156    pub llama_cpp_sys_4::ggml_numa_strategy,
157);
158
159impl TryFrom<llama_cpp_sys_4::ggml_numa_strategy> for NumaStrategy {
160    type Error = InvalidNumaStrategy;
161
162    fn try_from(value: llama_cpp_sys_4::ggml_numa_strategy) -> Result<Self, Self::Error> {
163        match value {
164            llama_cpp_sys_4::GGML_NUMA_STRATEGY_DISABLED => Ok(Self::DISABLED),
165            llama_cpp_sys_4::GGML_NUMA_STRATEGY_DISTRIBUTE => Ok(Self::DISTRIBUTE),
166            llama_cpp_sys_4::GGML_NUMA_STRATEGY_ISOLATE => Ok(Self::ISOLATE),
167            llama_cpp_sys_4::GGML_NUMA_STRATEGY_NUMACTL => Ok(Self::NUMACTL),
168            llama_cpp_sys_4::GGML_NUMA_STRATEGY_MIRROR => Ok(Self::MIRROR),
169            llama_cpp_sys_4::GGML_NUMA_STRATEGY_COUNT => Ok(Self::COUNT),
170            value => Err(InvalidNumaStrategy(value)),
171        }
172    }
173}
174
175impl From<NumaStrategy> for llama_cpp_sys_4::ggml_numa_strategy {
176    fn from(value: NumaStrategy) -> Self {
177        match value {
178            NumaStrategy::DISABLED => llama_cpp_sys_4::GGML_NUMA_STRATEGY_DISABLED,
179            NumaStrategy::DISTRIBUTE => llama_cpp_sys_4::GGML_NUMA_STRATEGY_DISTRIBUTE,
180            NumaStrategy::ISOLATE => llama_cpp_sys_4::GGML_NUMA_STRATEGY_ISOLATE,
181            NumaStrategy::NUMACTL => llama_cpp_sys_4::GGML_NUMA_STRATEGY_NUMACTL,
182            NumaStrategy::MIRROR => llama_cpp_sys_4::GGML_NUMA_STRATEGY_MIRROR,
183            NumaStrategy::COUNT => llama_cpp_sys_4::GGML_NUMA_STRATEGY_COUNT,
184        }
185    }
186}
187
188/// Drops the llama backend.
189/// ```
190///
191///# use llama_cpp_4::llama_backend::LlamaBackend;
192///# use std::error::Error;
193///
194///# fn main() -> Result<(), Box<dyn Error>> {
195/// let backend = LlamaBackend::init()?;
196/// drop(backend);
197/// // can be initialized again after being dropped
198/// let backend = LlamaBackend::init()?;
199///# Ok(())
200///# }
201///
202/// ```
203impl Drop for LlamaBackend {
204    fn drop(&mut self) {
205        match LLAMA_BACKEND_INITIALIZED.compare_exchange(true, false, SeqCst, SeqCst) {
206            Ok(_) => {}
207            Err(_) => {
208                unreachable!("This should not be reachable as the only ways to obtain a llama backend involve marking the backend as initialized.")
209            }
210        }
211        // SAFETY: paired with the `llama_backend_init` in `init`/`init_numa`.
212        // Reaching `Drop` means a `LlamaBackend` existed, which means the
213        // backend was initialized, and the compare-exchange just above flipped
214        // the global back to false — so this runs once and nothing else can be
215        // mid-call.
216        unsafe { llama_cpp_sys_4::llama_backend_free() }
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223
224    #[test]
225    fn numa_from_and_to() {
226        let numas = [
227            NumaStrategy::DISABLED,
228            NumaStrategy::DISTRIBUTE,
229            NumaStrategy::ISOLATE,
230            NumaStrategy::NUMACTL,
231            NumaStrategy::MIRROR,
232            NumaStrategy::COUNT,
233        ];
234
235        for numa in &numas {
236            let from = llama_cpp_sys_4::ggml_numa_strategy::from(*numa);
237            let to = NumaStrategy::try_from(from).expect("Failed to convert from and to");
238            assert_eq!(*numa, to);
239        }
240    }
241
242    #[test]
243    fn check_invalid_numa() {
244        let invalid = 800;
245        let invalid = NumaStrategy::try_from(invalid);
246        assert_eq!(invalid, Err(InvalidNumaStrategy(invalid.unwrap_err().0)));
247    }
248}