pub(crate) mod grad_fn;
mod graph;
mod ops;
mod tensor;
pub use grad_fn::GradFn;
pub use graph::ComputationGraph;
pub use tensor::{Tensor, TensorId};
pub use ops::{
additive_attention_mask, cosine_similarity_rows, embedding_gather, l2_normalize_rows,
masked_mean_pool, mse_loss, OpError, NEG_MASK,
};
use std::cell::RefCell;
thread_local! {
static GRAPH: RefCell<ComputationGraph> = RefCell::new(ComputationGraph::new());
static GRAD_ENABLED: RefCell<bool> = const { RefCell::new(true) };
}
pub fn no_grad<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
GRAD_ENABLED.with(|enabled| {
let prev = *enabled.borrow();
*enabled.borrow_mut() = false;
let result = f();
*enabled.borrow_mut() = prev;
result
})
}
#[must_use]
pub fn is_grad_enabled() -> bool {
GRAD_ENABLED.with(|enabled| *enabled.borrow())
}
pub(crate) fn with_graph<F, R>(f: F) -> R
where
F: FnOnce(&mut ComputationGraph) -> R,
{
GRAPH.with(|graph| f(&mut graph.borrow_mut()))
}
pub fn clear_graph() {
GRAPH.with(|graph| graph.borrow_mut().clear());
}
#[must_use]
pub fn graph_tape_len() -> usize {
with_graph(|graph| graph.len())
}
#[must_use]
pub fn get_grad(id: TensorId) -> Option<Tensor> {
with_graph(|graph| graph.get_grad(id))
}
pub fn clear_grad(id: TensorId) {
with_graph(|graph| graph.clear_grad(id));
}
#[cfg(test)]
#[path = "tests_tensor_contract.rs"]
mod tests_tensor_contract;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_grad_context() {
assert!(is_grad_enabled());
no_grad(|| {
assert!(!is_grad_enabled());
});
assert!(is_grad_enabled());
}
#[test]
fn test_nested_no_grad() {
assert!(is_grad_enabled());
no_grad(|| {
assert!(!is_grad_enabled());
no_grad(|| {
assert!(!is_grad_enabled());
});
assert!(!is_grad_enabled());
});
assert!(is_grad_enabled());
}
#[test]
fn autograd_graph_tape_len_observes_growth_and_clearing() {
clear_graph();
assert_eq!(graph_tape_len(), 0, "a cleared tape is empty");
let a = Tensor::from_slice(&[1.0, 2.0]).requires_grad();
let b = Tensor::from_slice(&[3.0, 4.0]).requires_grad();
let _ = a.add(&b);
let grew = graph_tape_len();
assert!(grew > 0, "a recorded op must be visible on the tape");
let _ = a.add(&b);
assert!(
graph_tape_len() > grew,
"a second op must append rather than replace",
);
clear_graph();
assert_eq!(graph_tape_len(), 0, "clear_graph must empty the tape");
}
#[test]
fn autograd_graph_tape_len_stays_zero_under_no_grad() {
clear_graph();
let a = Tensor::from_slice(&[1.0, 2.0]).requires_grad();
let b = Tensor::from_slice(&[3.0, 4.0]).requires_grad();
no_grad(|| {
let _ = a.add(&b);
});
assert_eq!(
graph_tape_len(),
0,
"no_grad must record no operation at all",
);
let _ = a.add(&b);
assert!(graph_tape_len() > 0, "the control must record");
clear_graph();
}
}