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

Reviewed-on: #18
This commit is contained in:
redclawsystems
2026-05-20 10:48:37 +00:00
2 changed files with 226 additions and 0 deletions
+204
View File
@@ -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);
}
} }
+22
View File
@@ -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,
);
}
}