201 lines
6.5 KiB
Rust
201 lines
6.5 KiB
Rust
//! Integration tests for tensor autograd functionality
|
|
//!
|
|
//! These tests verify that the autograd engine works correctly with tensor operations.
|
|
//!
|
|
//! Note: These tests use the tape-based API which is not yet implemented in the
|
|
//! decorator pattern architecture. They are disabled until the tape API is available.
|
|
|
|
#![cfg(feature = "tape_api")]
|
|
|
|
use approx::assert_relative_eq;
|
|
use rtx_autograd::{TensorAutograd, clear_tape, enable_grad, tensor_with_grad};
|
|
use rtx_tensor::{Device, Tensor};
|
|
|
|
#[test]
|
|
fn test_simple_autograd_integration() {
|
|
let device = Device::cuda(0).unwrap_or(Device::default());
|
|
|
|
// Clear any existing tape
|
|
clear_tape();
|
|
|
|
enable_grad(|| {
|
|
// Create leaf tensors with gradients
|
|
let a = tensor_with_grad(vec![2.0], [1], &device).unwrap();
|
|
let b = tensor_with_grad(vec![3.0], [1], &device).unwrap();
|
|
|
|
// Test that tensors have gradient requirements
|
|
assert_eq!(a.requires_grad(), true);
|
|
assert_eq!(b.requires_grad(), true);
|
|
|
|
// Test that node IDs are assigned
|
|
assert!(a.autograd_node_id().is_some());
|
|
assert!(b.autograd_node_id().is_some());
|
|
|
|
// Perform operations with autograd recording
|
|
let c = a.add_grad(&b).unwrap();
|
|
|
|
// Verify the result has gradients enabled and node ID
|
|
assert_eq!(c.requires_grad(), true);
|
|
assert!(c.autograd_node_id().is_some());
|
|
|
|
// Check the computation result
|
|
let c_data = c.to_cpu().unwrap();
|
|
assert_relative_eq!(c_data[0], 5.0, epsilon = 1e-6);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiplication_with_autograd() {
|
|
let device = Device::cuda(0).unwrap_or(Device::default());
|
|
|
|
clear_tape();
|
|
|
|
enable_grad(|| {
|
|
let a = tensor_with_grad(vec![2.0, 3.0], [2, 1], &device).unwrap();
|
|
let b = tensor_with_grad(vec![4.0, 5.0], [2, 1], &device).unwrap();
|
|
|
|
// Test multiplication
|
|
let c = a.mul_grad(&b).unwrap();
|
|
|
|
assert_eq!(c.requires_grad(), true);
|
|
assert!(c.autograd_node_id().is_some());
|
|
|
|
let c_data = c.to_cpu().unwrap();
|
|
assert_relative_eq!(c_data[0], 8.0, epsilon = 1e-6);
|
|
assert_relative_eq!(c_data[1], 15.0, epsilon = 1e-6);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_matrix_multiplication_with_autograd() {
|
|
let device = Device::cuda(0).unwrap_or(Device::default());
|
|
|
|
clear_tape();
|
|
|
|
enable_grad(|| {
|
|
// 2x2 @ 2x2 -> 2x2
|
|
let a = tensor_with_grad(vec![1.0, 2.0, 3.0, 4.0], [2, 2], &device).unwrap();
|
|
let b = tensor_with_grad(vec![5.0, 6.0, 7.0, 8.0], [2, 2], &device).unwrap();
|
|
|
|
let c = a.matmul_grad(&b).unwrap();
|
|
|
|
assert_eq!(c.requires_grad(), true);
|
|
assert!(c.autograd_node_id().is_some());
|
|
|
|
let c_data = c.to_cpu().unwrap();
|
|
// Expected: [[1*5+2*7, 1*6+2*8], [3*5+4*7, 3*6+4*8]] = [[19, 22], [43, 50]]
|
|
assert_relative_eq!(c_data[0], 19.0, epsilon = 1e-6);
|
|
assert_relative_eq!(c_data[1], 22.0, epsilon = 1e-6);
|
|
assert_relative_eq!(c_data[2], 43.0, epsilon = 1e-6);
|
|
assert_relative_eq!(c_data[3], 50.0, epsilon = 1e-6);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_sum_reduction_with_autograd() {
|
|
let device = Device::cuda(0).unwrap_or(Device::default());
|
|
|
|
clear_tape();
|
|
|
|
enable_grad(|| {
|
|
let a = tensor_with_grad(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3], &device).unwrap();
|
|
|
|
// Sum all elements
|
|
let sum_all = a.sum_grad(None).unwrap();
|
|
|
|
assert_eq!(sum_all.requires_grad(), true);
|
|
assert!(sum_all.autograd_node_id().is_some());
|
|
|
|
let sum_data = sum_all.to_cpu().unwrap();
|
|
assert_relative_eq!(sum_data[0], 21.0, epsilon = 1e-6);
|
|
|
|
// Sum along dimension 0
|
|
let sum_dim0 = a.sum_grad(Some(0)).unwrap();
|
|
|
|
assert_eq!(sum_dim0.requires_grad(), true);
|
|
assert!(sum_dim0.autograd_node_id().is_some());
|
|
|
|
let sum0_data = sum_dim0.to_cpu().unwrap();
|
|
assert_relative_eq!(sum0_data[0], 5.0, epsilon = 1e-6); // 1+4
|
|
assert_relative_eq!(sum0_data[1], 7.0, epsilon = 1e-6); // 2+5
|
|
assert_relative_eq!(sum0_data[2], 9.0, epsilon = 1e-6); // 3+6
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_complex_computation_graph() {
|
|
let device = Device::cuda(0).unwrap_or(Device::default());
|
|
|
|
clear_tape();
|
|
|
|
enable_grad(|| {
|
|
// Create a simple neural network-like computation: y = (x * w + b)^2
|
|
let x = tensor_with_grad(vec![1.0, 2.0], [2, 1], &device).unwrap();
|
|
let w = tensor_with_grad(vec![0.5, 1.5], [2, 1], &device).unwrap();
|
|
let b = tensor_with_grad(vec![0.1, 0.2], [2, 1], &device).unwrap();
|
|
|
|
// Forward pass
|
|
let linear = x.mul_grad(&w).unwrap();
|
|
let activated = linear.add_grad(&b).unwrap();
|
|
let squared = activated.mul_grad(&activated).unwrap();
|
|
|
|
// Verify all intermediate results have gradients
|
|
assert_eq!(linear.requires_grad(), true);
|
|
assert_eq!(activated.requires_grad(), true);
|
|
assert_eq!(squared.requires_grad(), true);
|
|
|
|
// Check final result
|
|
let result_data = squared.to_cpu().unwrap();
|
|
assert_relative_eq!(result_data[0], (1.0f32 * 0.5 + 0.1).powi(2), epsilon = 1e-6);
|
|
assert_relative_eq!(result_data[1], (2.0f32 * 1.5 + 0.2).powi(2), epsilon = 1e-6);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_gradient_storage() {
|
|
let device = Device::cuda(0).unwrap_or(Device::default());
|
|
|
|
let a = tensor_with_grad(vec![2.0], [1], &device).unwrap();
|
|
|
|
// Initially no gradient
|
|
assert!(a.grad().is_none());
|
|
|
|
// Set a gradient
|
|
let grad_tensor = Tensor::ones([1], &device).unwrap();
|
|
a.set_grad(Some(grad_tensor.clone()));
|
|
|
|
// Verify gradient is stored
|
|
let stored_grad = a.grad().unwrap();
|
|
let grad_data = stored_grad.to_cpu().unwrap();
|
|
assert_relative_eq!(grad_data[0], 1.0, epsilon = 1e-6);
|
|
|
|
// Clear gradient
|
|
a.set_grad(None);
|
|
assert!(a.grad().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_mixed_grad_and_non_grad_tensors() {
|
|
let device = Device::cuda(0).unwrap_or(Device::default());
|
|
|
|
clear_tape();
|
|
|
|
enable_grad(|| {
|
|
// One tensor requires gradients, the other doesn't
|
|
let a = tensor_with_grad(vec![2.0], [1], &device).unwrap();
|
|
let b = Tensor::from_data(vec![3.0], [1], &device).unwrap(); // No gradients
|
|
|
|
assert_eq!(a.requires_grad(), true);
|
|
assert_eq!(b.requires_grad(), false);
|
|
|
|
// Operation result should require gradients
|
|
let c = a.add_grad(&b).unwrap();
|
|
|
|
assert_eq!(c.requires_grad(), true);
|
|
assert!(c.autograd_node_id().is_some());
|
|
|
|
let c_data = c.to_cpu().unwrap();
|
|
assert_relative_eq!(c_data[0], 5.0, epsilon = 1e-6);
|
|
});
|
|
}
|