Skip to main content

mlx_c_rust/runtime/
platform.rs

1//! The remaining MLX platform surfaces: Metal queries, wired memory limits,
2//! CUDA availability, and the distributed collective domain.
3
4use std::ffi::CString;
5
6use crate::error::check_status;
7use crate::raw;
8use crate::{MlxArray, MlxCError, MlxDtype, MlxStream};
9
10/// The linked MLX-Metal backend's metallib path.
11///
12/// # Errors
13/// Returns the captured MLX-C description when the query fails.
14pub fn metallib_path() -> Result<String, MlxCError> {
15    let operation: &'static str = "read the MLX metallib path";
16    let mut output = crate::MlxString::empty();
17    // SAFETY: The output pointer is valid writable storage for one string
18    // handle.
19    let status = unsafe { raw::mlx_metal_get_metallib_path(output.raw_mut()) };
20    check_status(status, operation)?;
21    output.require_populated(operation)?;
22    Ok(output.to_string_lossy())
23}
24
25/// Whether the MLX-Metal backend is available in this process.
26///
27/// # Errors
28/// Returns the captured MLX-C description when the query fails.
29pub fn metal_is_available() -> Result<bool, MlxCError> {
30    let operation: &'static str = "query MLX Metal availability";
31    let mut available = false;
32    // SAFETY: The output pointer is valid writable storage.
33    let status = unsafe { raw::mlx_metal_is_available(&mut available) };
34    check_status(status, operation)?;
35    Ok(available)
36}
37
38/// Raises the wired-memory ceiling for MLX allocations.
39///
40/// Returns the previously configured wired limit.
41///
42/// # Errors
43/// Returns the captured MLX-C description when the platform rejects the
44/// request.
45pub fn set_wired_limit(limit_bytes: usize) -> Result<usize, MlxCError> {
46    let operation: &'static str = "set the MLX wired memory limit";
47    let mut previous_limit: usize = 0;
48    // SAFETY: The output pointer is valid writable storage and the limit is
49    // a plain value.
50    let status = unsafe { raw::mlx_set_wired_limit(&mut previous_limit, limit_bytes) };
51    check_status(status, operation)?;
52    Ok(previous_limit)
53}
54
55/// Whether the CUDA backend reports availability in this process.
56///
57/// # Errors
58/// Returns the captured MLX-C description when the query fails.
59pub fn cuda_is_available() -> Result<bool, MlxCError> {
60    let operation: &'static str = "query MLX CUDA availability";
61    let mut available = false;
62    // SAFETY: The output pointer is valid writable storage.
63    let status = unsafe { raw::mlx_cuda_is_available(&mut available) };
64    check_status(status, operation)?;
65    Ok(available)
66}
67
68/// A backend name argument translated into a C string.
69fn backend_argument(backend: &str, operation: &'static str) -> Result<CString, MlxCError> {
70    CString::new(backend).map_err(|_| MlxCError {
71        operation,
72        description: "backend name contains an interior null byte".to_owned(),
73    })
74}
75
76/// Whether MLX's distributed backend is initialized in this process.
77///
78/// # Errors
79/// Returns the captured MLX-C description when the query fails.
80pub fn distributed_is_available(backend: &str) -> Result<bool, MlxCError> {
81    let operation: &'static str = "query MLX distributed availability";
82    let backend_argument = backend_argument(backend, operation)?;
83    // SAFETY: The backend name outlives the call.
84    let available = unsafe { raw::mlx_distributed_is_available(backend_argument.as_ptr()) };
85    Ok(available)
86}
87
88/// Initializes MLX's distributed backend.
89///
90/// # Errors
91/// Returns the captured MLX-C description when initialization fails.
92pub fn initialize_distributed(
93    strict: bool,
94    backend: &str,
95) -> Result<MlxDistributedGroup, MlxCError> {
96    let operation: &'static str = "initialize MLX distributed";
97    let backend_argument = backend_argument(backend, operation)?;
98    let mut output = MlxDistributedGroup::empty();
99    // SAFETY: The backend name outlives the call and the output pointer is
100    // valid writable storage.
101    let status =
102        unsafe { raw::mlx_distributed_init(output.raw_mut(), strict, backend_argument.as_ptr()) };
103    check_status(status, operation)?;
104    output.require_populated(operation)?;
105    Ok(output)
106}
107
108/// Owned MLX distributed group handle released exactly once.
109#[derive(Debug)]
110pub struct MlxDistributedGroup(raw::mlx_distributed_group);
111
112impl MlxDistributedGroup {
113    /// Creates an empty group handle.
114    pub(crate) fn empty() -> Self {
115        // SAFETY: The returned handle enters RAII ownership immediately.
116        let raw_group = unsafe { raw::mlx_distributed_group_new() };
117        Self(raw_group)
118    }
119
120    /// This process's rank in the group.
121    #[must_use]
122    pub fn rank(&self) -> i32 {
123        // SAFETY: `self` owns a live group handle.
124        unsafe { raw::mlx_distributed_group_rank(self.0) }
125    }
126
127    /// The group's world size.
128    #[must_use]
129    pub fn size(&self) -> i32 {
130        // SAFETY: `self` owns a live group handle.
131        unsafe { raw::mlx_distributed_group_size(self.0) }
132    }
133
134    /// Splits the group by color and key.
135    ///
136    /// # Errors
137    /// Returns the captured MLX-C description when the split fails.
138    pub fn split(&self, color: i32, key: i32) -> Result<Self, MlxCError> {
139        let operation: &'static str = "split an MLX distributed group";
140        let mut output = Self::empty();
141        // SAFETY: `self` owns a live group and the output pointer is valid
142        // writable storage.
143        let status =
144            unsafe { raw::mlx_distributed_group_split(output.raw_mut(), self.0, color, key) };
145        check_status(status, operation)?;
146        output.require_populated(operation)?;
147        Ok(output)
148    }
149
150    pub(crate) fn raw_mut(&mut self) -> *mut raw::mlx_distributed_group {
151        &mut self.0
152    }
153
154    pub(crate) const fn raw(&self) -> raw::mlx_distributed_group {
155        self.0
156    }
157
158    fn require_populated(&self, operation: &'static str) -> Result<(), MlxCError> {
159        if self.0.ctx.is_null() {
160            return Err(MlxCError {
161                operation,
162                description: "MLX returned an empty distributed group handle".to_owned(),
163            });
164        }
165        Ok(())
166    }
167}
168
169impl Drop for MlxDistributedGroup {
170    fn drop(&mut self) {
171        // SAFETY: This owner releases its live group exactly once and never
172        // accesses the handle afterward.
173        unsafe {
174            raw::mlx_distributed_group_free(self.0);
175        }
176    }
177}
178
179impl MlxArray {
180    /// Gathers every rank's copy of the array in the group.
181    ///
182    /// # Errors
183    /// Returns the captured MLX-C description when the collective fails.
184    pub fn all_gather(
185        &self,
186        group: &MlxDistributedGroup,
187        stream: &MlxStream,
188    ) -> Result<Self, MlxCError> {
189        let operation: &'static str = "run the MLX distributed all-gather";
190        let mut output = Self::empty();
191        // SAFETY: All handles are live and the output pointer is valid
192        // writable storage.
193        let status = unsafe {
194            raw::mlx_distributed_all_gather(output.raw_mut(), self.raw(), group.raw(), stream.raw())
195        };
196        check_status(status, operation)?;
197        output.require_populated(operation)?;
198        Ok(output)
199    }
200
201    /// Reduces the array across the group with a maximum.
202    ///
203    /// # Errors
204    /// Returns the captured MLX-C description when the collective fails.
205    pub fn all_max(
206        &self,
207        group: &MlxDistributedGroup,
208        stream: &MlxStream,
209    ) -> Result<Self, MlxCError> {
210        let operation: &'static str = "run the MLX distributed all-max";
211        let mut output = Self::empty();
212        // SAFETY: All handles are live and the output pointer is valid
213        // writable storage.
214        let status = unsafe {
215            raw::mlx_distributed_all_max(output.raw_mut(), self.raw(), group.raw(), stream.raw())
216        };
217        check_status(status, operation)?;
218        output.require_populated(operation)?;
219        Ok(output)
220    }
221
222    /// Reduces the array across the group with a minimum.
223    ///
224    /// # Errors
225    /// Returns the captured MLX-C description when the collective fails.
226    pub fn all_min(
227        &self,
228        group: &MlxDistributedGroup,
229        stream: &MlxStream,
230    ) -> Result<Self, MlxCError> {
231        let operation: &'static str = "run the MLX distributed all-min";
232        let mut output = Self::empty();
233        // SAFETY: All handles are live and the output pointer is valid
234        // writable storage.
235        let status = unsafe {
236            raw::mlx_distributed_all_min(output.raw_mut(), self.raw(), group.raw(), stream.raw())
237        };
238        check_status(status, operation)?;
239        output.require_populated(operation)?;
240        Ok(output)
241    }
242
243    /// Reduces the array across the group with a sum.
244    ///
245    /// # Errors
246    /// Returns the captured MLX-C description when the collective fails.
247    pub fn all_sum(
248        &self,
249        group: &MlxDistributedGroup,
250        stream: &MlxStream,
251    ) -> Result<Self, MlxCError> {
252        let operation: &'static str = "run the MLX distributed all-sum";
253        let mut output = Self::empty();
254        // SAFETY: All handles are live and the output pointer is valid
255        // writable storage.
256        let status = unsafe {
257            raw::mlx_distributed_all_sum(output.raw_mut(), self.raw(), group.raw(), stream.raw())
258        };
259        check_status(status, operation)?;
260        output.require_populated(operation)?;
261        Ok(output)
262    }
263
264    /// Scatters the array across the group with a sum.
265    ///
266    /// # Errors
267    /// Returns the captured MLX-C description when the collective fails.
268    pub fn sum_scatter(
269        &self,
270        group: &MlxDistributedGroup,
271        stream: &MlxStream,
272    ) -> Result<Self, MlxCError> {
273        let operation: &'static str = "run the MLX distributed sum-scatter";
274        let mut output = Self::empty();
275        // SAFETY: All handles are live and the output pointer is valid
276        // writable storage.
277        let status = unsafe {
278            raw::mlx_distributed_sum_scatter(
279                output.raw_mut(),
280                self.raw(),
281                group.raw(),
282                stream.raw(),
283            )
284        };
285        check_status(status, operation)?;
286        output.require_populated(operation)?;
287        Ok(output)
288    }
289
290    /// Sends this array to the destination rank.
291    ///
292    /// # Errors
293    /// Returns the captured MLX-C description when the send fails.
294    pub fn send_to_rank(
295        &self,
296        destination: i32,
297        group: &MlxDistributedGroup,
298        stream: &MlxStream,
299    ) -> Result<Self, MlxCError> {
300        let operation: &'static str = "run the MLX distributed send";
301        let mut output = Self::empty();
302        // SAFETY: All handles are live and the output pointer is valid
303        // writable storage.
304        let status = unsafe {
305            raw::mlx_distributed_send(
306                output.raw_mut(),
307                self.raw(),
308                destination,
309                group.raw(),
310                stream.raw(),
311            )
312        };
313        check_status(status, operation)?;
314        output.require_populated(operation)?;
315        Ok(output)
316    }
317
318    /// Receives an array of the given shape from the source rank.
319    ///
320    /// # Errors
321    /// Returns the captured MLX-C description when the receive fails.
322    pub fn receive_from_rank(
323        shape: &[i32],
324        dtype: MlxDtype,
325        source: i32,
326        group: &MlxDistributedGroup,
327        stream: &MlxStream,
328    ) -> Result<Self, MlxCError> {
329        let operation: &'static str = "run the MLX distributed receive";
330        let mut output = Self::empty();
331        // SAFETY: All handles are live, the shape slice remains valid for the
332        // call, and the output pointer is valid writable storage.
333        let status = unsafe {
334            raw::mlx_distributed_recv(
335                output.raw_mut(),
336                shape.as_ptr(),
337                shape.len(),
338                dtype.to_raw(),
339                source,
340                group.raw(),
341                stream.raw(),
342            )
343        };
344        check_status(status, operation)?;
345        output.require_populated(operation)?;
346        Ok(output)
347    }
348
349    /// Receives an array matching this array's shape from the source rank.
350    ///
351    /// # Errors
352    /// Returns the captured MLX-C description when the receive fails.
353    pub fn receive_like(
354        &self,
355        source: i32,
356        group: &MlxDistributedGroup,
357        stream: &MlxStream,
358    ) -> Result<Self, MlxCError> {
359        let operation: &'static str = "run the MLX distributed receive-like";
360        let mut output = Self::empty();
361        // SAFETY: All handles are live and the output pointer is valid
362        // writable storage.
363        let status = unsafe {
364            raw::mlx_distributed_recv_like(
365                output.raw_mut(),
366                self.raw(),
367                source,
368                group.raw(),
369                stream.raw(),
370            )
371        };
372        check_status(status, operation)?;
373        output.require_populated(operation)?;
374        Ok(output)
375    }
376}