Skip to main content

llama_cpp_bindings/
llama_backend.rs

1//! Representation of an initialized llama backend
2
3use crate::LlamaCppError;
4use crate::llama_backend_numa_strategy::NumaStrategy;
5use llama_cpp_bindings_sys::ggml_log_level;
6use std::sync::atomic::AtomicBool;
7use std::sync::atomic::Ordering::SeqCst;
8
9/// Representation of an initialized llama backend.
10///
11/// This is required as a parameter for most llama functions as the backend must be initialized
12/// before any llama functions are called. This type is proof of initialization.
13#[derive(Eq, PartialEq, Debug)]
14pub struct LlamaBackend {}
15
16static LLAMA_BACKEND_INITIALIZED: AtomicBool = AtomicBool::new(false);
17
18impl LlamaBackend {
19    /// Mark the llama backend as initialized
20    fn mark_init() -> crate::Result<()> {
21        match LLAMA_BACKEND_INITIALIZED.compare_exchange(false, true, SeqCst, SeqCst) {
22            Ok(_was_uninitialized) => Ok(()),
23            Err(_was_already_initialized) => Err(LlamaCppError::BackendAlreadyInitialized),
24        }
25    }
26
27    /// Initialize the llama backend (without numa).
28    ///
29    /// # Examples
30    ///
31    /// ```
32    ///# use llama_cpp_bindings::llama_backend::LlamaBackend;
33    ///# use llama_cpp_bindings::LlamaCppError;
34    ///# use std::error::Error;
35    ///
36    ///# fn main() -> Result<(), Box<dyn Error>> {
37    ///
38    ///
39    /// let backend = LlamaBackend::init()?;
40    /// // the llama backend can only be initialized once
41    /// assert_eq!(Err(LlamaCppError::BackendAlreadyInitialized), LlamaBackend::init());
42    ///
43    ///# Ok(())
44    ///# }
45    /// ```
46    /// # Errors
47    /// Returns an error if the backend was already initialized.
48    #[tracing::instrument(skip_all)]
49    pub fn init() -> crate::Result<Self> {
50        Self::mark_init()?;
51        unsafe { llama_cpp_bindings_sys::llama_backend_init() }
52        Ok(Self {})
53    }
54
55    /// Initialize the llama backend (with numa).
56    /// ```
57    ///# use llama_cpp_bindings::llama_backend::LlamaBackend;
58    ///# use std::error::Error;
59    ///# use llama_cpp_bindings::llama_backend_numa_strategy::NumaStrategy;
60    ///
61    ///# fn main() -> Result<(), Box<dyn Error>> {
62    ///
63    /// let llama_backend = LlamaBackend::init_numa(NumaStrategy::Mirror)?;
64    ///
65    ///# Ok(())
66    ///# }
67    /// ```
68    /// # Errors
69    /// Returns an error if the backend was already initialized.
70    #[tracing::instrument(skip_all)]
71    pub fn init_numa(strategy: NumaStrategy) -> crate::Result<Self> {
72        Self::mark_init()?;
73        unsafe {
74            llama_cpp_bindings_sys::llama_numa_init(
75                llama_cpp_bindings_sys::ggml_numa_strategy::from(strategy),
76            );
77        }
78        Ok(Self {})
79    }
80
81    /// Was the code built for a GPU backend & is a supported one available.
82    #[must_use]
83    pub fn supports_gpu_offload(&self) -> bool {
84        unsafe { llama_cpp_bindings_sys::llama_supports_gpu_offload() }
85    }
86
87    /// Does this platform support loading the model via mmap.
88    #[must_use]
89    pub fn supports_mmap(&self) -> bool {
90        unsafe { llama_cpp_bindings_sys::llama_supports_mmap() }
91    }
92
93    /// Does this platform support locking the model in RAM.
94    #[must_use]
95    pub fn supports_mlock(&self) -> bool {
96        unsafe { llama_cpp_bindings_sys::llama_supports_mlock() }
97    }
98
99    /// Change the output of llama.cpp's logging to be voided instead of pushed to `stderr`.
100    pub fn void_logs(&mut self) {
101        unsafe {
102            llama_cpp_bindings_sys::llama_log_set(Some(void_log), std::ptr::null_mut());
103        }
104    }
105}
106
107const unsafe extern "C" fn void_log(
108    _level: ggml_log_level,
109    _text: *const ::std::os::raw::c_char,
110    _user_data: *mut ::std::os::raw::c_void,
111) {
112}
113
114/// Drops the llama backend.
115/// ```
116///
117///# use llama_cpp_bindings::llama_backend::LlamaBackend;
118///# use std::error::Error;
119///
120///# fn main() -> Result<(), Box<dyn Error>> {
121/// let backend = LlamaBackend::init()?;
122/// drop(backend);
123/// // can be initialized again after being dropped
124/// let backend = LlamaBackend::init()?;
125///# Ok(())
126///# }
127///
128/// ```
129impl Drop for LlamaBackend {
130    fn drop(&mut self) {
131        LLAMA_BACKEND_INITIALIZED.store(false, SeqCst);
132        unsafe { llama_cpp_bindings_sys::llama_backend_free() }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use serial_test::serial;
139
140    use super::LlamaBackend;
141    use crate::LlamaCppError;
142
143    #[test]
144    fn void_log_callback_does_not_panic() {
145        unsafe {
146            super::void_log(
147                llama_cpp_bindings_sys::GGML_LOG_LEVEL_INFO,
148                c"test".as_ptr(),
149                std::ptr::null_mut(),
150            );
151        }
152    }
153
154    #[test]
155    #[serial]
156    fn init_succeeds() {
157        let backend = LlamaBackend::init();
158        assert!(backend.is_ok());
159    }
160
161    #[test]
162    #[serial]
163    fn double_init_returns_error() {
164        let _backend = LlamaBackend::init().unwrap();
165        let second = LlamaBackend::init();
166        assert_eq!(
167            second.unwrap_err(),
168            LlamaCppError::BackendAlreadyInitialized
169        );
170    }
171
172    #[test]
173    #[serial]
174    fn feature_queries_return_bools() {
175        let backend = LlamaBackend::init().unwrap();
176        let _gpu = backend.supports_gpu_offload();
177        let _mmap = backend.supports_mmap();
178        let _mlock = backend.supports_mlock();
179    }
180
181    #[test]
182    #[serial]
183    fn drop_and_reinit_works() {
184        let backend = LlamaBackend::init().unwrap();
185        drop(backend);
186        let backend = LlamaBackend::init();
187        assert!(backend.is_ok());
188    }
189
190    #[test]
191    #[serial]
192    fn init_numa_succeeds() {
193        use crate::llama_backend_numa_strategy::NumaStrategy;
194
195        let backend = LlamaBackend::init_numa(NumaStrategy::Disabled);
196        assert!(backend.is_ok());
197    }
198}