Merge pull request 'test(rtx-tensor): add coverage for uncovered GenericTensor ops' (#18) from ci-doctor/coverage-20260514-192156 into main
CI / Format Check (push) Failing after 58s
CI / Clippy Check (push) Failing after 6m16s
CI / Build (ubuntu-latest) (push) Failing after 5m52s
Performance Benchmarks / Run Benchmarks (push) Successful in 15m54s
Documentation / Build User Guide (push) Successful in 39s
CI / Build CPU-Only (Explicit) (push) Failing after 6m53s
Documentation / Build API Documentation (push) Failing after 5m49s
CI / Build (macos-latest) (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / CI Success (push) Has been cancelled
CI / Format Check (push) Failing after 58s
CI / Clippy Check (push) Failing after 6m16s
CI / Build (ubuntu-latest) (push) Failing after 5m52s
Performance Benchmarks / Run Benchmarks (push) Successful in 15m54s
Documentation / Build User Guide (push) Successful in 39s
CI / Build CPU-Only (Explicit) (push) Failing after 6m53s
Documentation / Build API Documentation (push) Failing after 5m49s
CI / Build (macos-latest) (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / CI Success (push) Has been cancelled
Reviewed-on: #18
This commit is contained in:
@@ -652,4 +652,208 @@ mod tests {
|
|||||||
assert!((cos_data[1] - 0.0).abs() < 1e-5);
|
assert!((cos_data[1] - 0.0).abs() < 1e-5);
|
||||||
assert!((cos_data[2] - (-1.0)).abs() < 1e-5);
|
assert!((cos_data[2] - (-1.0)).abs() < 1e-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== Leaky ReLU & ELU Activation Tests ====================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_leaky_relu_positive_passthrough() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
let a: GenericTensor<CpuBackend, 1> =
|
||||||
|
GenericTensor::from_slice(&[1.0, 2.0, 3.0], [3], &device);
|
||||||
|
let b = a.leaky_relu(0.01);
|
||||||
|
assert_eq!(b.to_vec(), vec![1.0, 2.0, 3.0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_leaky_relu_negative_scaled() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
let a: GenericTensor<CpuBackend, 1> =
|
||||||
|
GenericTensor::from_slice(&[-1.0, -2.0, 0.0], [3], &device);
|
||||||
|
let b = a.leaky_relu(0.1);
|
||||||
|
let data = b.to_vec();
|
||||||
|
assert!((data[0] - (-0.1)).abs() < 1e-5);
|
||||||
|
assert!((data[1] - (-0.2)).abs() < 1e-5);
|
||||||
|
assert!((data[2] - 0.0).abs() < 1e-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_elu_positive_passthrough() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
let a: GenericTensor<CpuBackend, 1> =
|
||||||
|
GenericTensor::from_slice(&[1.0, 2.0, 3.0], [3], &device);
|
||||||
|
let b = a.elu(1.0);
|
||||||
|
assert_eq!(b.to_vec(), vec![1.0, 2.0, 3.0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_elu_negative_exponential() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
let a: GenericTensor<CpuBackend, 1> = GenericTensor::from_slice(&[-1.0, 0.0], [2], &device);
|
||||||
|
let b = a.elu(1.0);
|
||||||
|
let data = b.to_vec();
|
||||||
|
// ELU(-1) = 1.0 * (exp(-1) - 1) ≈ -0.6321
|
||||||
|
assert!((data[0] - (-0.6321)).abs() < 0.01);
|
||||||
|
// ELU(0) = 0
|
||||||
|
assert!((data[1] - 0.0).abs() < 1e-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Comparison Operation Tests ====================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gt_scalar() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
let a: GenericTensor<CpuBackend, 1> =
|
||||||
|
GenericTensor::from_slice(&[-1.0, 0.0, 1.0, 2.0, 3.0], [5], &device);
|
||||||
|
let b = a.gt_scalar(1.0);
|
||||||
|
let data = b.to_vec();
|
||||||
|
// Elements > 1.0 should be 1.0, others 0.0
|
||||||
|
assert_eq!(data[0], 0.0); // -1.0 not > 1.0
|
||||||
|
assert_eq!(data[1], 0.0); // 0.0 not > 1.0
|
||||||
|
assert_eq!(data[2], 0.0); // 1.0 not > 1.0 (not strictly greater)
|
||||||
|
assert_eq!(data[3], 1.0); // 2.0 > 1.0
|
||||||
|
assert_eq!(data[4], 1.0); // 3.0 > 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gt_scalar_does_not_require_grad() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
let a: GenericTensor<CpuBackend, 1> =
|
||||||
|
GenericTensor::from_slice(&[1.0, 2.0], [2], &device).with_requires_grad(true);
|
||||||
|
let b = a.gt_scalar(0.5);
|
||||||
|
assert!(!b.requires_grad(), "Comparison ops should not require grad");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Variance Tests ====================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_var_all() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
let a: GenericTensor<CpuBackend, 1> =
|
||||||
|
GenericTensor::from_slice(&[2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0], [8], &device);
|
||||||
|
let v = a.var();
|
||||||
|
let data = v.to_vec();
|
||||||
|
// Population variance of [2,4,4,4,5,5,7,9]: mean=5, var=4.0
|
||||||
|
// Note: some implementations use sample variance (N-1), so allow tolerance
|
||||||
|
assert!(
|
||||||
|
(data[0] - 4.0).abs() < 1.0,
|
||||||
|
"Variance should be close to 4.0, got {}",
|
||||||
|
data[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_var_dim() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
// [[1, 2, 3], [4, 5, 6]] shape [2, 3]
|
||||||
|
let a: GenericTensor<CpuBackend, 2> =
|
||||||
|
GenericTensor::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3], &device);
|
||||||
|
let v = a.var_dim(1);
|
||||||
|
let data = v.to_vec();
|
||||||
|
// Var of [1,2,3] ≈ 0.667 (pop) or 1.0 (sample)
|
||||||
|
// Var of [4,5,6] ≈ 0.667 (pop) or 1.0 (sample)
|
||||||
|
assert!(data.len() == 2);
|
||||||
|
// Both rows have the same spread, so variances should be equal
|
||||||
|
assert!(
|
||||||
|
(data[0] - data[1]).abs() < 1e-4,
|
||||||
|
"Variances should be equal for rows with same spread"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Convolution Tests ====================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_conv2d_identity_kernel() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
// Input: [1, 1, 3, 3] - single batch, single channel, 3x3
|
||||||
|
let input: GenericTensor<CpuBackend, 4> = GenericTensor::from_slice(
|
||||||
|
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
|
||||||
|
[1, 1, 3, 3],
|
||||||
|
&device,
|
||||||
|
);
|
||||||
|
// Weight: [1, 1, 1, 1] - 1x1 kernel that passes through
|
||||||
|
let weight: GenericTensor<CpuBackend, 4> =
|
||||||
|
GenericTensor::from_slice(&[1.0], [1, 1, 1, 1], &device);
|
||||||
|
let output = input.conv2d(&weight, None, [1, 1], [0, 0], [1, 1], 1);
|
||||||
|
assert_eq!(output.shape(), [1, 1, 3, 3]);
|
||||||
|
assert_eq!(
|
||||||
|
output.to_vec(),
|
||||||
|
vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_conv2d_with_stride() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
// Input: [1, 1, 4, 4]
|
||||||
|
let input: GenericTensor<CpuBackend, 4> = GenericTensor::from_slice(
|
||||||
|
&[
|
||||||
|
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
|
||||||
|
16.0,
|
||||||
|
],
|
||||||
|
[1, 1, 4, 4],
|
||||||
|
&device,
|
||||||
|
);
|
||||||
|
// Weight: [1, 1, 1, 1] - 1x1 kernel
|
||||||
|
let weight: GenericTensor<CpuBackend, 4> =
|
||||||
|
GenericTensor::from_slice(&[1.0], [1, 1, 1, 1], &device);
|
||||||
|
// Stride 2 should reduce spatial dims by half
|
||||||
|
let output = input.conv2d(&weight, None, [2, 2], [0, 0], [1, 1], 1);
|
||||||
|
assert_eq!(output.shape(), [1, 1, 2, 2]);
|
||||||
|
assert_eq!(output.to_vec(), vec![1.0, 3.0, 9.0, 11.0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_conv2d_requires_grad_propagation() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
let input: GenericTensor<CpuBackend, 4> =
|
||||||
|
GenericTensor::ones([1, 1, 3, 3], &device).with_requires_grad(true);
|
||||||
|
let weight: GenericTensor<CpuBackend, 4> = GenericTensor::ones([1, 1, 1, 1], &device);
|
||||||
|
let output = input.conv2d(&weight, None, [1, 1], [0, 0], [1, 1], 1);
|
||||||
|
assert!(output.requires_grad());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Pooling Tests ====================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_max_pool2d_basic() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
// Input: [1, 1, 4, 4]
|
||||||
|
let input: GenericTensor<CpuBackend, 4> = GenericTensor::from_slice(
|
||||||
|
&[
|
||||||
|
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
|
||||||
|
16.0,
|
||||||
|
],
|
||||||
|
[1, 1, 4, 4],
|
||||||
|
&device,
|
||||||
|
);
|
||||||
|
// 2x2 kernel, stride 2, no padding → [1, 1, 2, 2]
|
||||||
|
let output = input.max_pool2d([2, 2], [2, 2], [0, 0]);
|
||||||
|
assert_eq!(output.shape(), [1, 1, 2, 2]);
|
||||||
|
// Max of each 2x2 block: [6, 8, 14, 16]
|
||||||
|
assert_eq!(output.to_vec(), vec![6.0, 8.0, 14.0, 16.0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_avg_pool2d_basic() {
|
||||||
|
let device = CpuDevice::default();
|
||||||
|
// Input: [1, 1, 4, 4]
|
||||||
|
let input: GenericTensor<CpuBackend, 4> = GenericTensor::from_slice(
|
||||||
|
&[
|
||||||
|
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
|
||||||
|
16.0,
|
||||||
|
],
|
||||||
|
[1, 1, 4, 4],
|
||||||
|
&device,
|
||||||
|
);
|
||||||
|
// 2x2 kernel, stride 2, no padding → [1, 1, 2, 2]
|
||||||
|
let output = input.avg_pool2d([2, 2], [2, 2], [0, 0], false);
|
||||||
|
assert_eq!(output.shape(), [1, 1, 2, 2]);
|
||||||
|
// Avg of each 2x2 block: [(1+2+5+6)/4, (3+4+7+8)/4, (9+10+13+14)/4, (11+12+15+16)/4]
|
||||||
|
// = [3.5, 5.5, 11.5, 13.5]
|
||||||
|
let data = output.to_vec();
|
||||||
|
assert!((data[0] - 3.5).abs() < 1e-5);
|
||||||
|
assert!((data[1] - 5.5).abs() < 1e-5);
|
||||||
|
assert!((data[2] - 11.5).abs() < 1e-5);
|
||||||
|
assert!((data[3] - 13.5).abs() < 1e-5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//! Test utilities for rtx-tensor tests.
|
||||||
|
|
||||||
|
/// Assert two f32 slices are approximately equal within tolerance.
|
||||||
|
pub fn assert_approx_eq(a: &[f32], b: &[f32], tolerance: f32) {
|
||||||
|
assert_eq!(
|
||||||
|
a.len(),
|
||||||
|
b.len(),
|
||||||
|
"Lengths differ: {} vs {}",
|
||||||
|
a.len(),
|
||||||
|
b.len()
|
||||||
|
);
|
||||||
|
for (i, (x, y)) in a.iter().zip(b.iter()).enumerate() {
|
||||||
|
assert!(
|
||||||
|
(x - y).abs() < tolerance,
|
||||||
|
"Element {} differs: {} vs {} (tolerance {})",
|
||||||
|
i,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
tolerance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user