1use mlx_sys::mlx_closure_value_and_grad;
54
55use crate::{
56 error::{get_and_clear_closure_error, Result},
57 module::ModuleParamRef,
58 utils::{guard::Guarded, Closure, VectorArray, SUCCESS},
59 Array,
60};
61
62pub mod compile;
63mod grad;
64mod keyed_value_and_grad;
65mod value_and_grad;
66
67pub use grad::*;
68pub use keyed_value_and_grad::*;
69pub use value_and_grad::*;
70
71pub fn eval<'a>(outputs: impl IntoIterator<Item = &'a Array>) -> Result<()> {
73 let vec = VectorArray::try_from_iter(outputs.into_iter())?;
74 <() as Guarded>::try_from_op(|_| unsafe { mlx_sys::mlx_eval(vec.as_ptr()) })
75}
76
77pub fn eval_params(params: ModuleParamRef<'_>) -> Result<()> {
81 eval(params.flatten().values().copied())
82}
83
84pub fn async_eval<'a>(outputs: impl IntoIterator<Item = &'a Array>) -> Result<()> {
88 let vec = VectorArray::try_from_iter(outputs.into_iter())?;
89 <() as Guarded>::try_from_op(|_| unsafe { mlx_sys::mlx_async_eval(vec.as_ptr()) })
90}
91
92pub fn async_eval_params(params: ModuleParamRef<'_>) -> Result<()> {
96 async_eval(params.flatten().values().copied())
97}
98
99#[inline]
100fn jvp_inner(
101 closure: Closure<'_>,
102 primals: &[Array],
103 tangents: &[Array],
104) -> Result<(Vec<Array>, Vec<Array>)> {
105 let c_primals = VectorArray::try_from_iter(primals.iter())?;
106 let c_tangents = VectorArray::try_from_iter(tangents.iter())?;
107
108 <(Vec<Array>, Vec<Array>) as Guarded>::try_from_op(|(res_0, res_1)| unsafe {
109 mlx_sys::mlx_jvp(
110 res_0,
111 res_1,
112 closure.as_ptr(),
113 c_primals.as_ptr(),
114 c_tangents.as_ptr(),
115 )
116 })
117 .map_err(|e| match get_and_clear_closure_error() {
118 Some(err) => err,
119 None => e,
120 })
121}
122
123pub fn jvp<'a, F>(f: F, primals: &[Array], tangents: &[Array]) -> Result<(Vec<Array>, Vec<Array>)>
142where
143 F: FnMut(&[Array]) -> Vec<Array> + 'a,
144{
145 let closure = Closure::new(f);
146 jvp_inner(closure, primals, tangents)
147}
148
149pub fn fallible_jvp<'a, F>(
151 f: F,
152 primals: &[Array],
153 tangents: &[Array],
154) -> Result<(Vec<Array>, Vec<Array>)>
155where
156 F: FnMut(&[Array]) -> Result<Vec<Array>> + 'a,
157{
158 let closure = Closure::new_fallible(f);
159 jvp_inner(closure, primals, tangents)
160}
161
162#[inline]
163fn vjp_inner(
164 closure: Closure<'_>,
165 primals: &[Array],
166 cotangents: &[Array],
167) -> Result<(Vec<Array>, Vec<Array>)> {
168 let c_primals = VectorArray::try_from_iter(primals.iter())?;
169 let c_cotangents = VectorArray::try_from_iter(cotangents.iter())?;
170
171 <(Vec<Array>, Vec<Array>) as Guarded>::try_from_op(|(res_0, res_1)| unsafe {
172 mlx_sys::mlx_vjp(
173 res_0,
174 res_1,
175 closure.as_ptr(),
176 c_primals.as_ptr(),
177 c_cotangents.as_ptr(),
178 )
179 })
180 .map_err(|e| match get_and_clear_closure_error() {
181 Some(err) => err,
182 None => e,
183 })
184}
185
186pub fn vjp<'a, F>(f: F, primals: &[Array], cotangents: &[Array]) -> Result<(Vec<Array>, Vec<Array>)>
203where
204 F: FnMut(&[Array]) -> Vec<Array> + 'a,
205{
206 let closure = Closure::new(f);
207 vjp_inner(closure, primals, cotangents)
208}
209
210pub fn fallible_vjp<'a, F>(
212 f: F,
213 primals: &[Array],
214 cotangents: &[Array],
215) -> Result<(Vec<Array>, Vec<Array>)>
216where
217 F: FnMut(&[Array]) -> Result<Vec<Array>> + 'a,
218{
219 let closure = Closure::new_fallible(f);
220 vjp_inner(closure, primals, cotangents)
221}
222
223pub(crate) struct ClosureValueAndGrad {
224 pub(crate) c_closure_value_and_grad: mlx_closure_value_and_grad,
225}
226
227impl ClosureValueAndGrad {
228 pub fn as_ptr(&self) -> mlx_closure_value_and_grad {
229 self.c_closure_value_and_grad
230 }
231}
232
233impl Drop for ClosureValueAndGrad {
234 fn drop(&mut self) {
235 let status =
236 unsafe { mlx_sys::mlx_closure_value_and_grad_free(self.c_closure_value_and_grad) };
237 debug_assert_eq!(status, SUCCESS);
238 }
239}
240
241fn value_and_gradient(
242 value_and_grad: mlx_closure_value_and_grad,
243 arrays: impl Iterator<Item = impl AsRef<Array>>,
244) -> Result<(Vec<Array>, Vec<Array>)> {
245 let input_vector = VectorArray::try_from_iter(arrays)?;
246
247 <(Vec<Array>, Vec<Array>) as Guarded>::try_from_op(|(res_0, res_1)| unsafe {
248 mlx_sys::mlx_closure_value_and_grad_apply(
249 res_0,
250 res_1,
251 value_and_grad,
252 input_vector.as_ptr(),
253 )
254 })
255 .map_err(|e| match get_and_clear_closure_error() {
256 Some(err) => err,
257 None => e,
258 })
259}
260
261#[cfg(test)]
262mod tests {
263
264 use crate::{
265 array,
266 transforms::{jvp, vjp},
267 Array,
268 };
269
270 use super::*;
271
272 #[test]
275 fn test_jvp() {
276 let f = |inputs: &[Array]| -> Vec<Array> { vec![&inputs[0] + &inputs[1]] };
277 let x = array!(1.0f32);
278 let y = array!(1.0f32);
279 let (out, dout) = jvp(f, &[x, y], &[array!(1.0f32), array!(3.0f32)]).unwrap();
280 assert_eq!(out[0].item_exact::<f32>(), 2.0f32);
281 assert_eq!(dout[0].item_exact::<f32>(), 4.0f32);
282 }
283
284 #[test]
285 fn test_jvp_with_error() {
286 let f = |inputs: &[Array]| -> Result<Vec<Array>> {
287 inputs[0].add(&inputs[1]).map(|res| vec![res])
288 };
289
290 let x = array!(1.0f32);
292 let y = array!(1.0f32);
293 let (out, dout) = fallible_jvp(f, &[x, y], &[array!(1.0f32), array!(3.0f32)]).unwrap();
294 assert_eq!(out[0].item_exact::<f32>(), 2.0f32);
295 assert_eq!(dout[0].item_exact::<f32>(), 4.0f32);
296
297 let a = array!([1.0, 2.0, 3.0]);
300 let b = array!([4.0, 5.0]);
301 let result = fallible_jvp(f, &[a, b], &[array!(1.0f32), array!(3.0f32)]);
302 assert!(result.is_err());
303
304 let err = result.unwrap_err();
306 assert!(!err.what().contains("non-zero value"))
307 }
308
309 #[test]
310 fn test_vjp() {
311 let f = |inputs: &[Array]| -> Vec<Array> { vec![&inputs[0] + &inputs[1]] };
312 let x = array!(1.0f32);
313 let y = array!(1.0f32);
314 let primals = vec![x, y];
315 let cotangents = vec![array!(1.0f32)];
316 let (out, dout) = vjp(f, &primals, &cotangents).unwrap();
317 assert_eq!(out[0].item_exact::<f32>(), 2.0f32);
318 assert_eq!(dout[0].item_exact::<f32>(), 1.0f32);
319 }
320
321 #[test]
322 fn test_vjp_with_error() {
323 let f = |inputs: &[Array]| -> Result<Vec<Array>> {
324 inputs[0].add(&inputs[1]).map(|res| vec![res])
325 };
326
327 let x = array!(1.0f32);
329 let y = array!(1.0f32);
330 let primals = vec![x, y];
331 let cotangents = vec![array!(1.0f32)];
332 let (out, dout) = fallible_vjp(f, &primals, &cotangents).unwrap();
333 assert_eq!(out[0].item_exact::<f32>(), 2.0f32);
334 assert_eq!(dout[0].item_exact::<f32>(), 1.0f32);
335
336 let a = array!([1.0, 2.0, 3.0]);
339 let b = array!([4.0, 5.0]);
340 let result = fallible_vjp(f, &[a, b], &[array!(1.0f32)]);
341 assert!(result.is_err());
342
343 let err = result.unwrap_err();
345 assert!(!err.what().contains("non-zero value"))
346 }
347}