diff --git a/tests/Furnace.Tests/Furnace.Tests.fsproj b/tests/Furnace.Tests/Furnace.Tests.fsproj index cc6b9840..0adbdb94 100644 --- a/tests/Furnace.Tests/Furnace.Tests.fsproj +++ b/tests/Furnace.Tests/Furnace.Tests.fsproj @@ -34,6 +34,8 @@ + + diff --git a/tests/Furnace.Tests/TestReferenceBackend.fs b/tests/Furnace.Tests/TestReferenceBackend.fs new file mode 100644 index 00000000..dc4c0bc5 --- /dev/null +++ b/tests/Furnace.Tests/TestReferenceBackend.fs @@ -0,0 +1,247 @@ +// Copyright (c) 2016- University of Oxford (Atılım Güneş Baydin ) +// and other contributors, see LICENSE in root of repository. +// +// BSD 2-Clause License. See LICENSE in root of repository. + +namespace Tests + +open System +open NUnit.Framework +open Furnace + +[] +type TestReferenceBackend () = + + [] + member _.TestReferenceBackendFloat16Operations () = + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.Float16) + + // Basic tensor operations for Float16 + let t1 = combo.tensor([1.0f; 2.0f; 3.0f]) + let t2 = combo.tensor([2.0f; 4.0f; 6.0f]) + + // Test arithmetic operations + let addResult = t1 + t2 + let expectedAdd = combo.tensor([3.0f; 6.0f; 9.0f]) + Assert.True(expectedAdd.allclose(addResult, 0.001)) + + let subResult = t2 - t1 + let expectedSub = combo.tensor([1.0f; 2.0f; 3.0f]) + Assert.True(expectedSub.allclose(subResult, 0.001)) + + let mulResult = t1 * t2 + let expectedMul = combo.tensor([2.0f; 8.0f; 18.0f]) + Assert.True(expectedMul.allclose(mulResult, 0.001)) + + let divResult = t2 / t1 + let expectedDiv = combo.tensor([2.0f; 2.0f; 2.0f]) + Assert.True(expectedDiv.allclose(divResult, 0.001)) + + [] + member _.TestReferenceBackendBFloat16Operations () = + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.BFloat16) + + // Basic tensor operations for BFloat16 + let t1 = combo.tensor([1.0f; 2.0f; 3.0f]) + let t2 = combo.tensor([4.0f; 5.0f; 6.0f]) + + // Test comparison operations + let ltResult = t1.lt(t2) + let expectedLt = FurnaceImage.tensor([true; true; true], dtype=Dtype.Bool, device=combo.device, backend=combo.backend) + Assert.CheckEqual(expectedLt, ltResult) + + let gtResult = t1.gt(t2) + let expectedGt = FurnaceImage.tensor([false; false; false], dtype=Dtype.Bool, device=combo.device, backend=combo.backend) + Assert.CheckEqual(expectedGt, gtResult) + + [] + member _.TestReferenceBackendBoolOperations () = + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.Bool) + + // Test boolean tensor operations + let t1 = combo.tensor([true; false; true]) + let t2 = combo.tensor([false; true; true]) + + // Test basic properties + Assert.AreEqual(Dtype.Bool, t1.dtype) + Assert.AreEqual(Backend.Reference, t1.backend) + + // Test element access + let item0 = t1[0].toScalar() + let item1 = t1[1].toScalar() + let item2 = t1[2].toScalar() + Assert.AreEqual(1.0, item0.toDouble()) // true converted to 1.0 + Assert.AreEqual(0.0, item1.toDouble()) // false converted to 0.0 + Assert.AreEqual(1.0, item2.toDouble()) // true converted to 1.0 + + [] + member _.TestReferenceBackendFloat16MatrixOperations () = + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.Float16) + + // Test 2D operations for Float16 + let m1 = combo.tensor([[1.0f; 2.0f]; [3.0f; 4.0f]]) + let m2 = combo.tensor([[2.0f; 0.0f]; [1.0f; 2.0f]]) + + // Matrix multiplication + let matmulResult = m1.matmul(m2) + let expectedMatmul = combo.tensor([[4.0f; 4.0f]; [10.0f; 8.0f]]) + Assert.True(expectedMatmul.allclose(matmulResult, 0.001)) + + // Transpose + let transposeResult = m1.transpose() + let expectedTranspose = combo.tensor([[1.0f; 3.0f]; [2.0f; 4.0f]]) + Assert.True(expectedTranspose.allclose(transposeResult, 0.001)) + + [] + member _.TestReferenceBackendBFloat16ReductionOperations () = + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.BFloat16) + + // Test reduction operations for BFloat16 + let t = combo.tensor([[1.0f; 2.0f; 3.0f]; [4.0f; 5.0f; 6.0f]]) + + // Sum operations + let sumResult = t.sum() + Assert.True(abs(sumResult.toScalar().toSingle() - 21.0f) < 0.001f) + + let sumDim0 = t.sum(0) + let expectedSumDim0 = combo.tensor([5.0f; 7.0f; 9.0f]) + Assert.True(expectedSumDim0.allclose(sumDim0, 0.001)) + + let sumDim1 = t.sum(1) + let expectedSumDim1 = combo.tensor([6.0f; 15.0f]) + Assert.True(expectedSumDim1.allclose(sumDim1, 0.001)) + + [] + member _.TestReferenceBackendFloat16IndexingOperations () = + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.Float16) + + // Test indexing and slicing operations + let t = combo.tensor([1.0f; 2.0f; 3.0f; 4.0f; 5.0f]) + + // Single element indexing + let item = t[2] + Assert.True(abs(item.toScalar().toSingle() - 3.0f) < 0.001f) + + // Slice operations + let slice = t[1..3] + let expectedSlice = combo.tensor([2.0f; 3.0f; 4.0f]) + Assert.True(expectedSlice.allclose(slice, 0.001)) + + [] + member _.TestReferenceBackendMixedTypeOperations () = + let comboFloat16 = ComboInfo(Backend.Reference, Device.CPU, Dtype.Float16) + let comboBFloat16 = ComboInfo(Backend.Reference, Device.CPU, Dtype.BFloat16) + + // Test operations between different precisions + let t1 = comboFloat16.tensor([1.0f; 2.0f]) + let t2 = comboBFloat16.tensor([3.0f; 4.0f]) + + // These should work through type coercion + let result = t1.cast(Dtype.Float32) + t2.cast(Dtype.Float32) + let expected = FurnaceImage.tensor([4.0f; 6.0f], dtype=Dtype.Float32, backend=Backend.Reference) + Assert.True(expected.allclose(result, 0.001)) + + [] + member _.TestReferenceBackendActivationFunctions () = + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.Float16) + + // Test activation functions on Float16 + let t = combo.tensor([-2.0f; -1.0f; 0.0f; 1.0f; 2.0f]) + + // Sigmoid + let sigmoidResult = t.sigmoid() + Assert.AreEqual(5, sigmoidResult.nelement) + + // Tanh + let tanhResult = t.tanh() + Assert.AreEqual(5, tanhResult.nelement) + + // ReLU + let reluResult = t.relu() + let expectedRelu = combo.tensor([0.0f; 0.0f; 0.0f; 1.0f; 2.0f]) + Assert.True(expectedRelu.allclose(reluResult, 0.001)) + + [] + member _.TestReferenceBackendShapeOperations () = + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.BFloat16) + + // Test shape manipulation operations + let t = combo.tensor([1.0f; 2.0f; 3.0f; 4.0f; 5.0f; 6.0f]) + + // Reshape + let reshaped = t.view([2; 3]) + Assert.AreEqual([|2; 3|], reshaped.shape) + + // Flatten + let flattened = reshaped.view([-1]) + Assert.AreEqual([|6|], flattened.shape) + + // Squeeze/unsqueeze + let unsqueezed = t.unsqueeze(0) + Assert.AreEqual([|1; 6|], unsqueezed.shape) + + let squeezed = unsqueezed.squeeze(0) + Assert.AreEqual([|6|], squeezed.shape) + + [] + member _.TestReferenceBackendEdgeCases () = + // Test edge cases for lower precision types + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.Float16) + + // Empty tensors (create with shape) + let empty = FurnaceImage.zeros([0], dtype=combo.dtype, backend=combo.backend) + Assert.AreEqual(0, empty.nelement) + Assert.AreEqual([|0|], empty.shape) + + // Single element tensors + let single = combo.tensor([42.0f]) + Assert.True(abs(single.toScalar().toSingle() - 42.0f) < 0.001f) + + // Large tensors for stress testing + let large = combo.randn([100; 50]) + Assert.AreEqual([|100; 50|], large.shape) + Assert.AreEqual(5000, large.nelement) + + [] + member _.TestReferenceBackendFloat16ActivationDerivatives () = + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.Float16) + + // Test activation function operations that might not be well covered + let t = combo.tensor([0.5f; 1.0f; 1.5f]) + + // Test softplus (might be less covered) + let softplusResult = t.softplus() + Assert.AreEqual(3, softplusResult.nelement) + Assert.AreEqual(Dtype.Float16, softplusResult.dtype) + + // Test exp and log operations + let expResult = t.exp() + Assert.AreEqual(3, expResult.nelement) + Assert.AreEqual(Dtype.Float16, expResult.dtype) + + let logResult = t.log() + Assert.AreEqual(3, logResult.nelement) + Assert.AreEqual(Dtype.Float16, logResult.dtype) + + [] + member _.TestReferenceBackendBFloat16ComparisonEdgeCases () = + let combo = ComboInfo(Backend.Reference, Device.CPU, Dtype.BFloat16) + + // Test edge cases in comparison operations + let t1 = combo.tensor([1.0f; 2.0f; 3.0f]) + let t2 = combo.tensor([1.0f; 1.5f; 4.0f]) + + // Test less than or equal + let leResult = t1.le(t2) + let expectedLe = FurnaceImage.tensor([true; false; true], dtype=Dtype.Bool, device=combo.device, backend=combo.backend) + Assert.CheckEqual(expectedLe, leResult) + + // Test greater than or equal + let geResult = t1.ge(t2) + let expectedGe = FurnaceImage.tensor([true; true; false], dtype=Dtype.Bool, device=combo.device, backend=combo.backend) + Assert.CheckEqual(expectedGe, geResult) + + // Test not equal (using .ne instead of .neq) + let neResult = t1.ne(t2) + let expectedNe = FurnaceImage.tensor([false; true; true], dtype=Dtype.Bool, device=combo.device, backend=combo.backend) + Assert.CheckEqual(expectedNe, neResult) \ No newline at end of file