Skip to main content

llama_cpp_4/rpc/
backend.rs

1//! RPC backend for distributed inference
2
3use crate::rpc::error::RpcError;
4use llama_cpp_sys_4 as sys;
5use std::ffi::CString;
6use std::ptr::NonNull;
7
8/// RPC backend for distributed inference across multiple machines
9pub struct RpcBackend {
10    backend: NonNull<sys::ggml_backend>,
11    endpoint: String,
12    device: u32,
13}
14
15impl RpcBackend {
16    /// Initialize a new RPC backend for the given endpoint and remote device.
17    ///
18    /// # Arguments
19    /// * `endpoint` - The RPC server endpoint (e.g., "127.0.0.1:50052")
20    /// * `device` - Index of the device to use on the remote server. A single
21    ///   endpoint can expose several devices; pass `0` for the first.
22    ///
23    /// # Errors
24    ///
25    /// Returns [`RpcError::StringConversion`] if `endpoint` contains an interior
26    /// NUL, or [`RpcError::InitializationFailed`] if llama.cpp could not reach
27    /// the endpoint or the device index is out of range.
28    ///
29    /// # Example
30    /// ```no_run
31    /// use llama_cpp_4::rpc::RpcBackend;
32    ///
33    /// let backend = RpcBackend::init("127.0.0.1:50052", 0)?;
34    /// # Ok::<(), llama_cpp_4::rpc::RpcError>(())
35    /// ```
36    pub fn init(endpoint: &str, device: u32) -> Result<Self, RpcError> {
37        let c_endpoint = CString::new(endpoint)?;
38
39        let backend = unsafe { sys::ggml_backend_rpc_init(c_endpoint.as_ptr(), device) };
40
41        NonNull::new(backend)
42            .map(|ptr| Self {
43                backend: ptr,
44                endpoint: endpoint.to_string(),
45                device,
46            })
47            .ok_or_else(|| RpcError::InitializationFailed {
48                endpoint: endpoint.to_string(),
49            })
50    }
51
52    /// Check if a backend is an RPC backend
53    #[must_use]
54    pub fn is_rpc(&self) -> bool {
55        unsafe { sys::ggml_backend_is_rpc(self.backend.as_ptr()) }
56    }
57
58    /// Get the buffer type for this RPC backend
59    #[must_use]
60    pub fn buffer_type(&self) -> Option<NonNull<sys::ggml_backend_buffer_type>> {
61        let c_endpoint = CString::new(self.endpoint.as_str()).ok()?;
62        let buffer_type =
63            unsafe { sys::ggml_backend_rpc_buffer_type(c_endpoint.as_ptr(), self.device) };
64        NonNull::new(buffer_type)
65    }
66
67    /// Query the available memory on the remote device
68    ///
69    /// Returns (`free_memory`, `total_memory`) in bytes.
70    ///
71    /// # Errors
72    ///
73    /// Returns [`RpcError::MemoryQueryFailed`] when the server reports a total
74    /// of zero, which is how an unreachable endpoint surfaces here.
75    pub fn get_device_memory(&self) -> Result<(usize, usize), RpcError> {
76        let c_endpoint = CString::new(self.endpoint.as_str())?;
77
78        let mut free: usize = 0;
79        let mut total: usize = 0;
80
81        unsafe {
82            sys::ggml_backend_rpc_get_device_memory(
83                c_endpoint.as_ptr(),
84                self.device,
85                std::ptr::from_mut(&mut free),
86                std::ptr::from_mut(&mut total),
87            );
88        }
89
90        if total == 0 {
91            Err(RpcError::MemoryQueryFailed)
92        } else {
93            Ok((free, total))
94        }
95    }
96
97    /// Get the endpoint this backend is connected to
98    #[must_use]
99    pub fn endpoint(&self) -> &str {
100        &self.endpoint
101    }
102
103    /// Index of the remote device this backend is bound to.
104    #[must_use]
105    pub fn device(&self) -> u32 {
106        self.device
107    }
108
109    /// Get the raw backend pointer for FFI calls.
110    ///
111    /// The pointer is owned by this `RpcBackend` and is freed on drop; do not
112    /// free it, and do not use it after this value goes out of scope.
113    #[must_use]
114    pub fn as_ptr(&self) -> NonNull<sys::ggml_backend> {
115        self.backend
116    }
117}
118
119impl Drop for RpcBackend {
120    fn drop(&mut self) {
121        unsafe {
122            sys::ggml_backend_free(self.backend.as_ptr());
123        }
124    }
125}
126
127// Safety: RpcBackend can be sent between threads
128unsafe impl Send for RpcBackend {}
129// Safety: RpcBackend can be shared between threads (the C API is thread-safe)
130unsafe impl Sync for RpcBackend {}
131
132impl std::fmt::Debug for RpcBackend {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        f.debug_struct("RpcBackend")
135            .field("endpoint", &self.endpoint)
136            .field("device", &self.device)
137            .field("is_rpc", &self.is_rpc())
138            // `backend` is an opaque llama.cpp pointer with nothing useful to show.
139            .finish_non_exhaustive()
140    }
141}