diff --git a/.gitmodules b/.gitmodules index 27228d1..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "ext/satsumagraph"] - path = ext/satsumagraph - url = https://github.com/allisterb/satsumagraph.git diff --git a/AGENTS.md b/AGENTS.md index 5ede9c4..943ff56 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,3 +15,31 @@ When writing complex features or significant refactors, use an ExecPlan (as desc ## Local models The ONNX model at `models\multilingual-e5-small\model.onnx` is already downloaded from Hugging Face and is git-ignored. Use it for the `lonnx run` e5 example instead of fetching the URL. + +## Repo map (high-signal) +- `Lokad.Onnx.slnx`: root solution (XML format); targets net10.0 across projects; includes vendored `ext\NLog`. +- `src\Lokad.Onnx.Base`: shared infra (logging, profiling, hardware config, utilities). +- `src\Lokad.Onnx.Tensors`: core tensor types + ops (managed port of ONNX Runtime tensors); unsafe + SIMD options. +- `src\Lokad.Onnx.Backend`: ONNX graph execution (Model/Node/ComputationalGraph/CPUExecutionProvider). +- `src\Lokad.Onnx.Data`: text + image IO helpers (tokenizers, ImageSharp). +- `src\Lokad.Onnx.CLI`: `lonnx` CLI (verbs: `info`, `run`, `benchmark`); output in `src\Lokad.Onnx.CLI\bin\Release\net10.0\`. +- `src\Lokad.Onnx.Interop`: .NET assembly used by Python interop (`src\interop`). +- `src\interop`: Python bridge + helpers; used by `tests\python` (requires pythonnet + transformers + onnx). +- `src\Lokad.Onnx.Package`: NuGet pack that compiles Base/Tensors/Backend + satsumagraph into single `Lokad.Onnx` package. +- `tests\Lokad.Onnx.Backend.Tests`: xUnit backend tests + MNIST assets. +- `tests\Lokad.Onnx.Tensors.Tests`: xUnit tensor tests. +- `tests\python`: pytest suite comparing .NET vs NumPy/transformers. +- `ext\NLog`: vendored logging source; solution includes project reference. +- `ext\satsumagraph`: git submodule used for graph algorithms (Backend/Package references it). + +## Build / run / test (repo conventions) +- Build all: `build.cmd` (restore + CLI build + interop publish). Uses `--tl:off`. +- Pack NuGet: `pack.cmd` (builds `src\Lokad.Onnx.Package`). +- CLI wrapper: `lonnx.cmd` calls Release `Lokad.Onnx.CLI.exe`. +- Run tests: `dotnet test --tl:off --nologo -v minimal --no-build` from repo root. +- Python tests: build interop first (`build.cmd`), then `pip install -r src\interop\requirements.txt`, run `pytest` from repo root. + +## CLI quick notes +- `lonnx info `: model metadata; supports `--ops`, `--init`, `--op-filter`. +- `lonnx run `: supports image/text inputs, `--softmax`, `--print-input`, profiling, SIMD toggles. +- `lonnx benchmark `: `matmul2d`, `matmul`, `indexing`, `me5s-load`, `me5s-run` with BenchmarkDotNet flags. diff --git a/Lokad.Onnx.slnx b/Lokad.Onnx.slnx new file mode 100644 index 0000000..37375a6 --- /dev/null +++ b/Lokad.Onnx.slnx @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/build.cmd b/build.cmd index 12c0a23..10f0511 100644 --- a/build.cmd +++ b/build.cmd @@ -4,9 +4,9 @@ set ERROR_CODE=0 echo Building Lokad Onnx projects.. -dotnet restore --tl:off -v minimal src\Lokad.Onnx.sln +dotnet restore --tl:off -v minimal Lokad.Onnx.slnx if not %ERRORLEVEL%==0 ( - echo Error restoring NuGet packages for Lokad.Onnx.sln. + echo Error restoring NuGet packages for Lokad.Onnx.slnx. set ERROR_CODE=1 goto End ) diff --git a/ext/satsumagraph b/ext/satsumagraph deleted file mode 160000 index 890f500..0000000 --- a/ext/satsumagraph +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 890f500bba179c74afbb4cc60dcee1059488def9 diff --git a/lonnx.cmd b/lonnx.cmd index 1ed2d72..c163ebb 100644 --- a/lonnx.cmd +++ b/lonnx.cmd @@ -6,7 +6,7 @@ set ERROR_CODE=0 REM From Alec Mev https://superuser.com/questions/35698/how-to-supress-terminate-batch-job-y-n-confirmation/715798#715798 IF [%JUSTTERMINATE%] == [OKAY] ( SET JUSTTERMINATE= - src\Lokad.Onnx.CLI\bin\Release\net6.0\Lokad.Onnx.CLI.exe %* + src\Lokad.Onnx.CLI\bin\Release\net10.0\Lokad.Onnx.CLI.exe %* ) ELSE ( SET JUSTTERMINATE=OKAY CALL %0 %* Nodes { get; set; } = new List(); - public WeightedDirectedGraph WeightedDirectedGraph { get; } = new WeightedDirectedGraph(); - public Dictionary Opset = new Dictionary(); public Dictionary Metadata = new Dictionary(); @@ -349,13 +345,3 @@ public void Reset(bool gc = false) } #endregion } - -public class WeightedDirectedGraph : AbstractGraph -{ - public Satsuma.Node AddNode(string id) - { - var _id = id.GetHashCode(); - this.AddNode(_id); - return new Satsuma.Node(_id); - } -} \ No newline at end of file diff --git a/src/Lokad.Onnx.Backend/Lokad.Onnx.Backend.csproj b/src/Lokad.Onnx.Backend/Lokad.Onnx.Backend.csproj index 93d2662..28be426 100644 --- a/src/Lokad.Onnx.Backend/Lokad.Onnx.Backend.csproj +++ b/src/Lokad.Onnx.Backend/Lokad.Onnx.Backend.csproj @@ -1,7 +1,7 @@ - + - net8.0 + net10.0 enable 10.0 @@ -13,9 +13,9 @@ - + diff --git a/src/Lokad.Onnx.Backend/Node.cs b/src/Lokad.Onnx.Backend/Node.cs index b862096..fb5fb1a 100644 --- a/src/Lokad.Onnx.Backend/Node.cs +++ b/src/Lokad.Onnx.Backend/Node.cs @@ -13,7 +13,6 @@ public partial struct Node public long ID; public string Name; public Dictionary? Attributes; - public Satsuma.Node WeightedGraphNode; public OpType Op; public string[] Inputs; public string[] Outputs; diff --git a/src/Lokad.Onnx.Backend/NodeUtil.cs b/src/Lokad.Onnx.Backend/NodeUtil.cs index 357b0ed..80b6ef4 100644 --- a/src/Lokad.Onnx.Backend/NodeUtil.cs +++ b/src/Lokad.Onnx.Backend/NodeUtil.cs @@ -28,7 +28,6 @@ public static Node ToNode(this NodeProto np, ComputationalGraph graph) { Name = np.Name, ID = np.Name.GetHashCode(), - WeightedGraphNode = new Satsuma.Node(np.Name.GetHashCode()), Attributes = np.Attribute.ToDictionary(k => k.Name, v => v.Value()), Op = (OpType) Enum.Parse(typeof(OpType), np.OpType), Inputs = np.Input.ToArray(), @@ -41,18 +40,6 @@ public static Node ToNode(this NodeProto np, ComputationalGraph graph) graph.IntermediateOutputs.Add(o, null); } } - graph.WeightedDirectedGraph.AddNode(node.Name); - foreach (var n in graph.Nodes) - { - foreach (var i in node.Inputs) - { - if (n.Outputs.Contains(i)) - { - graph.WeightedDirectedGraph.AddArc(n.WeightedGraphNode, node.WeightedGraphNode, Satsuma.Directedness.Directed, label: i); - Runtime.Debug("Node {dest} has predecessor {src}.", node.Name, n.Name); - } - } - } return node; } } diff --git a/src/Lokad.Onnx.Base/HardwareConfig.cs b/src/Lokad.Onnx.Base/HardwareConfig.cs index 9bd6588..d3e3a33 100644 --- a/src/Lokad.Onnx.Base/HardwareConfig.cs +++ b/src/Lokad.Onnx.Base/HardwareConfig.cs @@ -4,7 +4,7 @@ public static class HardwareConfig { public static bool UseSimd { get; set; } = true; - public static bool UseIntrinsics { get; set; } = false; + public static bool UseIntrinsics { get; set; } = HardwareIntrinsics.IsX86FmaSupported; public static void EnableIntrinsics() { diff --git a/src/Lokad.Onnx.Base/Lokad.Onnx.Base.csproj b/src/Lokad.Onnx.Base/Lokad.Onnx.Base.csproj index 5d8a784..2bc5c0f 100644 --- a/src/Lokad.Onnx.Base/Lokad.Onnx.Base.csproj +++ b/src/Lokad.Onnx.Base/Lokad.Onnx.Base.csproj @@ -1,7 +1,7 @@ - + - net8.0 + net10.0 latest disable enable @@ -14,3 +14,4 @@ + diff --git a/src/Lokad.Onnx.CLI/Lokad.Onnx.CLI.csproj b/src/Lokad.Onnx.CLI/Lokad.Onnx.CLI.csproj index 8b619aa..f76721e 100644 --- a/src/Lokad.Onnx.CLI/Lokad.Onnx.CLI.csproj +++ b/src/Lokad.Onnx.CLI/Lokad.Onnx.CLI.csproj @@ -1,8 +1,8 @@ - + Exe - net8.0 + net10.0 latest disable enable @@ -31,3 +31,4 @@ + diff --git a/src/Lokad.Onnx.CLI/Program.cs b/src/Lokad.Onnx.CLI/Program.cs index 8ac56de..8ee6f18 100644 --- a/src/Lokad.Onnx.CLI/Program.cs +++ b/src/Lokad.Onnx.CLI/Program.cs @@ -194,6 +194,7 @@ static void Run(RunOptions ro) if (ro.DisableSimd) { HardwareConfig.UseSimd = false; + HardwareConfig.UseIntrinsics = false; Info("CPU SIMD features disabled."); } else @@ -206,9 +207,12 @@ static void Run(RunOptions ro) } } - if (ro.EnableIntrinsics && System.Numerics.Vector.IsHardwareAccelerated) + if (!ro.DisableSimd && System.Numerics.Vector.IsHardwareAccelerated) + { + HardwareConfig.UseIntrinsics = (ro.EnableIntrinsics || HardwareConfig.UseIntrinsics); + } + if (HardwareConfig.UseIntrinsics) { - HardwareConfig.UseIntrinsics = true; Info("CPU SIMD available intrinsics: {s}.", HardwareIntrinsics.GetFullInfo()); } else diff --git a/src/Lokad.Onnx.Data/Lokad.Onnx.Data.csproj b/src/Lokad.Onnx.Data/Lokad.Onnx.Data.csproj index 91e970b..5fedf0c 100644 --- a/src/Lokad.Onnx.Data/Lokad.Onnx.Data.csproj +++ b/src/Lokad.Onnx.Data/Lokad.Onnx.Data.csproj @@ -1,15 +1,15 @@ - + - net80 + net10.0 enable 11.0 - - + + @@ -18,3 +18,4 @@ + diff --git a/src/Lokad.Onnx.Interop/Lokad.Onnx.Interop.csproj b/src/Lokad.Onnx.Interop/Lokad.Onnx.Interop.csproj index c9a2c04..0b4ebb1 100644 --- a/src/Lokad.Onnx.Interop/Lokad.Onnx.Interop.csproj +++ b/src/Lokad.Onnx.Interop/Lokad.Onnx.Interop.csproj @@ -1,7 +1,7 @@ - + - net8.0 + net10.0 latest disable enable @@ -21,3 +21,4 @@ + diff --git a/src/Lokad.Onnx.Package/Lokad.Onnx.Package.csproj b/src/Lokad.Onnx.Package/Lokad.Onnx.Package.csproj index 74b3ab3..92945c7 100644 --- a/src/Lokad.Onnx.Package/Lokad.Onnx.Package.csproj +++ b/src/Lokad.Onnx.Package/Lokad.Onnx.Package.csproj @@ -1,7 +1,7 @@ - + - net8.0 + net10.0 preview disable annotations @@ -23,7 +23,6 @@ - @@ -47,3 +46,4 @@ + diff --git a/src/Lokad.Onnx.Tensors/Lokad.Onnx.Tensors.csproj b/src/Lokad.Onnx.Tensors/Lokad.Onnx.Tensors.csproj index cba3fdb..dd14a28 100644 --- a/src/Lokad.Onnx.Tensors/Lokad.Onnx.Tensors.csproj +++ b/src/Lokad.Onnx.Tensors/Lokad.Onnx.Tensors.csproj @@ -1,7 +1,7 @@ - + - net8.0 + net10.0 preview disable warnings @@ -17,3 +17,4 @@ + diff --git a/src/Lokad.Onnx.Tensors/TensorOps.cs b/src/Lokad.Onnx.Tensors/TensorOps.cs index 1ba41dd..77e0370 100644 --- a/src/Lokad.Onnx.Tensors/TensorOps.cs +++ b/src/Lokad.Onnx.Tensors/TensorOps.cs @@ -325,8 +325,10 @@ public static Tensor MatMul2D(Tensor x, Tensor y) var n = x.Dimensions[1]; var k = y.Dimensions[1]; - var _x = x.ToDenseTensor(); - var _y = y.ToDenseTensor(); + var dx = x as DenseTensor; + var dy = y as DenseTensor; + var _x = dx is not null && !dx.IsReversedStride ? dx : x.ToDenseTensor(); + var _y = dy is not null && !dy.IsReversedStride ? dy : y.ToDenseTensor(); var output = DenseTensor.OfShape(new int[] { x.Dimensions[0], y.Dimensions[1] }); var xh = _x.Buffer.Pin(); @@ -363,9 +365,16 @@ public static Tensor MatMul2D(Tensor x, Tensor y) var n = x.Dimensions[1]; var k = y.Dimensions[1]; - StartOpStage(OpStage.Copy); - var _x = x.ToDenseTensor(); - var _y = y.ToDenseTensor(); + var dx = x as DenseTensor; + var dy = y as DenseTensor; + var needsCopyX = dx is null || dx.IsReversedStride; + var needsCopyY = dy is null || dy.IsReversedStride; + if (needsCopyX || needsCopyY) + { + StartOpStage(OpStage.Copy); + } + var _x = needsCopyX ? x.ToDenseTensor() : dx!; + var _y = needsCopyY ? y.ToDenseTensor() : dy!; var output = DenseTensor.OfShape(new int[] { x.Dimensions[0], y.Dimensions[1] }); StartOpStage(OpStage.Math); @@ -416,8 +425,10 @@ public static Tensor MatMul2D(Tensor x, Tensor y) var k = y.Dimensions[1]; - var _x = x.ToDenseTensor(); - var _y = y.ToDenseTensor(); + var dx = x as DenseTensor; + var dy = y as DenseTensor; + var _x = dx is not null && !dx.IsReversedStride ? dx : x.ToDenseTensor(); + var _y = dy is not null && !dy.IsReversedStride ? dy : y.ToDenseTensor(); var output = DenseTensor.OfShape(new int[] { x.Dimensions[0], y.Dimensions[1] }); var xh = _x.Buffer.Pin(); diff --git a/src/Lokad.Onnx.sln b/src/Lokad.Onnx.sln deleted file mode 100644 index a50d35f..0000000 --- a/src/Lokad.Onnx.sln +++ /dev/null @@ -1,133 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.33103.201 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lokad.Onnx.Backend", "Lokad.Onnx.Backend\Lokad.Onnx.Backend.csproj", "{8BCDE99B-F78F-4297-ABBB-044215DCF7E4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "External", "External", "{A907284B-7B27-4E4A-9ADD-E5617FD44911}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Satsuma", "..\ext\satsumagraph\Satsuma.csproj", "{3C752CC8-07E6-41BC-8089-E000E9CC16FC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{2014E9FF-F5EC-4203-BA92-D495E9013ABF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{7620AA98-619B-4984-A038-D5731106FF04}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lokad.Onnx.Tensors", "Lokad.Onnx.Tensors\Lokad.Onnx.Tensors.csproj", "{CBD59431-025B-42E2-AA2B-B3EDDC4E8B1C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{B8BA7D1B-BCEC-4A48-9EFC-4F1FA14D13F5}" - ProjectSection(SolutionItems) = preProject - ..\README.md = ..\README.md - ..\THIRD-PARTY NOTICES = ..\THIRD-PARTY NOTICES - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lokad.Onnx.CLI", "Lokad.Onnx.CLI\Lokad.Onnx.CLI.csproj", "{321FFC7A-4773-41B2-BC88-ABA5FC93C48D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lokad.Onnx.Base", "Lokad.Onnx.Base\Lokad.Onnx.Base.csproj", "{905C91C3-083A-4BBB-A6D3-C52A69143844}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog", "..\ext\NLog\src\NLog\NLog.csproj", "{A0BFF0DB-ED9A-4639-AE86-8E709A1EFC66}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{20A6957C-819F-4A54-9246-7FE732AA4F1D}" - ProjectSection(SolutionItems) = preProject - ..\build.cmd = ..\build.cmd - ..\lonnx.cmd = ..\lonnx.cmd - ..\pack.cmd = ..\pack.cmd - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lokad.Onnx.Interop", "Lokad.Onnx.Interop\Lokad.Onnx.Interop.csproj", "{07FA371F-7765-4DC0-8B84-110672DA5483}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Python", "Python", "{0049778C-13D5-4142-8DEA-866F9CA1B68F}" -EndProject -Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "interop", "interop\interop.pyproj", "{91C2B1FC-37CF-46F8-9AC7-A5F920660D7C}" -EndProject -Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "tests", "..\tests\python\tests.pyproj", "{A85C13C8-3263-4158-BACF-44BAAE8B240F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lokad.Onnx.Tensors.Tests", "..\tests\Lokad.Onnx.Tensors.Tests\Lokad.Onnx.Tensors.Tests.csproj", "{CFFCD35A-AA1D-41FE-BBFC-4F6FE3912319}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lokad.Onnx.Backend.Tests", "..\tests\Lokad.Onnx.Backend.Tests\Lokad.Onnx.Backend.Tests.csproj", "{2B8BEF79-6700-4721-BD34-7268790BD8CF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interface", "Interface", "{85F89195-4B97-41EB-BAAD-78EA15916D2C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lokad.Onnx.Data", "Lokad.Onnx.Data\Lokad.Onnx.Data.csproj", "{0A393A22-7056-47F3-A32C-60BED16AB97B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Package", "Package", "{A2CB283F-7338-4E50-8024-FC61B14F8577}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lokad.Onnx.Package", "Lokad.Onnx.Package\Lokad.Onnx.Package.csproj", "{247D05C2-5660-41D5-8FB9-6E85DAC640E9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8BCDE99B-F78F-4297-ABBB-044215DCF7E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8BCDE99B-F78F-4297-ABBB-044215DCF7E4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8BCDE99B-F78F-4297-ABBB-044215DCF7E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8BCDE99B-F78F-4297-ABBB-044215DCF7E4}.Release|Any CPU.Build.0 = Release|Any CPU - {3C752CC8-07E6-41BC-8089-E000E9CC16FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C752CC8-07E6-41BC-8089-E000E9CC16FC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C752CC8-07E6-41BC-8089-E000E9CC16FC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C752CC8-07E6-41BC-8089-E000E9CC16FC}.Release|Any CPU.Build.0 = Release|Any CPU - {CBD59431-025B-42E2-AA2B-B3EDDC4E8B1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CBD59431-025B-42E2-AA2B-B3EDDC4E8B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CBD59431-025B-42E2-AA2B-B3EDDC4E8B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CBD59431-025B-42E2-AA2B-B3EDDC4E8B1C}.Release|Any CPU.Build.0 = Release|Any CPU - {321FFC7A-4773-41B2-BC88-ABA5FC93C48D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {321FFC7A-4773-41B2-BC88-ABA5FC93C48D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {321FFC7A-4773-41B2-BC88-ABA5FC93C48D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {321FFC7A-4773-41B2-BC88-ABA5FC93C48D}.Release|Any CPU.Build.0 = Release|Any CPU - {905C91C3-083A-4BBB-A6D3-C52A69143844}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {905C91C3-083A-4BBB-A6D3-C52A69143844}.Debug|Any CPU.Build.0 = Debug|Any CPU - {905C91C3-083A-4BBB-A6D3-C52A69143844}.Release|Any CPU.ActiveCfg = Release|Any CPU - {905C91C3-083A-4BBB-A6D3-C52A69143844}.Release|Any CPU.Build.0 = Release|Any CPU - {A0BFF0DB-ED9A-4639-AE86-8E709A1EFC66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A0BFF0DB-ED9A-4639-AE86-8E709A1EFC66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A0BFF0DB-ED9A-4639-AE86-8E709A1EFC66}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A0BFF0DB-ED9A-4639-AE86-8E709A1EFC66}.Release|Any CPU.Build.0 = Release|Any CPU - {07FA371F-7765-4DC0-8B84-110672DA5483}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07FA371F-7765-4DC0-8B84-110672DA5483}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07FA371F-7765-4DC0-8B84-110672DA5483}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07FA371F-7765-4DC0-8B84-110672DA5483}.Release|Any CPU.Build.0 = Release|Any CPU - {91C2B1FC-37CF-46F8-9AC7-A5F920660D7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {91C2B1FC-37CF-46F8-9AC7-A5F920660D7C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A85C13C8-3263-4158-BACF-44BAAE8B240F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A85C13C8-3263-4158-BACF-44BAAE8B240F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CFFCD35A-AA1D-41FE-BBFC-4F6FE3912319}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CFFCD35A-AA1D-41FE-BBFC-4F6FE3912319}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CFFCD35A-AA1D-41FE-BBFC-4F6FE3912319}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CFFCD35A-AA1D-41FE-BBFC-4F6FE3912319}.Release|Any CPU.Build.0 = Release|Any CPU - {2B8BEF79-6700-4721-BD34-7268790BD8CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2B8BEF79-6700-4721-BD34-7268790BD8CF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2B8BEF79-6700-4721-BD34-7268790BD8CF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2B8BEF79-6700-4721-BD34-7268790BD8CF}.Release|Any CPU.Build.0 = Release|Any CPU - {0A393A22-7056-47F3-A32C-60BED16AB97B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0A393A22-7056-47F3-A32C-60BED16AB97B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0A393A22-7056-47F3-A32C-60BED16AB97B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0A393A22-7056-47F3-A32C-60BED16AB97B}.Release|Any CPU.Build.0 = Release|Any CPU - {247D05C2-5660-41D5-8FB9-6E85DAC640E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {247D05C2-5660-41D5-8FB9-6E85DAC640E9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {247D05C2-5660-41D5-8FB9-6E85DAC640E9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {247D05C2-5660-41D5-8FB9-6E85DAC640E9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {8BCDE99B-F78F-4297-ABBB-044215DCF7E4} = {2014E9FF-F5EC-4203-BA92-D495E9013ABF} - {3C752CC8-07E6-41BC-8089-E000E9CC16FC} = {A907284B-7B27-4E4A-9ADD-E5617FD44911} - {CBD59431-025B-42E2-AA2B-B3EDDC4E8B1C} = {2014E9FF-F5EC-4203-BA92-D495E9013ABF} - {321FFC7A-4773-41B2-BC88-ABA5FC93C48D} = {85F89195-4B97-41EB-BAAD-78EA15916D2C} - {905C91C3-083A-4BBB-A6D3-C52A69143844} = {2014E9FF-F5EC-4203-BA92-D495E9013ABF} - {A0BFF0DB-ED9A-4639-AE86-8E709A1EFC66} = {A907284B-7B27-4E4A-9ADD-E5617FD44911} - {07FA371F-7765-4DC0-8B84-110672DA5483} = {2014E9FF-F5EC-4203-BA92-D495E9013ABF} - {91C2B1FC-37CF-46F8-9AC7-A5F920660D7C} = {0049778C-13D5-4142-8DEA-866F9CA1B68F} - {A85C13C8-3263-4158-BACF-44BAAE8B240F} = {0049778C-13D5-4142-8DEA-866F9CA1B68F} - {CFFCD35A-AA1D-41FE-BBFC-4F6FE3912319} = {7620AA98-619B-4984-A038-D5731106FF04} - {2B8BEF79-6700-4721-BD34-7268790BD8CF} = {7620AA98-619B-4984-A038-D5731106FF04} - {0A393A22-7056-47F3-A32C-60BED16AB97B} = {2014E9FF-F5EC-4203-BA92-D495E9013ABF} - {247D05C2-5660-41D5-8FB9-6E85DAC640E9} = {A2CB283F-7338-4E50-8024-FC61B14F8577} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {6B835BFE-A6A4-4235-A6F1-0B6F3F4BE6DF} - EndGlobalSection -EndGlobal diff --git a/src/interop/__init__.py b/src/interop/__init__.py index 90b9acf..6de01b7 100644 --- a/src/interop/__init__.py +++ b/src/interop/__init__.py @@ -2,6 +2,6 @@ from pythonnet import load, get_runtime_info file_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) -load("coreclr", runtime_config=os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net8.0", "publish", "Lokad.Onnx.Interop.runtimeconfig.json")) +load("coreclr", runtime_config=os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net10.0", "publish", "Lokad.Onnx.Interop.runtimeconfig.json")) -#print(get_runtime_info()) \ No newline at end of file +#print(get_runtime_info()) diff --git a/src/interop/backend.py b/src/interop/backend.py index 662eec2..ee16a1f 100644 --- a/src/interop/backend.py +++ b/src/interop/backend.py @@ -10,10 +10,10 @@ import clr file_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) -clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net8.0", "publish", "Lokad.Onnx.Interop.dll")) -clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net8.0", "publish", "Lokad.Onnx.Tensors.dll")) -clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net8.0", "publish", "Lokad.Onnx.Backend.dll")) -clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net8.0", "publish", "Lokad.Onnx.Data.dll")) +clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net10.0", "publish", "Lokad.Onnx.Interop.dll")) +clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net10.0", "publish", "Lokad.Onnx.Tensors.dll")) +clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net10.0", "publish", "Lokad.Onnx.Backend.dll")) +clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net10.0", "publish", "Lokad.Onnx.Data.dll")) from System import Array, String from System.Collections.Generic import Dictionary @@ -226,4 +226,4 @@ def load_graph(cls, model) -> ComputationalGraph: get_input_ndarray_from_text = LokadOnnxBackend.get_input_ndarray_from_text -load_graph = LokadOnnxBackend.load_graph \ No newline at end of file +load_graph = LokadOnnxBackend.load_graph diff --git a/src/interop/tensors.py b/src/interop/tensors.py index 236c4b9..281c24a 100644 --- a/src/interop/tensors.py +++ b/src/interop/tensors.py @@ -6,8 +6,8 @@ import clr file_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) -clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net8.0", "publish", "Lokad.Onnx.Interop.dll")) -clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net8.0", "publish", "Lokad.Onnx.Tensors.dll")) +clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net10.0", "publish", "Lokad.Onnx.Interop.dll")) +clr.AddReference(os.path.join(file_dir, "..", "Lokad.Onnx.Interop", "bin", "Release", "net10.0", "publish", "Lokad.Onnx.Tensors.dll")) import System from System import Array, Int32, Single, Double, String @@ -179,4 +179,4 @@ def slice(t: ITensor, *dims) -> ITensor: def add(x:ITensor, y) -> ITensor: return Tensors.Add(x, y) -def matmul(x:ITensor, y) -> ITensor: return Tensors.MatMul(x, y) \ No newline at end of file +def matmul(x:ITensor, y) -> ITensor: return Tensors.MatMul(x, y) diff --git a/tests/Lokad.Onnx.Backend.Tests/CpuExecutionProviderOpTests.cs b/tests/Lokad.Onnx.Backend.Tests/CpuExecutionProviderOpTests.cs new file mode 100644 index 0000000..f982045 --- /dev/null +++ b/tests/Lokad.Onnx.Backend.Tests/CpuExecutionProviderOpTests.cs @@ -0,0 +1,173 @@ +using System; +using System.Linq; +using CPU = Lokad.Onnx.CPUExecutionProvider; + +namespace Lokad.Onnx.Backend.Tests; + +public class CpuExecutionProviderOpTests +{ + [Fact] + public void Add_Sub_Mul_Div_Broadcast_Int32() + { + var a = DenseTensor.OfValues(new int[,] { { 1, 2 }, { 3, 4 } }); + var b = DenseTensor.OfValues(new int[] { 10, 20 }); + + var add = CPU.Add(a, b); + var sub = CPU.Sub(a, b); + var mul = CPU.Mul(a, b); + + Assert.Equal(OpStatus.Success, add.Status); + Assert.Equal(11, ((Tensor)add.Outputs![0])[0, 0]); + Assert.Equal(24, ((Tensor)add.Outputs![0])[1, 1]); + Assert.Equal(-9, ((Tensor)sub.Outputs![0])[0, 0]); + Assert.Equal(-16, ((Tensor)sub.Outputs![0])[1, 1]); + Assert.Equal(10, ((Tensor)mul.Outputs![0])[0, 0]); + Assert.Equal(80, ((Tensor)mul.Outputs![0])[1, 1]); + + var div = CPU.Div(DenseTensor.OfValues(new float[,] { { 8f, 6f }, { 4f, 2f } }), + DenseTensor.OfValues(new float[,] { { 2f, 3f }, { 4f, 2f } })); + Assert.Equal(OpStatus.Success, div.Status); + Assert.Equal(4f, ((Tensor)div.Outputs![0])[0, 0], 5); + Assert.Equal(1f, ((Tensor)div.Outputs![0])[1, 0], 5); + } + + [Fact] + public void Pow_Sqrt_Erf_Relu_Float() + { + var x = DenseTensor.OfValues(new float[,] { { 4f, 9f }, { -1f, 2f } }); + var y = DenseTensor.OfValues(new float[,] { { 0.5f, 0.5f }, { 2f, 3f } }); + + var pow = CPU.Pow(x, y); + Assert.Equal(OpStatus.Success, pow.Status); + Assert.Equal(2f, ((Tensor)pow.Outputs![0])[0, 0], 5); + Assert.Equal(3f, ((Tensor)pow.Outputs![0])[0, 1], 5); + Assert.Equal(1f, ((Tensor)pow.Outputs![0])[1, 0], 5); + Assert.Equal(8f, ((Tensor)pow.Outputs![0])[1, 1], 5); + + var sqrt = CPU.Sqrt(DenseTensor.OfValues(new float[] { 4f, 9f })); + Assert.Equal(OpStatus.Success, sqrt.Status); + Assert.Equal(2f, ((Tensor)sqrt.Outputs![0])[0], 5); + Assert.Equal(3f, ((Tensor)sqrt.Outputs![0])[1], 5); + + var relu = CPU.Relu(DenseTensor.OfValues(new float[] { -1f, 0f, 2f })); + Assert.Equal(OpStatus.Success, relu.Status); + Assert.Equal(0f, ((Tensor)relu.Outputs![0])[0], 5); + Assert.Equal(2f, ((Tensor)relu.Outputs![0])[2], 5); + + var erf = CPU.Erf(DenseTensor.OfValues(new float[] { 0f, 1f })); + Assert.Equal(OpStatus.Success, erf.Status); + Assert.Equal(0f, ((Tensor)erf.Outputs![0])[0], 5); + Assert.Equal(0.8427f, ((Tensor)erf.Outputs![0])[1], 3); + } + + [Fact] + public void MatMul_Transpose_Softmax() + { + var a = DenseTensor.OfValues(new float[,] { { 1f, 2f }, { 3f, 4f } }); + var b = DenseTensor.OfValues(new float[,] { { 5f, 6f }, { 7f, 8f } }); + + var mm = CPU.MatMul(a, b); + Assert.Equal(OpStatus.Success, mm.Status); + var mmOut = (Tensor)mm.Outputs![0]; + Assert.Equal(19f, mmOut[0, 0], 5); + Assert.Equal(50f, mmOut[1, 1], 5); + + var trans = CPU.Transpose(a, new[] { 1, 0 }); + Assert.Equal(OpStatus.Success, trans.Status); + var t = (Tensor)trans.Outputs![0]; + Assert.Equal(2f, t[1, 0], 5); + Assert.Equal(3f, t[0, 1], 5); + + var logits = DenseTensor.OfValues(new float[,] { { 0f, 1f }, { 1f, 1f } }); + var sm = CPU.Softmax(logits, 1); + Assert.Equal(OpStatus.Success, sm.Status); + var smOut = (Tensor)sm.Outputs![0]; + Assert.Equal(1f, smOut[0, 0] + smOut[0, 1], 5); + Assert.Equal(0.5f, smOut[1, 0], 5); + Assert.Equal(0.5f, smOut[1, 1], 5); + } + + [Fact] + public void Conv_MaxPool_Reduce_Concat_Gather_Slice_Unsqueeze_Cast_Constant() + { + var x = DenseTensor.OfValues(new float[1, 1, 3, 3] { { { + { 1f, 2f, 3f }, { 4f, 5f, 6f }, { 7f, 8f, 9f } + } } }); + var w = DenseTensor.OfValues(new float[1, 1, 2, 2] { { { { 1f, 1f }, { 1f, 1f } } } }); + var b = DenseTensor.OfValues(new float[] { 0f }); + + var conv = CPU.Conv(x, w, b, auto_pad: "VALID"); + Assert.Equal(OpStatus.Success, conv.Status); + var convOut = (Tensor)conv.Outputs![0]; + Assert.Equal(new[] { 1, 1, 2, 2 }, convOut.Dimensions.ToArray()); + Assert.Equal(12f, convOut[0, 0, 0, 0], 5); + Assert.Equal(16f, convOut[0, 0, 0, 1], 5); + + var pool = CPU.MaxPool(x, auto_pad: "VALID", kernel_shape: new[] { 2, 2 }); + Assert.Equal(OpStatus.Success, pool.Status); + var poolOut = (Tensor)pool.Outputs![0]; + Assert.Equal(new[] { 1, 1, 1, 1 }, poolOut.Dimensions.ToArray()); + Assert.Equal(5f, poolOut[0, 0, 0, 0], 5); + + var data = DenseTensor.OfValues(new float[2, 2] { { 1f, 2f }, { 3f, 4f } }); + var axes = DenseTensor.OfValues(new int[] { 1 }); + var rsum = CPU.ReduceSum(data, axes, 0, 0); + Assert.Equal(OpStatus.Success, rsum.Status); + var rsumOut = (Tensor)rsum.Outputs![0]; + Assert.Equal(new[] { 2 }, rsumOut.Dimensions.ToArray()); + Assert.Equal(3f, rsumOut[0], 5); + + var rmean = CPU.ReduceMean(data, axes, 0, 0); + Assert.Equal(OpStatus.Success, rmean.Status); + var rmeanOut = (Tensor)rmean.Outputs![0]; + Assert.Equal(1.5f, rmeanOut[0], 5); + + var rmax = CPU.ReduceMax(data, axes, 0); + Assert.Equal(OpStatus.Success, rmax.Status); + var rmaxOut = (Tensor)rmax.Outputs![0]; + Assert.Equal(2f, rmaxOut[0], 5); + + var c1 = DenseTensor.OfValues(new int[,] { { 1, 2 } }); + var c2 = DenseTensor.OfValues(new int[,] { { 3, 4 } }); + var concat = CPU.Concat(new ITensor[] { c1, c2 }, 0); + Assert.Equal(OpStatus.Success, concat.Status); + var concatOut = (Tensor)concat.Outputs![0]; + Assert.Equal(new[] { 2, 2 }, concatOut.Dimensions.ToArray()); + Assert.Equal(3, concatOut[1, 0]); + + var gdata = DenseTensor.OfValues(new int[,] { { 10, 20 }, { 30, 40 } }); + var gidx = DenseTensor.OfValues(new int[] { 1, 0 }); + var gather = CPU.Gather(gdata, gidx, 0); + Assert.Equal(OpStatus.Success, gather.Status); + var gatherOut = (Tensor)gather.Outputs![0]; + Assert.Equal(30, gatherOut[0, 0]); + Assert.Equal(10, gatherOut[1, 0]); + + var sdata = DenseTensor.OfValues(new int[,] { { 1, 2, 3 }, { 4, 5, 6 } }); + var starts = DenseTensor.OfValues(new int[] { 0, 1 }); + var ends = DenseTensor.OfValues(new int[] { 2, 3 }); + var slice = CPU.Slice(sdata, starts, ends, null, null); + Assert.Equal(OpStatus.Success, slice.Status); + var sliceOut = (Tensor)slice.Outputs![0]; + Assert.Equal(new[] { 2, 2 }, sliceOut.Dimensions.ToArray()); + Assert.Equal(2, sliceOut[0, 0]); + Assert.Equal(6, sliceOut[1, 1]); + + var unsq = CPU.Unsqueeze(DenseTensor.OfValues(new int[] { 7, 8 }), new[] { 0 }); + Assert.Equal(OpStatus.Success, unsq.Status); + var unsqOut = (Tensor)unsq.Outputs![0]; + Assert.Equal(new[] { 1, 2 }, unsqOut.Dimensions.ToArray()); + Assert.Equal(7, unsqOut[0, 0]); + + var cast = CPU.Cast(DenseTensor.OfValues(new float[] { 1.2f, 2.6f }), (long)TensorElementType.Int32); + Assert.Equal(OpStatus.Success, cast.Status); + var castOut = (Tensor)cast.Outputs![0]; + Assert.Equal(1, castOut[0]); + Assert.Equal(3, castOut[1]); + + var cst = CPU.Constant(42); + Assert.Equal(OpStatus.Success, cst.Status); + var cstOut = (Tensor)cst.Outputs![0]; + Assert.Equal(42, cstOut[0]); + } +} diff --git a/tests/Lokad.Onnx.Backend.Tests/CpuExecutionProviderReshapeTests.cs b/tests/Lokad.Onnx.Backend.Tests/CpuExecutionProviderReshapeTests.cs new file mode 100644 index 0000000..2925ef5 --- /dev/null +++ b/tests/Lokad.Onnx.Backend.Tests/CpuExecutionProviderReshapeTests.cs @@ -0,0 +1,27 @@ +using CPU = Lokad.Onnx.CPUExecutionProvider; + +namespace Lokad.Onnx.Backend.Tests +{ + public class CpuExecutionProviderReshapeTests + { + [Fact] + public void CanReshape() + { + var X = DenseTensor.Ones(2, 3, 4); + var s = DenseTensor.OfValues(new long[] { 4, 2, 3 }); + var r = CPU.Reshape(X, s); + Assert.Equal(OpStatus.Success, r.Status); + Assert.Equal(r.Outputs![0].Dims, new int[3] { 4, 2, 3 }); + + s = DenseTensor.OfValues(new long[] { -1, 2, 3, 4 }); + r = CPU.Reshape(X, s); + Assert.Equal(OpStatus.Success, r.Status); + Assert.Equal(r.Outputs![0].Dims, new int[4] { 1, 2, 3, 4 }); + + r = CPU.Reshape((ITensor) X, null); + Assert.Equal(OpStatus.Failure, r.Status); + + Assert.Throws(() => CPU.Reshape((ITensor)X, DenseTensor.OfValues(new long[,] { { 2, 2 }, { 2, 1 } }))); + } + } +} diff --git a/tests/Lokad.Onnx.Backend.Tests/CpuExecutionProviderShapeTests.cs b/tests/Lokad.Onnx.Backend.Tests/CpuExecutionProviderShapeTests.cs new file mode 100644 index 0000000..6d741c9 --- /dev/null +++ b/tests/Lokad.Onnx.Backend.Tests/CpuExecutionProviderShapeTests.cs @@ -0,0 +1,17 @@ +using CPU = Lokad.Onnx.CPUExecutionProvider; + +namespace Lokad.Onnx.Backend.Tests +{ + public class CpuExecutionProviderShapeTests + { + [Fact] + public void CanGetShape() + { + var t = DenseTensor.OfShape(3, 4, 5); + var o = CPU.Shape(t, 1); + Assert.Equal(OpStatus.Success, o.Status); + var shape = (Tensor)o.Outputs![0]; + Assert.Equal(new long[] { 4, 5 }, shape.ToArray()); + } + } +} diff --git a/tests/Lokad.Onnx.Backend.Tests/DataInputSmokeTests.cs b/tests/Lokad.Onnx.Backend.Tests/DataInputSmokeTests.cs new file mode 100644 index 0000000..332d6e0 --- /dev/null +++ b/tests/Lokad.Onnx.Backend.Tests/DataInputSmokeTests.cs @@ -0,0 +1,14 @@ +namespace Lokad.Onnx.Backend.Tests +{ + public class DataInputSmokeTests + { + [Fact] + public void CanGetMnistInputTensor() + { + var r = Data.GetInputTensorsFromFileArgs(new[] { "images\\mnist4.png::mnist" }); + Assert.NotNull(r); + Assert.Single(r!); + Assert.Equal(4, r![0].Rank); + } + } +} diff --git a/tests/Lokad.Onnx.Backend.Tests/DataTests.cs b/tests/Lokad.Onnx.Backend.Tests/DataTests.cs deleted file mode 100644 index ca54fe2..0000000 --- a/tests/Lokad.Onnx.Backend.Tests/DataTests.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Lokad.Onnx.Backend.Tests; - -using static Lokad.Onnx.Text; -public class DataTests -{ - [Fact] - public void CanPad() - { - string s1 = "Hello world"; - var t1 = GetTextTensors(s1, "me5s"); - string[] t = { "Hello world", "This is a test", "of padding" }; - var tensors = GetTextTensors(t, "me5s"); - Assert.NotNull(tensors); - } -} diff --git a/tests/Lokad.Onnx.Backend.Tests/GraphTests.cs b/tests/Lokad.Onnx.Backend.Tests/GraphExecutionMnistTests.cs similarity index 67% rename from tests/Lokad.Onnx.Backend.Tests/GraphTests.cs rename to tests/Lokad.Onnx.Backend.Tests/GraphExecutionMnistTests.cs index a1ed6e8..91dacf2 100644 --- a/tests/Lokad.Onnx.Backend.Tests/GraphTests.cs +++ b/tests/Lokad.Onnx.Backend.Tests/GraphExecutionMnistTests.cs @@ -1,8 +1,10 @@ -using Lokad.Onnx; +using Lokad.Onnx; +using System.Collections.Generic; +using System.Linq; namespace Lokad.Onnx.Backend.Tests { - public class GraphTests + public class GraphExecutionMnistTests { [Fact] public void CanLoadFromFile() @@ -40,5 +42,21 @@ public void CanInferWithMnist2() var o = (Tensor) g.Outputs.Values.First().RemoveDim(0); Assert.True(r.AlmostEqual(o, 4)); } + + [Fact] + public void CanInferWithMnist_DictionaryInputs() + { + var g = Model.Load("models\\mnist-8.onnx")!; + var inputName = g.Model.Graph.Input[0].Name; + var outputName = g.Model.Graph.Output[0].Name; + var ui = Data.GetInputTensorsFromFileArgs(new[] { "images\\mnist4.png::mnist" })!; + var inputs = new Dictionary { { inputName, ui[0] } }; + + Assert.True(g.Execute(inputs, true)); + Assert.True(g.Outputs.ContainsKey(outputName)); + + var probs = (Tensor) g.Outputs[outputName].RemoveDim(0).Softmax(); + Assert.True(probs[4] > 0.9); + } } -} \ No newline at end of file +} diff --git a/tests/Lokad.Onnx.Backend.Tests/InteropGraphMnistTests.cs b/tests/Lokad.Onnx.Backend.Tests/InteropGraphMnistTests.cs new file mode 100644 index 0000000..bb843e4 --- /dev/null +++ b/tests/Lokad.Onnx.Backend.Tests/InteropGraphMnistTests.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Lokad.Onnx.Interop; + +namespace Lokad.Onnx.Backend.Tests; + +public class InteropGraphMnistTests +{ + [Fact] + public void CanLoadAndExecuteWithFileArgs() + { + var modelPath = Path.Combine(AppContext.BaseDirectory, "models", "mnist-8.onnx"); + var buffer = File.ReadAllBytes(modelPath); + var graph = Graph.Load(buffer); + Assert.NotNull(graph); + + var inputs = Graph.GetInputTensorsFromFileArgs(new[] { "images\\mnist4.png::mnist" })!; + Assert.True(graph!.Execute(inputs, true)); + + var output = (Tensor)graph.Outputs.Values.First().RemoveDim(0).Softmax(); + Assert.True(output[4] > 0.9); + } + + [Fact] + public void CanLoadAndExecuteWithNamedInputs() + { + var modelPath = Path.Combine(AppContext.BaseDirectory, "models", "mnist-8.onnx"); + var buffer = File.ReadAllBytes(modelPath); + var graph = Graph.Load(buffer); + Assert.NotNull(graph); + + var inputName = graph!.Model.Graph.Input[0].Name; + var input = Graph.GetInputTensorFromFileArg("images\\mnist2.png::mnist")!; + var inputs = new Dictionary { { inputName, input } }; + + Assert.True(graph.Execute(inputs, true)); + var output = (Tensor)graph.Outputs.Values.First().RemoveDim(0).Softmax(); + Assert.True(output[2] > 0.9); + } +} diff --git a/tests/Lokad.Onnx.Backend.Tests/Lokad.Onnx.Backend.Tests.csproj b/tests/Lokad.Onnx.Backend.Tests/Lokad.Onnx.Backend.Tests.csproj index 2ef4c43..f4bfa51 100644 --- a/tests/Lokad.Onnx.Backend.Tests/Lokad.Onnx.Backend.Tests.csproj +++ b/tests/Lokad.Onnx.Backend.Tests/Lokad.Onnx.Backend.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -11,6 +11,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -25,6 +26,7 @@ + diff --git a/tests/Lokad.Onnx.Backend.Tests/Me5sModelTests.cs b/tests/Lokad.Onnx.Backend.Tests/Me5sModelTests.cs deleted file mode 100644 index 60daa66..0000000 --- a/tests/Lokad.Onnx.Backend.Tests/Me5sModelTests.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Lokad.Onnx.Backend.Tests; - -using Xunit; -public class Me5sModelTests : Runtime -{ - static Me5sModelTests() - { - - } -} - diff --git a/tests/Lokad.Onnx.Backend.Tests/ModelTests.cs b/tests/Lokad.Onnx.Backend.Tests/ModelTests.cs index 572764a..6b4bc89 100644 --- a/tests/Lokad.Onnx.Backend.Tests/ModelTests.cs +++ b/tests/Lokad.Onnx.Backend.Tests/ModelTests.cs @@ -1,6 +1,7 @@ using Lokad.Onnx.Backend; using System.Runtime.Versioning; using System.Xml.Schema; +using System.IO; namespace Lokad.Onnx.Backend.Tests { @@ -10,8 +11,10 @@ public class ModelTests [Fact] public void CanParseFile() { - var m = Model.Parse("models\\mnist-8.onnx"); + var modelPath = Path.Combine(AppContext.BaseDirectory, "models", "mnist-8.onnx"); + var buffer = File.ReadAllBytes(modelPath); + var m = Model.Parse(buffer); Assert.NotNull(m); } } -} \ No newline at end of file +} diff --git a/tests/Lokad.Onnx.Backend.Tests/NodeAttributeMappingTests.cs b/tests/Lokad.Onnx.Backend.Tests/NodeAttributeMappingTests.cs new file mode 100644 index 0000000..2655bf4 --- /dev/null +++ b/tests/Lokad.Onnx.Backend.Tests/NodeAttributeMappingTests.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Lokad.Onnx.Backend.Tests; + +public class NodeAttributeMappingTests +{ + private static ComputationalGraph CreateGraph(int opsetVersion = 13) + { + return new ComputationalGraph + { + Opset = new Dictionary { [""] = opsetVersion }, + Metadata = new Dictionary { ["Name"] = "test" }, + }; + } + + [Fact] + public void Transpose_UsesPermAttribute() + { + var graph = CreateGraph(); + var x = DenseTensor.OfValues(new float[,] { { 1f, 2f }, { 3f, 4f } }); + graph.Inputs["x"] = x; + + var node = new Node + { + Name = "transpose", + Op = OpType.Transpose, + Inputs = new[] { "x" }, + Outputs = new[] { "y" }, + Attributes = new Dictionary { ["perm"] = new[] { 1, 0 } } + }; + + var result = node.Execute(graph); + Assert.Equal(OpStatus.Success, result.Status); + var y = (Tensor)result.Outputs[0]; + Assert.Equal(new[] { 2, 2 }, y.Dimensions.ToArray()); + Assert.Equal(2f, y[1, 0], 5); + Assert.Equal(3f, y[0, 1], 5); + } + + [Fact] + public void Concat_UsesAxisAttribute() + { + var graph = CreateGraph(); + graph.Inputs["a"] = DenseTensor.OfValues(new int[,] { { 1, 2 } }); + graph.Inputs["b"] = DenseTensor.OfValues(new int[,] { { 3, 4 } }); + + var node = new Node + { + Name = "concat", + Op = OpType.Concat, + Inputs = new[] { "a", "b" }, + Outputs = new[] { "y" }, + Attributes = new Dictionary { ["axis"] = 0 } + }; + + var result = node.Execute(graph); + Assert.Equal(OpStatus.Success, result.Status); + var y = (Tensor)result.Outputs[0]; + Assert.Equal(new[] { 2, 2 }, y.Dimensions.ToArray()); + Assert.Equal(3, y[1, 0]); + } + + [Fact] + public void Softmax_UsesAxisAttribute() + { + var graph = CreateGraph(); + graph.Inputs["x"] = DenseTensor.OfValues(new float[,] { { 0f, 1f }, { 1f, 1f } }); + + var node = new Node + { + Name = "softmax", + Op = OpType.Softmax, + Inputs = new[] { "x" }, + Outputs = new[] { "y" }, + Attributes = new Dictionary { ["axis"] = 1 } + }; + + var result = node.Execute(graph); + Assert.Equal(OpStatus.Success, result.Status); + var y = (Tensor)result.Outputs[0]; + Assert.Equal(1f, y[0, 0] + y[0, 1], 5); + Assert.Equal(0.5f, y[1, 0], 5); + Assert.Equal(0.5f, y[1, 1], 5); + } + + [Fact] + public void Conv_UsesPadsStridesKernelShapeAttributes() + { + var graph = CreateGraph(); + var x = DenseTensor.OfValues(new float[1, 1, 3, 3] { { { + { 1f, 2f, 3f }, { 4f, 5f, 6f }, { 7f, 8f, 9f } + } } }); + var w = DenseTensor.OfValues(new float[1, 1, 2, 2] { { { { 1f, 1f }, { 1f, 1f } } } }); + var b = DenseTensor.OfValues(new float[] { 0f }); + graph.Inputs["x"] = x; + graph.Inputs["w"] = w; + graph.Inputs["b"] = b; + + var node = new Node + { + Name = "conv", + Op = OpType.Conv, + Inputs = new[] { "x", "w", "b" }, + Outputs = new[] { "y" }, + Attributes = new Dictionary + { + ["auto_pad"] = "NOTSET", + ["pads"] = new[] { 1, 1, 1, 1 }, + ["strides"] = new[] { 1, 1 }, + ["kernel_shape"] = new[] { 2, 2 } + } + }; + + var result = node.Execute(graph); + Assert.Equal(OpStatus.Success, result.Status); + var y = (Tensor)result.Outputs[0]; + var expected = Tensor.Conv2D(x, w, 1, MathOps.PadType.Value, padvalue: 1, bias: (Tensor)b, kernelshape: new[] { 2, 2 }, strides: new[] { 1, 1 }); + Assert.Equal(expected, y); + } + + [Fact] + public void MaxPool_UsesKernelStridesAttributes() + { + var graph = CreateGraph(); + var x = DenseTensor.OfValues(new float[1, 1, 4, 4] { { { + {12.0f, 20.0f, 30.0f, 0.0f }, { 8.0f, 12.0f, 2.0f, 0.0f }, { 34.0f, 70.0f, 37.0f, 4.0f }, { 112.0f, 100.0f, 25.0f, 12.0f } } } }); + graph.Inputs["x"] = x; + + var node = new Node + { + Name = "maxpool", + Op = OpType.MaxPool, + Inputs = new[] { "x" }, + Outputs = new[] { "y" }, + Attributes = new Dictionary + { + ["auto_pad"] = "VALID", + ["kernel_shape"] = new[] { 2, 2 }, + ["strides"] = new[] { 2, 2 } + } + }; + + var result = node.Execute(graph); + Assert.Equal(OpStatus.Success, result.Status); + var y = (Tensor)result.Outputs[0]; + var expected = Tensor.MaxPool2D(x, new[] { 2, 2 }, MathOps.PadType.Valid, strides: new[] { 2, 2 }); + Assert.Equal(expected, y); + } + + [Fact] + public void Unsqueeze_UsesAxesAttribute_ForOpset12() + { + var graph = CreateGraph(opsetVersion: 12); + graph.Inputs["x"] = DenseTensor.OfValues(new int[] { 7, 8 }); + + var node = new Node + { + Name = "unsqueeze", + Op = OpType.Unsqueeze, + Inputs = new[] { "x" }, + Outputs = new[] { "y" }, + Attributes = new Dictionary { ["axes"] = new[] { 0 } } + }; + + var result = node.Execute(graph); + Assert.Equal(OpStatus.Success, result.Status); + var y = (Tensor)result.Outputs[0]; + Assert.Equal(new[] { 1, 2 }, y.Dimensions.ToArray()); + Assert.Equal(7, y[0, 0]); + } + + [Fact] + public void ReduceMean_UsesAxesAttribute_ForOpset12() + { + var graph = CreateGraph(opsetVersion: 12); + graph.Inputs["x"] = DenseTensor.OfValues(new float[2, 2] { { 1f, 2f }, { 3f, 4f } }); + + var node = new Node + { + Name = "reducesum", + Op = OpType.ReduceMean, + Inputs = new[] { "x" }, + Outputs = new[] { "y" }, + Attributes = new Dictionary + { + ["axes"] = new[] { 1 }, + ["keepdims"] = 0 + } + }; + + var result = node.Execute(graph); + Assert.Equal(OpStatus.Success, result.Status); + var y = (Tensor)result.Outputs[0]; + Assert.Equal(new[] { 2 }, y.Dimensions.ToArray()); + Assert.Equal(1.5f, y[0], 5); + } +} diff --git a/tests/Lokad.Onnx.Backend.Tests/OnnxRuntime.cs b/tests/Lokad.Onnx.Backend.Tests/OnnxRuntime.cs index 4532e95..294da98 100644 --- a/tests/Lokad.Onnx.Backend.Tests/OnnxRuntime.cs +++ b/tests/Lokad.Onnx.Backend.Tests/OnnxRuntime.cs @@ -1,34 +1,28 @@ -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Linq; -using Microsoft.ML.OnnxRuntime; -using Microsoft.ML.OnnxRuntime.Tensors; -using MSTensors=Microsoft.ML.OnnxRuntime.Tensors; - namespace Lokad.Onnx.Backend.Tests { internal class OnnxRuntime { - - public static float[] MnistInfer(string filepath) + public static Tensor MnistInfer(string filepath) { - string modelPath = Path.Combine("models", "mnist-8.onnx"); - using var session = new InferenceSession(modelPath); - var inputMeta = session.InputMetadata; - var container = new List(); - var tensor = Images.LoadMnistImageFromFile(filepath).ToTensor(); - container.Add(NamedOnnxValue.CreateFromTensor(inputMeta.Keys.First(), tensor)); - using var results = session.Run(container); - return results.First().AsTensor().ToArray(); - } + var modelPath = Path.Combine(AppContext.BaseDirectory, "models", "mnist-8.onnx"); + var modelBuffer = File.ReadAllBytes(modelPath); + using var session = new Microsoft.ML.OnnxRuntime.InferenceSession(modelBuffer); - public static float[] Softmax(MSTensors.Tensor output) - { - float sum = output.Sum(x => (float)Math.Exp(x)); - IEnumerable softmax = output.Select(x => (float)Math.Exp(x) / sum); - return softmax.ToArray(); + var input = (Tensor)Data.GetInputTensorsFromFileArgs(new[] { filepath + "::mnist" })!.First(); + var name = session.InputMetadata.Keys.First(); + var ortTensor = new Microsoft.ML.OnnxRuntime.Tensors.DenseTensor(input.ToArray(), input.Dimensions); + var container = new System.Collections.Generic.List + { + Microsoft.ML.OnnxRuntime.NamedOnnxValue.CreateFromTensor(name, ortTensor) + }; + + using var results = session.Run(container); + var t = results.First().AsTensor(); + return new DenseTensor(t.ToArray(), t.Dimensions.ToArray()); } } } diff --git a/tests/Lokad.Onnx.Backend.Tests/OpTests.cs b/tests/Lokad.Onnx.Backend.Tests/OpTests.cs deleted file mode 100644 index 2aeed32..0000000 --- a/tests/Lokad.Onnx.Backend.Tests/OpTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using CPU = Lokad.Onnx.CPUExecutionProvider; - -namespace Lokad.Onnx.Backend.Tests -{ - public class OpTests - { - [Fact] - public void CanReshape() - { - var X = DenseTensor.Ones(2, 3, 4); - var s = DenseTensor.OfValues(new long[] { 4, 2, 3 }); - var r = CPU.Reshape(X, s); - Assert.Equal(OpStatus.Success, r.Status); - Assert.Equal(r.Outputs![0].Dims, new int[3] { 4, 2, 3 }); - - s = DenseTensor.OfValues(new long[] { -1, 2, 3, 4 }); - r = CPU.Reshape(X, s); - Assert.Equal(OpStatus.Success, r.Status); - Assert.Equal(r.Outputs![0].Dims, new int[4] { 1, 2, 3, 4 }); - - r = CPU.Reshape((ITensor) X, null); - Assert.Equal(OpStatus.Failure, r.Status); - - Assert.Throws(() => CPU.Reshape((ITensor)X, DenseTensor.OfValues(new long[,] { { 2, 2 }, { 2, 1 } }))); - } - - [Fact] - public void CanBroadcast() - { - var a = new DenseTensor(new[] { 256, 256, 3, }); - var b = new DenseTensor(new[] { 3 }); - //var c = a.Add(b); - var r = Tensor.Broadcast(a, b); - Assert.NotNull(r); - var ba = (Tensor) r[0]; - var bb = (Tensor)r[1]; - Assert.NotEmpty(r); - - var c = new DenseTensor(new[] { 22, 3 }); - r = Tensor.Broadcast(a, c); - - r = Tensor.Broadcast(a, new DenseTensor(new[] { 256, 3 })); - Assert.NotEmpty(r); - r = Tensor.Broadcast(a, new DenseTensor(new[] { 1, 256, 3 })); - Assert.NotEmpty(r); - r = Tensor.Broadcast(a, new DenseTensor(new[] { 256, 1 })); - Assert.NotEmpty(r); - } - - [Fact] - public void CanGetShape() - { - var t = DenseTensor.OfShape(3, 4, 5); - var o = CPU.Shape(t, 1); - } - - } -} diff --git a/tests/Lokad.Onnx.Backend.Tests/TensorProtoConversionTests.cs b/tests/Lokad.Onnx.Backend.Tests/TensorProtoConversionTests.cs new file mode 100644 index 0000000..f6ed0cf --- /dev/null +++ b/tests/Lokad.Onnx.Backend.Tests/TensorProtoConversionTests.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Linq; + +namespace Lokad.Onnx.Backend.Tests; + +public class TensorProtoConversionTests +{ + [Fact] + public void CanConvertMnistInitializers() + { + var modelPath = Path.Combine(AppContext.BaseDirectory, "models", "mnist-8.onnx"); + byte[] buffer; + using (var stream = new FileStream(modelPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + buffer = new byte[stream.Length]; + stream.ReadExactly(buffer); + } + var model = Model.Parse(buffer); + Assert.NotNull(model); + + var candidates = model!.Graph.Initializer + .Where(tp => tp.DataType == (int)TensorElementType.Float + || tp.DataType == (int)TensorElementType.Double + || tp.DataType == (int)TensorElementType.Int32 + || tp.DataType == (int)TensorElementType.Int64) + .ToArray(); + Assert.NotEmpty(candidates); + + foreach (var tp in candidates) + { + var tensor = tp.ToTensor(); + Assert.Equal(tp.Name, tensor.Name); + Assert.Equal(tp.Dims.Select(d => (int)d).ToArray(), tensor.Dims); + Assert.Equal((TensorElementType)tp.DataType, tensor.ElementType); + + var data = (Array)tp.GetTensorData(); + Assert.Equal(data.Length, (int)tensor.Length); + if (data.Length == 0) + { + continue; + } + + switch ((TensorElementType)tp.DataType) + { + case TensorElementType.Float: + { + var arr = (float[])data; + var first = (float)tensor.GetValue(0); + var last = (float)tensor.GetValue(arr.Length - 1); + Assert.Equal(arr[0], first, 5); + Assert.Equal(arr[^1], last, 5); + break; + } + case TensorElementType.Double: + { + var arr = (double[])data; + var first = (double)tensor.GetValue(0); + var last = (double)tensor.GetValue(arr.Length - 1); + Assert.Equal(arr[0], first, 10); + Assert.Equal(arr[^1], last, 10); + break; + } + case TensorElementType.Int32: + { + var arr = (int[])data; + var first = (int)tensor.GetValue(0); + var last = (int)tensor.GetValue(arr.Length - 1); + Assert.Equal(arr[0], first); + Assert.Equal(arr[^1], last); + break; + } + case TensorElementType.Int64: + { + var arr = (long[])data; + var first = (long)tensor.GetValue(0); + var last = (long)tensor.GetValue(arr.Length - 1); + Assert.Equal(arr[0], first); + Assert.Equal(arr[^1], last); + break; + } + } + } + } +} diff --git a/tests/Lokad.Onnx.Tensors.Tests/Float16Tests.cs b/tests/Lokad.Onnx.Tensors.Tests/Float16Tests.cs new file mode 100644 index 0000000..92d2cae --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/Float16Tests.cs @@ -0,0 +1,42 @@ +using System; + +namespace Lokad.Onnx.Tensors.Tests; + +public class Float16Tests +{ + [Fact] + public void Float16_Roundtrip_And_Compare() + { + var values = new float[] { -2.5f, -1f, 0f, 0.5f, 1f, 2.25f, 10f }; + foreach (var value in values) + { + var f16 = (Float16)value; + var back = (float)f16; + Assert.True(Math.Abs(back - value) < 0.01f); + } + + var zero = Float16.Zero; + var one = Float16.One; + Assert.True(zero < one); + Assert.True(one > zero); + Assert.True(one == Float16.One); + } + + [Fact] + public void BFloat16_Roundtrip_And_Compare() + { + var values = new float[] { -3f, -1f, 0f, 0.25f, 1f, 3.5f, 12f }; + foreach (var value in values) + { + var bf = (BFloat16)value; + var back = (float)bf; + Assert.True(Math.Abs(back - value) < 0.01f); + } + + var zero = BFloat16.Zero; + var one = BFloat16.One; + Assert.True(zero < one); + Assert.True(one > zero); + Assert.True(one == BFloat16.One); + } +} diff --git a/tests/Lokad.Onnx.Tensors.Tests/Lokad.Onnx.Tensors.Tests.csproj b/tests/Lokad.Onnx.Tensors.Tests/Lokad.Onnx.Tensors.Tests.csproj index 130d07b..f1dde9e 100644 --- a/tests/Lokad.Onnx.Tensors.Tests/Lokad.Onnx.Tensors.Tests.csproj +++ b/tests/Lokad.Onnx.Tensors.Tests/Lokad.Onnx.Tensors.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable false @@ -26,3 +26,4 @@ + diff --git a/tests/Lokad.Onnx.Tensors.Tests/MathTests.cs b/tests/Lokad.Onnx.Tensors.Tests/MathTests.cs deleted file mode 100644 index a3b13eb..0000000 --- a/tests/Lokad.Onnx.Tensors.Tests/MathTests.cs +++ /dev/null @@ -1,202 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security.Cryptography; - -namespace Lokad.Onnx.Tensors.Tests; - -public class MathTests -{ - [Fact] - public void CanAdd() - { - var a = new DenseTensor(new[] { 256, 256, 3, }); - var b = a.Clone(); - - a[0, 0, 1] = 1; - b[0, 0, 1] = 1; - var c = Tensor.Add(a, b); - Assert.Equal(2, c[0, 0, 1]); - Assert.Equal(0, c[0, 0, 0]); - - var a1 = Tensor.Ones(16, 1, 1); - var b1 = Tensor.Ones(1, 1, 28, 28); - Assert.True(Tensor.Broadcast(a1, b1, out var a2, out var b2)); - var d = Tensor.Add(a2, b2); - - Assert.Equal(d.Dimensions, new int[] { 1, 16, 28, 28 }); - Assert.Equal(2, d[0, 15, 27, 27]); - - var a3 = Tensor.Ones(3, 4, 5); - var b3 = Tensor.Ones(5); - Assert.True(Tensor.Broadcast(a3, b3, out var a4, out var b4)); - Assert.Equal(a4.Dimensions, new int[] { 3, 4, 5 }); - var e = Tensor.Add(a4, b4); - - } - - [Fact] - public void CanVectorizedAdd() - { - var a = Tensor.Rand(8, 8, 8, 8); - var b = Tensor.Rand(8, 8, 8, 8); - var c = Tensor.Add(a, b); - } - - [Fact] - public unsafe void CanMatMul2D() - { - var a = Tensor.Ones(9, 5, 7, 4); - var b = Tensor.Ones(9, 5, 4, 3); - var a0 = a[0, 0, ..]; - Assert.Equal(28, a0.Length); - Assert.Equal(2, a0.Rank); - var b0 = b[0, 0, ..]; - Assert.Equal(12, b0.Length); - Assert.Equal(2, b0.Rank); - var c = Tensor.MatMul2D(a0, b0); - Assert.Equal(2, c.Rank); - Assert.Equal(21, c.Length); - - a = new DenseTensor(new[] { 1, 0, 0, 1 }, new[] { 2, 2, }); - b = new DenseTensor(new[] { 4, 1, 2, 2 }, new[] { 2, 2, }); - c = Tensor.MatMul2D(a, b); - //Assert.Equal(1, c[0, 1]); - - a = Tensor.Arange(0, 2 * 2 * 4).Reshape(2, 2, 4); - b = Tensor.Arange(0, 2 * 2 * 4).Reshape(2, 4, 2); - c = new DenseTensor(new int[] { 2, 2, 2 }); - var la = a.Dimensions[^2..]; - var di = a.GetDimensionsIterator(0..^2); - foreach (var _ in di) - { - Assert.Equal(2, a[di[..]].Rank); - Assert.Equal(2, b[di[..]].Rank); - c[di[..]] = Tensor.MatMul2D(a[di[..]], b[di[..]]); - Assert.Equal(c[di[..]], Tensor.MatMul2D_managed(a[di[..]], b[di[..]])); - } - Assert.Equal(98, c[0, 1, 1]); - } - - [Fact] - public void CanMatMul() - { - var a = Tensor.Ones(1, 1, 5, 6); - var b = Tensor.Ones(3, 6, 7); - var c = Tensor.MatMul(a, b); - Assert.Equal(new int[] { 1, 3, 5, 7 }, c.Dimensions.ToArray()); - a = Tensor.Ones(2, 3, 5, 6); - b = Tensor.Ones(3, 6, 7); - c = Tensor.MatMul(a, b); - Assert.Equal(new int[] { 2, 3, 5, 7 }, c.Dimensions.ToArray()); - a = Tensor.Ones(4, 1, 5, 6); - b = Tensor.Ones(4, 2, 6, 7); - c = Tensor.MatMul(a, b); - Assert.Equal(new int[] { 4, 2, 5, 7 }, c.Dimensions.ToArray()); - a = Tensor.Arange(0, 2 * 2 * 4).Reshape(2, 2, 4); - b = Tensor.Arange(0, 2 * 2 * 4).Reshape(2, 4, 2); - c = Tensor.MatMul(a, b); - Assert.Equal(98, c[0, 1, 1]); - } - - [Fact] - public void CanVectorizedMatMul() - { - var a = Tensor.Ones(1, 1, 5, 6); - var b = Tensor.Ones(3, 6, 7); - var c = Tensor.MatMul(a, b); - Assert.Equal(new int[] { 1, 3, 5, 7 }, c.Dimensions.ToArray()); - a = Tensor.Ones(2, 3, 5, 6); - b = Tensor.Ones(3, 6, 7); - c = Tensor.MatMul(a, b); - Assert.Equal(new int[] { 2, 3, 5, 7 }, c.Dimensions.ToArray()); - a = Tensor.Ones(4, 1, 5, 6); - b = Tensor.Ones(4, 2, 6, 7); - c = Tensor.MatMul(a, b); - Assert.Equal(new int[] { 4, 2, 5, 7 }, c.Dimensions.ToArray()); - a = Tensor.Arange(0.0f, 2.0f * 2 * 4).Reshape(2, 2, 4); - b = Tensor.Arange(0.0f, 2.0f * 4 * 8).Reshape(2, 4, 8); - c = Tensor.MatMul(a, b); - var a_ = Tensor.Arange(0, 2 * 2 * 4).Reshape(2, 2, 4); - var b_ = Tensor.Arange(0, 2 * 4 * 8).Reshape(2, 4, 8); - var c_ = Tensor.MatMul(a_, b_); - Assert.Equal(326, c[0, 1, 1]); - } - // Based on https://github.com/onnx/onnx/blob/main/docs/Operators.md#Conv - [Fact] - public void CanConv2D() - { - var X = Tensor.Arange(0.0f, 25.0f).Reshape(1, 1, 5, 5); - var W = Tensor.Ones(1, 1, 3, 3); - var Y = Tensor.Conv2D(X, W, 1, MathOps.PadType.Value, padvalue: 1); - var Ye = DenseTensor.OfValues(new float[1, 1, 5, 5] { { { - {12.0f, 21.0f, 27.0f, 33.0f, 24.0f}, {33.0f, 54.0f, 63.0f, 72.0f, 51.0f}, {63.0f, 99.0f, 108.0f, 117.0f, 81.0f}, {93.0f, 144.0f, 153.0f, 162.0f, 111.0f}, {72.0f, 111.0f, 117.0f, 123.0f, 84.0f} - } } }); - Assert.Equal(Ye, Y); - - - Y = Tensor.Conv2D(X, W, 1, MathOps.PadType.Valid); - Ye = DenseTensor.OfValues(new float[1, 1, 3, 3] { { { - {54.0f, 63.0f, 72.0f}, {99.0f, 108.0f, 117.0f}, {144.0f, 153.0f, 162.0f} - } } }); - Assert.Equal(Y.Dimensions.ToArray(), W.Dimensions.ToArray()); - Assert.Equal(Ye, Y); - - X = Tensor.Arange(0.0f, 35.0f).Reshape(1, 1, 7, 5); - Y = Tensor.Conv2D(X, W, 1, MathOps.PadType.Value, strides: new int[] { 2, 2 }, padvalue: 1); - Ye = DenseTensor.OfValues(new float[1, 1, 4, 3] { { { - { 12.0f, 27.0f, 24.0f }, { 63.0f, 108.0f, 81.0f },{ 123.0f, 198.0f, 141.0f }, { 112.0f, 177.0f, 124.0f }, - } } }); - Assert.Equal(Ye, Y); - } - - [Fact] - public void CanMaxPool2D() - { - var X = DenseTensor.OfValues(new float[1, 1, 4, 4] { { { - {12.0f, 20.0f, 30.0f, 0.0f }, { 8.0f, 12.0f, 2.0f, 0.0f }, { 34.0f, 70.0f, 37.0f, 4.0f }, { 112.0f, 100.0f, 25.0f, 12.0f } } } }); - var Y = Tensor.MaxPool2D(X, new int[] { 2, 2 }, MathOps.PadType.Value, padvalue: 0, strides: new int[2] { 2, 2 }); - Assert.Equal(DenseTensor.OfValues(new float[2, 2] { { 20.0f, 30.0f }, { 112.0f, 37.0f } }), Y); - Y = Tensor.MaxPool2D(X, new int[] { 2, 2 }, MathOps.PadType.Value, padvalue: 0, strides: new int[2] { 1, 1 }); - Assert.Equal(DenseTensor.OfValues(new float[3, 3] { { 20.0f, 30.0f, 30.0f }, { 70.0f, 70.0f, 37.0f }, { 112.0f, 100.0f, 37.0f } }), Y); - var N = DenseTensor.OfValues(new int[1, 1, 4, 4] - {{{ - {1,1,2,4 }, {5, 6, 7, 8 }, {3, 2, 1, 0 }, { 1, 2, 3, 4} - }}}); - var Y2 = Tensor.MaxPool2D(N, new int[] { 2, 2 }); - Assert.Equal(DenseTensor.OfValues(new int[2, 2] { { 6, 8 }, { 3, 4 } }), Y2); - } - - [Fact] - public void CanReduceMean() - { - var data = new int[3, 2, 2] { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } }, { { 9, 10 }, { 11, 12 } } }; - var axes = new int[1] { 1 }; - var output = Tensor.ReduceSum(data.ToTensor(), axes.ToTensor()); - Assert.NotNull(output); - } - - [Fact] - public void CanSoftmax() - { - var data = Tensor.Arange(0, (3 * 4 * 5)).ToArray().Convert().ToTensor().Reshape(3, 4, 5); - var o = Tensor.Softmax(data, 0); - } - - - /* - public unsafe void CanTranspose() - { - var t = Tensor.Rand(5, 6).ToDenseTensor(); - var mt = MathOps.TransposeMatrix((float*)t.Buffer.Pin().Pointer, 5, 6); - for (int i = 0; i < 5; i++) - { - for (int j = 0; j < 6; j++) - { - Assert.Equal(t[i, j], mt[j, i]); - } - } - //Marshal.FreeCoTaskMem(new IntPtr(mt)); - } - */ -} \ No newline at end of file diff --git a/tests/Lokad.Onnx.Tensors.Tests/ShapeTests.cs b/tests/Lokad.Onnx.Tensors.Tests/ShapeTests.cs deleted file mode 100644 index 4e513b5..0000000 --- a/tests/Lokad.Onnx.Tensors.Tests/ShapeTests.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Lokad.Onnx.Tensors.Tests -{ - public class ShapeTests - { - [Fact] - public void CanPadLeft() - { - var a = new DenseTensor(new[] { 256, 256, 3, }); - var b = new DenseTensor(new[] { 3, 1 }); - b[0, 0] = 1; - b[1, 0] = 2; - b[2, 0] = 3; - var pb = b.PadLeft(); - Assert.Equal(3, pb.Rank); - Assert.Equal(2, pb[0,1,0]); - - } - - [Fact] - public void CanBroadcastShape() - { - Assert.True(Tensor.BroadcastShape(new int[] { 1, 1 }, new int[] { 2 }, out var b)); - Assert.Equal(b, new int[] { 1, 2 }); - - Assert.True(Tensor.BroadcastShape(new int[] { 4, 1, 1 }, new int[] { 2 }, out b)); - Assert.Equal(b, new int[] { 4, 1, 2 }); - - Assert.True(Tensor.BroadcastShape(new int[] { 1, 2 }, new int[] { 2 }, out b)); - Assert.Equal(b, new int[] { 1, 2 }); - - Assert.False(Tensor.BroadcastShape(new int[] { 1, 3 }, new int[] { 2 }, out b)); - Assert.False(Tensor.BroadcastShape(new int[] { 4, 3, 3 }, new int[] { 2 }, out b)); - - Assert.True(Tensor.BroadcastShape(new int[] { 2, 1 }, new int[] { 2 }, out b)); - Assert.Equal(b.Append(5).Append(6), new int[] { 2, 2, 5, 6 }); - - Assert.True(Tensor.BroadcastShape(new int[] { 2, 1 }, new int[] { 2, 3 }, out b)); - Assert.Equal(b.Append(5).Append(6), new int[] { 2, 3, 5, 6 }); - } - [Fact] - public void CanBroadcast() - { - Tensor a = new DenseTensor(new[] { 256, 256, 3, }); - Tensor b = new DenseTensor(new[] { 3, 1 }); - b[0,0] = 1; - b[1, 0] = 2; - b[2, 0] = 3; - var bc1 = b.BroadcastDim(1, 255); - Assert.Equal(1, bc1[0, 204]); - Assert.Equal(2, bc1[1, 254]); - Assert.Equal(3, bc1[2, 164]); - Assert.Throws(() => bc1[3, 256]); - - var ba = Tensor.Broadcast(Tensor.Ones(1, 2), Tensor.Ones(3, 1)); - Assert.Equal(2, ba.Length); - } - - [Fact] - public void CanIterateDims() - { - var a = new DenseTensor(new[] { 256, 212, 3, }); - var di = a.GetDimensionsIterator(0..^1); - di = a.GetDimensionsIterator(); - while (di.Next() != null) - { - var i = di.Index; - } - - } - - [Fact] - public void CanReshape() - { - var X = DenseTensor.Ones(2, 3, 4); - - var s = DenseTensor.OfValues(new long[] {4,2,3}); - var Y = Tensor.Reshape(X, s); - Assert.Equal(new int[] {4,2,3}, Y.Dimensions.ToArray()); - - s = DenseTensor.OfValues(new long[] { 2, 4, 3 }); - Y = Tensor.Reshape(X, s); - Assert.Equal(new int[] { 2, 4, 3 }, Y.Dimensions.ToArray()); - - s = DenseTensor.OfValues(new long[] { 2, 12 }); - Y = Tensor.Reshape(X, s); - Assert.Equal(new int[] { 2, 12 }, Y.Dimensions.ToArray()); - - s = DenseTensor.OfValues(new long[] { 2, -1, 2}); - Y = Tensor.Reshape(X, s); - Assert.Equal(new int[] { 2, 6, 2 }, Y.Dimensions.ToArray()); - - s = DenseTensor.OfValues(new long[] { -1, 2, 3, 4 }); - Y = Tensor.Reshape(X, s); - Assert.Equal(new int[] { 1, 2, 3, 4 }, Y.Dimensions.ToArray()); - - s = DenseTensor.OfValues(new long[] { 2, 0, 1, -1 }); - Y = Tensor.Reshape(X, s); - Assert.Equal(new int[] { 2, 3, 1, 4 }, Y.Dimensions.ToArray()); - } - - [Fact] - public void CanTranspose() - { - var X = DenseTensor.Ones(2, 3, 4); - var tX = Tensor.Transpose(X); - Assert.Equal(2, tX.Dimensions[2]); - tX = Tensor.Transpose(X, new int[] { 2, 0, 1 }); - Assert.Equal(4, tX.Dimensions[0]); - Assert.Equal(3, tX.Dimensions[2]); - Assert.Throws(() => Tensor.Transpose(X, new int[] { 0, 0, 2 })); - Assert.Throws(() => Tensor.Transpose(X, new int[] { 0 })); - Assert.Throws(() => Tensor.Transpose(X, new int[] { 2, 6 })); - } - - [Fact] - public void CanConcat() - { - var x = DenseTensor.OfValues(new float[2, 3] { { 0.6580f, -1.0969f, -0.4614f }, { -0.1034f, -0.5790f, 0.149f } }); - var y = Tensor.Concat(x, x, 0); - Assert.NotNull(y); - y = Tensor.Concat(x, x, 1); - Assert.NotNull(y); - } - - [Fact] - public void CanUnsqueeze() - { - var X = (ITensor) DenseTensor.Ones(3, 4, 5); - var tX = X.Unsqueeze(new int[] { 1 }); - Assert.NotNull(tX); -; - } - } -} diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorBroadcastTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorBroadcastTests.cs new file mode 100644 index 0000000..a452763 --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorBroadcastTests.cs @@ -0,0 +1,61 @@ +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorBroadcastTests +{ + [Fact] + public void CanBroadcastShape() + { + Assert.True(Tensor.BroadcastShape(new int[] { 1, 1 }, new int[] { 2 }, out var b)); + Assert.Equal(b, new int[] { 1, 2 }); + + Assert.True(Tensor.BroadcastShape(new int[] { 4, 1, 1 }, new int[] { 2 }, out b)); + Assert.Equal(b, new int[] { 4, 1, 2 }); + + Assert.True(Tensor.BroadcastShape(new int[] { 1, 2 }, new int[] { 2 }, out b)); + Assert.Equal(b, new int[] { 1, 2 }); + + Assert.False(Tensor.BroadcastShape(new int[] { 1, 3 }, new int[] { 2 }, out b)); + Assert.False(Tensor.BroadcastShape(new int[] { 4, 3, 3 }, new int[] { 2 }, out b)); + + Assert.True(Tensor.BroadcastShape(new int[] { 2, 1 }, new int[] { 2 }, out b)); + Assert.Equal(b.Append(5).Append(6), new int[] { 2, 2, 5, 6 }); + + Assert.True(Tensor.BroadcastShape(new int[] { 2, 1 }, new int[] { 2, 3 }, out b)); + Assert.Equal(b.Append(5).Append(6), new int[] { 2, 3, 5, 6 }); + } + + [Fact] + public void CanBroadcast() + { + Tensor a = new DenseTensor(new[] { 256, 256, 3, }); + Tensor b = new DenseTensor(new[] { 3, 1 }); + b[0,0] = 1; + b[1, 0] = 2; + b[2, 0] = 3; + var bc1 = b.BroadcastDim(1, 255); + Assert.Equal(1, bc1[0, 204]); + Assert.Equal(2, bc1[1, 254]); + Assert.Equal(3, bc1[2, 164]); + Assert.Throws(() => bc1[3, 256]); + + var ba = Tensor.Broadcast(Tensor.Ones(1, 2), Tensor.Ones(3, 1)); + Assert.Equal(2, ba.Length); + } + + [Fact] + public void CanBroadcastLargeShapes() + { + var a = new DenseTensor(new[] { 256, 256, 3, }); + var c = new DenseTensor(new[] { 22, 3 }); + var r = Tensor.Broadcast(a, c); + Assert.NotNull(r); + + r = Tensor.Broadcast(a, new DenseTensor(new[] { 256, 3 })); + Assert.NotEmpty(r); + r = Tensor.Broadcast(a, new DenseTensor(new[] { 1, 256, 3 })); + Assert.NotEmpty(r); + r = Tensor.Broadcast(a, new DenseTensor(new[] { 256, 1 })); + Assert.NotEmpty(r); + } +} + diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorConcatUnsqueezeTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorConcatUnsqueezeTests.cs new file mode 100644 index 0000000..62e15e0 --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorConcatUnsqueezeTests.cs @@ -0,0 +1,23 @@ +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorConcatUnsqueezeTests +{ + [Fact] + public void CanConcat() + { + var x = DenseTensor.OfValues(new float[2, 3] { { 0.6580f, -1.0969f, -0.4614f }, { -0.1034f, -0.5790f, 0.149f } }); + var y = Tensor.Concat(x, x, 0); + Assert.NotNull(y); + y = Tensor.Concat(x, x, 1); + Assert.NotNull(y); + } + + [Fact] + public void CanUnsqueeze() + { + var X = (ITensor) DenseTensor.Ones(3, 4, 5); + var tX = X.Unsqueeze(new int[] { 1 }); + Assert.NotNull(tX); + } +} + diff --git a/tests/Lokad.Onnx.Tensors.Tests/CreationTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorCreationTests.cs similarity index 74% rename from tests/Lokad.Onnx.Tensors.Tests/CreationTests.cs rename to tests/Lokad.Onnx.Tensors.Tests/TensorCreationTests.cs index dbe09bb..9a58a85 100644 --- a/tests/Lokad.Onnx.Tensors.Tests/CreationTests.cs +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorCreationTests.cs @@ -1,6 +1,6 @@ -namespace Lokad.Onnx.Tensors.Tests; +namespace Lokad.Onnx.Tensors.Tests; -public class CreationTests +public class TensorCreationTests { [Fact] public void CanCreateARange() @@ -11,4 +11,3 @@ public void CanCreateARange() Assert.Equal(20, t2[6, 2]); } } - diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorIteratorTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorIteratorTests.cs new file mode 100644 index 0000000..f1433ca --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorIteratorTests.cs @@ -0,0 +1,17 @@ +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorIteratorTests +{ + [Fact] + public void CanIterateDims() + { + var a = new DenseTensor(new[] { 256, 212, 3, }); + var di = a.GetDimensionsIterator(0..^1); + di = a.GetDimensionsIterator(); + while (di.Next() != null) + { + var i = di.Index; + } + } +} + diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorOpsConvPoolTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsConvPoolTests.cs new file mode 100644 index 0000000..74a0bf3 --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsConvPoolTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; + +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorOpsConvPoolTests +{ + // Based on https://github.com/onnx/onnx/blob/main/docs/Operators.md#Conv + [Fact] + public void Conv2D_Float_MatchesReference() + { + var x = Tensor.Arange(0.0f, 25.0f).Reshape(1, 1, 5, 5); + var w = Tensor.Ones(1, 1, 3, 3); + var y = Tensor.Conv2D(x, w, 1, MathOps.PadType.Value, padvalue: 1); + var ye = DenseTensor.OfValues(new float[1, 1, 5, 5] { { { + {12.0f, 21.0f, 27.0f, 33.0f, 24.0f}, {33.0f, 54.0f, 63.0f, 72.0f, 51.0f}, {63.0f, 99.0f, 108.0f, 117.0f, 81.0f}, {93.0f, 144.0f, 153.0f, 162.0f, 111.0f}, {72.0f, 111.0f, 117.0f, 123.0f, 84.0f} + } } }); + Assert.Equal(ye, y); + + y = Tensor.Conv2D(x, w, 1, MathOps.PadType.Valid); + ye = DenseTensor.OfValues(new float[1, 1, 3, 3] { { { + {54.0f, 63.0f, 72.0f}, {99.0f, 108.0f, 117.0f}, {144.0f, 153.0f, 162.0f} + } } }); + Assert.Equal(y.Dimensions.ToArray(), w.Dimensions.ToArray()); + Assert.Equal(ye, y); + + x = Tensor.Arange(0.0f, 35.0f).Reshape(1, 1, 7, 5); + y = Tensor.Conv2D(x, w, 1, MathOps.PadType.Value, strides: new int[] { 2, 2 }, padvalue: 1); + ye = DenseTensor.OfValues(new float[1, 1, 4, 3] { { { + { 12.0f, 27.0f, 24.0f }, { 63.0f, 108.0f, 81.0f },{ 123.0f, 198.0f, 141.0f }, { 112.0f, 177.0f, 124.0f }, + } } }); + Assert.Equal(ye, y); + } + + [Fact] + public void Conv2D_Double_SmokeTest() + { + var x = DenseTensor.OfValues(new double[1, 1, 3, 3] + { { + { { 0.0, 1.0, 2.0 }, { 3.0, 4.0, 5.0 }, { 6.0, 7.0, 8.0 } } + } }); + var w = Tensor.Ones(1, 1, 2, 2); + var bias = Tensor.Zeros(1); + var y = Tensor.Conv2D(x, w, 1, MathOps.PadType.Valid, bias: bias); + + Assert.Equal(new[] { 1, 1, 2, 2 }, y.Dimensions.ToArray()); + Assert.Equal(8.0, y[0, 0, 0, 0], 10); + Assert.Equal(12.0, y[0, 0, 0, 1], 10); + } + + [Fact] + public void MaxPool2D_Float_Int_Double() + { + var x = DenseTensor.OfValues(new float[1, 1, 4, 4] { { { + {12.0f, 20.0f, 30.0f, 0.0f }, { 8.0f, 12.0f, 2.0f, 0.0f }, { 34.0f, 70.0f, 37.0f, 4.0f }, { 112.0f, 100.0f, 25.0f, 12.0f } } } }); + var y = Tensor.MaxPool2D(x, new int[] { 2, 2 }, MathOps.PadType.Value, padvalue: 0, strides: new int[2] { 2, 2 }); + Assert.Equal(DenseTensor.OfValues(new float[2, 2] { { 20.0f, 30.0f }, { 112.0f, 37.0f } }), y); + y = Tensor.MaxPool2D(x, new int[] { 2, 2 }, MathOps.PadType.Value, padvalue: 0, strides: new int[2] { 1, 1 }); + Assert.Equal(DenseTensor.OfValues(new float[3, 3] { { 20.0f, 30.0f, 30.0f }, { 70.0f, 70.0f, 37.0f }, { 112.0f, 100.0f, 37.0f } }), y); + + var n = DenseTensor.OfValues(new int[1, 1, 4, 4] + { { + { { 1, 1, 2, 4 }, { 5, 6, 7, 8 }, { 3, 2, 1, 0 }, { 1, 2, 3, 4 } } + } }); + var y2 = Tensor.MaxPool2D(n, new int[] { 2, 2 }); + Assert.Equal(DenseTensor.OfValues(new int[2, 2] { { 6, 8 }, { 3, 4 } }), y2); + + var d = DenseTensor.OfValues(new double[1, 1, 3, 3] { { { + { 1.0, 5.0, 2.0 }, { 4.0, 3.0, 6.0 }, { 7.0, 0.0, 8.0 } + } } }); + var yd = Tensor.MaxPool2D(d, new int[] { 2, 2 }); + Assert.Equal(new[] { 1, 1, 1, 1 }, yd.Dimensions.ToArray()); + Assert.Equal(5.0, yd[0, 0, 0, 0], 10); + } +} diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorOpsElementwiseTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsElementwiseTests.cs new file mode 100644 index 0000000..65424be --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsElementwiseTests.cs @@ -0,0 +1,114 @@ +using System; + +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorOpsElementwiseTests +{ + [Fact] + public void Add_Subtract_Multiply_Divide_IntTensorAndScalar() + { + var a = DenseTensor.OfValues(new int[,] { { 8, 6 }, { 4, 2 } }); + var b = DenseTensor.OfValues(new int[,] { { 1, 2 }, { 3, 4 } }); + + var add = Tensor.Add(a, b); + var sub = Tensor.Subtract(a, b); + var mul = Tensor.Multiply(a, b); + var div = Tensor.Divide(a, b); + + Assert.Equal(9, add[0, 0]); + Assert.Equal(6, add[1, 1]); + Assert.Equal(7, sub[0, 0]); + Assert.Equal(-2, sub[1, 1]); + Assert.Equal(8, mul[0, 0]); + Assert.Equal(8, mul[1, 1]); + Assert.Equal(8, div[0, 0]); + Assert.Equal(0, div[1, 1]); + + var addScalar = Tensor.Add(a, 3); + var subScalar = Tensor.Subtract(a, 3); + var mulScalar = Tensor.Multiply(a, 3); + var divScalar = Tensor.Divide(a, 2); + + Assert.Equal(11, addScalar[0, 0]); + Assert.Equal(-1, subScalar[1, 1]); + Assert.Equal(12, mulScalar[1, 0]); + Assert.Equal(3, divScalar[0, 1]); + } + + [Fact] + public void Add_Subtract_Multiply_Divide_FloatTensorAndScalar() + { + var a = DenseTensor.OfValues(new float[,] { { 1.5f, -2f }, { 3f, 4f } }); + var b = DenseTensor.OfValues(new float[,] { { 0.5f, 1f }, { -1f, 2f } }); + + var add = Tensor.Add(a, b); + var sub = Tensor.Subtract(a, b); + var mul = Tensor.Multiply(a, b); + var div = Tensor.Divide(a, b); + + Assert.Equal(2.0f, add[0, 0], 5); + Assert.Equal(-1.0f, add[0, 1], 5); + Assert.Equal(1.0f, sub[0, 0], 5); + Assert.Equal(-3.0f, sub[0, 1], 5); + Assert.Equal(0.75f, mul[0, 0], 5); + Assert.Equal(-2.0f, mul[0, 1], 5); + Assert.Equal(2.0f, div[1, 1], 5); + + var addScalar = Tensor.Add(a, 0.5f); + var subScalar = Tensor.Subtract(a, 1.0f); + var mulScalar = Tensor.Multiply(a, 2.0f); + var divScalar = Tensor.Divide(a, 2.0f); + + Assert.Equal(2.0f, addScalar[0, 0], 5); + Assert.Equal(3.0f, subScalar[1, 1], 5); + Assert.Equal(6.0f, mulScalar[1, 0], 5); + Assert.Equal(-1.0f, divScalar[0, 1], 5); + } + + [Fact] + public void Add_Subtract_Multiply_Divide_DoubleTensorAndScalar() + { + var a = DenseTensor.OfValues(new double[,] { { 2d, -4d }, { 6d, 8d } }); + var b = DenseTensor.OfValues(new double[,] { { 1d, 2d }, { 3d, 4d } }); + + var add = Tensor.Add(a, b); + var sub = Tensor.Subtract(a, b); + var mul = Tensor.Multiply(a, b); + var div = Tensor.Divide(a, b); + + Assert.Equal(3d, add[0, 0], 10); + Assert.Equal(-2d, add[0, 1], 10); + Assert.Equal(1d, sub[0, 0], 10); + Assert.Equal(-6d, sub[0, 1], 10); + Assert.Equal(2d, mul[0, 0], 10); + Assert.Equal(-8d, mul[0, 1], 10); + Assert.Equal(2d, div[1, 1], 10); + + var addScalar = Tensor.Add(a, 1d); + var subScalar = Tensor.Subtract(a, 2d); + var mulScalar = Tensor.Multiply(a, 2d); + var divScalar = Tensor.Divide(a, 4d); + + Assert.Equal(3d, addScalar[0, 0], 10); + Assert.Equal(6d, subScalar[1, 1], 10); + Assert.Equal(12d, mulScalar[1, 0], 10); + Assert.Equal(-1d, divScalar[0, 1], 10); + } + + [Fact] + public void Add_Subtract_Multiply_Divide_ByteTensor() + { + var a = DenseTensor.OfValues(new byte[,] { { 4, 8 }, { 12, 16 } }); + var b = DenseTensor.OfValues(new byte[,] { { 1, 2 }, { 3, 4 } }); + + var add = Tensor.Add(a, b); + var sub = Tensor.Subtract(a, b); + var mul = Tensor.Multiply(a, b); + var div = Tensor.Divide(a, b); + + Assert.Equal((byte)5, add[0, 0]); + Assert.Equal((byte)9, sub[1, 0]); + Assert.Equal((byte)16, mul[0, 1]); + Assert.Equal((byte)4, div[1, 1]); + } +} diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorOpsMatMul2DTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsMatMul2DTests.cs new file mode 100644 index 0000000..9c67dd4 --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsMatMul2DTests.cs @@ -0,0 +1,164 @@ +using System; +using System.Linq; +using System.Runtime.Intrinsics.X86; + +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorOpsMatMul2DTests +{ + [Fact] + public void MatMul2D_Int_ComputesExpected() + { + var a = DenseTensor.OfValues(new int[,] { { 1, 2 }, { 3, 4 } }); + var b = DenseTensor.OfValues(new int[,] { { 5, 6 }, { 7, 8 } }); + + var c = Tensor.MatMul2D(a, b); + + Assert.Equal(new[] { 2, 2 }, c.Dimensions.ToArray()); + Assert.Equal(19, c[0, 0]); + Assert.Equal(22, c[0, 1]); + Assert.Equal(43, c[1, 0]); + Assert.Equal(50, c[1, 1]); + } + + [Fact] + public void MatMul2D_Float_ComputesExpected() + { + var a = DenseTensor.OfValues(new float[,] { { 1f, 2f }, { 3f, 4f } }); + var b = DenseTensor.OfValues(new float[,] { { 5f, 6f }, { 7f, 8f } }); + + var c = Tensor.MatMul2D(a, b); + + Assert.Equal(new[] { 2, 2 }, c.Dimensions.ToArray()); + Assert.Equal(19f, c[0, 0], 5); + Assert.Equal(22f, c[0, 1], 5); + Assert.Equal(43f, c[1, 0], 5); + Assert.Equal(50f, c[1, 1], 5); + } + + [Fact] + public void MatMul2D_Double_ComputesExpected() + { + var a = DenseTensor.OfValues(new double[,] { { 1d, 2d }, { 3d, 4d } }); + var b = DenseTensor.OfValues(new double[,] { { 5d, 6d }, { 7d, 8d } }); + + var c = Tensor.MatMul2D(a, b); + + Assert.Equal(new[] { 2, 2 }, c.Dimensions.ToArray()); + Assert.Equal(19d, c[0, 0], 10); + Assert.Equal(22d, c[0, 1], 10); + Assert.Equal(43d, c[1, 0], 10); + Assert.Equal(50d, c[1, 1], 10); + } + + [Fact] + public void MatMul2D_Int_MatchesManagedOnSlices() + { + var a = Tensor.Arange(0, 2 * 2 * 3).Reshape(2, 2, 3); + var b = Tensor.Arange(0, 2 * 3 * 2).Reshape(2, 3, 2); + + var sliceA = a[1, ..]; + var sliceB = b[1, ..]; + + var expected = Tensor.MatMul2D_managed(sliceA, sliceB); + var actual = Tensor.MatMul2D(sliceA, sliceB); + + Assert.Equal(expected, actual); + } + + [Fact] + public void MatMul2D_Float_MatchesManaged() + { + var a = DenseTensor.OfValues(new float[,] { { 0.5f, 1.5f, -2f }, { 3f, 0f, 1f } }); + var b = DenseTensor.OfValues(new float[,] { { 2f, 1f }, { -1f, 0.5f }, { 4f, -3f } }); + + var expected = Tensor.MatMul2D_managed(a, b); + var actual = Tensor.MatMul2D(a, b); + + Assert.Equal(expected[0, 0], actual[0, 0], 5); + Assert.Equal(expected[0, 1], actual[0, 1], 5); + Assert.Equal(expected[1, 0], actual[1, 0], 5); + Assert.Equal(expected[1, 1], actual[1, 1], 5); + } + + [Fact] + public void MatMul2D_Float_RespectsHardwareFlags() + { + var a = DenseTensor.OfValues(new float[,] { { 1f, 2f }, { 3f, 4f } }); + var b = DenseTensor.OfValues(new float[,] { { 5f, 6f }, { 7f, 8f } }); + + using var _ = new HardwareConfigScope(HardwareConfig.UseSimd, HardwareConfig.UseIntrinsics); + Assert.Equal(HardwareIntrinsics.IsX86FmaSupported, HardwareConfig.UseIntrinsics); + + using var baseline = new HardwareConfigScope(useSimd: false, useIntrinsics: false); + var scalar = Tensor.MatMul2D(a, b); + + using var simdOnly = new HardwareConfigScope(useSimd: true, useIntrinsics: false); + var simd = Tensor.MatMul2D(a, b); + Assert.Equal(scalar, simd); + + if (Fma.IsSupported) + { + using var intrinsics = new HardwareConfigScope(useSimd: true, useIntrinsics: true); + var intrinsicsResult = Tensor.MatMul2D(a, b); + Assert.Equal(scalar, intrinsicsResult); + } + } + + [Fact] + public void MatMul2D_DenseVsReversedStride_MatchesManaged() + { + var ax = new float[,] { { 1f, 2f, 3f }, { 4f, 5f, 6f } }; + var by = new float[,] { { 7f, 8f }, { 9f, 10f }, { 11f, 12f } }; + + var revX = ax.ToTensor(reverseStride: true); + var revY = by.ToTensor(reverseStride: true); + + var denseX = revX.ToDenseTensor(); + var denseY = revY.ToDenseTensor(); + var expected = Tensor.MatMul2D(denseX, denseY); + var reversedResult = Tensor.MatMul2D(revX, revY); + + Assert.Equal(expected[0, 0], reversedResult[0, 0], 5); + Assert.Equal(expected[1, 1], reversedResult[1, 1], 5); + } + + [Fact] + public void MatMul2D_IntrinsicsBranch_LargeK_MatchesManaged() + { + if (!HardwareIntrinsics.IsX86FmaSupported) + { + return; + } + + var x = Tensor.Ones(2, 32); + var y = Tensor.Ones(32, 32); + + using var intrinsics = new HardwareConfigScope(useSimd: true, useIntrinsics: true); + Tensor expected; + using (new HardwareConfigScope(useSimd: false, useIntrinsics: false)) + { + expected = Tensor.MatMul2D(x, y); + } + var actual = Tensor.MatMul2D(x, y); + + Assert.Equal(expected[0, 0], actual[0, 0], 5); + Assert.Equal(expected[1, 31], actual[1, 31], 5); + } + + [Fact] + public void MatMul2D_RejectsRankMismatch() + { + Assert.Throws(() => Tensor.MatMul2D(Tensor.Ones(2, 2, 2), Tensor.Ones(2, 2))); + Assert.Throws(() => Tensor.MatMul2D(Tensor.Ones(2, 2), Tensor.Ones(2))); + Assert.Throws(() => Tensor.MatMul2D(Tensor.Ones(2, 2, 2), Tensor.Ones(2, 2))); + } + + [Fact] + public void MatMul2D_RejectsDimensionMismatch() + { + Assert.Throws(() => Tensor.MatMul2D(Tensor.Ones(2, 3), Tensor.Ones(2, 2))); + Assert.Throws(() => Tensor.MatMul2D(Tensor.Ones(3, 2), Tensor.Ones(3, 1))); + Assert.Throws(() => Tensor.MatMul2D(Tensor.Ones(1, 4), Tensor.Ones(3, 2))); + } +} diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorOpsMatMulTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsMatMulTests.cs new file mode 100644 index 0000000..d1af9cb --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsMatMulTests.cs @@ -0,0 +1,74 @@ +using System; +using System.Linq; + +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorOpsMatMulTests +{ + [Fact] + public void MatMul_Int_BroadcastsBatch() + { + var a = Tensor.Ones(1, 1, 5, 6); + var b = Tensor.Ones(3, 6, 7); + var c = Tensor.MatMul(a, b); + Assert.Equal(new int[] { 1, 3, 5, 7 }, c.Dimensions.ToArray()); + + a = Tensor.Ones(2, 3, 5, 6); + b = Tensor.Ones(3, 6, 7); + c = Tensor.MatMul(a, b); + Assert.Equal(new int[] { 2, 3, 5, 7 }, c.Dimensions.ToArray()); + + a = Tensor.Ones(4, 1, 5, 6); + b = Tensor.Ones(4, 2, 6, 7); + c = Tensor.MatMul(a, b); + Assert.Equal(new int[] { 4, 2, 5, 7 }, c.Dimensions.ToArray()); + + a = Tensor.Arange(0, 2 * 2 * 4).Reshape(2, 2, 4); + b = Tensor.Arange(0, 2 * 2 * 4).Reshape(2, 4, 2); + c = Tensor.MatMul(a, b); + Assert.Equal(98, c[0, 1, 1]); + } + + [Fact] + public void MatMul_Float_BroadcastsBatch() + { + var a = Tensor.Ones(1, 1, 5, 6); + var b = Tensor.Ones(3, 6, 7); + var c = Tensor.MatMul(a, b); + Assert.Equal(new int[] { 1, 3, 5, 7 }, c.Dimensions.ToArray()); + + a = Tensor.Ones(2, 3, 5, 6); + b = Tensor.Ones(3, 6, 7); + c = Tensor.MatMul(a, b); + Assert.Equal(new int[] { 2, 3, 5, 7 }, c.Dimensions.ToArray()); + + a = Tensor.Ones(4, 1, 5, 6); + b = Tensor.Ones(4, 2, 6, 7); + c = Tensor.MatMul(a, b); + Assert.Equal(new int[] { 4, 2, 5, 7 }, c.Dimensions.ToArray()); + + a = Tensor.Arange(0.0f, 2.0f * 2 * 4).Reshape(2, 2, 4); + b = Tensor.Arange(0.0f, 2.0f * 4 * 8).Reshape(2, 4, 8); + c = Tensor.MatMul(a, b); + Assert.Equal(326f, c[0, 1, 1], 5); + } + + [Fact] + public void MatMul_Int_RejectsVectorVector() + { + var x = DenseTensor.OfValues(new[] { 1, 2, 3 }); + var y = DenseTensor.OfValues(new[] { 4, 5, 6 }); + + Assert.Throws(() => Tensor.MatMul(x, y)); + } + + [Fact] + public void MatMul_ThrowsOnRankZero() + { + var scalar = new DenseTensor(new int[1], Array.Empty()); + var matrix = Tensor.Ones(2, 2); + + Assert.Throws(() => Tensor.MatMul(scalar, matrix)); + Assert.Throws(() => Tensor.MatMul(matrix, scalar)); + } +} diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorOpsReductionTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsReductionTests.cs new file mode 100644 index 0000000..5265135 --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsReductionTests.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; + +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorOpsReductionTests +{ + [Fact] + public void ReduceSum_Mean_Max_Int() + { + var data = DenseTensor.OfValues(new int[2, 2] { { 1, 2 }, { 3, 4 } }); + var axes = new int[] { 0 }.ToTensor(); + + var sum = Tensor.ReduceSum(data, axes); + var mean = Tensor.ReduceMean(data, axes); + + Assert.Equal(new[] { 2 }, sum.Dimensions.ToArray()); + Assert.Equal(4, sum[0]); + Assert.Equal(6, sum[1]); + Assert.Equal(1, mean[0]); + Assert.Equal(3, mean[1]); + } + + [Fact] + public void ReduceSum_Mean_Max_Float() + { + var data = DenseTensor.OfValues(new float[2, 3] { { 1f, 2f, 3f }, { 4f, 5f, 6f } }); + var axes = new int[] { 1 }.ToTensor(); + + var sum = Tensor.ReduceSum(data, axes); + var mean = Tensor.ReduceMean(data, axes); + var max = Tensor.ReduceMax(data, axes); + + Assert.Equal(new[] { 2 }, sum.Dimensions.ToArray()); + Assert.Equal(6f, sum[0], 5); + Assert.Equal(15f, sum[1], 5); + Assert.Equal(2f, mean[0], 5); + Assert.Equal(5f, mean[1], 5); + Assert.Equal(3f, max[0], 5); + Assert.Equal(6f, max[1], 5); + } + + [Fact] + public void ReduceSum_Mean_Max_Double() + { + var data = DenseTensor.OfValues(new double[2, 2] { { 1d, 2d }, { 3d, 4d } }); + var axes = new int[] { 1 }.ToTensor(); + + var sum = Tensor.ReduceSum(data, axes); + var mean = Tensor.ReduceMean(data, axes); + var max = Tensor.ReduceMax(data, axes); + + Assert.Equal(new[] { 2 }, sum.Dimensions.ToArray()); + Assert.Equal(3d, sum[0], 10); + Assert.Equal(7d, sum[1], 10); + Assert.Equal(1.5d, mean[0], 10); + Assert.Equal(3.5d, mean[1], 10); + Assert.Equal(2d, max[0], 10); + Assert.Equal(4d, max[1], 10); + } + + [Fact] + public void Softmax_NormalizesAlongAxis() + { + var data = DenseTensor.OfValues(new float[2, 2] { { 0f, 1f }, { -1f, 1f } }); + var output = Tensor.Softmax(data, axis: 1); + + var expected0 = MathF.Exp(0f) / (MathF.Exp(0f) + MathF.Exp(1f)); + var expected1 = MathF.Exp(1f) / (MathF.Exp(0f) + MathF.Exp(1f)); + var expected2 = MathF.Exp(-1f) / (MathF.Exp(-1f) + MathF.Exp(1f)); + var expected3 = MathF.Exp(1f) / (MathF.Exp(-1f) + MathF.Exp(1f)); + + Assert.Equal(expected0, output[0, 0], 5); + Assert.Equal(expected1, output[0, 1], 5); + Assert.Equal(expected2, output[1, 0], 5); + Assert.Equal(expected3, output[1, 1], 5); + Assert.Equal(1f, output[0, 0] + output[0, 1], 5); + Assert.Equal(1f, output[1, 0] + output[1, 1], 5); + } +} diff --git a/tests/Lokad.Onnx.Tensors.Tests/SimdTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsSimdTests.cs similarity index 91% rename from tests/Lokad.Onnx.Tensors.Tests/SimdTests.cs rename to tests/Lokad.Onnx.Tensors.Tests/TensorOpsSimdTests.cs index 04924d5..4899be5 100644 --- a/tests/Lokad.Onnx.Tensors.Tests/SimdTests.cs +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsSimdTests.cs @@ -1,13 +1,11 @@ -namespace Lokad.Onnx.Tensors.Tests; +namespace Lokad.Onnx.Tensors.Tests; using System.Buffers; -using Xunit; - using static Lokad.Onnx.MathOps; -public class SimdTests +public class TensorOpsSimdTests { - public SimdTests() + public TensorOpsSimdTests() { t_384_384_a = Tensor.Rand(384, 384); t_384_384_b = Tensor.Rand(384, 384); @@ -38,7 +36,7 @@ public unsafe void CanMatMulVectorized() public unsafe void CanMatMulVectorizedIntrinsics() { mm_unsafe_vectorized_intrinsics(384, 384, 384, (float*)ah.Pointer, (float*)bh.Pointer, (float*)c2h.Pointer); - Assert.Equal(t_384_384_c2[0,1], t_384_384_cr[0,1], .00001f); + Assert.Equal(t_384_384_c2[0,1], t_384_384_cr[0,1], .0002f); } #region Fields Tensor t_384_384_a = Tensor.Zeros(0); diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorOpsUnaryOpsTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsUnaryOpsTests.cs new file mode 100644 index 0000000..5ccc534 --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorOpsUnaryOpsTests.cs @@ -0,0 +1,78 @@ +using System; + +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorOpsUnaryOpsTests +{ + [Fact] + public void Negate_Abs_Square_Sqrt_Float() + { + var a = DenseTensor.OfValues(new float[] { -4f, -1f, 0f, 3f }); + + var neg = Tensor.Negate(a); + var abs = Tensor.Abs(a); + var sqr = Tensor.Square(a); + var sqrt = Tensor.Sqrt(DenseTensor.OfValues(new float[] { 4f, 9f, 16f })); + + Assert.Equal(4f, neg[0], 5); + Assert.Equal(1f, abs[1], 5); + Assert.Equal(9f, sqr[3], 5); + Assert.Equal(2f, sqrt[0], 5); + Assert.Equal(3f, sqrt[1], 5); + Assert.Equal(4f, sqrt[2], 5); + } + + [Fact] + public void Negate_Abs_Square_Sqrt_Double() + { + var a = DenseTensor.OfValues(new double[] { -9d, -2d, 0d, 5d }); + + var neg = Tensor.Negate(a); + var abs = Tensor.Abs(a); + var sqr = Tensor.Square(a); + var sqrt = Tensor.Sqrt(DenseTensor.OfValues(new double[] { 1d, 4d, 25d })); + + Assert.Equal(9d, neg[0], 10); + Assert.Equal(2d, abs[1], 10); + Assert.Equal(25d, sqr[3], 10); + Assert.Equal(1d, sqrt[0], 10); + Assert.Equal(2d, sqrt[1], 10); + Assert.Equal(5d, sqrt[2], 10); + } + + [Fact] + public void Pow_Relu_Erf_Float() + { + var a = DenseTensor.OfValues(new float[] { -2f, -1f, 0f, 3f }); + var b = DenseTensor.OfValues(new float[] { 2f, 3f, 1f, 2f }); + + var pow = Tensor.Pow(a, b); + var relu = Tensor.Relu(a); + var erf = Tensor.Erf(DenseTensor.OfValues(new float[] { 0f, 1f })); + + Assert.Equal(4f, pow[0], 5); + Assert.Equal(-1f, pow[1], 5); + Assert.Equal(0f, relu[1], 5); + Assert.Equal(3f, relu[3], 5); + Assert.Equal(0f, erf[0], 5); + Assert.Equal(0.8427f, erf[1], 3); + } + + [Fact] + public void Pow_Relu_Erf_Double() + { + var a = DenseTensor.OfValues(new double[] { -2d, -1d, 0d, 3d }); + var b = DenseTensor.OfValues(new double[] { 2d, 3d, 1d, 2d }); + + var pow = Tensor.Pow(a, b); + var relu = Tensor.Relu(a); + var erf = Tensor.Erf(DenseTensor.OfValues(new double[] { 0d, 1d })); + + Assert.Equal(4d, pow[0], 10); + Assert.Equal(-1d, pow[1], 10); + Assert.Equal(0d, relu[1], 10); + Assert.Equal(3d, relu[3], 10); + Assert.InRange(Math.Abs(erf[0]), 0d, 1e-6d); + Assert.Equal(0.8427d, erf[1], 3); + } +} diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorPadTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorPadTests.cs new file mode 100644 index 0000000..41ffdda --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorPadTests.cs @@ -0,0 +1,18 @@ +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorPadTests +{ + [Fact] + public void CanPadLeft() + { + var a = new DenseTensor(new[] { 256, 256, 3, }); + var b = new DenseTensor(new[] { 3, 1 }); + b[0, 0] = 1; + b[1, 0] = 2; + b[2, 0] = 3; + var pb = b.PadLeft(); + Assert.Equal(3, pb.Rank); + Assert.Equal(2, pb[0,1,0]); + } +} + diff --git a/tests/Lokad.Onnx.Tensors.Tests/TensorReshapeTransposeTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorReshapeTransposeTests.cs new file mode 100644 index 0000000..de53147 --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorReshapeTransposeTests.cs @@ -0,0 +1,49 @@ +namespace Lokad.Onnx.Tensors.Tests; + +public class TensorReshapeTransposeTests +{ + [Fact] + public void CanReshape() + { + var X = DenseTensor.Ones(2, 3, 4); + + var s = DenseTensor.OfValues(new long[] {4,2,3}); + var Y = Tensor.Reshape(X, s); + Assert.Equal(new int[] {4,2,3}, Y.Dimensions.ToArray()); + + s = DenseTensor.OfValues(new long[] { 2, 4, 3 }); + Y = Tensor.Reshape(X, s); + Assert.Equal(new int[] { 2, 4, 3 }, Y.Dimensions.ToArray()); + + s = DenseTensor.OfValues(new long[] { 2, 12 }); + Y = Tensor.Reshape(X, s); + Assert.Equal(new int[] { 2, 12 }, Y.Dimensions.ToArray()); + + s = DenseTensor.OfValues(new long[] { 2, -1, 2}); + Y = Tensor.Reshape(X, s); + Assert.Equal(new int[] { 2, 6, 2 }, Y.Dimensions.ToArray()); + + s = DenseTensor.OfValues(new long[] { -1, 2, 3, 4 }); + Y = Tensor.Reshape(X, s); + Assert.Equal(new int[] { 1, 2, 3, 4 }, Y.Dimensions.ToArray()); + + s = DenseTensor.OfValues(new long[] { 2, 0, 1, -1 }); + Y = Tensor.Reshape(X, s); + Assert.Equal(new int[] { 2, 3, 1, 4 }, Y.Dimensions.ToArray()); + } + + [Fact] + public void CanTranspose() + { + var X = DenseTensor.Ones(2, 3, 4); + var tX = Tensor.Transpose(X); + Assert.Equal(2, tX.Dimensions[2]); + tX = Tensor.Transpose(X, new int[] { 2, 0, 1 }); + Assert.Equal(4, tX.Dimensions[0]); + Assert.Equal(3, tX.Dimensions[2]); + Assert.Throws(() => Tensor.Transpose(X, new int[] { 0, 0, 2 })); + Assert.Throws(() => Tensor.Transpose(X, new int[] { 0 })); + Assert.Throws(() => Tensor.Transpose(X, new int[] { 2, 6 })); + } +} + diff --git a/tests/Lokad.Onnx.Tensors.Tests/SliceTests.cs b/tests/Lokad.Onnx.Tensors.Tests/TensorSliceTests.cs similarity index 98% rename from tests/Lokad.Onnx.Tensors.Tests/SliceTests.cs rename to tests/Lokad.Onnx.Tensors.Tests/TensorSliceTests.cs index c8f1d76..a59a237 100644 --- a/tests/Lokad.Onnx.Tensors.Tests/SliceTests.cs +++ b/tests/Lokad.Onnx.Tensors.Tests/TensorSliceTests.cs @@ -1,6 +1,6 @@ -namespace Lokad.Onnx.Tensors.Tests; +namespace Lokad.Onnx.Tensors.Tests; -public class SliceTests +public class TensorSliceTests { [Fact] public void CanSlice() diff --git a/tests/Lokad.Onnx.Tensors.Tests/TestHelpers.cs b/tests/Lokad.Onnx.Tensors.Tests/TestHelpers.cs new file mode 100644 index 0000000..78873b7 --- /dev/null +++ b/tests/Lokad.Onnx.Tensors.Tests/TestHelpers.cs @@ -0,0 +1,31 @@ +using System; + +namespace Lokad.Onnx.Tensors.Tests; + +internal sealed class HardwareConfigScope : IDisposable +{ + private static readonly object sync = new(); + private readonly bool lockHeld; + private readonly bool useSimd; + private readonly bool useIntrinsics; + + public HardwareConfigScope(bool useSimd, bool useIntrinsics) + { + System.Threading.Monitor.Enter(sync); + lockHeld = true; + this.useSimd = HardwareConfig.UseSimd; + this.useIntrinsics = HardwareConfig.UseIntrinsics; + HardwareConfig.UseSimd = useSimd; + HardwareConfig.UseIntrinsics = useIntrinsics; + } + + public void Dispose() + { + HardwareConfig.UseSimd = useSimd; + HardwareConfig.UseIntrinsics = useIntrinsics; + if (lockHeld) + { + System.Threading.Monitor.Exit(sync); + } + } +}