Skip to main content

llama_cpp_4/
ggml.rs

1//! Safe wrappers around core ggml graph computation APIs.
2//!
3//! This module provides the building blocks for creating and executing tensor computation
4//! graphs using ggml backends. It's used for operations like `LoRA` merging, importance
5//! matrix computation, and control vector generation.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use llama_cpp_4::ggml::*;
11//!
12//! // Create a backend and context
13//! let backend = GgmlBackend::cpu()?;
14//! let mut ctx = GgmlContext::new(1024 * 1024, true)?;
15//!
16//! // Create tensors
17//! let a = ctx.new_tensor_2d(GgmlType::F32, 4, 4);
18//! let b = ctx.new_tensor_2d(GgmlType::F32, 4, 4);
19//!
20//! // Build computation graph
21//! let sum = ctx.add(&a, &b);
22//! let mut graph = ctx.new_graph();
23//! graph.build_forward(&sum);
24//!
25//! // Allocate and compute
26//! let mut alloc = GgmlAllocr::new(&backend);
27//! alloc.alloc_graph(&mut graph);
28//! // ... set tensor data ...
29//! backend.graph_compute(&mut graph);
30//! // ... get results ...
31//! ```
32
33use std::ffi::CStr;
34use std::ptr::NonNull;
35
36/// Re-export the raw ggml types for advanced usage.
37pub use llama_cpp_sys_4::ggml_type;
38
39/// A safe wrapper around `ggml_context`.
40#[derive(Debug)]
41pub struct GgmlContext {
42    ctx: NonNull<llama_cpp_sys_4::ggml_context>,
43}
44
45impl GgmlContext {
46    /// Create a new ggml context.
47    ///
48    /// # Parameters
49    /// - `mem_size`: Memory pool size in bytes for tensor metadata.
50    /// - `no_alloc`: If true, tensor data is not allocated (use with backend allocation).
51    ///
52    /// # Sizing the pool
53    ///
54    /// Every tensor and graph created from this context is carved out of
55    /// `mem_size`, and **running out is not a recoverable error**:
56    ///
57    /// - a *release* build of ggml returns null, which the constructors here
58    ///   turn into a panic;
59    /// - a *debug* build of ggml calls `GGML_ABORT` and kills the process
60    ///   before returning, so no amount of Rust-side care helps.
61    ///
62    /// So size it up front rather than guessing. [`Self::sized_for`] does the
63    /// arithmetic; [`Self::used_mem`] and [`Self::mem_size`] let you check
64    /// headroom while building a graph.
65    ///
66    /// # Panics
67    ///
68    /// Panics if ggml returns a null pointer.
69    #[must_use]
70    pub fn new(mem_size: usize, no_alloc: bool) -> Self {
71        let params = llama_cpp_sys_4::ggml_init_params {
72            mem_size,
73            mem_buffer: std::ptr::null_mut(),
74            no_alloc,
75        };
76        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
77        // the library owns whatever it returns.
78        let ctx = unsafe { llama_cpp_sys_4::ggml_init(params) };
79        Self {
80            ctx: NonNull::new(ctx).expect("ggml_init returned null"),
81        }
82    }
83
84    /// Create a context sized for `n_tensors` tensors and `n_graphs` graphs.
85    ///
86    /// Turns "pick a number and hope" into arithmetic: the pool holds metadata,
87    /// whose per-item cost ggml reports through [`tensor_overhead`] and
88    /// [`graph_overhead`]. A little slack is added for ggml's own bookkeeping.
89    ///
90    /// Views, reshapes and every intermediate an operation produces are tensors
91    /// too — count graph *nodes*, not just the tensors you name.
92    ///
93    /// ```
94    /// # use llama_cpp_4::ggml::GgmlContext;
95    /// // Room for 64 tensors and one graph.
96    /// let ctx = GgmlContext::sized_for(64, 1, true);
97    /// assert!(ctx.mem_size() >= 64 * llama_cpp_4::ggml::tensor_overhead());
98    /// ```
99    ///
100    /// # Panics
101    ///
102    /// Panics if ggml returns a null pointer.
103    #[must_use]
104    pub fn sized_for(n_tensors: usize, n_graphs: usize, no_alloc: bool) -> Self {
105        // The +1 tensor and the 1 KiB tail cover ggml's per-object headers and
106        // alignment padding, which are not part of the published overheads.
107        let mem_size = (n_tensors + 1) * tensor_overhead() + n_graphs * graph_overhead() + 1024;
108        Self::new(mem_size, no_alloc)
109    }
110
111    /// Bytes of the pool used so far.
112    ///
113    /// Compare with [`Self::mem_size`] while building a graph to see whether it
114    /// will fit, rather than finding out by aborting.
115    #[must_use]
116    pub fn used_mem(&self) -> usize {
117        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
118        // arguments are scalars the library validates itself.
119        unsafe { llama_cpp_sys_4::ggml_used_mem(self.ctx.as_ptr()) }
120    }
121
122    /// Total size of the pool, as passed to [`Self::new`].
123    #[must_use]
124    pub fn mem_size(&self) -> usize {
125        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
126        // arguments are scalars the library validates itself.
127        unsafe { llama_cpp_sys_4::ggml_get_mem_size(self.ctx.as_ptr()) }
128    }
129
130    /// Bytes still available in the pool.
131    #[must_use]
132    pub fn free_mem(&self) -> usize {
133        self.mem_size().saturating_sub(self.used_mem())
134    }
135
136    /// Get the raw context pointer.
137    #[must_use]
138    pub fn as_ptr(&self) -> *mut llama_cpp_sys_4::ggml_context {
139        self.ctx.as_ptr()
140    }
141
142    // ── Tensor creation ──────────────────────────────────────
143
144    /// Create a 1D tensor.
145    ///
146    /// # Panics
147    ///
148    /// Panics if ggml returns null, which in a release build means the
149    /// context's memory pool is exhausted. A debug build of ggml aborts
150    /// before returning — see [`GgmlContext::new`] for how to size the pool.
151    #[must_use]
152    pub fn new_tensor_1d(&self, typ: ggml_type, ne0: i64) -> GgmlTensor {
153        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
154        // arguments are scalars the library validates itself.
155        let t = unsafe { llama_cpp_sys_4::ggml_new_tensor_1d(self.ctx.as_ptr(), typ, ne0) };
156        GgmlTensor(NonNull::new(t).expect("ggml_new_tensor_1d returned null"))
157    }
158
159    /// Create a 2D tensor.
160    ///
161    /// # Panics
162    ///
163    /// Panics if ggml returns null, which in a release build means the
164    /// context's memory pool is exhausted. A debug build of ggml aborts
165    /// before returning — see [`GgmlContext::new`] for how to size the pool.
166    #[must_use]
167    pub fn new_tensor_2d(&self, typ: ggml_type, ne0: i64, ne1: i64) -> GgmlTensor {
168        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
169        // arguments are scalars the library validates itself.
170        let t = unsafe { llama_cpp_sys_4::ggml_new_tensor_2d(self.ctx.as_ptr(), typ, ne0, ne1) };
171        GgmlTensor(NonNull::new(t).expect("ggml_new_tensor_2d returned null"))
172    }
173
174    /// Create a 3D tensor.
175    ///
176    /// # Panics
177    ///
178    /// Panics if ggml returns null, which in a release build means the
179    /// context's memory pool is exhausted. A debug build of ggml aborts
180    /// before returning — see [`GgmlContext::new`] for how to size the pool.
181    #[must_use]
182    pub fn new_tensor_3d(&self, typ: ggml_type, ne0: i64, ne1: i64, ne2: i64) -> GgmlTensor {
183        let t =
184            // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
185            // arguments are scalars the library validates itself.
186            unsafe { llama_cpp_sys_4::ggml_new_tensor_3d(self.ctx.as_ptr(), typ, ne0, ne1, ne2) };
187        GgmlTensor(NonNull::new(t).expect("ggml_new_tensor_3d returned null"))
188    }
189
190    /// Create a 4D tensor.
191    ///
192    /// # Panics
193    ///
194    /// Panics if ggml returns null, which in a release build means the
195    /// context's memory pool is exhausted. A debug build of ggml aborts
196    /// before returning — see [`GgmlContext::new`] for how to size the pool.
197    #[must_use]
198    pub fn new_tensor_4d(
199        &self,
200        typ: ggml_type,
201        ne0: i64,
202        ne1: i64,
203        ne2: i64,
204        ne3: i64,
205    ) -> GgmlTensor {
206        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
207        // arguments are scalars the library validates itself.
208        let t = unsafe {
209            llama_cpp_sys_4::ggml_new_tensor_4d(self.ctx.as_ptr(), typ, ne0, ne1, ne2, ne3)
210        };
211        GgmlTensor(NonNull::new(t).expect("ggml_new_tensor_4d returned null"))
212    }
213
214    /// Create a tensor with the same shape and type as another.
215    ///
216    /// # Panics
217    ///
218    /// Panics if ggml returns null, which in a release build means the
219    /// context's memory pool is exhausted. A debug build of ggml aborts
220    /// before returning — see [`GgmlContext::new`] for how to size the pool.
221    #[must_use]
222    pub fn dup_tensor(&self, src: &GgmlTensor) -> GgmlTensor {
223        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
224        // arguments are scalars the library validates itself.
225        let t = unsafe { llama_cpp_sys_4::ggml_dup_tensor(self.ctx.as_ptr(), src.0.as_ptr()) };
226        GgmlTensor(NonNull::new(t).expect("ggml_dup_tensor returned null"))
227    }
228
229    /// Create a new tensor with arbitrary dimensions.
230    ///
231    /// # Panics
232    ///
233    /// Panics if ggml returns null, which in a release build means the
234    /// context's memory pool is exhausted. A debug build of ggml aborts
235    /// before returning — see [`GgmlContext::new`] for how to size the pool.
236    #[must_use]
237    pub fn new_tensor(&self, typ: ggml_type, ne: &[i64]) -> GgmlTensor {
238        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
239        // arguments are scalars the library validates itself.
240        let t = unsafe {
241            llama_cpp_sys_4::ggml_new_tensor(
242                self.ctx.as_ptr(),
243                typ,
244                // ggml caps dimensions at GGML_MAX_DIMS (4); anything longer is
245                // rejected by ggml itself, so this cannot truncate meaningfully.
246                i32::try_from(ne.len()).unwrap_or(i32::MAX),
247                ne.as_ptr(),
248            )
249        };
250        GgmlTensor(NonNull::new(t).expect("ggml_new_tensor returned null"))
251    }
252
253    // ── Tensor operations (build graph nodes) ────────────────
254
255    /// Element-wise addition: `a + b`
256    ///
257    /// # Panics
258    ///
259    /// Panics if ggml returns null, which in a release build means the
260    /// context's memory pool is exhausted. A debug build of ggml aborts
261    /// before returning — see [`GgmlContext::new`] for how to size the pool.
262    #[must_use]
263    pub fn add(&self, a: &GgmlTensor, b: &GgmlTensor) -> GgmlTensor {
264        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
265        // arguments are scalars the library validates itself.
266        let t = unsafe { llama_cpp_sys_4::ggml_add(self.ctx.as_ptr(), a.0.as_ptr(), b.0.as_ptr()) };
267        GgmlTensor(NonNull::new(t).expect("ggml_add returned null"))
268    }
269
270    /// Matrix multiplication: `a @ b`
271    ///
272    /// # Panics
273    ///
274    /// Panics if ggml returns null, which in a release build means the
275    /// context's memory pool is exhausted. A debug build of ggml aborts
276    /// before returning — see [`GgmlContext::new`] for how to size the pool.
277    #[must_use]
278    pub fn mul_mat(&self, a: &GgmlTensor, b: &GgmlTensor) -> GgmlTensor {
279        let t =
280            // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
281            // arguments are scalars the library validates itself.
282            unsafe { llama_cpp_sys_4::ggml_mul_mat(self.ctx.as_ptr(), a.0.as_ptr(), b.0.as_ptr()) };
283        GgmlTensor(NonNull::new(t).expect("ggml_mul_mat returned null"))
284    }
285
286    /// Scale tensor: `a * s`
287    ///
288    /// # Panics
289    ///
290    /// Panics if ggml returns null, which in a release build means the
291    /// context's memory pool is exhausted. A debug build of ggml aborts
292    /// before returning — see [`GgmlContext::new`] for how to size the pool.
293    #[must_use]
294    pub fn scale(&self, a: &GgmlTensor, s: f32) -> GgmlTensor {
295        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
296        // arguments are scalars the library validates itself.
297        let t = unsafe { llama_cpp_sys_4::ggml_scale(self.ctx.as_ptr(), a.0.as_ptr(), s) };
298        GgmlTensor(NonNull::new(t).expect("ggml_scale returned null"))
299    }
300
301    /// Cast tensor to a different type.
302    ///
303    /// # Panics
304    ///
305    /// Panics if ggml returns null, which in a release build means the
306    /// context's memory pool is exhausted. A debug build of ggml aborts
307    /// before returning — see [`GgmlContext::new`] for how to size the pool.
308    #[must_use]
309    pub fn cast(&self, a: &GgmlTensor, typ: ggml_type) -> GgmlTensor {
310        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
311        // arguments are scalars the library validates itself.
312        let t = unsafe { llama_cpp_sys_4::ggml_cast(self.ctx.as_ptr(), a.0.as_ptr(), typ) };
313        GgmlTensor(NonNull::new(t).expect("ggml_cast returned null"))
314    }
315
316    /// Make tensor contiguous in memory.
317    ///
318    /// # Panics
319    ///
320    /// Panics if ggml returns null, which in a release build means the
321    /// context's memory pool is exhausted. A debug build of ggml aborts
322    /// before returning — see [`GgmlContext::new`] for how to size the pool.
323    #[must_use]
324    pub fn cont(&self, a: &GgmlTensor) -> GgmlTensor {
325        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
326        // arguments are scalars the library validates itself.
327        let t = unsafe { llama_cpp_sys_4::ggml_cont(self.ctx.as_ptr(), a.0.as_ptr()) };
328        GgmlTensor(NonNull::new(t).expect("ggml_cont returned null"))
329    }
330
331    /// Transpose a tensor.
332    ///
333    /// # Panics
334    ///
335    /// Panics if ggml returns null, which in a release build means the
336    /// context's memory pool is exhausted. A debug build of ggml aborts
337    /// before returning — see [`GgmlContext::new`] for how to size the pool.
338    #[must_use]
339    pub fn transpose(&self, a: &GgmlTensor) -> GgmlTensor {
340        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
341        // arguments are scalars the library validates itself.
342        let t = unsafe { llama_cpp_sys_4::ggml_transpose(self.ctx.as_ptr(), a.0.as_ptr()) };
343        GgmlTensor(NonNull::new(t).expect("ggml_transpose returned null"))
344    }
345
346    /// Reshape to 1D.
347    ///
348    /// # Panics
349    ///
350    /// Panics if ggml returns null, which in a release build means the
351    /// context's memory pool is exhausted. A debug build of ggml aborts
352    /// before returning — see [`GgmlContext::new`] for how to size the pool.
353    #[must_use]
354    pub fn reshape_1d(&self, a: &GgmlTensor, ne0: i64) -> GgmlTensor {
355        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
356        // arguments are scalars the library validates itself.
357        let t = unsafe { llama_cpp_sys_4::ggml_reshape_1d(self.ctx.as_ptr(), a.0.as_ptr(), ne0) };
358        GgmlTensor(NonNull::new(t).expect("ggml_reshape_1d returned null"))
359    }
360
361    /// Reshape to 2D.
362    ///
363    /// # Panics
364    ///
365    /// Panics if ggml returns null, which in a release build means the
366    /// context's memory pool is exhausted. A debug build of ggml aborts
367    /// before returning — see [`GgmlContext::new`] for how to size the pool.
368    #[must_use]
369    pub fn reshape_2d(&self, a: &GgmlTensor, ne0: i64, ne1: i64) -> GgmlTensor {
370        let t =
371            // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
372            // arguments are scalars the library validates itself.
373            unsafe { llama_cpp_sys_4::ggml_reshape_2d(self.ctx.as_ptr(), a.0.as_ptr(), ne0, ne1) };
374        GgmlTensor(NonNull::new(t).expect("ggml_reshape_2d returned null"))
375    }
376
377    /// Create a 1D view of a tensor.
378    ///
379    /// # Panics
380    ///
381    /// Panics if ggml returns null, which in a release build means the
382    /// context's memory pool is exhausted. A debug build of ggml aborts
383    /// before returning — see [`GgmlContext::new`] for how to size the pool.
384    #[must_use]
385    pub fn view_1d(&self, a: &GgmlTensor, ne0: i64, offset: usize) -> GgmlTensor {
386        let t =
387            // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
388            // arguments are scalars the library validates itself.
389            unsafe { llama_cpp_sys_4::ggml_view_1d(self.ctx.as_ptr(), a.0.as_ptr(), ne0, offset) };
390        GgmlTensor(NonNull::new(t).expect("ggml_view_1d returned null"))
391    }
392
393    // ── Graph creation ───────────────────────────────────────
394
395    /// Create a new computation graph.
396    ///
397    /// # Panics
398    ///
399    /// Panics if ggml returns null, which in a release build means the
400    /// context's memory pool is exhausted. A debug build of ggml aborts
401    /// before returning — see [`GgmlContext::new`] for how to size the pool.
402    #[must_use]
403    pub fn new_graph(&self) -> GgmlGraph {
404        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
405        // arguments are scalars the library validates itself.
406        let g = unsafe { llama_cpp_sys_4::ggml_new_graph(self.ctx.as_ptr()) };
407        GgmlGraph(NonNull::new(g).expect("ggml_new_graph returned null"))
408    }
409
410    // ── Tensor iteration ─────────────────────────────────────
411
412    /// Get the first tensor in this context.
413    #[must_use]
414    pub fn first_tensor(&self) -> Option<GgmlTensor> {
415        // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
416        // arguments are scalars the library validates itself.
417        let t = unsafe { llama_cpp_sys_4::ggml_get_first_tensor(self.ctx.as_ptr()) };
418        NonNull::new(t).map(GgmlTensor)
419    }
420
421    /// Get the next tensor after `tensor` in this context.
422    #[must_use]
423    pub fn next_tensor(&self, tensor: &GgmlTensor) -> Option<GgmlTensor> {
424        let t =
425            // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
426            // arguments are scalars the library validates itself.
427            unsafe { llama_cpp_sys_4::ggml_get_next_tensor(self.ctx.as_ptr(), tensor.0.as_ptr()) };
428        NonNull::new(t).map(GgmlTensor)
429    }
430}
431
432impl Drop for GgmlContext {
433    fn drop(&mut self) {
434        // SAFETY: this runs once from `Drop` on a non-null handle, and nothing reads it
435        // afterwards.
436        unsafe { llama_cpp_sys_4::ggml_free(self.ctx.as_ptr()) }
437    }
438}
439
440// ── Tensor ──────────────────────────────────────────────────
441
442/// A wrapper around a `ggml_tensor` pointer.
443///
444/// Tensors are owned by their `GgmlContext` and must not outlive it.
445/// This wrapper does NOT free the tensor on drop.
446#[derive(Clone, Copy)]
447pub struct GgmlTensor(pub(crate) NonNull<llama_cpp_sys_4::ggml_tensor>);
448
449impl GgmlTensor {
450    /// Get the raw tensor pointer.
451    #[must_use]
452    pub fn as_ptr(&self) -> *mut llama_cpp_sys_4::ggml_tensor {
453        self.0.as_ptr()
454    }
455
456    /// Set the tensor's name.
457    ///
458    /// # Panics
459    ///
460    /// Panics if ggml returns null, which in a release build means the
461    /// context's memory pool is exhausted. A debug build of ggml aborts
462    /// before returning — see [`GgmlContext::new`] for how to size the pool.
463    pub fn set_name(&self, name: &str) {
464        let c_name = std::ffi::CString::new(name).expect("name contains null bytes");
465        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
466        // the library owns whatever it returns.
467        unsafe { llama_cpp_sys_4::ggml_set_name(self.0.as_ptr(), c_name.as_ptr()) };
468    }
469
470    /// Get the number of elements.
471    #[must_use]
472    pub fn nelements(&self) -> i64 {
473        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
474        // the library owns whatever it returns.
475        unsafe { llama_cpp_sys_4::ggml_nelements(self.0.as_ptr()) }
476    }
477
478    /// Get the total size in bytes.
479    #[must_use]
480    pub fn nbytes(&self) -> usize {
481        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
482        // the library owns whatever it returns.
483        unsafe { llama_cpp_sys_4::ggml_nbytes(self.0.as_ptr()) }
484    }
485
486    /// Get the element size in bytes.
487    #[must_use]
488    pub fn element_size(&self) -> usize {
489        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
490        // the library owns whatever it returns.
491        unsafe { llama_cpp_sys_4::ggml_element_size(self.0.as_ptr()) }
492    }
493
494    /// Get the tensor type.
495    #[must_use]
496    pub fn typ(&self) -> ggml_type {
497        // SAFETY: `GgmlTensor` wraps a non-null `ggml_tensor` owned by the context that
498        // created it, and the type's docs require the tensor not to outlive that
499        // context. Reading a field is a plain load from that live allocation.
500        unsafe { (*self.0.as_ptr()).type_ }
501    }
502
503    /// Get the tensor dimensions (ne).
504    #[must_use]
505    pub fn ne(&self) -> [i64; 4] {
506        // SAFETY: `GgmlTensor` wraps a non-null `ggml_tensor` owned by the context that
507        // created it, and the type's docs require the tensor not to outlive that
508        // context. Reading a field is a plain load from that live allocation.
509        unsafe { (*self.0.as_ptr()).ne }
510    }
511
512    /// Get the tensor name.
513    #[must_use]
514    pub fn name(&self) -> &str {
515        // SAFETY: the pointer is non-null and points at a NUL-terminated string the
516        // library owns, which outlives the borrow taken here.
517        unsafe {
518            let ptr = (*self.0.as_ptr()).name.as_ptr();
519            CStr::from_ptr(ptr).to_str().unwrap_or("")
520        }
521    }
522}
523
524impl std::fmt::Debug for GgmlTensor {
525    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
526        let ne = self.ne();
527        write!(
528            f,
529            "GgmlTensor({:?}, [{}, {}, {}, {}], {} bytes)",
530            self.name(),
531            ne[0],
532            ne[1],
533            ne[2],
534            ne[3],
535            self.nbytes()
536        )
537    }
538}
539
540// ── Graph ───────────────────────────────────────────────────
541
542/// A wrapper around `ggml_cgraph`.
543///
544/// Graphs are owned by their `GgmlContext` and must not outlive it.
545pub struct GgmlGraph(NonNull<llama_cpp_sys_4::ggml_cgraph>);
546
547impl std::fmt::Debug for GgmlGraph {
548    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549        f.debug_struct("GgmlGraph").finish()
550    }
551}
552
553impl GgmlGraph {
554    /// Get the raw graph pointer.
555    pub fn as_ptr(&mut self) -> *mut llama_cpp_sys_4::ggml_cgraph {
556        self.0.as_ptr()
557    }
558
559    /// Add a tensor and its dependencies to the forward computation graph.
560    pub fn build_forward(&mut self, tensor: &GgmlTensor) {
561        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
562        // the library owns whatever it returns.
563        unsafe { llama_cpp_sys_4::ggml_build_forward_expand(self.0.as_ptr(), tensor.0.as_ptr()) }
564    }
565
566    /// Get a node (output tensor) by index. Use -1 for the last node.
567    ///
568    /// # Panics
569    ///
570    /// Panics if ggml returns null, which in a release build means the
571    /// context's memory pool is exhausted. A debug build of ggml aborts
572    /// before returning — see [`GgmlContext::new`] for how to size the pool.
573    #[must_use]
574    pub fn node(&mut self, i: i32) -> GgmlTensor {
575        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
576        // the library owns whatever it returns.
577        let t = unsafe { llama_cpp_sys_4::ggml_graph_node(self.0.as_ptr(), i) };
578        GgmlTensor(NonNull::new(t).expect("graph_node returned null"))
579    }
580}
581
582// ── Backend ─────────────────────────────────────────────────
583
584/// A safe wrapper around `ggml_backend`.
585#[derive(Debug)]
586pub struct GgmlBackend {
587    backend: llama_cpp_sys_4::ggml_backend_t,
588}
589
590impl GgmlBackend {
591    /// Create a CPU backend.
592    ///
593    /// # Panics
594    ///
595    /// Panics if ggml cannot initialise the CPU backend, which would mean the
596    /// build has no CPU backend compiled in.
597    #[must_use]
598    pub fn cpu() -> Self {
599        // SAFETY: no borrowed pointer is involved beyond arguments alive for the call;
600        // the returned handle is checked for null before being wrapped.
601        let backend = unsafe { llama_cpp_sys_4::ggml_backend_cpu_init() };
602        assert!(!backend.is_null(), "ggml_backend_cpu_init returned null");
603        Self { backend }
604    }
605
606    /// Set the number of threads for the CPU backend.
607    pub fn set_n_threads(&self, n_threads: i32) {
608        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
609        // the library owns whatever it returns.
610        unsafe { llama_cpp_sys_4::ggml_backend_cpu_set_n_threads(self.backend, n_threads) }
611    }
612
613    /// Allocate all tensors in a context on this backend.
614    ///
615    /// Returns the buffer handle which must be kept alive.
616    #[must_use]
617    pub fn alloc_ctx_tensors(
618        &self,
619        ctx: &GgmlContext,
620    ) -> *mut llama_cpp_sys_4::ggml_backend_buffer {
621        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
622        // the library owns whatever it returns.
623        unsafe { llama_cpp_sys_4::ggml_backend_alloc_ctx_tensors(ctx.as_ptr(), self.backend) }
624    }
625
626    /// Compute a graph.
627    pub fn graph_compute(&self, graph: &mut GgmlGraph) {
628        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
629        // the library owns whatever it returns.
630        unsafe { llama_cpp_sys_4::ggml_backend_graph_compute(self.backend, graph.as_ptr()) };
631    }
632
633    /// Get the default buffer type for this backend.
634    #[must_use]
635    pub fn default_buffer_type(&self) -> llama_cpp_sys_4::ggml_backend_buffer_type_t {
636        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
637        // the library owns whatever it returns.
638        unsafe { llama_cpp_sys_4::ggml_backend_get_default_buffer_type(self.backend) }
639    }
640
641    /// Get the raw backend pointer.
642    #[must_use]
643    pub fn as_ptr(&self) -> llama_cpp_sys_4::ggml_backend_t {
644        self.backend
645    }
646}
647
648impl Drop for GgmlBackend {
649    fn drop(&mut self) {
650        // SAFETY: this runs once from `Drop` on a non-null handle, and nothing reads it
651        // afterwards.
652        unsafe { llama_cpp_sys_4::ggml_backend_free(self.backend) }
653    }
654}
655
656// ── Graph allocator ─────────────────────────────────────────
657
658/// A safe wrapper around `ggml_gallocr`.
659#[derive(Debug)]
660pub struct GgmlAllocr {
661    alloc: llama_cpp_sys_4::ggml_gallocr_t,
662}
663
664impl GgmlAllocr {
665    /// Create a new graph allocator for the given backend.
666    ///
667    /// # Panics
668    ///
669    /// Panics if ggml cannot allocate the graph allocator.
670    #[must_use]
671    pub fn new(backend: &GgmlBackend) -> Self {
672        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
673        // the library owns whatever it returns.
674        let alloc = unsafe { llama_cpp_sys_4::ggml_gallocr_new(backend.default_buffer_type()) };
675        assert!(!alloc.is_null(), "ggml_gallocr_new returned null");
676        Self { alloc }
677    }
678
679    /// Allocate all tensors in a graph.
680    pub fn alloc_graph(&self, graph: &mut GgmlGraph) -> bool {
681        // SAFETY: the arguments are either scalars or pointers that outlive the call, and
682        // the library owns whatever it returns.
683        unsafe { llama_cpp_sys_4::ggml_gallocr_alloc_graph(self.alloc, graph.as_ptr()) }
684    }
685}
686
687impl Drop for GgmlAllocr {
688    fn drop(&mut self) {
689        // SAFETY: this runs once from `Drop` on a non-null handle, and nothing reads it
690        // afterwards.
691        unsafe { llama_cpp_sys_4::ggml_gallocr_free(self.alloc) }
692    }
693}
694
695// ── Utility functions ───────────────────────────────────────
696
697/// Set tensor data from a byte slice.
698///
699/// # Safety
700///
701/// The tensor must be allocated and the data must be the correct size.
702pub unsafe fn tensor_set(tensor: &GgmlTensor, data: &[u8]) {
703    llama_cpp_sys_4::ggml_backend_tensor_set(
704        tensor.0.as_ptr(),
705        data.as_ptr().cast(),
706        0,
707        data.len(),
708    );
709}
710
711/// Get tensor data into a byte slice.
712///
713/// # Safety
714///
715/// The tensor must be allocated and the buffer must be large enough.
716pub unsafe fn tensor_get(tensor: &GgmlTensor, data: &mut [u8]) {
717    llama_cpp_sys_4::ggml_backend_tensor_get(
718        tensor.0.as_ptr(),
719        data.as_mut_ptr().cast(),
720        0,
721        data.len(),
722    );
723}
724
725/// Free a backend buffer.
726///
727/// # Safety
728///
729/// The buffer must be valid and not already freed.
730pub unsafe fn buffer_free(buffer: *mut llama_cpp_sys_4::ggml_backend_buffer) {
731    llama_cpp_sys_4::ggml_backend_buffer_free(buffer);
732}
733
734/// Get the overhead in bytes for tensor metadata.
735#[must_use]
736pub fn tensor_overhead() -> usize {
737    // SAFETY: the arguments are either scalars or pointers that outlive the call, and
738    // the library owns whatever it returns.
739    unsafe { llama_cpp_sys_4::ggml_tensor_overhead() }
740}
741
742/// Get the overhead in bytes for a computation graph.
743#[must_use]
744pub fn graph_overhead() -> usize {
745    // SAFETY: the arguments are either scalars or pointers that outlive the call, and
746    // the library owns whatever it returns.
747    unsafe { llama_cpp_sys_4::ggml_graph_overhead() }
748}
749
750/// Check if a type is quantized.
751#[must_use]
752pub fn is_quantized(typ: ggml_type) -> bool {
753    // SAFETY: the arguments are either scalars or pointers that outlive the call, and
754    // the library owns whatever it returns.
755    unsafe { llama_cpp_sys_4::ggml_is_quantized(typ) }
756}
757
758/// Get the name of a ggml type.
759#[must_use]
760pub fn type_name(typ: ggml_type) -> &'static str {
761    // SAFETY: `ggml_type_name` takes a plain enum and returns a pointer to a
762    // static NUL-terminated string compiled into ggml, so it outlives any
763    // borrow. The null case is handled below rather than assumed away.
764    unsafe {
765        let ptr = llama_cpp_sys_4::ggml_type_name(typ);
766        if ptr.is_null() {
767            "unknown"
768        } else {
769            CStr::from_ptr(ptr).to_str().unwrap_or("unknown")
770        }
771    }
772}