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 for (o, r) in step.outputs().iter().zip(results) {
553 batched.insert(key(o), (r, axis));
554 }
555 }
556 outputs
557 .iter()
558 .map(|o| match batched.get(&key(o)) {
559 Some((b, axis)) => crate::batching::move_axis(b, *axis, 0),
560 None => {
562 let mut shape = vec![size];
563 shape.extend_from_slice(o.dims());
564 ops::broadcast_to(&ops::expand_dims(o, 0)?, &shape)
565 }
566 })
567 .collect()
568}
569
570fn normalize_axis(axis: isize, ndim: usize) -> Result<usize> {
572 let a = if axis < 0 { axis + ndim as isize } else { axis };
573 if (0..ndim as isize).contains(&a) {
574 Ok(a as usize)
575 } else {
576 Err(Error::InvalidArgument(format!(
577 "vmap: axis {axis} out of range for {ndim} dimensions"
578 )))
579 }
580}
581
582pub fn vmap_flat<F>(
603 f: F,
604 in_axes: &[Option<i32>],
605 out_axes: &[i32],
606) -> impl Fn(&[Array]) -> Result<Vec<Array>>
607where
608 F: Fn(&[Array]) -> Result<Vec<Array>>,
609{
610 let (in_axes, out_axes) = (in_axes.to_vec(), out_axes.to_vec());
611 move |args| {
612 let axes = if in_axes.is_empty() {
613 vec![Some(0); args.len()]
614 } else if in_axes.len() == args.len() {
615 in_axes
616 .iter()
617 .zip(args)
618 .map(|(axis, a)| {
619 axis.map(|axis| normalize_axis(axis as isize, a.ndim()))
620 .transpose()
621 })
622 .collect::<Result<Vec<_>>>()?
623 } else {
624 return Err(Error::InvalidArgument(format!(
625 "vmap: {} input axes for {} arguments",
626 in_axes.len(),
627 args.len()
628 )));
629 };
630 let outputs = batch(&f, args, &axes)?;
631 if !out_axes.is_empty() && out_axes.len() != outputs.len() {
632 return Err(Error::InvalidArgument(format!(
633 "vmap: {} output axes for {} outputs",
634 out_axes.len(),
635 outputs.len()
636 )));
637 }
638 outputs
639 .iter()
640 .enumerate()
641 .map(|(i, o)| {
642 let axis =
643 normalize_axis(out_axes.get(i).copied().unwrap_or(0) as isize, o.ndim())?;
644 crate::batching::move_axis(o, 0, axis)
645 })
646 .collect()
647 }
648}
649
650pub trait IntoResult {
658 type Ok;
660 fn into_result(self) -> Result<Self::Ok>;
662}
663
664impl<T: Tree> IntoResult for T {
665 type Ok = T;
666 fn into_result(self) -> Result<T> {
667 Ok(self)
668 }
669}
670
671impl<T> IntoResult for Result<T> {
672 type Ok = T;
673 fn into_result(self) -> Result<T> {
674 self
675 }
676}
677
678pub trait GradOutput: Tree {
682 fn loss(&self) -> &Array;
684}
685
686impl GradOutput for Array {
687 fn loss(&self) -> &Array {
688 self
689 }
690}
691
692impl<A: Tree> GradOutput for (Array, A) {
693 fn loss(&self) -> &Array {
694 &self.0
695 }
696}
697
698fn differentiated<T: Leaves + ?Sized>(op: &str, tree: &T) -> Result<(Vec<Array>, Vec<bool>)> {
703 struct D(Vec<Array>, Vec<bool>, Option<usize>);
704 impl Visitor for D {
705 fn leaf(&mut self, _: &str, a: &Array, kind: LeafKind, trainable: bool) {
706 let float = differentiable(a.dtype());
707 if kind == LeafKind::Array && !float && self.2.is_none() {
708 self.2 = Some(self.0.len());
709 }
710 self.1.push(trainable && float);
711 self.0.push(a.clone());
712 }
713 fn paths(&self) -> bool {
714 false
715 }
716 }
717 let mut d = D(Vec::new(), Vec::new(), None);
718 tree.visit(&mut Visit::new(&mut d));
719 if let Some(i) = d.2 {
720 struct Nth(usize, Option<String>);
722 impl Visitor for Nth {
723 fn leaf(&mut self, path: &str, _: &Array, _: LeafKind, _: bool) {
724 if self.0 == 0 && self.1.is_none() {
725 self.1 = Some(path.to_owned());
726 }
727 self.0 = self.0.wrapping_sub(1);
728 }
729 }
730 let mut nth = Nth(i, None);
731 tree.visit(&mut Visit::new(&mut nth));
732 let path = nth.1.unwrap_or_default();
733 let dtype = d.0[i].dtype();
734 return Err(Error::InvalidArgument(format!(
735 "{op}: the array {path:?} is {dtype}; only floating-point and complex arrays can be \
736 differentiated (capture it in the closure, or make it a frozen `Param`)"
737 )));
738 }
739 Ok((d.0, d.1))
740}
741
742fn traced(tracing: &Tracing, leaves: &[Array], mask: &[bool]) -> Result<(Vec<Array>, Vec<Array>)> {
744 let mut inputs = Vec::with_capacity(leaves.len());
745 let mut tracers = Vec::new();
746 for (a, &d) in leaves.iter().zip(mask) {
747 if d {
748 let t = tracing.tracer(a)?;
749 tracers.push(t.clone());
750 inputs.push(t);
751 } else {
752 inputs.push(a.clone());
753 }
754 }
755 Ok((inputs, tracers))
756}
757
758fn assemble(leaves: &[Array], mask: &[bool], grads: Vec<Array>) -> Vec<Array> {
761 let mut grads = grads.into_iter();
762 leaves
763 .iter()
764 .zip(mask)
765 .map(|(a, &d)| match d {
766 true => grads.next().expect("one gradient per differentiated leaf"),
767 false => broadcast_zero(a),
768 })
769 .collect()
770}
771
772fn check_tree<A: Tree, B: Tree>(op: &str, what: &str, like: &A, given: &B) -> Result<()> {
774 let (lp, gp) = (like.flatten(), given.flatten());
775 if lp.len() != gp.len() {
776 return Err(Error::InvalidArgument(format!(
777 "{op}: {} {what} leaves for {} leaves",
778 gp.len(),
779 lp.len()
780 )));
781 }
782 for ((p, a), (q, g)) in lp.iter().zip(&gp) {
783 if p != q {
784 return Err(Error::InvalidArgument(format!(
785 "{op}: the {what} leaf {q:?} is not at the path {p:?}"
786 )));
787 }
788 if a.shape() != g.shape() || a.dtype() != g.dtype() {
789 return Err(Error::InvalidArgument(format!(
790 "{op}: the {what} at {p:?} is {} {:?}, expected {} {:?}",
791 g.dtype(),
792 g.shape(),
793 a.dtype(),
794 a.shape()
795 )));
796 }
797 }
798 Ok(())
799}
800
801fn value_and_grad_tree<P: Tree, O: GradOutput>(
803 f: impl FnOnce(&P) -> Result<O>,
804 params: &P,
805) -> Result<(O, P)> {
806 let (leaves, mask) = differentiated("value_and_grad", params)?;
807 let tracing = Tracing::enter();
808 let (inputs, tracers) = traced(&tracing, &leaves, &mask)?;
809 let out = f(¶ms.unflatten(inputs)?)?;
810 let loss = out.loss().clone().check()?;
811 if loss.ndim() != 0 {
812 return Err(Error::InvalidArgument(format!(
813 "value_and_grad: the function must return a scalar (0-D) loss, got shape {:?}",
814 loss.dims()
815 )));
816 }
817 let seed = ops::filled(&[], loss.dtype(), |_| Canon::Int(1))?;
818 let grads = reverse(std::slice::from_ref(&loss), &[seed], &tracers)?;
819 let grads = params.unflatten(assemble(&leaves, &mask, grads))?;
820 Ok((out, grads))
821}
822
823pub fn value_and_grad<P, F, R>(f: F) -> impl Fn(&P) -> Result<(R::Ok, P)>
854where
855 P: Tree,
856 F: Fn(&P) -> R,
857 R: IntoResult,
858 R::Ok: GradOutput,
859{
860 move |params| value_and_grad_tree(|p: &P| f(p).into_result(), params)
861}
862
863pub fn grad<P, F, R>(f: F) -> impl Fn(&P) -> Result<P>
880where
881 P: Tree,
882 F: Fn(&P) -> R,
883 R: IntoResult<Ok = Array>,
884{
885 move |params| Ok(value_and_grad_tree(|p: &P| f(p).into_result(), params)?.1)
886}
887
888pub fn vjp<P, O, F, R>(f: F, primals: &P, cotangents: &O) -> Result<(O, P)>
904where
905 P: Tree,
906 O: Tree,
907 F: FnOnce(&P) -> R,
908 R: IntoResult<Ok = O>,
909{
910 let (leaves, mask) = differentiated("vjp", primals)?;
911 let tracing = Tracing::enter();
912 let (inputs, tracers) = traced(&tracing, &leaves, &mask)?;
913 let out = f(&primals.unflatten(inputs)?).into_result()?;
914 let outputs = checked(out.leaves())?;
915 check_tree("vjp", "cotangent", &out, cotangents)?;
916 let grads = reverse(&outputs, &cotangents.leaves(), &tracers)?;
917 let grads = primals.unflatten(assemble(&leaves, &mask, grads))?;
918 Ok((out.unflatten(outputs)?, grads))
919}
920
921pub fn jvp<P, O, F, R>(f: F, primals: &P, tangents: &P) -> Result<(O, O)>
935where
936 P: Tree,
937 O: Tree,
938 F: FnOnce(&P) -> R,
939 R: IntoResult<Ok = O>,
940{
941 check_tree("jvp", "tangent", primals, tangents)?;
942 let (leaves, mask) = differentiated("jvp", primals)?;
943 let tangents: Vec<Array> = tangents
944 .leaves()
945 .into_iter()
946 .zip(&mask)
947 .filter_map(|(t, &d)| d.then_some(t))
948 .collect();
949 let tracing = Tracing::enter();
950 let (inputs, tracers) = traced(&tracing, &leaves, &mask)?;
951 let out = f(&primals.unflatten(inputs)?).into_result()?;
952 let outputs = checked(out.leaves())?;
953 let out_tangents = forward(&outputs, &tracers, &tangents)?;
954 Ok((out.unflatten(outputs)?, out.unflatten(out_tangents)?))
955}
956
957pub trait InAxes {
960 fn axes(self) -> Vec<Option<i32>>;
962}
963
964pub trait InAxis {
966 fn axis(self) -> Option<i32>;
968}
969
970impl InAxis for i32 {
971 fn axis(self) -> Option<i32> {
972 Some(self)
973 }
974}
975
976impl InAxis for Option<i32> {
977 fn axis(self) -> Option<i32> {
978 self
979 }
980}
981
982impl InAxes for i32 {
983 fn axes(self) -> Vec<Option<i32>> {
984 vec![Some(self)]
985 }
986}
987
988impl InAxes for Option<i32> {
989 fn axes(self) -> Vec<Option<i32>> {
990 vec![self]
991 }
992}
993
994impl InAxes for Vec<Option<i32>> {
995 fn axes(self) -> Vec<Option<i32>> {
996 self
997 }
998}
999
1000pub trait OutAxes {
1003 fn axes(self) -> Vec<i32>;
1005}
1006
1007impl OutAxes for i32 {
1008 fn axes(self) -> Vec<i32> {
1009 vec![self]
1010 }
1011}
1012
1013impl OutAxes for Vec<i32> {
1014 fn axes(self) -> Vec<i32> {
1015 self
1016 }
1017}
1018
1019macro_rules! axes_tuples {
1020 ($(($($t:ident $i:tt),+)),*) => {$(
1021 impl<$($t: InAxis),+> InAxes for ($($t,)+) {
1022 fn axes(self) -> Vec<Option<i32>> {
1023 vec![$(self.$i.axis()),+]
1024 }
1025 }
1026 impl OutAxes for ($(axes_tuples!(@i32 $t),)+) {
1027 fn axes(self) -> Vec<i32> {
1028 vec![$(self.$i),+]
1029 }
1030 }
1031 )*};
1032 (@i32 $t:ident) => { i32 };
1033}
1034
1035axes_tuples!(
1036 (A 0, B 1),
1037 (A 0, B 1, C 2),
1038 (A 0, B 1, C 2, D 3),
1039 (A 0, B 1, C 2, D 3, E 4),
1040 (A 0, B 1, C 2, D 3, E 4, F 5),
1041 (A 0, B 1, C 2, D 3, E 4, F 5, G 6),
1042 (A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7)
1043);
1044
1045fn top_level(paths: &[String]) -> (Vec<usize>, usize) {
1048 let mut firsts: Vec<&str> = Vec::new();
1049 let groups = paths
1050 .iter()
1051 .map(|p| {
1052 let first = p.split('.').next().unwrap_or("");
1053 match firsts.iter().position(|f| *f == first) {
1054 Some(i) => i,
1055 None => {
1056 firsts.push(first);
1057 firsts.len() - 1
1058 }
1059 }
1060 })
1061 .collect();
1062 (groups, firsts.len())
1063}
1064
1065pub struct Vmap<I, O, F> {
1069 f: F,
1070 in_axes: Vec<Option<i32>>,
1071 out_axes: Vec<i32>,
1072 types: PhantomData<fn(&I) -> O>,
1073}
1074
1075pub fn vmap<I, O, F, R>(f: F) -> Vmap<I, O, F>
1095where
1096 I: Tree,
1097 O: Tree,
1098 F: Fn(&I) -> R,
1099 R: IntoResult<Ok = O>,
1100{
1101 Vmap {
1102 f,
1103 in_axes: vec![Some(0)],
1104 out_axes: vec![0],
1105 types: PhantomData,
1106 }
1107}
1108
1109impl<I: Tree, O: Tree, F> Vmap<I, O, F> {
1110 pub fn in_axes(mut self, axes: impl InAxes) -> Self {
1113 self.in_axes = axes.axes();
1114 self
1115 }
1116
1117 pub fn out_axes(mut self, axes: impl OutAxes) -> Self {
1119 self.out_axes = axes.axes();
1120 self
1121 }
1122
1123 pub fn call<R>(&self, inputs: &I) -> Result<O>
1125 where
1126 F: Fn(&I) -> R,
1127 R: IntoResult<Ok = O>,
1128 {
1129 let named = inputs.flatten();
1130 let paths: Vec<String> = named.iter().map(|(p, _)| p.clone()).collect();
1131 let args: Vec<Array> = named.into_iter().map(|(_, a)| a).collect();
1132 let (groups, n) = top_level(&paths);
1133 let per_arg = |k: usize| match self.in_axes.as_slice() {
1134 [one] => Ok(*one),
1135 axes if axes.len() == n => Ok(axes[groups[k]]),
1136 axes => Err(Error::InvalidArgument(format!(
1137 "vmap: {} input axes for {n} top-level arguments",
1138 axes.len()
1139 ))),
1140 };
1141 let axes = args
1142 .iter()
1143 .enumerate()
1144 .map(|(k, a)| {
1145 per_arg(k)?
1146 .map(|axis| normalize_axis(axis as isize, a.ndim()))
1147 .transpose()
1148 .map_err(|e| match e {
1149 Error::InvalidArgument(m) => {
1150 Error::InvalidArgument(format!("{m} (argument {:?})", paths[k]))
1151 }
1152 e => e,
1153 })
1154 })
1155 .collect::<Result<Vec<_>>>()?;
1156 let template: RefCell<Option<O>> = RefCell::new(None);
1157 let body = |xs: &[Array]| -> Result<Vec<Array>> {
1158 let out = (self.f)(&inputs.unflatten(xs.iter().cloned())?).into_result()?;
1159 let leaves = out.leaves();
1160 *template.borrow_mut() = Some(out);
1161 Ok(leaves)
1162 };
1163 let outputs = batch(&body, &args, &axes)?;
1164 let template = template
1165 .into_inner()
1166 .ok_or_else(|| Error::InvalidArgument("vmap: the function did not run".into()))?;
1167 let out_paths = template.paths();
1168 let (out_groups, m) = top_level(&out_paths);
1169 let moved = outputs
1170 .iter()
1171 .enumerate()
1172 .map(|(k, o)| {
1173 let axis = match self.out_axes.as_slice() {
1174 [one] => *one,
1175 axes if axes.len() == m => axes[out_groups[k]],
1176 axes => {
1177 return Err(Error::InvalidArgument(format!(
1178 "vmap: {} output axes for {m} top-level outputs",
1179 axes.len()
1180 )));
1181 }
1182 };
1183 let axis = normalize_axis(axis as isize, o.ndim())?;
1184 crate::batching::move_axis(o, 0, axis)
1185 })
1186 .collect::<Result<Vec<_>>>()?;
1187 template.unflatten(moved)
1188 }
1189
1190 pub fn into_fn<R>(self) -> impl Fn(&I) -> Result<O>
1193 where
1194 F: Fn(&I) -> R,
1195 R: IntoResult<Ok = O>,
1196 {
1197 move |inputs| self.call(inputs)
1198 }
1199}