1use std::cell::RefCell;
11use std::collections::HashSet;
12use std::marker::PhantomData;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::{Arc, Weak};
15
16use crate::array::{Node, NodeMap, NodeSet, State};
17use crate::cast::Canon;
18use crate::derivatives::zeros_like;
19use crate::ops::imp as ops;
20use crate::primitive::Primitive;
21use crate::tree::{LeafKind, Leaves, Tree, Visit, Visitor, broadcast_zero, differentiable};
22use crate::{Array, Category, Error, Result};
23
24pub(crate) type TraceId = u64;
27
28pub(crate) const PLACEHOLDER: TraceId = 0;
30
31static NEXT_TRACE: AtomicU64 = AtomicU64::new(PLACEHOLDER + 1);
32
33struct Frame {
36 id: TraceId,
37 retained: Vec<Weak<Node>>,
38}
39
40thread_local!(static LIVE: RefCell<Vec<Frame>> = const { RefCell::new(Vec::new()) });
41
42pub(crate) fn is_live(id: TraceId) -> bool {
44 id == PLACEHOLDER || LIVE.with_borrow(|live| live.iter().any(|f| f.id == id))
45}
46
47pub(crate) fn inherited(inputs: &[Array]) -> Option<TraceId> {
49 inputs
50 .iter()
51 .filter_map(|a| a.node.trace)
52 .filter(|&id| is_live(id))
53 .min()
54}
55
56pub(crate) fn retain(node: &Arc<Node>) -> bool {
59 let Some(id) = node.trace.filter(|&id| id != PLACEHOLDER) else {
60 return false;
61 };
62 LIVE.with_borrow_mut(|live| match live.iter_mut().find(|f| f.id == id) {
63 Some(frame) => {
64 frame.retained.push(Arc::downgrade(node));
65 true
66 }
67 None => false,
68 })
69}
70
71struct Tracing {
74 id: TraceId,
75}
76
77impl Tracing {
78 fn enter() -> Self {
79 let id = NEXT_TRACE.fetch_add(1, Ordering::Relaxed);
80 LIVE.with_borrow_mut(|live| {
81 live.push(Frame {
82 id,
83 retained: Vec::new(),
84 });
85 });
86 Tracing { id }
87 }
88
89 fn tracer(&self, primal: &Array) -> Result<Array> {
92 let shape = primal.dims().to_vec();
93 let inputs = vec![primal.clone()];
94 let trace = Some(inherited(&inputs).map_or(self.id, |outer| outer.min(self.id)));
95 Ok(Array::record(Primitive::Broadcast(shape), inputs, trace)?.swap_remove(0))
96 }
97}
98
99impl Drop for Tracing {
100 fn drop(&mut self) {
101 let frame = LIVE.with_borrow_mut(|live| {
102 let at = live.iter().rposition(|f| f.id == self.id)?;
103 Some(live.remove(at))
104 });
105 for node in frame.into_iter().flat_map(|f| f.retained) {
106 let Some(node) = node.upgrade() else { continue };
107 let inputs = {
108 let mut state = node.lock();
109 match std::mem::replace(&mut *state, State::Done) {
110 State::Retained { inputs, .. } => inputs,
111 other => {
112 *state = other;
113 continue;
114 }
115 }
116 };
117 drop(inputs); }
119 }
120}
121
122type Key = (*const Node, u32);
123
124fn key(a: &Array) -> Key {
125 (Arc::as_ptr(&a.node), a.output)
126}
127
128struct Step {
130 node: Arc<Node>,
131 primitive: Primitive,
132 inputs: Vec<Array>,
133 wanted: Vec<bool>,
135}
136
137impl Step {
138 fn outputs(&self) -> Vec<Array> {
139 (0..self.node.outputs.len() as u32)
140 .map(|output| Array {
141 node: self.node.clone(),
142 output,
143 })
144 .collect()
145 }
146}
147
148type Graph = (Primitive, Vec<Array>);
150
151fn graph(node: &Node) -> Option<Graph> {
153 match &*node.lock() {
154 State::Pending { primitive, inputs } | State::Retained { primitive, inputs } => {
155 Some((primitive.clone(), inputs.clone()))
156 }
157 State::Running { .. } | State::Done | State::Failed { .. } => None,
158 }
159}
160
161fn followed(primitive: &Primitive, inputs: &[Array]) -> usize {
164 match primitive {
165 Primitive::Custom(call) => call.primals,
166 _ => inputs.len(),
167 }
168}
169
170fn tape(outputs: &[Array], sources: &NodeSet, stop_gradient: bool) -> Vec<Step> {
174 let mut depends: NodeMap<*const Node, bool> = NodeMap::default();
175 let mut visited = NodeSet::default();
176 let mut order = Vec::new();
177 let mut stack: Vec<(Arc<Node>, Option<Graph>)> =
178 outputs.iter().map(|a| (a.node.clone(), None)).collect();
179 while let Some((node, expanded)) = stack.pop() {
180 let ptr = Arc::as_ptr(&node);
181 if let Some((primitive, inputs)) = expanded {
182 let n = followed(&primitive, &inputs);
183 let wanted: Vec<bool> = inputs
184 .iter()
185 .enumerate()
186 .map(|(i, a)| i < n && depends.get(&Arc::as_ptr(&a.node)).copied().unwrap_or(false))
187 .collect();
188 let cut = stop_gradient && primitive == Primitive::StopGradient;
189 let dep = !cut && wanted.contains(&true);
190 depends.insert(ptr, dep);
191 if dep {
192 order.push(Step {
193 node,
194 primitive,
195 inputs,
196 wanted,
197 });
198 }
199 continue;
200 }
201 if !visited.insert(ptr) {
202 continue;
203 }
204 if sources.contains(&ptr) {
205 depends.insert(ptr, true);
206 continue;
207 }
208 let Some((primitive, inputs)) = graph(&node) else {
209 continue; };
211 let next: Vec<Arc<Node>> = inputs[..followed(&primitive, &inputs)]
212 .iter()
213 .map(|a| a.node.clone())
214 .filter(|n| !visited.contains(&Arc::as_ptr(n)))
215 .collect();
216 stack.push((node, Some((primitive, inputs))));
217 stack.extend(next.into_iter().map(|n| (n, None)));
218 }
219 order
220}
221
222fn accumulate(map: &mut NodeMap<Key, Array>, key: Key, value: Array) -> Result<()> {
223 let value = match map.remove(&key) {
224 Some(previous) => ops::add(&previous, &value)?,
225 None => value,
226 };
227 map.insert(key, value);
228 Ok(())
229}
230
231fn check_shapes(op: &str, what: &str, arrays: &[Array], given: &[Array]) -> Result<()> {
232 if arrays.len() != given.len() {
233 return Err(Error::InvalidArgument(format!(
234 "{op}: {} {what}s for {} arrays",
235 given.len(),
236 arrays.len()
237 )));
238 }
239 for (a, g) in arrays.iter().zip(given) {
240 if a.dims() != g.dims() {
241 return Err(Error::InvalidArgument(format!(
242 "{op}: shape {:?} does not match {what} shape {:?}",
243 a.dims(),
244 g.dims()
245 )));
246 }
247 if a.dtype() != g.dtype() {
248 return Err(Error::InvalidArgument(format!(
249 "{op}: dtype {} does not match {what} dtype {}",
250 a.dtype(),
251 g.dtype()
252 )));
253 }
254 }
255 Ok(())
256}
257
258fn reverse(outputs: &[Array], cotangents: &[Array], tracers: &[Array]) -> Result<Vec<Array>> {
260 let sources = tracers.iter().map(|t| Arc::as_ptr(&t.node)).collect();
261 let mut cot = NodeMap::default();
262 for (o, c) in outputs.iter().zip(cotangents) {
263 accumulate(&mut cot, key(o), c.clone())?;
264 }
265 for step in tape(outputs, &sources, true).iter().rev() {
266 let outs = step.outputs();
267 if !outs.iter().any(|o| cot.contains_key(&key(o))) {
268 continue;
269 }
270 let cots = outs
271 .iter()
272 .map(|o| cot.remove(&key(o)).map_or_else(|| zeros_like(o), Ok))
273 .collect::<Result<Vec<_>>>()?;
274 let grads = step
275 .primitive
276 .vjp(&step.inputs, &outs, &cots, &step.wanted)?;
277 for ((input, grad), &wanted) in step.inputs.iter().zip(grads).zip(&step.wanted) {
278 if let (Some(grad), true) = (grad, wanted) {
279 accumulate(&mut cot, key(input), grad)?;
280 }
281 }
282 }
283 tracers
284 .iter()
285 .map(|t| cot.remove(&key(t)).map_or_else(|| zeros_like(t), Ok))
286 .collect()
287}
288
289fn checked(outputs: Vec<Array>) -> Result<Vec<Array>> {
292 outputs.into_iter().map(Array::check).collect()
293}
294
295pub fn vjp_flat(
314 f: impl FnOnce(&[Array]) -> Result<Vec<Array>>,
315 primals: &[Array],
316 cotangents: &[Array],
317) -> Result<(Vec<Array>, Vec<Array>)> {
318 let tracing = Tracing::enter();
319 let tracers = primals
320 .iter()
321 .map(|p| tracing.tracer(p))
322 .collect::<Result<Vec<_>>>()?;
323 let outputs = checked(f(&tracers)?)?;
324 check_shapes("vjp", "cotangent", &outputs, cotangents)?;
325 let grads = reverse(&outputs, cotangents, &tracers)?;
326 Ok((outputs, grads))
327}
328
329pub fn jvp_flat(
344 f: impl FnOnce(&[Array]) -> Result<Vec<Array>>,
345 primals: &[Array],
346 tangents: &[Array],
347) -> Result<(Vec<Array>, Vec<Array>)> {
348 check_shapes("jvp", "tangent", primals, tangents)?;
349 let tracing = Tracing::enter();
350 let tracers = primals
351 .iter()
352 .map(|p| tracing.tracer(p))
353 .collect::<Result<Vec<_>>>()?;
354 let outputs = checked(f(&tracers)?)?;
355 let tangents = forward(&outputs, &tracers, tangents)?;
356 Ok((outputs, tangents))
357}
358
359fn forward(outputs: &[Array], tracers: &[Array], tangents: &[Array]) -> Result<Vec<Array>> {
361 let sources = tracers.iter().map(|t| Arc::as_ptr(&t.node)).collect();
362 let mut tan: NodeMap<Key, Array> = tracers
363 .iter()
364 .zip(tangents)
365 .map(|(t, v)| (key(t), v.clone()))
366 .collect();
367 for step in tape(outputs, &sources, true) {
368 let inputs: Vec<Option<Array>> = step
369 .inputs
370 .iter()
371 .map(|a| tan.get(&key(a)).cloned())
372 .collect();
373 if inputs.iter().all(Option::is_none) {
374 continue;
375 }
376 let outs = step.outputs();
377 for (o, t) in outs
378 .iter()
379 .zip(step.primitive.jvp(&step.inputs, &outs, &inputs)?)
380 {
381 tan.insert(key(o), t);
382 }
383 }
384 outputs
385 .iter()
386 .map(|o| tan.get(&key(o)).cloned().map_or_else(|| zeros_like(o), Ok))
387 .collect()
388}
389
390pub fn value_and_grad_flat<F>(
413 f: F,
414 argnums: &[usize],
415) -> impl Fn(&[Array]) -> Result<(Array, Vec<Array>)>
416where
417 F: Fn(&[Array]) -> Result<Array>,
418{
419 let argnums = argnums.to_vec();
420 move |args| {
421 if argnums.is_empty() {
422 return Err(Error::InvalidArgument(
423 "value_and_grad: no argument to differentiate".into(),
424 ));
425 }
426 let mut seen = HashSet::new();
427 for &i in &argnums {
428 if i >= args.len() || !seen.insert(i) {
429 return Err(Error::InvalidArgument(format!(
430 "value_and_grad: invalid or repeated argument {i} for {} arguments",
431 args.len()
432 )));
433 }
434 if !matches!(
435 args[i].dtype().category(),
436 Category::Float | Category::Complex
437 ) {
438 return Err(Error::InvalidArgument(format!(
439 "value_and_grad: argument {i} is {}; only floating-point and complex \
440 arrays can be differentiated",
441 args[i].dtype()
442 )));
443 }
444 }
445 let tracing = Tracing::enter();
446 let mut inputs = args.to_vec();
447 let mut tracers = Vec::with_capacity(argnums.len());
448 for &i in &argnums {
449 inputs[i] = tracing.tracer(&args[i])?;
450 tracers.push(inputs[i].clone());
451 }
452 let value = f(&inputs)?.check()?;
453 if value.ndim() != 0 {
454 return Err(Error::InvalidArgument(format!(
455 "value_and_grad: the function must return a scalar (0-D) array, got shape {:?}",
456 value.dims()
457 )));
458 }
459 let seed = ops::filled(&[], value.dtype(), |_| Canon::Int(1))?;
460 let grads = reverse(std::slice::from_ref(&value), &[seed], &tracers)?;
461 Ok((value, grads))
462 }
463}
464
465pub fn grad_flat<F>(f: F, argnums: &[usize]) -> impl Fn(&[Array]) -> Result<Vec<Array>>
479where
480 F: Fn(&[Array]) -> Result<Array>,
481{
482 let value_and_grad = value_and_grad_flat(f, argnums);
483 move |args| Ok(value_and_grad(args)?.1)
484}
485
486fn placeholder(like: &Array, shape: &[usize], tracing: &Tracing) -> Result<Array> {
489 let failed = Array::failed(
490 Error::InvalidArgument("vmap: arrays cannot be evaluated inside vmap".into()),
491 like.dtype(),
492 &[],
493 );
494 let primitive = Primitive::Broadcast(shape.to_vec());
495 Ok(Array::record_unchecked(primitive, vec![failed], Some(tracing.id))?.swap_remove(0))
496}
497
498pub(crate) fn batch(
501 f: &dyn Fn(&[Array]) -> Result<Vec<Array>>,
502 args: &[Array],
503 axes: &[Option<usize>],
504) -> Result<Vec<Array>> {
505 let mut size = None;
506 for (a, axis) in args.iter().zip(axes) {
507 let Some(axis) = *axis else { continue };
508 let n = *a.dims().get(axis).ok_or_else(|| {
509 Error::InvalidArgument(format!(
510 "vmap: axis {axis} out of range for shape {:?}",
511 a.dims()
512 ))
513 })?;
514 if size.is_some_and(|s| s != n) {
515 return Err(Error::InvalidArgument(format!(
516 "vmap: inconsistent batch sizes {} and {n}",
517 size.unwrap_or(0)
518 )));
519 }
520 size = Some(n);
521 }
522 let size = size
523 .ok_or_else(|| Error::InvalidArgument("vmap: at least one input must be batched".into()))?;
524 let tracing = Tracing::enter();
525 let mut batched: NodeMap<Key, (Array, usize)> = NodeMap::default();
526 let mut sources = NodeSet::default();
527 let mut inputs = Vec::with_capacity(args.len());
528 for (a, axis) in args.iter().zip(axes) {
529 inputs.push(match *axis {
530 Some(axis) => {
531 let mut shape = a.dims().to_vec();
532 shape.remove(axis);
533 let p = placeholder(a, &shape, &tracing)?;
534 sources.insert(Arc::as_ptr(&p.node));
535 batched.insert(key(&p), (a.clone(), axis));
536 p
537 }
538 None => a.clone(),
539 });
540 }
541 let outputs = checked(f(&inputs)?)?;
542 for step in tape(&outputs, &sources, false) {
543 let (arrays, axes): (Vec<Array>, Vec<Option<usize>>) = step
544 .inputs
545 .iter()
546 .map(|a| match batched.get(&key(a)) {
547 Some((b, axis)) => (b.clone(), Some(*axis)),
548 None => (a.clone(), None),
549 })
550 .unzip();
551 let (results, axis) = step.primitive.vmap(&arrays, &axes)?;
552 let outs = step.outputs();
555 if results.len() != outs.len() || results.iter().any(|r| axis >= r.ndim()) {
556 return Err(Error::InvalidArgument(format!(
557 "vmap: the batching rule of {} returned {} outputs with batch axis {axis} \
558 ({} expected, each with that axis)",
559 step.primitive.name(),
560 results.len(),
561 outs.len()
562 )));
563 }
564 for (o, r) in outs.iter().zip(results) {
565 batched.insert(key(o), (r, axis));
566 }
567 }
568 outputs
569 .iter()
570 .map(|o| match batched.get(&key(o)) {
571 Some((b, axis)) => crate::batching::move_axis(b, *axis, 0),
572 None => {
574 let mut shape = vec![size];
575 shape.extend_from_slice(o.dims());
576 ops::broadcast_to(&ops::expand_dims(o, 0)?, &shape)
577 }
578 })
579 .collect()
580}
581
582fn normalize_axis(axis: isize, ndim: usize) -> Result<usize> {
584 let a = if axis < 0 { axis + ndim as isize } else { axis };
585 if (0..ndim as isize).contains(&a) {
586 Ok(a as usize)
587 } else {
588 Err(Error::InvalidArgument(format!(
589 "vmap: axis {axis} out of range for {ndim} dimensions"
590 )))
591 }
592}
593
594pub fn vmap_flat<F>(
615 f: F,
616 in_axes: &[Option<i32>],
617 out_axes: &[i32],
618) -> impl Fn(&[Array]) -> Result<Vec<Array>>
619where
620 F: Fn(&[Array]) -> Result<Vec<Array>>,
621{
622 let (in_axes, out_axes) = (in_axes.to_vec(), out_axes.to_vec());
623 move |args| {
624 let axes = if in_axes.is_empty() {
625 vec![Some(0); args.len()]
626 } else if in_axes.len() == args.len() {
627 in_axes
628 .iter()
629 .zip(args)
630 .map(|(axis, a)| {
631 axis.map(|axis| normalize_axis(axis as isize, a.ndim()))
632 .transpose()
633 })
634 .collect::<Result<Vec<_>>>()?
635 } else {
636 return Err(Error::InvalidArgument(format!(
637 "vmap: {} input axes for {} arguments",
638 in_axes.len(),
639 args.len()
640 )));
641 };
642 let outputs = batch(&f, args, &axes)?;
643 if !out_axes.is_empty() && out_axes.len() != outputs.len() {
644 return Err(Error::InvalidArgument(format!(
645 "vmap: {} output axes for {} outputs",
646 out_axes.len(),
647 outputs.len()
648 )));
649 }
650 outputs
651 .iter()
652 .enumerate()
653 .map(|(i, o)| {
654 let axis =
655 normalize_axis(out_axes.get(i).copied().unwrap_or(0) as isize, o.ndim())?;
656 crate::batching::move_axis(o, 0, axis)
657 })
658 .collect()
659 }
660}
661
662pub trait IntoResult {
670 type Ok;
672 fn into_result(self) -> Result<Self::Ok>;
674}
675
676impl<T: Tree> IntoResult for T {
677 type Ok = T;
678 fn into_result(self) -> Result<T> {
679 Ok(self)
680 }
681}
682
683impl<T> IntoResult for Result<T> {
684 type Ok = T;
685 fn into_result(self) -> Result<T> {
686 self
687 }
688}
689
690pub trait GradOutput: Tree {
694 fn loss(&self) -> &Array;
696}
697
698impl GradOutput for Array {
699 fn loss(&self) -> &Array {
700 self
701 }
702}
703
704impl<A: Tree> GradOutput for (Array, A) {
705 fn loss(&self) -> &Array {
706 &self.0
707 }
708}
709
710fn differentiated<T: Leaves + ?Sized>(op: &str, tree: &T) -> Result<(Vec<Array>, Vec<bool>)> {
715 struct D(Vec<Array>, Vec<bool>, Option<usize>);
716 impl Visitor for D {
717 fn leaf(&mut self, _: &str, a: &Array, kind: LeafKind, trainable: bool) {
718 let float = differentiable(a.dtype());
719 if kind == LeafKind::Array && !float && self.2.is_none() {
720 self.2 = Some(self.0.len());
721 }
722 self.1.push(trainable && float);
723 self.0.push(a.clone());
724 }
725 fn paths(&self) -> bool {
726 false
727 }
728 }
729 let mut d = D(Vec::new(), Vec::new(), None);
730 tree.visit(&mut Visit::new(&mut d));
731 if let Some(i) = d.2 {
732 struct Nth(usize, Option<String>);
734 impl Visitor for Nth {
735 fn leaf(&mut self, path: &str, _: &Array, _: LeafKind, _: bool) {
736 if self.0 == 0 && self.1.is_none() {
737 self.1 = Some(path.to_owned());
738 }
739 self.0 = self.0.wrapping_sub(1);
740 }
741 }
742 let mut nth = Nth(i, None);
743 tree.visit(&mut Visit::new(&mut nth));
744 let path = nth.1.unwrap_or_default();
745 let dtype = d.0[i].dtype();
746 return Err(Error::InvalidArgument(format!(
747 "{op}: the array {path:?} is {dtype}; only floating-point and complex arrays can be \
748 differentiated (capture it in the closure, or make it a frozen `Param`)"
749 )));
750 }
751 Ok((d.0, d.1))
752}
753
754fn traced(tracing: &Tracing, leaves: &[Array], mask: &[bool]) -> Result<(Vec<Array>, Vec<Array>)> {
756 let mut inputs = Vec::with_capacity(leaves.len());
757 let mut tracers = Vec::new();
758 for (a, &d) in leaves.iter().zip(mask) {
759 if d {
760 let t = tracing.tracer(a)?;
761 tracers.push(t.clone());
762 inputs.push(t);
763 } else {
764 inputs.push(a.clone());
765 }
766 }
767 Ok((inputs, tracers))
768}
769
770fn assemble(leaves: &[Array], mask: &[bool], grads: Vec<Array>) -> Vec<Array> {
773 let mut grads = grads.into_iter();
774 leaves
775 .iter()
776 .zip(mask)
777 .map(|(a, &d)| match d {
778 true => grads.next().expect("one gradient per differentiated leaf"),
779 false => broadcast_zero(a),
780 })
781 .collect()
782}
783
784fn check_tree<A: Tree, B: Tree>(op: &str, what: &str, like: &A, given: &B) -> Result<()> {
786 let (lp, gp) = (like.flatten(), given.flatten());
787 if lp.len() != gp.len() {
788 return Err(Error::InvalidArgument(format!(
789 "{op}: {} {what} leaves for {} leaves",
790 gp.len(),
791 lp.len()
792 )));
793 }
794 for ((p, a), (q, g)) in lp.iter().zip(&gp) {
795 if p != q {
796 return Err(Error::InvalidArgument(format!(
797 "{op}: the {what} leaf {q:?} is not at the path {p:?}"
798 )));
799 }
800 if a.shape() != g.shape() || a.dtype() != g.dtype() {
801 return Err(Error::InvalidArgument(format!(
802 "{op}: the {what} at {p:?} is {} {:?}, expected {} {:?}",
803 g.dtype(),
804 g.shape(),
805 a.dtype(),
806 a.shape()
807 )));
808 }
809 }
810 Ok(())
811}
812
813fn value_and_grad_tree<P: Tree, O: GradOutput>(
815 f: impl FnOnce(&P) -> Result<O>,
816 params: &P,
817) -> Result<(O, P)> {
818 let (leaves, mask) = differentiated("value_and_grad", params)?;
819 let tracing = Tracing::enter();
820 let (inputs, tracers) = traced(&tracing, &leaves, &mask)?;
821 let out = f(¶ms.unflatten(inputs)?)?;
822 let loss = out.loss().clone().check()?;
823 if loss.ndim() != 0 {
824 return Err(Error::InvalidArgument(format!(
825 "value_and_grad: the function must return a scalar (0-D) loss, got shape {:?}",
826 loss.dims()
827 )));
828 }
829 let seed = ops::filled(&[], loss.dtype(), |_| Canon::Int(1))?;
830 let grads = reverse(std::slice::from_ref(&loss), &[seed], &tracers)?;
831 let grads = params.unflatten(assemble(&leaves, &mask, grads))?;
832 Ok((out, grads))
833}
834
835pub fn value_and_grad<P, F, R>(f: F) -> impl Fn(&P) -> Result<(R::Ok, P)>
866where
867 P: Tree,
868 F: Fn(&P) -> R,
869 R: IntoResult,
870 R::Ok: GradOutput,
871{
872 move |params| value_and_grad_tree(|p: &P| f(p).into_result(), params)
873}
874
875pub fn grad<P, F, R>(f: F) -> impl Fn(&P) -> Result<P>
892where
893 P: Tree,
894 F: Fn(&P) -> R,
895 R: IntoResult<Ok = Array>,
896{
897 move |params| Ok(value_and_grad_tree(|p: &P| f(p).into_result(), params)?.1)
898}
899
900pub fn vjp<P, O, F, R>(f: F, primals: &P, cotangents: &O) -> Result<(O, P)>
916where
917 P: Tree,
918 O: Tree,
919 F: FnOnce(&P) -> R,
920 R: IntoResult<Ok = O>,
921{
922 let (leaves, mask) = differentiated("vjp", primals)?;
923 let tracing = Tracing::enter();
924 let (inputs, tracers) = traced(&tracing, &leaves, &mask)?;
925 let out = f(&primals.unflatten(inputs)?).into_result()?;
926 let outputs = checked(out.leaves())?;
927 check_tree("vjp", "cotangent", &out, cotangents)?;
928 let grads = reverse(&outputs, &cotangents.leaves(), &tracers)?;
929 let grads = primals.unflatten(assemble(&leaves, &mask, grads))?;
930 Ok((out.unflatten(outputs)?, grads))
931}
932
933pub fn jvp<P, O, F, R>(f: F, primals: &P, tangents: &P) -> Result<(O, O)>
947where
948 P: Tree,
949 O: Tree,
950 F: FnOnce(&P) -> R,
951 R: IntoResult<Ok = O>,
952{
953 check_tree("jvp", "tangent", primals, tangents)?;
954 let (leaves, mask) = differentiated("jvp", primals)?;
955 let tangents: Vec<Array> = tangents
956 .leaves()
957 .into_iter()
958 .zip(&mask)
959 .filter_map(|(t, &d)| d.then_some(t))
960 .collect();
961 let tracing = Tracing::enter();
962 let (inputs, tracers) = traced(&tracing, &leaves, &mask)?;
963 let out = f(&primals.unflatten(inputs)?).into_result()?;
964 let outputs = checked(out.leaves())?;
965 let out_tangents = forward(&outputs, &tracers, &tangents)?;
966 Ok((out.unflatten(outputs)?, out.unflatten(out_tangents)?))
967}
968
969pub trait InAxes {
972 fn axes(self) -> Vec<Option<i32>>;
974}
975
976pub trait InAxis {
978 fn axis(self) -> Option<i32>;
980}
981
982impl InAxis for i32 {
983 fn axis(self) -> Option<i32> {
984 Some(self)
985 }
986}
987
988impl InAxis for Option<i32> {
989 fn axis(self) -> Option<i32> {
990 self
991 }
992}
993
994impl InAxes for i32 {
995 fn axes(self) -> Vec<Option<i32>> {
996 vec![Some(self)]
997 }
998}
999
1000impl InAxes for Option<i32> {
1001 fn axes(self) -> Vec<Option<i32>> {
1002 vec![self]
1003 }
1004}
1005
1006impl InAxes for Vec<Option<i32>> {
1007 fn axes(self) -> Vec<Option<i32>> {
1008 self
1009 }
1010}
1011
1012pub trait OutAxes {
1015 fn axes(self) -> Vec<i32>;
1017}
1018
1019impl OutAxes for i32 {
1020 fn axes(self) -> Vec<i32> {
1021 vec![self]
1022 }
1023}
1024
1025impl OutAxes for Vec<i32> {
1026 fn axes(self) -> Vec<i32> {
1027 self
1028 }
1029}
1030
1031macro_rules! axes_tuples {
1032 ($(($($t:ident $i:tt),+)),*) => {$(
1033 impl<$($t: InAxis),+> InAxes for ($($t,)+) {
1034 fn axes(self) -> Vec<Option<i32>> {
1035 vec![$(self.$i.axis()),+]
1036 }
1037 }
1038 impl OutAxes for ($(axes_tuples!(@i32 $t),)+) {
1039 fn axes(self) -> Vec<i32> {
1040 vec![$(self.$i),+]
1041 }
1042 }
1043 )*};
1044 (@i32 $t:ident) => { i32 };
1045}
1046
1047axes_tuples!(
1048 (A 0, B 1),
1049 (A 0, B 1, C 2),
1050 (A 0, B 1, C 2, D 3),
1051 (A 0, B 1, C 2, D 3, E 4),
1052 (A 0, B 1, C 2, D 3, E 4, F 5),
1053 (A 0, B 1, C 2, D 3, E 4, F 5, G 6),
1054 (A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7)
1055);
1056
1057fn top_level(paths: &[String]) -> (Vec<usize>, usize) {
1060 let mut firsts: Vec<&str> = Vec::new();
1061 let groups = paths
1062 .iter()
1063 .map(|p| {
1064 let first = p.split('.').next().unwrap_or("");
1065 match firsts.iter().position(|f| *f == first) {
1066 Some(i) => i,
1067 None => {
1068 firsts.push(first);
1069 firsts.len() - 1
1070 }
1071 }
1072 })
1073 .collect();
1074 (groups, firsts.len())
1075}
1076
1077pub struct Vmap<I, O, F> {
1081 f: F,
1082 in_axes: Vec<Option<i32>>,
1083 out_axes: Vec<i32>,
1084 types: PhantomData<fn(&I) -> O>,
1085}
1086
1087pub fn vmap<I, O, F, R>(f: F) -> Vmap<I, O, F>
1107where
1108 I: Tree,
1109 O: Tree,
1110 F: Fn(&I) -> R,
1111 R: IntoResult<Ok = O>,
1112{
1113 Vmap {
1114 f,
1115 in_axes: vec![Some(0)],
1116 out_axes: vec![0],
1117 types: PhantomData,
1118 }
1119}
1120
1121impl<I: Tree, O: Tree, F> Vmap<I, O, F> {
1122 pub fn in_axes(mut self, axes: impl InAxes) -> Self {
1125 self.in_axes = axes.axes();
1126 self
1127 }
1128
1129 pub fn out_axes(mut self, axes: impl OutAxes) -> Self {
1131 self.out_axes = axes.axes();
1132 self
1133 }
1134
1135 pub fn call<R>(&self, inputs: &I) -> Result<O>
1137 where
1138 F: Fn(&I) -> R,
1139 R: IntoResult<Ok = O>,
1140 {
1141 let named = inputs.flatten();
1142 let paths: Vec<String> = named.iter().map(|(p, _)| p.clone()).collect();
1143 let args: Vec<Array> = named.into_iter().map(|(_, a)| a).collect();
1144 let (groups, n) = top_level(&paths);
1145 let per_arg = |k: usize| match self.in_axes.as_slice() {
1146 [one] => Ok(*one),
1147 axes if axes.len() == n => Ok(axes[groups[k]]),
1148 axes => Err(Error::InvalidArgument(format!(
1149 "vmap: {} input axes for {n} top-level arguments",
1150 axes.len()
1151 ))),
1152 };
1153 let axes = args
1154 .iter()
1155 .enumerate()
1156 .map(|(k, a)| {
1157 per_arg(k)?
1158 .map(|axis| normalize_axis(axis as isize, a.ndim()))
1159 .transpose()
1160 .map_err(|e| match e {
1161 Error::InvalidArgument(m) => {
1162 Error::InvalidArgument(format!("{m} (argument {:?})", paths[k]))
1163 }
1164 e => e,
1165 })
1166 })
1167 .collect::<Result<Vec<_>>>()?;
1168 let template: RefCell<Option<O>> = RefCell::new(None);
1169 let body = |xs: &[Array]| -> Result<Vec<Array>> {
1170 let out = (self.f)(&inputs.unflatten(xs.iter().cloned())?).into_result()?;
1171 let leaves = out.leaves();
1172 *template.borrow_mut() = Some(out);
1173 Ok(leaves)
1174 };
1175 let outputs = batch(&body, &args, &axes)?;
1176 let template = template
1177 .into_inner()
1178 .ok_or_else(|| Error::InvalidArgument("vmap: the function did not run".into()))?;
1179 let out_paths = template.paths();
1180 let (out_groups, m) = top_level(&out_paths);
1181 let moved = outputs
1182 .iter()
1183 .enumerate()
1184 .map(|(k, o)| {
1185 let axis = match self.out_axes.as_slice() {
1186 [one] => *one,
1187 axes if axes.len() == m => axes[out_groups[k]],
1188 axes => {
1189 return Err(Error::InvalidArgument(format!(
1190 "vmap: {} output axes for {m} top-level outputs",
1191 axes.len()
1192 )));
1193 }
1194 };
1195 let axis = normalize_axis(axis as isize, o.ndim())?;
1196 crate::batching::move_axis(o, 0, axis)
1197 })
1198 .collect::<Result<Vec<_>>>()?;
1199 template.unflatten(moved)
1200 }
1201
1202 pub fn into_fn<R>(self) -> impl Fn(&I) -> Result<O>
1205 where
1206 F: Fn(&I) -> R,
1207 R: IntoResult<Ok = O>,
1208 {
1209 move |inputs| self.call(inputs)
1210 }
1211}