1use std::fmt;
5use std::hash::{Hash, Hasher};
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::{Arc, Mutex, PoisonError};
8
9use crate::primitive::Primitive;
10use crate::transforms::IntoResult;
11use crate::tree::Tree;
12use crate::{Array, Error, Result};
13
14type Body = dyn Fn(&[Array]) -> Result<Vec<Array>> + Send + Sync;
15type VjpRule = dyn Fn(&[Array], &[Array], &[Array]) -> Result<Vec<Array>> + Send + Sync;
16type JvpRule = dyn Fn(&[Array], &[Array]) -> Result<Vec<Array>> + Send + Sync;
17type VmapRule = dyn Fn(&[Array], &[Option<usize>]) -> Result<(Vec<Array>, usize)> + Send + Sync;
18
19#[derive(Clone)]
21pub(crate) struct Rules {
22 pub(crate) body: Arc<Body>,
23 pub(crate) vjp: Option<Arc<VjpRule>>,
24 pub(crate) jvp: Option<Arc<JvpRule>>,
25 pub(crate) vmap: Option<Arc<VmapRule>>,
26}
27
28#[derive(Clone)]
31pub struct CustomCall {
32 pub(crate) rules: Arc<Rules>,
33 pub(crate) primals: usize,
34}
35
36impl PartialEq for CustomCall {
37 fn eq(&self, other: &Self) -> bool {
38 Arc::ptr_eq(&self.rules, &other.rules) && self.primals == other.primals
39 }
40}
41
42impl Eq for CustomCall {}
43
44impl Hash for CustomCall {
45 fn hash<H: Hasher>(&self, state: &mut H) {
46 (Arc::as_ptr(&self.rules) as *const () as usize).hash(state);
47 self.primals.hash(state);
48 }
49}
50
51impl fmt::Debug for CustomCall {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "CustomCall({} primals)", self.primals)
54 }
55}
56
57#[derive(Clone)]
60pub struct CustomFunctionFlat {
61 rules: Arc<Rules>,
62}
63
64pub fn custom_function_flat(
86 f: impl Fn(&[Array]) -> Result<Vec<Array>> + Send + Sync + 'static,
87) -> CustomFunctionFlat {
88 CustomFunctionFlat {
89 rules: Arc::new(Rules {
90 body: Arc::new(f),
91 vjp: None,
92 jvp: None,
93 vmap: None,
94 }),
95 }
96}
97
98impl CustomFunctionFlat {
99 fn with(self, set: impl FnOnce(&mut Rules)) -> Self {
100 let mut rules = (*self.rules).clone();
101 set(&mut rules);
102 Self {
103 rules: Arc::new(rules),
104 }
105 }
106
107 pub fn with_vjp(
110 self,
111 rule: impl Fn(&[Array], &[Array], &[Array]) -> Result<Vec<Array>> + Send + Sync + 'static,
112 ) -> Self {
113 self.with(|r| r.vjp = Some(Arc::new(rule)))
114 }
115
116 pub fn with_jvp(
119 self,
120 rule: impl Fn(&[Array], &[Array]) -> Result<Vec<Array>> + Send + Sync + 'static,
121 ) -> Self {
122 self.with(|r| r.jvp = Some(Arc::new(rule)))
123 }
124
125 pub fn with_vmap(
128 self,
129 rule: impl Fn(&[Array], &[Option<i32>]) -> Result<(Vec<Array>, i32)> + Send + Sync + 'static,
130 ) -> Self {
131 let rule = move |xs: &[Array], axes: &[Option<usize>]| {
132 let axes: Vec<Option<i32>> = axes
133 .iter()
134 .map(|a| a.map(|a| i32::try_from(a).unwrap_or(i32::MAX)))
135 .collect();
136 let (out, axis) = rule(xs, &axes)?;
137 let axis = usize::try_from(axis).map_err(|_| {
138 Error::InvalidArgument(format!(
139 "custom_function: the vmap rule returned the negative output axis {axis}"
140 ))
141 })?;
142 Ok((out, axis))
143 };
144 self.with(|r| r.vmap = Some(Arc::new(rule)))
145 }
146
147 pub fn call(&self, inputs: &[Array]) -> Result<Vec<Array>> {
150 let views = inputs
153 .iter()
154 .map(|x| Ok(Array::record(Primitive::StopGradient, vec![x.clone()], None)?.remove(0)))
155 .collect::<Result<Vec<_>>>()?;
156 let outputs = (self.rules.body)(&views)?;
157 let trace = crate::transforms::inherited(inputs);
158 let call = CustomCall {
159 rules: self.rules.clone(),
160 primals: inputs.len(),
161 };
162 let all = inputs.iter().chain(&outputs).cloned().collect();
163 Array::record(Primitive::Custom(call), all, trace)
164 }
165}
166
167pub fn checkpoint_flat(
183 f: impl Fn(&[Array]) -> Result<Vec<Array>> + Send + Sync + 'static,
184) -> impl Fn(&[Array]) -> Result<Vec<Array>> + Send + Sync {
185 let function = custom_function_flat(f);
186 move |inputs| function.call(inputs)
187}
188
189type TreeBody<I, O> = dyn Fn(&I) -> Result<O> + Send + Sync;
192type TreeVjp<I, O> = dyn Fn(&I, &O, &O) -> Result<I> + Send + Sync;
193type TreeJvp<I, O> = dyn Fn(&I, &I) -> Result<O> + Send + Sync;
194type TreeVmap<I, O> = dyn Fn(&I, &[Option<i32>]) -> Result<(O, i32)> + Send + Sync;
195
196pub struct CustomFunction<I, O> {
198 body: Arc<TreeBody<I, O>>,
199 vjp: Option<Arc<TreeVjp<I, O>>>,
200 jvp: Option<Arc<TreeJvp<I, O>>>,
201 vmap: Option<Arc<TreeVmap<I, O>>>,
202 replay_random: bool,
204}
205
206impl<I, O> Clone for CustomFunction<I, O> {
207 fn clone(&self) -> Self {
208 Self {
209 body: self.body.clone(),
210 vjp: self.vjp.clone(),
211 jvp: self.jvp.clone(),
212 vmap: self.vmap.clone(),
213 replay_random: self.replay_random,
214 }
215 }
216}
217
218pub fn custom_function<I, O, F, R>(f: F) -> CustomFunction<I, O>
239where
240 I: Tree + Send + Sync,
241 O: Tree + Send + Sync,
242 F: Fn(&I) -> R + Send + Sync + 'static,
243 R: IntoResult<Ok = O>,
244{
245 CustomFunction {
246 body: Arc::new(move |i: &I| f(i).into_result()),
247 vjp: None,
248 jvp: None,
249 vmap: None,
250 replay_random: false,
251 }
252}
253
254impl<I, O> CustomFunction<I, O>
255where
256 I: Tree + Send + Sync,
257 O: Tree + Send + Sync,
258{
259 pub fn with_vjp(
262 mut self,
263 rule: impl Fn(&I, &O, &O) -> Result<I> + Send + Sync + 'static,
264 ) -> Self {
265 self.vjp = Some(Arc::new(rule));
266 self
267 }
268
269 pub fn with_jvp(mut self, rule: impl Fn(&I, &I) -> Result<O> + Send + Sync + 'static) -> Self {
272 self.jvp = Some(Arc::new(rule));
273 self
274 }
275
276 pub fn with_vmap(
279 mut self,
280 rule: impl Fn(&I, &[Option<i32>]) -> Result<(O, i32)> + Send + Sync + 'static,
281 ) -> Self {
282 self.vmap = Some(Arc::new(rule));
283 self
284 }
285
286 pub fn call(&self, inputs: &I) -> Result<O> {
289 let template = Arc::new(inputs.clone());
290 let output: Arc<Mutex<Option<O>>> = Arc::new(Mutex::new(None));
291 let out_template = {
292 let output = output.clone();
293 move || {
294 output
295 .lock()
296 .unwrap_or_else(PoisonError::into_inner)
297 .clone()
298 .ok_or_else(|| Error::InvalidArgument("custom_function: no output yet".into()))
299 }
300 };
301 let body = {
302 let (f, template, output) = (self.body.clone(), template.clone(), output.clone());
303 let key = self.replay_random.then(crate::random::sequence_key);
306 let first = AtomicBool::new(true);
307 move |xs: &[Array]| -> Result<Vec<Array>> {
308 let restore = match &key {
309 Some(k) => {
310 let current = crate::random::sequence_key();
311 crate::random::set_sequence_key(k.clone());
312 (!first.swap(false, Ordering::AcqRel)).then_some(current)
313 }
314 None => None,
315 };
316 let out = template.unflatten(xs.iter().cloned()).and_then(|i| f(&i));
317 if let Some(current) = restore {
318 crate::random::set_sequence_key(current);
319 }
320 let out = out?;
321 let leaves = out.leaves();
322 *output.lock().unwrap_or_else(PoisonError::into_inner) = Some(out);
323 Ok(leaves)
324 }
325 };
326 let vjp: Option<Arc<VjpRule>> = self.vjp.clone().map(|rule| {
327 let (template, out_template) = (template.clone(), out_template.clone());
328 Arc::new(move |p: &[Array], o: &[Array], c: &[Array]| {
329 let tpl = out_template()?;
330 let i = template.unflatten(p.iter().cloned())?;
331 let o = tpl.unflatten(o.iter().cloned())?;
332 let c = tpl.unflatten(c.iter().cloned())?;
333 Ok(rule(&i, &o, &c)?.leaves())
334 }) as Arc<VjpRule>
335 });
336 let jvp: Option<Arc<JvpRule>> = self.jvp.clone().map(|rule| {
337 let template = template.clone();
338 Arc::new(move |p: &[Array], t: &[Array]| {
339 let i = template.unflatten(p.iter().cloned())?;
340 let t = template.unflatten(t.iter().cloned())?;
341 Ok(rule(&i, &t)?.leaves())
342 }) as Arc<JvpRule>
343 });
344 let vmap: Option<Arc<VmapRule>> = self.vmap.clone().map(|rule| {
345 let (template, out_template) = (template.clone(), out_template.clone());
346 Arc::new(move |xs: &[Array], axes: &[Option<usize>]| {
347 let axes: Vec<Option<i32>> = axes
348 .iter()
349 .map(|a| a.map(|a| i32::try_from(a).unwrap_or(i32::MAX)))
350 .collect();
351 let i = template.unflatten(xs.iter().cloned())?;
352 let (o, axis) = rule(&i, &axes)?;
353 let axis = usize::try_from(axis).map_err(|_| {
354 Error::InvalidArgument(format!(
355 "custom_function: the vmap rule returned the negative output axis {axis}"
356 ))
357 })?;
358 let _ = out_template()?;
359 Ok((o.leaves(), axis))
360 }) as Arc<VmapRule>
361 });
362 let flat = CustomFunctionFlat {
363 rules: Arc::new(Rules {
364 body: Arc::new(body),
365 vjp,
366 jvp,
367 vmap,
368 }),
369 };
370 let leaves = flat.call(&inputs.leaves())?;
371 out_template()?.unflatten(leaves)
372 }
373}
374
375pub fn checkpoint<I, O, F, R>(f: F) -> impl Fn(&I) -> Result<O> + Send + Sync
396where
397 I: Tree + Send + Sync,
398 O: Tree + Send + Sync,
399 F: Fn(&I) -> R + Send + Sync + 'static,
400 R: IntoResult<Ok = O>,
401{
402 let mut function = custom_function(f);
403 function.replay_random = true;
404 move |inputs| function.call(inputs)
405}
406
407#[cfg(test)]
408mod tests {
409 use std::sync::Mutex;
410
411 use super::*;
412 use crate::array::State;
413
414 fn intermediate_retained(checkpointed: bool) -> bool {
417 let first = Arc::new(Mutex::new(None::<Array>));
418 let stored = first.clone();
419 let body = move |xs: &[Array]| {
420 let h = crate::ops::imp::sin(&xs[0])?;
421 stored.lock().unwrap().get_or_insert_with(|| h.clone());
422 Ok(vec![crate::ops::imp::sin(&h)?])
423 };
424 let f: Box<Body> = if checkpointed {
425 Box::new(checkpoint_flat(body))
426 } else {
427 Box::new(body)
428 };
429 let retained = Arc::new(Mutex::new(false));
431 let seen = retained.clone();
432 let g = crate::grad(move |x: &Array| {
433 let y = f(std::slice::from_ref(x))?.remove(0);
434 y.eval()?;
435 let h = first.lock().unwrap().take().unwrap();
436 *seen.lock().unwrap() = matches!(&*h.node.lock(), State::Retained { .. });
437 crate::ops::imp::sum(&y, &[], false)
438 });
439 let _ = g(&Array::from(0.5f32)).unwrap();
440 *retained.lock().unwrap()
441 }
442
443 #[test]
444 fn checkpoint_does_not_keep_intermediates() {
445 assert!(
446 intermediate_retained(false),
447 "without checkpoint the graph is kept"
448 );
449 assert!(
450 !intermediate_retained(true),
451 "with checkpoint it is released"
452 );
453 }
454}