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 f = Arc::new(f);
186 move |inputs| {
187 let key = crate::random::sequence_key();
190 let first = AtomicBool::new(true);
191 let f = f.clone();
192 let function = custom_function_flat(move |xs: &[Array]| {
193 let current = crate::random::sequence_key();
194 crate::random::set_sequence_key(key.clone());
195 let restore = (!first.swap(false, Ordering::AcqRel)).then_some(current);
196 let out = f(xs);
197 if let Some(current) = restore {
198 crate::random::set_sequence_key(current);
199 }
200 out
201 });
202 function.call(inputs)
203 }
204}
205
206type TreeBody<I, O> = dyn Fn(&I) -> Result<O> + Send + Sync;
209type TreeVjp<I, O> = dyn Fn(&I, &O, &O) -> Result<I> + Send + Sync;
210type TreeJvp<I, O> = dyn Fn(&I, &I) -> Result<O> + Send + Sync;
211type TreeVmap<I, O> = dyn Fn(&I, &[Option<i32>]) -> Result<(O, i32)> + Send + Sync;
212
213pub struct CustomFunction<I, O> {
215 body: Arc<TreeBody<I, O>>,
216 vjp: Option<Arc<TreeVjp<I, O>>>,
217 jvp: Option<Arc<TreeJvp<I, O>>>,
218 vmap: Option<Arc<TreeVmap<I, O>>>,
219 replay_random: bool,
221}
222
223impl<I, O> Clone for CustomFunction<I, O> {
224 fn clone(&self) -> Self {
225 Self {
226 body: self.body.clone(),
227 vjp: self.vjp.clone(),
228 jvp: self.jvp.clone(),
229 vmap: self.vmap.clone(),
230 replay_random: self.replay_random,
231 }
232 }
233}
234
235pub fn custom_function<I, O, F, R>(f: F) -> CustomFunction<I, O>
256where
257 I: Tree + Send + Sync,
258 O: Tree + Send + Sync,
259 F: Fn(&I) -> R + Send + Sync + 'static,
260 R: IntoResult<Ok = O>,
261{
262 CustomFunction {
263 body: Arc::new(move |i: &I| f(i).into_result()),
264 vjp: None,
265 jvp: None,
266 vmap: None,
267 replay_random: false,
268 }
269}
270
271impl<I, O> CustomFunction<I, O>
272where
273 I: Tree + Send + Sync,
274 O: Tree + Send + Sync,
275{
276 pub fn with_vjp(
279 mut self,
280 rule: impl Fn(&I, &O, &O) -> Result<I> + Send + Sync + 'static,
281 ) -> Self {
282 self.vjp = Some(Arc::new(rule));
283 self
284 }
285
286 pub fn with_jvp(mut self, rule: impl Fn(&I, &I) -> Result<O> + Send + Sync + 'static) -> Self {
289 self.jvp = Some(Arc::new(rule));
290 self
291 }
292
293 pub fn with_vmap(
296 mut self,
297 rule: impl Fn(&I, &[Option<i32>]) -> Result<(O, i32)> + Send + Sync + 'static,
298 ) -> Self {
299 self.vmap = Some(Arc::new(rule));
300 self
301 }
302
303 pub fn call(&self, inputs: &I) -> Result<O> {
306 let template = Arc::new(inputs.clone());
307 let output: Arc<Mutex<Option<O>>> = Arc::new(Mutex::new(None));
308 let out_template = {
309 let output = output.clone();
310 move || {
311 output
312 .lock()
313 .unwrap_or_else(PoisonError::into_inner)
314 .clone()
315 .ok_or_else(|| Error::InvalidArgument("custom_function: no output yet".into()))
316 }
317 };
318 let body = {
319 let (f, template, output) = (self.body.clone(), template.clone(), output.clone());
320 let key = self.replay_random.then(crate::random::sequence_key);
323 let first = AtomicBool::new(true);
324 move |xs: &[Array]| -> Result<Vec<Array>> {
325 let restore = match &key {
326 Some(k) => {
327 let current = crate::random::sequence_key();
328 crate::random::set_sequence_key(k.clone());
329 (!first.swap(false, Ordering::AcqRel)).then_some(current)
330 }
331 None => None,
332 };
333 let out = template.unflatten(xs.iter().cloned()).and_then(|i| f(&i));
334 if let Some(current) = restore {
335 crate::random::set_sequence_key(current);
336 }
337 let out = out?;
338 let leaves = out.leaves();
339 *output.lock().unwrap_or_else(PoisonError::into_inner) = Some(out);
340 Ok(leaves)
341 }
342 };
343 let vjp: Option<Arc<VjpRule>> = self.vjp.clone().map(|rule| {
344 let (template, out_template) = (template.clone(), out_template.clone());
345 Arc::new(move |p: &[Array], o: &[Array], c: &[Array]| {
346 let tpl = out_template()?;
347 let i = template.unflatten(p.iter().cloned())?;
348 let o = tpl.unflatten(o.iter().cloned())?;
349 let c = tpl.unflatten(c.iter().cloned())?;
350 Ok(rule(&i, &o, &c)?.leaves())
351 }) as Arc<VjpRule>
352 });
353 let jvp: Option<Arc<JvpRule>> = self.jvp.clone().map(|rule| {
354 let template = template.clone();
355 Arc::new(move |p: &[Array], t: &[Array]| {
356 let i = template.unflatten(p.iter().cloned())?;
357 let t = template.unflatten(t.iter().cloned())?;
358 Ok(rule(&i, &t)?.leaves())
359 }) as Arc<JvpRule>
360 });
361 let vmap: Option<Arc<VmapRule>> = self.vmap.clone().map(|rule| {
362 let (template, out_template) = (template.clone(), out_template.clone());
363 Arc::new(move |xs: &[Array], axes: &[Option<usize>]| {
364 let axes: Vec<Option<i32>> = axes
365 .iter()
366 .map(|a| a.map(|a| i32::try_from(a).unwrap_or(i32::MAX)))
367 .collect();
368 let i = template.unflatten(xs.iter().cloned())?;
369 let (o, axis) = rule(&i, &axes)?;
370 let axis = usize::try_from(axis).map_err(|_| {
371 Error::InvalidArgument(format!(
372 "custom_function: the vmap rule returned the negative output axis {axis}"
373 ))
374 })?;
375 let _ = out_template()?;
376 Ok((o.leaves(), axis))
377 }) as Arc<VmapRule>
378 });
379 let flat = CustomFunctionFlat {
380 rules: Arc::new(Rules {
381 body: Arc::new(body),
382 vjp,
383 jvp,
384 vmap,
385 }),
386 };
387 let leaves = flat.call(&inputs.leaves())?;
388 out_template()?.unflatten(leaves)
389 }
390}
391
392pub fn checkpoint<I, O, F, R>(f: F) -> impl Fn(&I) -> Result<O> + Send + Sync
413where
414 I: Tree + Send + Sync,
415 O: Tree + Send + Sync,
416 F: Fn(&I) -> R + Send + Sync + 'static,
417 R: IntoResult<Ok = O>,
418{
419 let mut function = custom_function(f);
420 function.replay_random = true;
421 move |inputs| function.call(inputs)
422}
423
424#[cfg(test)]
425mod tests {
426 use std::sync::Mutex;
427
428 use super::*;
429 use crate::array::State;
430
431 fn intermediate_retained(checkpointed: bool) -> bool {
434 let first = Arc::new(Mutex::new(None::<Array>));
435 let stored = first.clone();
436 let body = move |xs: &[Array]| {
437 let h = crate::ops::imp::sin(&xs[0])?;
438 stored.lock().unwrap().get_or_insert_with(|| h.clone());
439 Ok(vec![crate::ops::imp::sin(&h)?])
440 };
441 let f: Box<Body> = if checkpointed {
442 Box::new(checkpoint_flat(body))
443 } else {
444 Box::new(body)
445 };
446 let retained = Arc::new(Mutex::new(false));
448 let seen = retained.clone();
449 let g = crate::grad(move |x: &Array| {
450 let y = f(std::slice::from_ref(x))?.remove(0);
451 y.eval()?;
452 let h = first.lock().unwrap().take().unwrap();
453 *seen.lock().unwrap() = matches!(&*h.node.lock(), State::Retained { .. });
454 crate::ops::imp::sum(&y, &[], false)
455 });
456 let _ = g(&Array::from(0.5f32)).unwrap();
457 *retained.lock().unwrap()
458 }
459
460 #[test]
461 fn checkpoint_does_not_keep_intermediates() {
462 assert!(
463 intermediate_retained(false),
464 "without checkpoint the graph is kept"
465 );
466 assert!(
467 !intermediate_retained(true),
468 "with checkpoint it is released"
469 );
470 }
471}