1use std::fmt;
5use std::hash::{Hash, Hasher};
6use std::sync::Arc;
7
8use crate::primitive::Primitive;
9use crate::{Array, Result};
10
11type Body = dyn Fn(&[Array]) -> Result<Vec<Array>> + Send + Sync;
12type VjpRule = dyn Fn(&[Array], &[Array], &[Array]) -> Result<Vec<Array>> + Send + Sync;
13type JvpRule = dyn Fn(&[Array], &[Array]) -> Result<Vec<Array>> + Send + Sync;
14type VmapRule = dyn Fn(&[Array], &[Option<usize>]) -> Result<(Vec<Array>, usize)> + Send + Sync;
15
16#[derive(Clone)]
18pub(crate) struct Rules {
19 pub(crate) body: Arc<Body>,
20 pub(crate) vjp: Option<Arc<VjpRule>>,
21 pub(crate) jvp: Option<Arc<JvpRule>>,
22 pub(crate) vmap: Option<Arc<VmapRule>>,
23}
24
25#[derive(Clone)]
28pub struct CustomCall {
29 pub(crate) rules: Arc<Rules>,
30 pub(crate) primals: usize,
31}
32
33impl PartialEq for CustomCall {
34 fn eq(&self, other: &Self) -> bool {
35 Arc::ptr_eq(&self.rules, &other.rules) && self.primals == other.primals
36 }
37}
38
39impl Eq for CustomCall {}
40
41impl Hash for CustomCall {
42 fn hash<H: Hasher>(&self, state: &mut H) {
43 (Arc::as_ptr(&self.rules) as *const () as usize).hash(state);
44 self.primals.hash(state);
45 }
46}
47
48impl fmt::Debug for CustomCall {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(f, "CustomCall({} primals)", self.primals)
51 }
52}
53
54#[derive(Clone)]
56pub struct CustomFunction {
57 rules: Arc<Rules>,
58}
59
60pub fn custom_function(
82 f: impl Fn(&[Array]) -> Result<Vec<Array>> + Send + Sync + 'static,
83) -> CustomFunction {
84 CustomFunction {
85 rules: Arc::new(Rules {
86 body: Arc::new(f),
87 vjp: None,
88 jvp: None,
89 vmap: None,
90 }),
91 }
92}
93
94impl CustomFunction {
95 fn with(self, set: impl FnOnce(&mut Rules)) -> Self {
96 let mut rules = (*self.rules).clone();
97 set(&mut rules);
98 Self {
99 rules: Arc::new(rules),
100 }
101 }
102
103 pub fn with_vjp(
106 self,
107 rule: impl Fn(&[Array], &[Array], &[Array]) -> Result<Vec<Array>> + Send + Sync + 'static,
108 ) -> Self {
109 self.with(|r| r.vjp = Some(Arc::new(rule)))
110 }
111
112 pub fn with_jvp(
115 self,
116 rule: impl Fn(&[Array], &[Array]) -> Result<Vec<Array>> + Send + Sync + 'static,
117 ) -> Self {
118 self.with(|r| r.jvp = Some(Arc::new(rule)))
119 }
120
121 pub fn with_vmap(
124 self,
125 rule: impl Fn(&[Array], &[Option<usize>]) -> Result<(Vec<Array>, usize)> + Send + Sync + 'static,
126 ) -> Self {
127 self.with(|r| r.vmap = Some(Arc::new(rule)))
128 }
129
130 pub fn call(&self, inputs: &[Array]) -> Result<Vec<Array>> {
133 let views = inputs
136 .iter()
137 .map(|x| Ok(Array::record(Primitive::StopGradient, vec![x.clone()], false)?.remove(0)))
138 .collect::<Result<Vec<_>>>()?;
139 let outputs = (self.rules.body)(&views)?;
140 let tracer = inputs.iter().any(|a| a.node.tracer);
141 let call = CustomCall {
142 rules: self.rules.clone(),
143 primals: inputs.len(),
144 };
145 let all = inputs.iter().chain(&outputs).cloned().collect();
146 Array::record(Primitive::Custom(call), all, tracer)
147 }
148}
149
150pub fn checkpoint(
166 f: impl Fn(&[Array]) -> Result<Vec<Array>> + Send + Sync + 'static,
167) -> impl Fn(&[Array]) -> Result<Vec<Array>> + Send + Sync {
168 let function = custom_function(f);
169 move |inputs| function.call(inputs)
170}
171
172#[cfg(test)]
173mod tests {
174 use std::sync::Mutex;
175
176 use super::*;
177 use crate::array::State;
178
179 fn intermediate_retained(checkpointed: bool) -> bool {
182 let first = Arc::new(Mutex::new(None::<Array>));
183 let stored = first.clone();
184 let body = move |xs: &[Array]| {
185 let h = crate::sin(&xs[0])?;
186 stored.lock().unwrap().get_or_insert_with(|| h.clone());
187 Ok(vec![crate::sin(&h)?])
188 };
189 let f: Box<Body> = if checkpointed {
190 Box::new(checkpoint(body))
191 } else {
192 Box::new(body)
193 };
194 let g = crate::grad(move |x: &Array| {
195 let y = f(std::slice::from_ref(x))?.remove(0);
196 y.eval()?;
197 crate::sum(&y, &[], false)
198 });
199 g(&Array::scalar(0.5f32)).unwrap();
200 let h = first.lock().unwrap().take().unwrap();
201 let retained = matches!(&*h.node.lock(), State::Retained { .. });
202 retained
203 }
204
205 #[test]
206 fn checkpoint_does_not_keep_intermediates() {
207 assert!(
208 intermediate_retained(false),
209 "without checkpoint the graph is kept"
210 );
211 assert!(
212 !intermediate_retained(true),
213 "with checkpoint it is released"
214 );
215 }
216}