Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
## 2026-05-01 - Avoid High-Level Tensor Ops in Scalar Reductions
**Learning:** High-level `Tensor` operations like `sub()` and `mul()` trigger intermediate heap allocations for shape and stride metadata. When computing scalar reductions (like MSE, distances, or loss functions), using these operations introduces severe memory overhead inside hot loops. Attempting to use `.min()` length truncation as a safeguard is an anti-pattern as it masks shape mismatch errors.
**Action:** For scalar reductions, assert shape equality (`assert_eq!(a.shape, b.shape)`) and perform a single-pass iteration directly over the underlying borrowed data arrays (`a.data.borrow()`) to eliminate intermediate allocations and safely compute the result.

## 2026-06-03 - Avoid Metadata Clones in Autograd Backward Pass
**Learning:** The reverse-mode `backward` pass in autograd previously cloned gradients (`grads[out.index].clone()`) to avoid borrow checker errors, which led to unnecessary heap allocations for `Tensor` metadata (shape and strides).
**Action:** Use `Option::take()` to acquire ownership of the gradient during the backward pass, and update helper functions like `accumulate_grad` to accept `Tensor` by value, avoiding redundant metadata clones.
42 changes: 22 additions & 20 deletions crates/aether-core/src/ml/autograd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
// ═══════════════════════════════════════════════════════════════════════════════
//


#[cfg(not(feature = "std"))]
extern crate alloc;

Expand Down Expand Up @@ -200,53 +199,56 @@ impl<'a> Context<'a> {
for op in self.tape.ops.iter().rev() {
match op {
Op::Add { out, lhs, rhs } => {
// Solves borrow checker by cloning Option first
let grad_out = grads[out.index].clone();
if let Some(grad) = grad_out {
if let Some(grad) = grads[out.index].take() {
// dL/d(lhs) += dL/dout * 1
Self::accumulate_grad(&mut grads, lhs.index, &grad);
Self::accumulate_grad(&mut grads, rhs.index, &grad);
Self::accumulate_grad(&mut grads, lhs.index, grad.clone());
Self::accumulate_grad(&mut grads, rhs.index, grad.clone());

grads[out.index] = Some(grad);
}
}
Op::Mul { out, lhs, rhs } => {
let grad_out = grads[out.index].clone();
if let Some(grad) = grad_out {
if let Some(grad) = grads[out.index].take() {
let lhs_val = self.heap.get(*lhs).unwrap();
let rhs_val = self.heap.get(*rhs).unwrap();

// dL/dLhs = grad_out * rhs
let d_lhs: Tensor = grad.mul(rhs_val);
Self::accumulate_grad(&mut grads, lhs.index, &d_lhs);
Self::accumulate_grad(&mut grads, lhs.index, d_lhs);

// dL/dRhs = grad_out * lhs
let d_rhs: Tensor = grad.mul(lhs_val);
Self::accumulate_grad(&mut grads, rhs.index, &d_rhs);
Self::accumulate_grad(&mut grads, rhs.index, d_rhs);

grads[out.index] = Some(grad);
}
}
Op::MatMul { out, lhs, rhs } => {
let grad_out = grads[out.index].clone();
if let Some(grad) = grad_out {
if let Some(grad) = grads[out.index].take() {
let lhs_val = self.heap.get(*lhs).unwrap();
let rhs_val = self.heap.get(*rhs).unwrap();

// C = A @ B
// dA = dC @ B^T
let d_lhs: Tensor = grad.matmul(&rhs_val.transpose());
Self::accumulate_grad(&mut grads, lhs.index, &d_lhs);
Self::accumulate_grad(&mut grads, lhs.index, d_lhs);

// dB = A^T @ dC
let d_rhs: Tensor = lhs_val.transpose().matmul(&grad);
Self::accumulate_grad(&mut grads, rhs.index, &d_rhs);
Self::accumulate_grad(&mut grads, rhs.index, d_rhs);

grads[out.index] = Some(grad);
}
}
Op::ReLU { out, input } => {
let grad_out = grads[out.index].clone();
if let Some(grad) = grad_out {
if let Some(grad) = grads[out.index].take() {
let input_val = self.heap.get(*input).unwrap();
// dL/dx = grad_out * (1 if x > 0 else 0)
let mask = input_val.map(|x| if x > 0.0 { 1.0 } else { 0.0 });
let d_input: Tensor = grad.mul(&mask);
Self::accumulate_grad(&mut grads, input.index, &d_input);
Self::accumulate_grad(&mut grads, input.index, d_input);

grads[out.index] = Some(grad);
}
}
}
Expand All @@ -255,18 +257,18 @@ impl<'a> Context<'a> {
grads
}

fn accumulate_grad(grads: &mut Vec<Option<Tensor>>, idx: usize, grad: &Tensor) {
fn accumulate_grad(grads: &mut Vec<Option<Tensor>>, idx: usize, grad: Tensor) {
if idx >= grads.len() {
grads.resize(idx + 1 + 256, None);
}

match &mut grads[idx] {
Some(existing) => {
let new = existing.add(grad);
let new = existing.add(&grad);
grads[idx] = Some(new);
}
None => {
grads[idx] = Some(grad.clone());
grads[idx] = Some(grad);
}
}
}
Expand Down