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;
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 DecodeError {
141 #[error("Decode Error 1: NoKvCacheSlot")]
143 NoKvCacheSlot,
144 #[error("Decode Error -1: n_tokens == 0")]
146 NTokensZero,
147 #[error("Decode Error {0}: unknown")]
149 Unknown(c_int),
150}
151
152#[derive(Debug, Eq, PartialEq, thiserror::Error)]
154pub enum EncodeError {
155 #[error("Encode Error 1: NoKvCacheSlot")]
157 NoKvCacheSlot,
158 #[error("Encode Error -1: n_tokens == 0")]
160 NTokensZero,
161 #[error("Encode Error {0}: unknown")]
163 Unknown(c_int),
164}
165
166#[derive(Debug, Eq, PartialEq, thiserror::Error)]
168pub enum EmbeddingsError {
169 #[error("Embeddings weren't enabled in the context options")]
171 NotEnabled,
172 #[error("Logits were not enabled for the given token")]
174 LogitsNotEnabled,
175 #[error("Can't use sequence embeddings with a model supporting only LLAMA_POOLING_TYPE_NONE")]
177 NonePoolType,
178}
179
180#[derive(Debug, Eq, PartialEq, thiserror::Error)]
182pub enum GrammarError {
183 #[error("Grammar root not found in grammar string")]
185 RootNotFound,
186 #[error("Trigger word contains null bytes")]
188 TriggerWordNullBytes,
189 #[error("Grammar string or root contains null bytes")]
191 GrammarNullBytes,
192 #[error("Grammar call returned null")]
194 NullGrammar,
195}
196
197impl From<NonZeroI32> for DecodeError {
199 fn from(value: NonZeroI32) -> Self {
200 match value.get() {
201 1 => DecodeError::NoKvCacheSlot,
202 -1 => DecodeError::NTokensZero,
203 i => DecodeError::Unknown(i),
204 }
205 }
206}
207
208impl From<NonZeroI32> for EncodeError {
210 fn from(value: NonZeroI32) -> Self {
211 match value.get() {
212 1 => EncodeError::NoKvCacheSlot,
213 -1 => EncodeError::NTokensZero,
214 i => EncodeError::Unknown(i),
215 }
216 }
217}
218
219#[derive(Debug, Eq, PartialEq, thiserror::Error)]
221pub enum LlamaModelLoadError {
222 #[error("null byte in string {0}")]
224 NullError(#[from] NulError),
225 #[error("null result from llama cpp")]
227 NullResult,
228 #[error("failed to convert path {0} to str")]
230 PathToStrError(PathBuf),
231}
232
233#[derive(Debug, Eq, PartialEq, thiserror::Error)]
235pub enum LlamaLoraAdapterInitError {
236 #[error("null byte in string {0}")]
238 NullError(#[from] NulError),
239 #[error("null result from llama cpp")]
241 NullResult,
242 #[error("failed to convert path {0} to str")]
244 PathToStrError(PathBuf),
245}
246
247#[derive(Debug, Eq, PartialEq, thiserror::Error)]
249pub enum LlamaLoraAdapterSetError {
250 #[error("error code from llama cpp")]
252 ErrorResult(i32),
253}
254
255#[derive(Debug, Eq, PartialEq, thiserror::Error)]
257pub enum LlamaLoraAdapterRemoveError {
258 #[error("error code from llama cpp")]
260 ErrorResult(i32),
261}
262
263#[must_use]
272pub fn llama_time_us() -> i64 {
273 unsafe { llama_cpp_sys_2::llama_time_us() }
274}
275
276#[must_use]
283pub fn max_devices() -> usize {
284 unsafe { llama_cpp_sys_2::llama_max_devices() }
285}
286
287#[must_use]
296pub fn mmap_supported() -> bool {
297 unsafe { llama_cpp_sys_2::llama_supports_mmap() }
298}
299
300#[must_use]
309pub fn mlock_supported() -> bool {
310 unsafe { llama_cpp_sys_2::llama_supports_mlock() }
311}
312
313#[cfg(feature = "common")]
315pub fn json_schema_to_grammar(schema_json: &str) -> Result<String> {
316 let schema_cstr = CString::new(schema_json)
317 .map_err(|err| LlamaCppError::JsonSchemaToGrammarError(err.to_string()))?;
318 let mut out = std::ptr::null_mut();
319 let rc = unsafe {
320 llama_cpp_sys_2::llama_rs_json_schema_to_grammar(schema_cstr.as_ptr(), false, &mut out)
321 };
322
323 let result = {
324 if !status_is_ok(rc) || out.is_null() {
325 return Err(LlamaCppError::JsonSchemaToGrammarError(format!(
326 "ffi error {}",
327 rc
328 )));
329 }
330 let grammar_bytes = unsafe { CStr::from_ptr(out) }.to_bytes().to_vec();
331 let grammar = String::from_utf8(grammar_bytes)
332 .map_err(|err| LlamaCppError::JsonSchemaToGrammarError(err.to_string()))?;
333 Ok(grammar)
334 };
335
336 unsafe { llama_cpp_sys_2::llama_rs_string_free(out) };
337 result
338}
339
340#[cfg(all(test, feature = "common"))]
341mod tests {
342 use super::json_schema_to_grammar;
343
344 #[test]
345 fn json_schema_string_api_returns_grammar() {
346 let schema = r#"{
347 "type": "object",
348 "properties": {
349 "city": { "type": "string" },
350 "unit": { "enum": ["c", "f"] }
351 },
352 "required": ["city"]
353 }"#;
354
355 let grammar =
356 json_schema_to_grammar(schema).expect("string-based schema conversion should succeed");
357
358 assert!(grammar.contains("root ::="));
359 }
360}
361
362#[derive(Debug, thiserror::Error, Clone)]
364#[non_exhaustive]
365pub enum TokenToStringError {
366 #[error("Unknown Token Type")]
368 UnknownTokenType,
369 #[error("Insufficient Buffer Space {0}")]
371 InsufficientBufferSpace(c_int),
372 #[error("FromUtf8Error {0}")]
374 FromUtf8Error(#[from] FromUtf8Error),
375}
376
377#[derive(Debug, thiserror::Error)]
379pub enum StringToTokenError {
380 #[error("{0}")]
382 NulError(#[from] NulError),
383 #[error("{0}")]
384 CIntConversionError(#[from] std::num::TryFromIntError),
386}
387
388#[derive(Debug, thiserror::Error)]
390pub enum NewLlamaChatMessageError {
391 #[error("{0}")]
393 NulError(#[from] NulError),
394}
395
396#[derive(Debug, thiserror::Error)]
398pub enum ApplyChatTemplateError {
399 #[error("{0}")]
401 NulError(#[from] NulError),
402 #[error("{0}")]
404 FromUtf8Error(#[from] FromUtf8Error),
405 #[error("null result from llama.cpp")]
407 NullResult,
408 #[error("ffi error {0}")]
410 FfiError(i32),
411}
412
413#[derive(Debug, thiserror::Error)]
415pub enum SamplerAcceptError {
416 #[error("ffi error {0}")]
418 FfiError(i32),
419}
420
421#[must_use]
439pub fn ggml_time_us() -> i64 {
440 unsafe { llama_cpp_sys_2::ggml_time_us() }
441}
442
443#[must_use]
455pub fn llama_supports_mlock() -> bool {
456 unsafe { llama_cpp_sys_2::llama_supports_mlock() }
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq)]
461pub enum LlamaBackendDeviceType {
462 Cpu,
464 Accelerator,
466 Gpu,
468 IntegratedGpu,
470 Unknown,
472}
473
474#[derive(Debug, Clone)]
478pub struct LlamaBackendDevice {
479 pub index: usize,
483 pub name: String,
485 pub description: String,
487 pub backend: String,
489 pub memory_total: usize,
491 pub memory_free: usize,
493 pub device_type: LlamaBackendDeviceType,
495}
496
497#[must_use]
499pub fn list_llama_ggml_backend_devices() -> Vec<LlamaBackendDevice> {
500 let mut devices = Vec::new();
501 for i in 0..unsafe { llama_cpp_sys_2::ggml_backend_dev_count() } {
502 fn cstr_to_string(ptr: *const c_char) -> String {
503 if ptr.is_null() {
504 String::new()
505 } else {
506 unsafe { std::ffi::CStr::from_ptr(ptr) }
507 .to_string_lossy()
508 .to_string()
509 }
510 }
511 let dev = unsafe { llama_cpp_sys_2::ggml_backend_dev_get(i) };
512 let props = unsafe {
513 let mut props = std::mem::zeroed();
514 llama_cpp_sys_2::ggml_backend_dev_get_props(dev, &raw mut props);
515 props
516 };
517 let name = cstr_to_string(props.name);
518 let description = cstr_to_string(props.description);
519 let backend = unsafe { llama_cpp_sys_2::ggml_backend_dev_backend_reg(dev) };
520 let backend_name = unsafe { llama_cpp_sys_2::ggml_backend_reg_name(backend) };
521 let backend = cstr_to_string(backend_name);
522 let memory_total = props.memory_total;
523 let memory_free = props.memory_free;
524 let device_type = match props.type_ {
525 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_CPU => LlamaBackendDeviceType::Cpu,
526 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_ACCEL => LlamaBackendDeviceType::Accelerator,
527 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_GPU => LlamaBackendDeviceType::Gpu,
528 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_IGPU => LlamaBackendDeviceType::IntegratedGpu,
529 _ => LlamaBackendDeviceType::Unknown,
530 };
531 devices.push(LlamaBackendDevice {
532 index: i,
533 name,
534 description,
535 backend,
536 memory_total,
537 memory_free,
538 device_type,
539 });
540 }
541 devices
542}
543
544#[derive(Default, Debug, Clone)]
546pub struct LogOptions {
547 disabled: bool,
548}
549
550impl LogOptions {
551 #[must_use]
554 pub fn with_logs_enabled(mut self, enabled: bool) -> Self {
555 self.disabled = !enabled;
556 self
557 }
558}
559
560extern "C" fn logs_to_trace(
561 level: llama_cpp_sys_2::ggml_log_level,
562 text: *const ::std::os::raw::c_char,
563 data: *mut ::std::os::raw::c_void,
564) {
565 use std::borrow::Borrow;
570
571 let log_state = unsafe { &*(data as *const log::State) };
572
573 if log_state.options.disabled {
574 return;
575 }
576
577 if !log_state.is_enabled_for_level(level) {
579 log_state.update_previous_level_for_disabled_log(level);
580 return;
581 }
582
583 let text = unsafe { std::ffi::CStr::from_ptr(text) };
584 let text = text.to_string_lossy();
585 let text: &str = text.borrow();
586
587 if level == llama_cpp_sys_2::GGML_LOG_LEVEL_CONT {
593 log_state.cont_buffered_log(text);
594 } else if text.ends_with('\n') {
595 log_state.emit_non_cont_line(level, text);
596 } else {
597 log_state.buffer_non_cont(level, text);
598 }
599}
600
601pub fn send_logs_to_tracing(options: LogOptions) {
603 let llama_heap_state = Box::as_ref(
610 log::LLAMA_STATE
611 .get_or_init(|| Box::new(log::State::new(log::Module::LlamaCpp, options.clone()))),
612 ) as *const _;
613 let ggml_heap_state = Box::as_ref(
614 log::GGML_STATE.get_or_init(|| Box::new(log::State::new(log::Module::GGML, options))),
615 ) as *const _;
616
617 unsafe {
618 llama_cpp_sys_2::llama_log_set(Some(logs_to_trace), llama_heap_state as *mut _);
620 llama_cpp_sys_2::ggml_log_set(Some(logs_to_trace), ggml_heap_state as *mut _);
621 }
622}