1use std::cell::Cell;
11use std::collections::HashSet;
12use std::sync::Arc;
13
14use crate::array::{Node, NodeMap, NodeSet, State};
15use crate::cast::Canon;
16use crate::derivatives::zeros_like;
17use crate::primitive::Primitive;
18use crate::{Array, Error, Result, ops};
19
20thread_local!(static DEPTH: Cell<usize> = const { Cell::new(0) });
21
22pub(crate) fn tracing() -> bool {
24 DEPTH.get() > 0
25}
26
27struct Tracing;
29
30impl Tracing {
31 fn enter() -> Self {
32 DEPTH.set(DEPTH.get() + 1);
33 Tracing
34 }
35}
36
37impl Drop for Tracing {
38 fn drop(&mut self) {
39 DEPTH.set(DEPTH.get() - 1);
40 }
41}
42
43fn tracer(primal: &Array) -> Result<Array> {
46 let shape = primal.shape().to_vec();
47 Ok(Array::record(Primitive::Broadcast(shape), vec![primal.clone()], true)?.swap_remove(0))
48}
49
50type Key = (*const Node, u32);
51
52fn key(a: &Array) -> Key {
53 (Arc::as_ptr(&a.node), a.output)
54}
55
56struct Step {
58 node: Arc<Node>,
59 primitive: Primitive,
60 inputs: Vec<Array>,
61 wanted: Vec<bool>,
63}
64
65impl Step {
66 fn outputs(&self) -> Vec<Array> {
67 (0..self.node.outputs.len() as u32)
68 .map(|output| Array {
69 node: self.node.clone(),
70 output,
71 })
72 .collect()
73 }
74}
75
76type Graph = (Primitive, Vec<Array>);
78
79fn graph(node: &Node) -> Option<Graph> {
81 match &*node.lock() {
82 State::Pending { primitive, inputs } | State::Retained { primitive, inputs } => {
83 Some((primitive.clone(), inputs.clone()))
84 }
85 State::Done => None,
86 }
87}
88
89fn followed(primitive: &Primitive, inputs: &[Array]) -> usize {
92 match primitive {
93 Primitive::Custom(call) => call.primals,
94 _ => inputs.len(),
95 }
96}
97
98fn tape(outputs: &[Array], sources: &NodeSet, stop_gradient: bool) -> Vec<Step> {
102 let mut depends: NodeMap<*const Node, bool> = NodeMap::default();
103 let mut visited = NodeSet::default();
104 let mut order = Vec::new();
105 let mut stack: Vec<(Arc<Node>, Option<Graph>)> =
106 outputs.iter().map(|a| (a.node.clone(), None)).collect();
107 while let Some((node, expanded)) = stack.pop() {
108 let ptr = Arc::as_ptr(&node);
109 if let Some((primitive, inputs)) = expanded {
110 let n = followed(&primitive, &inputs);
111 let wanted: Vec<bool> = inputs
112 .iter()
113 .enumerate()
114 .map(|(i, a)| i < n && depends.get(&Arc::as_ptr(&a.node)).copied().unwrap_or(false))
115 .collect();
116 let cut = stop_gradient && primitive == Primitive::StopGradient;
117 let dep = !cut && wanted.contains(&true);
118 depends.insert(ptr, dep);
119 if dep {
120 order.push(Step {
121 node,
122 primitive,
123 inputs,
124 wanted,
125 });
126 }
127 continue;
128 }
129 if !visited.insert(ptr) {
130 continue;
131 }
132 if sources.contains(&ptr) {
133 depends.insert(ptr, true);
134 continue;
135 }
136 let Some((primitive, inputs)) = graph(&node) else {
137 continue; };
139 let next: Vec<Arc<Node>> = inputs[..followed(&primitive, &inputs)]
140 .iter()
141 .map(|a| a.node.clone())
142 .filter(|n| !visited.contains(&Arc::as_ptr(n)))
143 .collect();
144 stack.push((node, Some((primitive, inputs))));
145 stack.extend(next.into_iter().map(|n| (n, None)));
146 }
147 order
148}
149
150fn accumulate(map: &mut NodeMap<Key, Array>, key: Key, value: Array) -> Result<()> {
151 let value = match map.remove(&key) {
152 Some(previous) => ops::add(&previous, &value)?,
153 None => value,
154 };
155 map.insert(key, value);
156 Ok(())
157}
158
159fn check_shapes(op: &str, what: &str, arrays: &[Array], given: &[Array]) -> Result<()> {
160 if arrays.len() != given.len() {
161 return Err(Error::InvalidArgument(format!(
162 "{op}: {} {what}s for {} arrays",
163 given.len(),
164 arrays.len()
165 )));
166 }
167 for (a, g) in arrays.iter().zip(given) {
168 if a.shape() != g.shape() {
169 return Err(Error::InvalidArgument(format!(
170 "{op}: shape {:?} does not match {what} shape {:?}",
171 a.shape(),
172 g.shape()
173 )));
174 }
175 }
176 Ok(())
177}
178
179fn reverse(outputs: &[Array], cotangents: &[Array], tracers: &[Array]) -> Result<Vec<Array>> {
181 let sources = tracers.iter().map(|t| Arc::as_ptr(&t.node)).collect();
182 let mut cot = NodeMap::default();
183 for (o, c) in outputs.iter().zip(cotangents) {
184 accumulate(&mut cot, key(o), c.clone())?;
185 }
186 for step in tape(outputs, &sources, true).iter().rev() {
187 let outs = step.outputs();
188 if !outs.iter().any(|o| cot.contains_key(&key(o))) {
189 continue;
190 }
191 let cots = outs
192 .iter()
193 .map(|o| cot.remove(&key(o)).map_or_else(|| zeros_like(o), Ok))
194 .collect::<Result<Vec<_>>>()?;
195 let grads = step
196 .primitive
197 .vjp(&step.inputs, &outs, &cots, &step.wanted)?;
198 for ((input, grad), &wanted) in step.inputs.iter().zip(grads).zip(&step.wanted) {
199 if let (Some(grad), true) = (grad, wanted) {
200 accumulate(&mut cot, key(input), grad)?;
201 }
202 }
203 }
204 tracers
205 .iter()
206 .map(|t| cot.remove(&key(t)).map_or_else(|| zeros_like(t), Ok))
207 .collect()
208}
209
210pub fn vjp(
228 f: impl FnOnce(&[Array]) -> Result<Vec<Array>>,
229 primals: &[Array],
230 cotangents: &[Array],
231) -> Result<(Vec<Array>, Vec<Array>)> {
232 let _tracing = Tracing::enter();
233 let tracers = primals.iter().map(tracer).collect::<Result<Vec<_>>>()?;
234 let outputs = f(&tracers)?;
235 check_shapes("vjp", "cotangent", &outputs, cotangents)?;
236 let grads = reverse(&outputs, cotangents, &tracers)?;
237 Ok((outputs, grads))
238}
239
240pub fn jvp(
254 f: impl FnOnce(&[Array]) -> Result<Vec<Array>>,
255 primals: &[Array],
256 tangents: &[Array],
257) -> Result<(Vec<Array>, Vec<Array>)> {
258 check_shapes("jvp", "tangent", primals, tangents)?;
259 let _tracing = Tracing::enter();
260 let tracers = primals.iter().map(tracer).collect::<Result<Vec<_>>>()?;
261 let outputs = f(&tracers)?;
262 let sources = tracers.iter().map(|t| Arc::as_ptr(&t.node)).collect();
263 let mut tan: NodeMap<Key, Array> = tracers
264 .iter()
265 .zip(tangents)
266 .map(|(t, v)| (key(t), v.clone()))
267 .collect();
268 for step in tape(&outputs, &sources, true) {
269 let inputs: Vec<Option<Array>> = step
270 .inputs
271 .iter()
272 .map(|a| tan.get(&key(a)).cloned())
273 .collect();
274 if inputs.iter().all(Option::is_none) {
275 continue;
276 }
277 let outs = step.outputs();
278 for (o, t) in outs
279 .iter()
280 .zip(step.primitive.jvp(&step.inputs, &outs, &inputs)?)
281 {
282 tan.insert(key(o), t);
283 }
284 }
285 let tangents = outputs
286 .iter()
287 .map(|o| tan.get(&key(o)).cloned().map_or_else(|| zeros_like(o), Ok))
288 .collect::<Result<Vec<_>>>()?;
289 Ok((outputs, tangents))
290}
291
292pub fn value_and_grad<F>(
316 f: F,
317 argnums: &[usize],
318) -> impl Fn(&[Array]) -> Result<(Array, Vec<Array>)>
319where
320 F: Fn(&[Array]) -> Result<Array>,
321{
322 let argnums = argnums.to_vec();
323 move |args| {
324 if argnums.is_empty() {
325 return Err(Error::InvalidArgument(
326 "value_and_grad: no argument to differentiate".into(),
327 ));
328 }
329 let mut seen = HashSet::new();
330 for &i in &argnums {
331 if i >= args.len() || !seen.insert(i) {
332 return Err(Error::InvalidArgument(format!(
333 "value_and_grad: invalid or repeated argument {i} for {} arguments",
334 args.len()
335 )));
336 }
337 }
338 let _tracing = Tracing::enter();
339 let mut inputs = args.to_vec();
340 let mut tracers = Vec::with_capacity(argnums.len());
341 for &i in &argnums {
342 inputs[i] = tracer(&args[i])?;
343 tracers.push(inputs[i].clone());
344 }
345 let value = f(&inputs)?;
346 if value.ndim() != 0 {
347 return Err(Error::InvalidArgument(format!(
348 "value_and_grad: the function must return a scalar (0-D) array, got shape {:?}",
349 value.shape()
350 )));
351 }
352 let seed = ops::filled(&[], value.dtype(), |_| Canon::Int(1))?;
353 let grads = reverse(std::slice::from_ref(&value), &[seed], &tracers)?;
354 Ok((value, grads))
355 }
356}
357
358pub fn grad<F>(f: F) -> impl Fn(&Array) -> Result<Array>
373where
374 F: Fn(&Array) -> Result<Array>,
375{
376 let value_and_grad = value_and_grad(move |xs: &[Array]| f(&xs[0]), &[0]);
377 move |x| Ok(value_and_grad(std::slice::from_ref(x))?.1.swap_remove(0))
378}
379
380fn placeholder(like: &Array, shape: &[usize]) -> Result<Array> {
383 let failed = Array::failed(
384 Error::InvalidArgument("vmap: arrays cannot be evaluated inside vmap".into()),
385 like.dtype(),
386 &[],
387 );
388 let primitive = Primitive::Broadcast(shape.to_vec());
389 Ok(Array::record_unchecked(primitive, vec![failed], true)?.swap_remove(0))
390}
391
392pub(crate) fn batch(
395 f: &dyn Fn(&[Array]) -> Result<Vec<Array>>,
396 args: &[Array],
397 axes: &[Option<usize>],
398) -> Result<Vec<Array>> {
399 let mut size = None;
400 for (a, axis) in args.iter().zip(axes) {
401 let Some(axis) = *axis else { continue };
402 let n = *a.shape().get(axis).ok_or_else(|| {
403 Error::InvalidArgument(format!(
404 "vmap: axis {axis} out of range for shape {:?}",
405 a.shape()
406 ))
407 })?;
408 if size.is_some_and(|s| s != n) {
409 return Err(Error::InvalidArgument(format!(
410 "vmap: inconsistent batch sizes {} and {n}",
411 size.unwrap_or(0)
412 )));
413 }
414 size = Some(n);
415 }
416 let size = size
417 .ok_or_else(|| Error::InvalidArgument("vmap: at least one input must be batched".into()))?;
418 let _tracing = Tracing::enter();
419 let mut batched: NodeMap<Key, (Array, usize)> = NodeMap::default();
420 let mut sources = NodeSet::default();
421 let mut inputs = Vec::with_capacity(args.len());
422 for (a, axis) in args.iter().zip(axes) {
423 inputs.push(match *axis {
424 Some(axis) => {
425 let mut shape = a.shape().to_vec();
426 shape.remove(axis);
427 let p = placeholder(a, &shape)?;
428 sources.insert(Arc::as_ptr(&p.node));
429 batched.insert(key(&p), (a.clone(), axis));
430 p
431 }
432 None => a.clone(),
433 });
434 }
435 let outputs = f(&inputs)?;
436 for step in tape(&outputs, &sources, false) {
437 let (arrays, axes): (Vec<Array>, Vec<Option<usize>>) = step
438 .inputs
439 .iter()
440 .map(|a| match batched.get(&key(a)) {
441 Some((b, axis)) => (b.clone(), Some(*axis)),
442 None => (a.clone(), None),
443 })
444 .unzip();
445 let (results, axis) = step.primitive.vmap(&arrays, &axes)?;
446 for (o, r) in step.outputs().iter().zip(results) {
447 batched.insert(key(o), (r, axis));
448 }
449 }
450 outputs
451 .iter()
452 .map(|o| match batched.get(&key(o)) {
453 Some((b, axis)) => crate::batching::move_axis(b, *axis, 0),
454 None => {
456 let mut shape = vec![size];
457 shape.extend_from_slice(o.shape());
458 ops::broadcast_to(&ops::expand_dims(o, 0)?, &shape)
459 }
460 })
461 .collect()
462}
463
464fn normalize_axis(axis: isize, ndim: usize) -> Result<usize> {
466 let a = if axis < 0 { axis + ndim as isize } else { axis };
467 if (0..ndim as isize).contains(&a) {
468 Ok(a as usize)
469 } else {
470 Err(Error::InvalidArgument(format!(
471 "vmap: axis {axis} out of range for {ndim} dimensions"
472 )))
473 }
474}
475
476pub fn vmap<F>(
496 f: F,
497 in_axes: &[Option<isize>],
498 out_axes: &[isize],
499) -> impl Fn(&[Array]) -> Result<Vec<Array>>
500where
501 F: Fn(&[Array]) -> Result<Vec<Array>>,
502{
503 let (in_axes, out_axes) = (in_axes.to_vec(), out_axes.to_vec());
504 move |args| {
505 let axes = if in_axes.is_empty() {
506 vec![Some(0); args.len()]
507 } else if in_axes.len() == args.len() {
508 in_axes
509 .iter()
510 .zip(args)
511 .map(|(axis, a)| axis.map(|axis| normalize_axis(axis, a.ndim())).transpose())
512 .collect::<Result<Vec<_>>>()?
513 } else {
514 return Err(Error::InvalidArgument(format!(
515 "vmap: {} input axes for {} arguments",
516 in_axes.len(),
517 args.len()
518 )));
519 };
520 let outputs = batch(&f, args, &axes)?;
521 if !out_axes.is_empty() && out_axes.len() != outputs.len() {
522 return Err(Error::InvalidArgument(format!(
523 "vmap: {} output axes for {} outputs",
524 out_axes.len(),
525 outputs.len()
526 )));
527 }
528 outputs
529 .iter()
530 .enumerate()
531 .map(|(i, o)| {
532 let axis = normalize_axis(out_axes.get(i).copied().unwrap_or(0), o.ndim())?;
533 crate::batching::move_axis(o, 0, axis)
534 })
535 .collect()
536 }
537}