1use std::ffi::{c_char, CStr, CString, NulError};
18use std::fmt::Debug;
19use std::num::NonZeroI32;
20
21use crate::llama_batch::BatchAddError;
22use std::os::raw::c_int;
23use std::path::PathBuf;
24use std::string::FromUtf8Error;
25
26pub mod context;
27pub mod gguf;
28pub mod llama_backend;
29pub mod llama_batch;
30#[cfg(feature = "llguidance")]
31pub(crate) mod llguidance_sampler;
32mod log;
33pub mod model;
34#[cfg(feature = "mtmd")]
35pub mod mtmd;
36pub mod sampling;
37#[cfg(feature = "common")]
38pub mod speculative;
39pub mod timing;
40pub mod token;
41pub mod token_type;
42
43pub use crate::context::session::{LlamaStateSeqFlags, SeqState};
44
45#[cfg(feature = "common")]
46pub(crate) fn status_is_ok(status: llama_cpp_sys_2::llama_rs_status) -> bool {
47 status == llama_cpp_sys_2::LLAMA_RS_STATUS_OK
48}
49
50pub type Result<T> = std::result::Result<T, LlamaCppError>;
52
53#[derive(Debug, Eq, PartialEq, thiserror::Error)]
55pub enum LlamaCppError {
56 #[error("BackendAlreadyInitialized")]
59 BackendAlreadyInitialized,
60 #[error("{0}")]
62 ChatTemplateError(#[from] ChatTemplateError),
63 #[error("{0}")]
65 DecodeError(#[from] DecodeError),
66 #[error("{0}")]
68 EncodeError(#[from] EncodeError),
69 #[error("{0}")]
71 LlamaModelLoadError(#[from] LlamaModelLoadError),
72 #[error("{0}")]
74 LlamaContextLoadError(#[from] LlamaContextLoadError),
75 #[error["{0}"]]
77 BatchAddError(#[from] BatchAddError),
78 #[error(transparent)]
80 EmbeddingError(#[from] EmbeddingsError),
81 #[error("Backend device {0} not found")]
84 BackendDeviceNotFound(usize),
85 #[error("Max devices exceeded. Max devices is {0}")]
87 MaxDevicesExceeded(usize),
88 #[cfg(feature = "common")]
90 #[error("JsonSchemaToGrammarError: {0}")]
91 JsonSchemaToGrammarError(String),
92 #[cfg(feature = "common")]
94 #[error("{0}")]
95 FitError(#[from] crate::model::params::FitError),
96}
97
98#[derive(Debug, Eq, PartialEq, thiserror::Error)]
100pub enum ChatTemplateError {
101 #[error("chat template not found - returned null pointer")]
103 MissingTemplate,
104
105 #[error("null byte in string {0}")]
107 NullError(#[from] NulError),
108
109 #[error(transparent)]
111 Utf8Error(#[from] std::str::Utf8Error),
112}
113
114#[derive(Debug, Eq, PartialEq, thiserror::Error)]
116pub enum MetaValError {
117 #[error("null byte in string {0}")]
119 NullError(#[from] NulError),
120
121 #[error("FromUtf8Error {0}")]
123 FromUtf8Error(#[from] FromUtf8Error),
124
125 #[error("Negative return value. Likely due to a missing index or key. Got return value: {0}")]
127 NegativeReturn(i32),
128}
129
130#[derive(Debug, Eq, PartialEq, thiserror::Error)]
132pub enum LlamaContextLoadError {
133 #[error("null reference from llama.cpp")]
135 NullReturn,
136}
137
138#[derive(Debug, Eq, PartialEq, thiserror::Error)]
140pub enum StateSeqError {
141 #[error("state seq size mismatch: expected {expected}, actual {actual}")]
143 SizeMismatch {
144 expected: usize,
146 actual: usize,
148 },
149}
150
151#[derive(Debug, Eq, PartialEq, thiserror::Error)]
153pub enum DecodeError {
154 #[error("Decode Error 1: NoKvCacheSlot")]
156 NoKvCacheSlot,
157 #[error("Decode Error -1: n_tokens == 0")]
159 NTokensZero,
160 #[error("Decode Error {0}: unknown")]
162 Unknown(c_int),
163}
164
165#[derive(Debug, Eq, PartialEq, thiserror::Error)]
167pub enum EncodeError {
168 #[error("Encode Error 1: NoKvCacheSlot")]
170 NoKvCacheSlot,
171 #[error("Encode Error -1: n_tokens == 0")]
173 NTokensZero,
174 #[error("Encode Error {0}: unknown")]
176 Unknown(c_int),
177}
178
179#[derive(Debug, Eq, PartialEq, thiserror::Error)]
181pub enum EmbeddingsError {
182 #[error("Embeddings weren't enabled in the context options")]
184 NotEnabled,
185 #[error("Logits were not enabled for the given token")]
187 LogitsNotEnabled,
188 #[error("Can't use sequence embeddings with a model supporting only LLAMA_POOLING_TYPE_NONE")]
190 NonePoolType,
191}
192
193#[derive(Debug, Eq, PartialEq, thiserror::Error)]
195pub enum GrammarError {
196 #[error("Grammar root not found in grammar string")]
198 RootNotFound,
199 #[error("Trigger word contains null bytes")]
201 TriggerWordNullBytes,
202 #[error("Grammar string or root contains null bytes")]
204 GrammarNullBytes,
205 #[error("Grammar call returned null")]
207 NullGrammar,
208}
209
210impl From<NonZeroI32> for DecodeError {
212 fn from(value: NonZeroI32) -> Self {
213 match value.get() {
214 1 => DecodeError::NoKvCacheSlot,
215 -1 => DecodeError::NTokensZero,
216 i => DecodeError::Unknown(i),
217 }
218 }
219}
220
221impl From<NonZeroI32> for EncodeError {
223 fn from(value: NonZeroI32) -> Self {
224 match value.get() {
225 1 => EncodeError::NoKvCacheSlot,
226 -1 => EncodeError::NTokensZero,
227 i => EncodeError::Unknown(i),
228 }
229 }
230}
231
232#[derive(Debug, Eq, PartialEq, thiserror::Error)]
234pub enum LlamaModelLoadError {
235 #[error("null byte in string {0}")]
237 NullError(#[from] NulError),
238 #[error("null result from llama cpp")]
240 NullResult,
241 #[error("failed to convert path {0} to str")]
243 PathToStrError(PathBuf),
244}
245
246#[derive(Debug, Eq, PartialEq, thiserror::Error)]
248pub enum LlamaLoraAdapterInitError {
249 #[error("null byte in string {0}")]
251 NullError(#[from] NulError),
252 #[error("null result from llama cpp")]
254 NullResult,
255 #[error("failed to convert path {0} to str")]
257 PathToStrError(PathBuf),
258}
259
260#[derive(Debug, Eq, PartialEq, thiserror::Error)]
262pub enum LlamaLoraAdapterSetError {
263 #[error("error code from llama cpp")]
265 ErrorResult(i32),
266}
267
268#[derive(Debug, Eq, PartialEq, thiserror::Error)]
270pub enum LlamaLoraAdapterRemoveError {
271 #[error("error code from llama cpp")]
273 ErrorResult(i32),
274}
275
276#[must_use]
285pub fn llama_time_us() -> i64 {
286 unsafe { llama_cpp_sys_2::llama_time_us() }
287}
288
289#[must_use]
296pub fn max_devices() -> usize {
297 unsafe { llama_cpp_sys_2::llama_max_devices() }
298}
299
300#[must_use]
309pub fn mmap_supported() -> bool {
310 unsafe { llama_cpp_sys_2::llama_supports_mmap() }
311}
312
313#[must_use]
322pub fn mlock_supported() -> bool {
323 unsafe { llama_cpp_sys_2::llama_supports_mlock() }
324}
325
326#[cfg(feature = "common")]
328pub fn json_schema_to_grammar(schema_json: &str) -> Result<String> {
329 let schema_cstr = CString::new(schema_json)
330 .map_err(|err| LlamaCppError::JsonSchemaToGrammarError(err.to_string()))?;
331 let mut out = std::ptr::null_mut();
332 let rc = unsafe {
333 llama_cpp_sys_2::llama_rs_json_schema_to_grammar(schema_cstr.as_ptr(), false, &mut out)
334 };
335
336 let result = {
337 if !status_is_ok(rc) || out.is_null() {
338 return Err(LlamaCppError::JsonSchemaToGrammarError(format!(
339 "ffi error {}",
340 rc
341 )));
342 }
343 let grammar_bytes = unsafe { CStr::from_ptr(out) }.to_bytes().to_vec();
344 let grammar = String::from_utf8(grammar_bytes)
345 .map_err(|err| LlamaCppError::JsonSchemaToGrammarError(err.to_string()))?;
346 Ok(grammar)
347 };
348
349 unsafe { llama_cpp_sys_2::llama_rs_string_free(out) };
350 result
351}
352
353#[cfg(all(test, feature = "common"))]
354mod tests {
355 use super::json_schema_to_grammar;
356
357 #[test]
358 fn json_schema_string_api_returns_grammar() {
359 let schema = r#"{
360 "type": "object",
361 "properties": {
362 "city": { "type": "string" },
363 "unit": { "enum": ["c", "f"] }
364 },
365 "required": ["city"]
366 }"#;
367
368 let grammar =
369 json_schema_to_grammar(schema).expect("string-based schema conversion should succeed");
370
371 assert!(grammar.contains("root ::="));
372 }
373}
374
375#[derive(Debug, thiserror::Error, Clone)]
377#[non_exhaustive]
378pub enum TokenToStringError {
379 #[error("Unknown Token Type")]
381 UnknownTokenType,
382 #[error("Insufficient Buffer Space {0}")]
384 InsufficientBufferSpace(c_int),
385 #[error("FromUtf8Error {0}")]
387 FromUtf8Error(#[from] FromUtf8Error),
388}
389
390#[derive(Debug, thiserror::Error)]
392pub enum StringToTokenError {
393 #[error("{0}")]
395 NulError(#[from] NulError),
396 #[error("{0}")]
397 CIntConversionError(#[from] std::num::TryFromIntError),
399}
400
401#[derive(Debug, thiserror::Error)]
403pub enum NewLlamaChatMessageError {
404 #[error("{0}")]
406 NulError(#[from] NulError),
407}
408
409#[derive(Debug, thiserror::Error)]
411pub enum ApplyChatTemplateError {
412 #[error("{0}")]
414 NulError(#[from] NulError),
415 #[error("{0}")]
417 FromUtf8Error(#[from] FromUtf8Error),
418 #[error("null result from llama.cpp")]
420 NullResult,
421 #[error("ffi error {0}")]
423 FfiError(i32),
424}
425
426#[derive(Debug, thiserror::Error)]
428pub enum SamplerAcceptError {
429 #[error("ffi error {0}")]
431 FfiError(i32),
432}
433
434#[must_use]
452pub fn ggml_time_us() -> i64 {
453 unsafe { llama_cpp_sys_2::ggml_time_us() }
454}
455
456#[must_use]
468pub fn llama_supports_mlock() -> bool {
469 unsafe { llama_cpp_sys_2::llama_supports_mlock() }
470}
471
472#[derive(Debug, Clone, Copy, PartialEq, Eq)]
474pub enum LlamaBackendDeviceType {
475 Cpu,
477 Accelerator,
479 Gpu,
481 IntegratedGpu,
483 Unknown,
485}
486
487#[derive(Debug, Clone)]
491pub struct LlamaBackendDevice {
492 pub index: usize,
496 pub name: String,
498 pub description: String,
500 pub backend: String,
502 pub memory_total: usize,
504 pub memory_free: usize,
506 pub device_type: LlamaBackendDeviceType,
508}
509
510#[must_use]
512pub fn list_llama_ggml_backend_devices() -> Vec<LlamaBackendDevice> {
513 let mut devices = Vec::new();
514 for i in 0..unsafe { llama_cpp_sys_2::ggml_backend_dev_count() } {
515 fn cstr_to_string(ptr: *const c_char) -> String {
516 if ptr.is_null() {
517 String::new()
518 } else {
519 unsafe { std::ffi::CStr::from_ptr(ptr) }
520 .to_string_lossy()
521 .to_string()
522 }
523 }
524 let dev = unsafe { llama_cpp_sys_2::ggml_backend_dev_get(i) };
525 let props = unsafe {
526 let mut props = std::mem::zeroed();
527 llama_cpp_sys_2::ggml_backend_dev_get_props(dev, &raw mut props);
528 props
529 };
530 let name = cstr_to_string(props.name);
531 let description = cstr_to_string(props.description);
532 let backend = unsafe { llama_cpp_sys_2::ggml_backend_dev_backend_reg(dev) };
533 let backend_name = unsafe { llama_cpp_sys_2::ggml_backend_reg_name(backend) };
534 let backend = cstr_to_string(backend_name);
535 let memory_total = props.memory_total;
536 let memory_free = props.memory_free;
537 let device_type = match props.type_ {
538 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_CPU => LlamaBackendDeviceType::Cpu,
539 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_ACCEL => LlamaBackendDeviceType::Accelerator,
540 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_GPU => LlamaBackendDeviceType::Gpu,
541 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_IGPU => LlamaBackendDeviceType::IntegratedGpu,
542 _ => LlamaBackendDeviceType::Unknown,
543 };
544 devices.push(LlamaBackendDevice {
545 index: i,
546 name,
547 description,
548 backend,
549 memory_total,
550 memory_free,
551 device_type,
552 });
553 }
554 devices
555}
556
557#[derive(Default, Debug, Clone)]
559pub struct LogOptions {
560 disabled: bool,
561}
562
563impl LogOptions {
564 #[must_use]
567 pub fn with_logs_enabled(mut self, enabled: bool) -> Self {
568 self.disabled = !enabled;
569 self
570 }
571}
572
573extern "C" fn logs_to_trace(
574 level: llama_cpp_sys_2::ggml_log_level,
575 text: *const ::std::os::raw::c_char,
576 data: *mut ::std::os::raw::c_void,
577) {
578 use std::borrow::Borrow;
583
584 let log_state = unsafe { &*(data as *const log::State) };
585
586 if log_state.options.disabled {
587 return;
588 }
589
590 if !log_state.is_enabled_for_level(level) {
592 log_state.update_previous_level_for_disabled_log(level);
593 return;
594 }
595
596 let text = unsafe { std::ffi::CStr::from_ptr(text) };
597 let text = text.to_string_lossy();
598 let text: &str = text.borrow();
599
600 if level == llama_cpp_sys_2::GGML_LOG_LEVEL_CONT {
606 log_state.cont_buffered_log(text);
607 } else if text.ends_with('\n') {
608 log_state.emit_non_cont_line(level, text);
609 } else {
610 log_state.buffer_non_cont(level, text);
611 }
612}
613
614pub fn send_logs_to_tracing(options: LogOptions) {
616 let llama_heap_state = Box::as_ref(
623 log::LLAMA_STATE
624 .get_or_init(|| Box::new(log::State::new(log::Module::LlamaCpp, options.clone()))),
625 ) as *const _;
626 let ggml_heap_state = Box::as_ref(
627 log::GGML_STATE.get_or_init(|| Box::new(log::State::new(log::Module::GGML, options))),
628 ) as *const _;
629
630 unsafe {
631 llama_cpp_sys_2::llama_log_set(Some(logs_to_trace), llama_heap_state as *mut _);
633 llama_cpp_sys_2::ggml_log_set(Some(logs_to_trace), ggml_heap_state as *mut _);
634 }
635}