use std::collections::{HashMap, HashSet};
use crate::tensor::Tensor;
use super::node::{Node, NodeId};
pub fn backward(loss: &Tensor) {
assert_eq!(
loss.numel(),
1,
"backward() needs a scalar loss, got shape {:?}",
loss.shape()
);
let Some(root) = loss.grad_fn() else { return };
let mut grads: HashMap<NodeId, Tensor> = HashMap::new();
grads.insert(root.id(), Tensor::ones(&[1]).to(loss.device()));
for node in reverse_topological(root) {
let Some(grad) = grads.remove(&node.id()) else {
continue;
};
let op = match &node {
Node::Leaf(slot) => {
slot.accumulate(grad);
continue;
}
Node::Op(op) => op,
};
let input_grads = super::no_grad(|| op.rule.backward(&grad));
if super::anomaly::is_detecting() {
super::anomaly::check(op.rule.name(), &input_grads);
}
assert_eq!(
input_grads.len(),
op.inputs.len(),
"{} returned {} gradients for {} inputs",
op.rule.name(),
input_grads.len(),
op.inputs.len()
);
for (input, input_grad) in op.inputs.iter().zip(input_grads) {
let Some(input_node) = input.grad_fn() else {
continue;
};
grads
.entry(input_node.id())
.and_modify(|acc| *acc = acc.add(&input_grad))
.or_insert(input_grad);
}
}
}
fn reverse_topological(root: &Node) -> Vec<Node> {
let mut order = Vec::new();
let mut visited = HashSet::new();
let mut stack = vec![(root.clone(), false)];
while let Some((node, children_done)) = stack.pop() {
if children_done {
order.push(node);
continue;
}
if !visited.insert(node.id()) {
continue;
}
stack.push((node.clone(), true));
if let Node::Op(op) = &node {
for input in &op.inputs {
if let Some(child) = input.grad_fn() {
if !visited.contains(&child.id()) {
stack.push((child.clone(), false));
}
}
}
}
}
order.reverse();
order
}