From 14ac04d08610017a3c328da32362ab6e899cffe0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 15 Sep 2023 16:30:41 -0400 Subject: [PATCH 1/3] Add remaining set of TensorPrimitives APIs for .NET 8 Adds non-vectorized implementations of: - Max - Min - MaxMagnitude - MinMagnitude - IndexOfMax - IndexOfMin - IndexOfMaxMagnitude - ConvertToHalf (only on .NET Core) - ConvertToSingle (only on .NET Core) - IndexOfMinMagnitude Adds vectorized implementations of: - Sum - SumOfSquares - SumOfMagnitudes - Product - ProductOfSums - ProductOfDifferences Also includes the helpers that'll make it trivial to vectorize Dot. Beyond vectorizing the non-vectorized ones, the vectorized implementations should be improved further, including: - Handling alignment better - Vectorizing the remainder that doesn't fit in a vector rather than falling back to scalar --- src/coreclr/jit/utils.cpp | 8 +- .../ref/System.Numerics.Tensors.cs | 14 + .../ref/System.Numerics.Tensors.csproj | 4 + .../ref/System.Numerics.Tensors.netcore.cs | 14 + .../Numerics/Tensors/TensorPrimitives.cs | 443 +++++++++++++++ .../Tensors/TensorPrimitives.netcore.cs | 324 +++++++++++ .../Tensors/TensorPrimitives.netstandard.cs | 117 ++++ .../System.Numerics.Tensors.Tests.csproj | 4 + .../tests/TensorPrimitivesTests.cs | 535 +++++++++++++++++- .../tests/TensorPrimitivesTests.netcore.cs | 89 +++ .../System.Private.CoreLib/src/System/Math.cs | 6 +- .../src/System/MathF.cs | 2 +- 12 files changed, 1551 insertions(+), 9 deletions(-) create mode 100644 src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs create mode 100644 src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs diff --git a/src/coreclr/jit/utils.cpp b/src/coreclr/jit/utils.cpp index 245f84c7f62349..19bdb0fadaa8c5 100644 --- a/src/coreclr/jit/utils.cpp +++ b/src/coreclr/jit/utils.cpp @@ -2734,7 +2734,7 @@ float FloatingPointUtils::maximumNumber(float x, float y) // // It propagates NaN inputs back to the caller and // otherwise returns the lesser of the inputs. It -// treats +0 as lesser than -0 as per the specification. +// treats +0 as greater than -0 as per the specification. // // Arguments: // val1 - left operand @@ -2763,7 +2763,7 @@ double FloatingPointUtils::minimum(double val1, double val2) // // It propagates NaN inputs back to the caller and // otherwise returns the input with a lesser magnitude. -// It treats +0 as lesser than -0 as per the specification. +// It treats +0 as greater than -0 as per the specification. // // Arguments: // x - left operand @@ -2856,7 +2856,7 @@ double FloatingPointUtils::minimumNumber(double x, double y) // // It propagates NaN inputs back to the caller and // otherwise returns the lesser of the inputs. It -// treats +0 as lesser than -0 as per the specification. +// treats +0 as greater than -0 as per the specification. // // Arguments: // val1 - left operand @@ -2885,7 +2885,7 @@ float FloatingPointUtils::minimum(float val1, float val2) // // It propagates NaN inputs back to the caller and // otherwise returns the input with a lesser magnitude. -// It treats +0 as lesser than -0 as per the specification. +// It treats +0 as greater than -0 as per the specification. // // Arguments: // x - left operand diff --git a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs index c0c15cd6bfa19f..50eaa00160e5cb 100644 --- a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs @@ -20,19 +20,33 @@ public static void Divide(System.ReadOnlySpan x, System.ReadOnlySpan x, float y, System.Span destination) { } public static float Dot(System.ReadOnlySpan x, System.ReadOnlySpan y) { throw null; } public static void Exp(System.ReadOnlySpan x, System.Span destination) { } + public static int IndexOfMax(System.ReadOnlySpan x) { throw null; } + public static int IndexOfMaxMagnitude(System.ReadOnlySpan x) { throw null; } + public static int IndexOfMin(System.ReadOnlySpan x) { throw null; } + public static int IndexOfMinMagnitude(System.ReadOnlySpan x) { throw null; } public static float L2Normalize(System.ReadOnlySpan x) { throw null; } public static void Log(System.ReadOnlySpan x, System.Span destination) { } + public static float Max(System.ReadOnlySpan x) { throw null; } + public static float MaxMagnitude(System.ReadOnlySpan x) { throw null; } + public static float Min(System.ReadOnlySpan x) { throw null; } + public static float MinMagnitude(System.ReadOnlySpan x) { throw null; } public static void Multiply(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { } public static void Multiply(System.ReadOnlySpan x, float y, System.Span destination) { } public static void MultiplyAdd(System.ReadOnlySpan x, System.ReadOnlySpan y, System.ReadOnlySpan addend, System.Span destination) { } public static void MultiplyAdd(System.ReadOnlySpan x, System.ReadOnlySpan y, float addend, System.Span destination) { } public static void MultiplyAdd(System.ReadOnlySpan x, float y, System.ReadOnlySpan addend, System.Span destination) { } public static void Negate(System.ReadOnlySpan x, System.Span destination) { } + public static float Product(System.ReadOnlySpan x) { throw null; } + public static float ProductOfDifferences(System.ReadOnlySpan x, System.ReadOnlySpan y) { throw null; } + public static float ProductOfSums(System.ReadOnlySpan x, System.ReadOnlySpan y) { throw null; } public static void Sigmoid(System.ReadOnlySpan x, System.Span destination) { } public static void Sinh(System.ReadOnlySpan x, System.Span destination) { } public static void SoftMax(System.ReadOnlySpan x, System.Span destination) { } public static void Subtract(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { } public static void Subtract(System.ReadOnlySpan x, float y, System.Span destination) { } + public static float Sum(System.ReadOnlySpan x) { throw null; } + public static float SumOfMagnitudes(System.ReadOnlySpan x) { throw null; } + public static float SumOfSquares(System.ReadOnlySpan x) { throw null; } public static void Tanh(System.ReadOnlySpan x, System.Span destination) { } } } diff --git a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.csproj b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.csproj index 8d1d484b33ecd4..89bdef6ea38ed9 100644 --- a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.csproj +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.csproj @@ -8,6 +8,10 @@ + + + + diff --git a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs new file mode 100644 index 00000000000000..1cde4351546b26 --- /dev/null +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// ------------------------------------------------------------------------------ +// Changes to this file must follow the https://aka.ms/api-review process. +// ------------------------------------------------------------------------------ + +namespace System.Numerics.Tensors +{ + public static partial class TensorPrimitives + { + public static void ConvertToHalf(System.ReadOnlySpan source, System.Span destination) { throw null; } + public static void ConvertToSingle(System.ReadOnlySpan source, System.Span destination) { throw null; } + } +} diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs index 87d41d1f9c0a57..3dd7814dca9908 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs @@ -410,5 +410,448 @@ public static void Sigmoid(ReadOnlySpan x, Span destination) destination[i] = 1f / (1 + MathF.Exp(-x[i])); } } + + /// Computes the maximum element in . + /// The tensor, represented as a span. + /// The maximum element in . + /// Length of '' must be greater than zero. + public static float Max(ReadOnlySpan x) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + float result = float.NegativeInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `maximum` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the greater of the inputs. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + + if (current != result) + { + if (float.IsNaN(current)) + { + return current; + } + + if (result < current) + { + result = current; + } + } + else if (IsNegative(result)) + { + result = current; + } + } + + return result; + } + + /// Computes the minimum element in . + /// The tensor, represented as a span. + /// The minimum element in . + /// Length of '' must be greater than zero. + public static float Min(ReadOnlySpan x) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + float result = float.PositiveInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `minimum` function + // It propagates NaN inputs back to the caller and + // otherwise returns the lesser of the inputs. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + + if (current != result) + { + if (float.IsNaN(current)) + { + return current; + } + + if (current < result) + { + result = current; + } + } + else if (IsNegative(current)) + { + result = current; + } + } + + return result; + } + + /// Computes the maximum magntude of any element in . + /// The tensor, represented as a span. + /// The maximum magnitude of any element in . + /// Length of '' must be greater than zero. + public static float MaxMagnitude(ReadOnlySpan x) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + float result = float.NegativeInfinity; + float resultMag = float.NegativeInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `maximumMagnitude` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a greater magnitude. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + float currentMag = Math.Abs(current); + + if (currentMag != resultMag) + { + if (float.IsNaN(currentMag)) + { + return currentMag; + } + + if (resultMag < currentMag) + { + result = current; + resultMag = currentMag; + } + } + else if (IsNegative(result)) + { + result = current; + resultMag = currentMag; + } + } + + return resultMag; + } + + /// Computes the minimum magntude of any element in . + /// The tensor, represented as a span. + /// The minimum magnitude of any element in . + /// Length of '' must be greater than zero. + public static float MinMagnitude(ReadOnlySpan x) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + float result = float.PositiveInfinity; + float resultMag = float.PositiveInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `minimumMagnitude` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a lesser magnitude. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + float currentMag = Math.Abs(current); + + if (currentMag != resultMag) + { + if (float.IsNaN(currentMag)) + { + return currentMag; + } + + if (currentMag < resultMag) + { + result = current; + resultMag = currentMag; + } + } + else if (IsNegative(current)) + { + result = current; + resultMag = currentMag; + } + } + + return result; + } + + /// Computes the index of the maximum element in . + /// The tensor, represented as a span. + /// The index of the maximum element in , or -1 if is empty. + public static unsafe int IndexOfMax(ReadOnlySpan x) + { + int result = -1; + + if (!x.IsEmpty) + { + float max = float.NegativeInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `maximum` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the greater of the inputs. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + + if (current != max) + { + if (float.IsNaN(current)) + { + return i; + } + + if (max < current) + { + result = i; + max = current; + } + } + else if (IsNegative(max)) + { + result = i; + max = current; + } + } + } + + return result; + } + + /// Computes the index of the minimum element in . + /// The tensor, represented as a span. + /// The index of the minimum element in , or -1 if is empty. + public static unsafe int IndexOfMin(ReadOnlySpan x) + { + int result = -1; + + if (!x.IsEmpty) + { + float min = float.PositiveInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `minimum` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the lesser of the inputs. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + + if (current != min) + { + if (float.IsNaN(current)) + { + return i; + } + + if (current < min) + { + result = i; + min = current; + } + } + else if (IsNegative(current)) + { + result = i; + min = current; + } + } + } + + return result; + } + + /// Computes the index of the element in with the maximum magnitude. + /// The tensor, represented as a span. + /// The index of the element with the maximum magnitude, or -1 if is empty. + /// This method corresponds to the iamax method defined by BLAS1. + public static unsafe int IndexOfMaxMagnitude(ReadOnlySpan x) + { + int result = -1; + + if (!x.IsEmpty) + { + float max = float.NegativeInfinity; + float maxMag = float.NegativeInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `maximumMagnitude` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a greater magnitude. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + float currentMag = Math.Abs(current); + + if (currentMag != maxMag) + { + if (float.IsNaN(currentMag)) + { + return i; + } + + if (maxMag < currentMag) + { + result = i; + max = current; + maxMag = currentMag; + } + } + else if (IsNegative(max)) + { + result = i; + max = current; + maxMag = currentMag; + } + } + } + + return result; + } + + /// Computes the index of the element in with the minimum magnitude. + /// The tensor, represented as a span. + /// The index of the element with the minimum magnitude, or -1 if is empty. + public static unsafe int IndexOfMinMagnitude(ReadOnlySpan x) + { + int result = -1; + + if (!x.IsEmpty) + { + float minMag = float.PositiveInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `minimumMagnitude` function + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a lesser magnitude. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + float currentMag = Math.Abs(current); + + if (currentMag != minMag) + { + if (float.IsNaN(currentMag)) + { + return i; + } + + if (currentMag < minMag) + { + result = i; + minMag = currentMag; + } + } + else if (IsNegative(current)) + { + result = i; + minMag = currentMag; + } + } + } + + return result; + } + + /// Computes the sum of all elements in . + /// The tensor, represented as a span. + /// The result of adding all elements in , or zero if is empty. + public static float Sum(ReadOnlySpan x) => + Aggregate(0.0f, x); + + /// Computes the sum of the squares of every element in . + /// The tensor, represented as a span. + /// The result of adding every element in multiplied by itself, or zero if is empty. + /// This method effectively does .Sum(.Multiply(, )). + public static float SumOfSquares(ReadOnlySpan x) => + Aggregate(0.0f, x); + + /// Computes the sum of the absolute values of every element in . + /// The tensor, represented as a span. + /// The result of adding the absolute value of every element in , or zero if is empty. + /// + /// This method effectively does .Sum(.Abs()). + /// This method corresponds to the asum method defined by BLAS1. + /// + public static float SumOfMagnitudes(ReadOnlySpan x) => + Aggregate(0.0f, x); + + /// Computes the product of all elements in . + /// The tensor, represented as a span. + /// The result of multiplying all elements in . + /// Length of '' must be greater than zero. + public static float Product(ReadOnlySpan x) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + return Aggregate(1.0f, x); + } + + /// Computes the product of the element-wise result of: + . + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The result of multiplying the element-wise additions of the elements in each tensor. + /// Length of both input spans must be greater than zero. + /// and must have the same length. + /// This method effectively does .Product(.Add(, )). + public static float ProductOfSums(ReadOnlySpan x, ReadOnlySpan y) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + return Aggregate(1.0f, x, y); + } + + /// Computes the product of the element-wise result of: - . + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The result of multiplying the element-wise subtraction of the elements in the second tensor from the first tensor. + /// Length of both input spans must be greater than zero. + /// and must have the same length. + /// This method effectively does .Product(.Subtract(, )). + public static float ProductOfDifferences(ReadOnlySpan x, ReadOnlySpan y) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + return Aggregate(1.0f, x, y); + } } } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs index 1233f54901c80c..54cb82795eba2a 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -9,6 +10,224 @@ namespace System.Numerics.Tensors { public static partial class TensorPrimitives { + /// + /// Copies to , converting each + /// value to its nearest representable half-precision floating-point value. + /// + /// The source span from which to copy values. + /// The destination span into which the converted values should be written. + /// Destination is too short. + public static void ConvertToHalf(ReadOnlySpan source, Span destination) + { + if (source.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < source.Length; i++) + { + destination[i] = (Half)source[i]; + } + } + + /// + /// Copies to , converting each half-precision + /// floating-point value to its nearest representable value. + /// + /// The source span from which to copy values. + /// The destination span into which the converted values should be written. + /// Destination is too short. + public static void ConvertToSingle(ReadOnlySpan source, Span destination) + { + if (source.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < source.Length; i++) + { + destination[i] = (float)source[i]; + } + } + + private static bool IsNegative(float f) => float.IsNegative(f); + + private static float Aggregate( + float identityValue, ReadOnlySpan x) + where TLoad : IUnaryOperator + where TAggregate : IBinaryOperator + { + // Initialize the result to the identity value + float result = identityValue; + int i = 0; + +#if NET8_0_OR_GREATER + if (Vector512.IsHardwareAccelerated && x.Length >= Vector512.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + + // Load the first vector as the initial set of results + Vector512 resultVector = TLoad.Invoke(Vector512.LoadUnsafe(ref xRef, 0)); + int oneVectorFromEnd = x.Length - Vector512.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector512.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TLoad.Invoke(Vector512.LoadUnsafe(ref xRef, (uint)i))); + i += Vector512.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + else +#endif + if (Vector256.IsHardwareAccelerated && x.Length >= Vector256.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + + // Load the first vector as the initial set of results + Vector256 resultVector = TLoad.Invoke(Vector256.LoadUnsafe(ref xRef, 0)); + int oneVectorFromEnd = x.Length - Vector256.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector256.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TLoad.Invoke(Vector256.LoadUnsafe(ref xRef, (uint)i))); + i += Vector256.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + else if (Vector128.IsHardwareAccelerated && x.Length >= Vector128.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + + // Load the first vector as the initial set of results + Vector128 resultVector = TLoad.Invoke(Vector128.LoadUnsafe(ref xRef, 0)); + int oneVectorFromEnd = x.Length - Vector128.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector128.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TLoad.Invoke(Vector128.LoadUnsafe(ref xRef, (uint)i))); + i += Vector128.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + + // Aggregate the remaining items in the input span. + for (; (uint)i < (uint)x.Length; i++) + { + result = TAggregate.Invoke(result, TLoad.Invoke(x[i])); + } + + return result; + } + + private static float Aggregate( + float identityValue, ReadOnlySpan x, ReadOnlySpan y) + where TBinary : IBinaryOperator + where TAggregate : IBinaryOperator + { + Debug.Assert(!x.IsEmpty); + Debug.Assert(x.Length == y.Length); + + // Initialize the result to the identity value + float result = identityValue; + int i = 0; + +#if NET8_0_OR_GREATER + if (Vector512.IsHardwareAccelerated && x.Length >= Vector512.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + // Load the first vector as the initial set of results + Vector512 resultVector = TBinary.Invoke(Vector512.LoadUnsafe(ref xRef, 0), Vector512.LoadUnsafe(ref yRef, 0)); + int oneVectorFromEnd = x.Length - Vector512.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector512.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TBinary.Invoke(Vector512.LoadUnsafe(ref xRef, (uint)i), Vector512.LoadUnsafe(ref yRef, (uint)i))); + i += Vector512.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + else +#endif + if (Vector256.IsHardwareAccelerated && x.Length >= Vector256.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + // Load the first vector as the initial set of results + Vector256 resultVector = TBinary.Invoke(Vector256.LoadUnsafe(ref xRef, 0), Vector256.LoadUnsafe(ref yRef, 0)); + int oneVectorFromEnd = x.Length - Vector256.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector256.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TBinary.Invoke(Vector256.LoadUnsafe(ref xRef, (uint)i), Vector256.LoadUnsafe(ref yRef, (uint)i))); + i += Vector256.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + else if (Vector128.IsHardwareAccelerated && x.Length >= Vector128.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + // Load the first vector as the initial set of results + Vector128 resultVector = TBinary.Invoke(Vector128.LoadUnsafe(ref xRef, 0), Vector128.LoadUnsafe(ref yRef, 0)); + int oneVectorFromEnd = x.Length - Vector128.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector128.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TBinary.Invoke(Vector128.LoadUnsafe(ref xRef, (uint)i), Vector128.LoadUnsafe(ref yRef, (uint)i))); + i += Vector128.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + + // Aggregate the remaining items in the input span. + for (; (uint)i < (uint)x.Length; i++) + { + result = TAggregate.Invoke(result, TBinary.Invoke(x[i], y[i])); + } + + return result; + } + private static unsafe void InvokeSpanIntoSpan( ReadOnlySpan x, Span destination) where TUnaryOperator : IUnaryOperator @@ -704,6 +923,12 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( #if NET8_0_OR_GREATER public static Vector512 Invoke(Vector512 x, Vector512 y) => x + y; #endif + + public static float Invoke(Vector128 x) => Vector128.Sum(x); + public static float Invoke(Vector256 x) => Vector256.Sum(x); +#if NET8_0_OR_GREATER + public static float Invoke(Vector512 x) => Vector512.Sum(x); +#endif } private readonly struct SubtractOperator : IBinaryOperator @@ -714,6 +939,12 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( #if NET8_0_OR_GREATER public static Vector512 Invoke(Vector512 x, Vector512 y) => x - y; #endif + + public static float Invoke(Vector128 x) => throw new NotSupportedException(); + public static float Invoke(Vector256 x) => throw new NotSupportedException(); +#if NET8_0_OR_GREATER + public static float Invoke(Vector512 x) => throw new NotSupportedException(); +#endif } private readonly struct MultiplyOperator : IBinaryOperator @@ -724,6 +955,41 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( #if NET8_0_OR_GREATER public static Vector512 Invoke(Vector512 x, Vector512 y) => x * y; #endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Invoke(Vector128 x) + { + float f = x[0]; + for (int i = 1; i < Vector128.Count; i++) + { + f *= x[i]; + } + return f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Invoke(Vector256 x) + { + float f = x[0]; + for (int i = 1; i < Vector256.Count; i++) + { + f *= x[i]; + } + return f; + } + +#if NET8_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Invoke(Vector512 x) + { + float f = x[0]; + for (int i = 1; i < Vector512.Count; i++) + { + f *= x[i]; + } + return f; + } +#endif } private readonly struct DivideOperator : IBinaryOperator @@ -734,6 +1000,12 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( #if NET8_0_OR_GREATER public static Vector512 Invoke(Vector512 x, Vector512 y) => x / y; #endif + + public static float Invoke(Vector128 x) => throw new NotSupportedException(); + public static float Invoke(Vector256 x) => throw new NotSupportedException(); +#if NET8_0_OR_GREATER + public static float Invoke(Vector512 x) => throw new NotSupportedException(); +#endif } private readonly struct NegateOperator : IUnaryOperator @@ -760,6 +1032,54 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( public static Vector512 Invoke(Vector512 x, Vector512 y, Vector512 z) => (x * y) + z; } + private readonly struct LoadIdentity : IUnaryOperator + { + public static float Invoke(float x) => x; + public static Vector128 Invoke(Vector128 x) => x; + public static Vector256 Invoke(Vector256 x) => x; +#if NET8_0_OR_GREATER + public static Vector512 Invoke(Vector512 x) => x; +#endif + } + + private readonly struct LoadSquared : IUnaryOperator + { + public static float Invoke(float x) => x * x; + public static Vector128 Invoke(Vector128 x) => x * x; + public static Vector256 Invoke(Vector256 x) => x * x; +#if NET8_0_OR_GREATER + public static Vector512 Invoke(Vector512 x) => x * x; +#endif + } + + private readonly struct LoadAbsolute : IUnaryOperator + { + public static float Invoke(float x) => MathF.Abs(x); + + public static Vector128 Invoke(Vector128 x) + { + Vector128 raw = x.AsUInt32(); + Vector128 mask = Vector128.Create((uint)0x7FFFFFFF); + return (raw & mask).AsSingle(); + } + + public static Vector256 Invoke(Vector256 x) + { + Vector256 raw = x.AsUInt32(); + Vector256 mask = Vector256.Create((uint)0x7FFFFFFF); + return (raw & mask).AsSingle(); + } + +#if NET8_0_OR_GREATER + public static Vector512 Invoke(Vector512 x) + { + Vector512 raw = x.AsUInt32(); + Vector512 mask = Vector512.Create((uint)0x7FFFFFFF); + return (raw & mask).AsSingle(); + } +#endif + } + private interface IUnaryOperator { static abstract float Invoke(float x); @@ -773,10 +1093,14 @@ private interface IUnaryOperator private interface IBinaryOperator { static abstract float Invoke(float x, float y); + static abstract Vector128 Invoke(Vector128 x, Vector128 y); + static abstract float Invoke(Vector128 x); static abstract Vector256 Invoke(Vector256 x, Vector256 y); + static abstract float Invoke(Vector256 x); #if NET8_0_OR_GREATER static abstract Vector512 Invoke(Vector512 x, Vector512 y); + static abstract float Invoke(Vector512 x); #endif } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs index ddac0f47a685c2..39c20aeaa5996a 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -8,6 +9,98 @@ namespace System.Numerics.Tensors { public static partial class TensorPrimitives { + private static unsafe bool IsNegative(float f) => *(int*)&f < 0; + + private static float Aggregate( + float identityValue, ReadOnlySpan x, TLoad load = default, TAggregate aggregate = default) + where TLoad : IUnaryOperator + where TAggregate : IBinaryOperator + { + // Initialize the result to the identity value + float result = identityValue; + int i = 0; + + if (Vector.IsHardwareAccelerated && x.Length >= Vector.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + + // Load the first vector as the initial set of results + Vector resultVector = load.Invoke(AsVector(ref xRef, 0)); + int oneVectorFromEnd = x.Length - Vector.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector.Count; + do + { + resultVector = aggregate.Invoke(resultVector, load.Invoke(AsVector(ref xRef, i))); + i += Vector.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + for (int f = 0; f < Vector.Count; f++) + { + result = aggregate.Invoke(result, resultVector[f]); + } + } + + // Aggregate the remaining items in the input span. + for (; (uint)i < (uint)x.Length; i++) + { + result = aggregate.Invoke(result, load.Invoke(x[i])); + } + + return result; + } + + private static float Aggregate( + float identityValue, ReadOnlySpan x, ReadOnlySpan y, TBinary binary = default, TAggregate aggregate = default) + where TBinary : IBinaryOperator + where TAggregate : IBinaryOperator + { + Debug.Assert(!x.IsEmpty); + Debug.Assert(x.Length == y.Length); + + // Initialize the result to the identity value + float result = identityValue; + int i = 0; + + if (Vector.IsHardwareAccelerated && x.Length >= Vector.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + // Load the first vector as the initial set of results + Vector resultVector = binary.Invoke(AsVector(ref xRef, 0), AsVector(ref yRef, 0)); + int oneVectorFromEnd = x.Length - Vector.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector.Count; + do + { + resultVector = aggregate.Invoke(resultVector, binary.Invoke(AsVector(ref xRef, i), AsVector(ref yRef, i))); + i += Vector.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + for (int f = 0; f < Vector.Count; f++) + { + result = aggregate.Invoke(result, resultVector[f]); + } + } + + // Aggregate the remaining items in the input span. + for (; (uint)i < (uint)x.Length; i++) + { + result = aggregate.Invoke(result, binary.Invoke(x[i], y[i])); + } + + return result; + } + private static void InvokeSpanIntoSpan( ReadOnlySpan x, Span destination, TUnaryOperator op = default) where TUnaryOperator : IUnaryOperator @@ -390,6 +483,30 @@ ref Unsafe.As>( public Vector Invoke(Vector x, Vector y, Vector z) => (x * y) + z; } + private readonly struct LoadIdentity : IUnaryOperator + { + public float Invoke(float x) => x; + public Vector Invoke(Vector x) => x; + } + + private readonly struct LoadSquared : IUnaryOperator + { + public float Invoke(float x) => x * x; + public Vector Invoke(Vector x) => x * x; + } + + private readonly struct LoadAbsolute : IUnaryOperator + { + public float Invoke(float x) => MathF.Abs(x); + + public Vector Invoke(Vector x) + { + Vector raw = Vector.AsVectorUInt32(x); + Vector mask = new Vector(0x7FFFFFFF); + return Vector.AsVectorSingle(raw & mask); + } + } + private interface IUnaryOperator { float Invoke(float x); diff --git a/src/libraries/System.Numerics.Tensors/tests/System.Numerics.Tensors.Tests.csproj b/src/libraries/System.Numerics.Tensors/tests/System.Numerics.Tensors.Tests.csproj index d78f1cd7e9e035..be4a103d7256ce 100644 --- a/src/libraries/System.Numerics.Tensors/tests/System.Numerics.Tensors.Tests.csproj +++ b/src/libraries/System.Numerics.Tensors/tests/System.Numerics.Tensors.Tests.csproj @@ -8,6 +8,10 @@ + + + + diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs index e8bb48a50f6b20..5b4ba59eecf5c8 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs @@ -5,13 +5,16 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using System.Diagnostics; #pragma warning disable xUnit1025 // reporting duplicate test cases due to not distinguishing 0.0 from -0.0 namespace System.Numerics.Tensors.Tests { - public static class TensorPrimitivesTests + public static partial class TensorPrimitivesTests { + private const double Tolerance = 0.00001; + public static IEnumerable TensorLengths => from length in new[] { 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 17, 31, 32, 33, 100 } select new object[] { length }; @@ -886,5 +889,535 @@ public static void Sigmoid_ThrowsForEmpty_x_y() AssertExtensions.Throws(() => TensorPrimitives.Sigmoid(x, dest)); } + + [Fact] + public static void IndexOfMax_ReturnsNegative1OnEmpty() + { + Assert.Equal(-1, TensorPrimitives.IndexOfMax(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMax(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + float[] x = CreateAndFillTensor(tensorLength); + x[expected] = Enumerable.Max(x) + 1; + Assert.Equal(expected, TensorPrimitives.IndexOfMax(x)); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMax_FirstNaNReturned(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + float[] x = CreateAndFillTensor(tensorLength); + x[expected] = float.NaN; + x[tensorLength - 1] = float.NaN; + Assert.Equal(expected, TensorPrimitives.IndexOfMax(x)); + } + } + + [Fact] + public static void IndexOfMax_Negative0LesserThanPositive0() + { + Assert.Equal(1, TensorPrimitives.IndexOfMax([-0.0f, +0.0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMax([+0.0f, -0.0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMax([-1, -0.0f])); + Assert.Equal(2, TensorPrimitives.IndexOfMax([-1, -0.0f, 1])); + } + + [Fact] + public static void IndexOfMin_ReturnsNegative1OnEmpty() + { + Assert.Equal(-1, TensorPrimitives.IndexOfMin(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMin(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + float[] x = CreateAndFillTensor(tensorLength); + x[expected] = Enumerable.Min(x) - 1; + Assert.Equal(expected, TensorPrimitives.IndexOfMin(x)); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMin_FirstNaNReturned(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + float[] x = CreateAndFillTensor(tensorLength); + x[expected] = float.NaN; + x[tensorLength - 1] = float.NaN; + Assert.Equal(expected, TensorPrimitives.IndexOfMin(x)); + } + } + + [Fact] + public static void IndexOfMin_Negative0LesserThanPositive0() + { + Assert.Equal(0, TensorPrimitives.IndexOfMin([-0.0f, +0.0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMin([+0.0f, -0.0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMin([-1, -0.0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMin([-1, -0.0f, 1])); + } + + [Fact] + public static void IndexOfMaxMagnitude_ReturnsNegative1OnEmpty() + { + Assert.Equal(-1, TensorPrimitives.IndexOfMaxMagnitude(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMaxMagnitude(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + float[] x = CreateAndFillTensor(tensorLength); + x[expected] = x.Max(Math.Abs) + 1; + Assert.Equal(expected, TensorPrimitives.IndexOfMaxMagnitude(x)); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMaxMagnitude_FirstNaNReturned(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + float[] x = CreateAndFillTensor(tensorLength); + x[expected] = float.NaN; + x[tensorLength - 1] = float.NaN; + Assert.Equal(expected, TensorPrimitives.IndexOfMaxMagnitude(x)); + } + } + + [Fact] + public static void IndexOfMaxMagnitude_Negative0LesserThanPositive0() + { + Assert.Equal(1, TensorPrimitives.IndexOfMaxMagnitude([-0.0f, +0.0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMaxMagnitude([+0.0f, -0.0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMaxMagnitude([-1, -0.0f])); + Assert.Equal(2, TensorPrimitives.IndexOfMaxMagnitude([-1, -0.0f, 1])); + } + + [Fact] + public static void IndexOfMinMagnitude_ReturnsNegative1OnEmpty() + { + Assert.Equal(-1, TensorPrimitives.IndexOfMinMagnitude(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMinMagnitude(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + float[] x = new float[tensorLength]; + for (int i = 0; i < x.Length; i++) + { + x[i] = i % 2 == 0 ? 42 : -42; + } + + x[expected] = -41; + + Assert.Equal(expected, TensorPrimitives.IndexOfMinMagnitude(x)); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMinMagnitude_FirstNaNReturned(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + float[] x = CreateAndFillTensor(tensorLength); + x[expected] = float.NaN; + x[tensorLength - 1] = float.NaN; + Assert.Equal(expected, TensorPrimitives.IndexOfMinMagnitude(x)); + } + } + + [Fact] + public static void IndexOfMinMagnitude_Negative0LesserThanPositive0() + { + Assert.Equal(0, TensorPrimitives.IndexOfMinMagnitude([-0.0f, +0.0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([+0.0f, -0.0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([-1, -0.0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([-1, -0.0f, 1])); + } + + [Fact] + public static void Max_ThrowsForEmpty() + { + Assert.Throws(() => TensorPrimitives.Max(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Max(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + + Assert.Equal(Enumerable.Max(x), TensorPrimitives.Max(x)); + + float max = float.NegativeInfinity; + foreach (float f in x) + { + max = Math.Max(max, f); + } + Assert.Equal(max, TensorPrimitives.Max(x)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Max_NanReturned(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + x[expected] = float.NaN; + Assert.Equal(float.NaN, TensorPrimitives.Max(x)); + } + } + + [Fact] + public static void Max_Negative0LesserThanPositive0() + { + Assert.Equal(+0.0f, TensorPrimitives.Max([-0.0f, +0.0f])); + Assert.Equal(+0.0f, TensorPrimitives.Max([+0.0f, -0.0f])); + Assert.Equal(-0.0f, TensorPrimitives.Max([-1, -0.0f])); + Assert.Equal(1, TensorPrimitives.Max([-1, -0.0f, 1])); + } + + [Fact] + public static void MaxMagnitude_ThrowsForEmpty() + { + Assert.Throws(() => TensorPrimitives.MaxMagnitude(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MaxMagnitude(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + + Assert.Equal(x.Max(MathF.Abs), TensorPrimitives.MaxMagnitude(x)); + + float max = 0; + foreach (float f in x) + { + max = Math.Max(max, MathF.Abs(f)); + } + Assert.Equal(max, TensorPrimitives.MaxMagnitude(x)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MaxMagnitude_NanReturned(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + x[expected] = float.NaN; + Assert.Equal(float.NaN, TensorPrimitives.MaxMagnitude(x)); + } + } + + [Fact] + public static void MaxMagnitude_Negative0LesserThanPositive0() + { + Assert.Equal(+0.0f, TensorPrimitives.MaxMagnitude([-0.0f, +0.0f])); + Assert.Equal(+0.0f, TensorPrimitives.MaxMagnitude([+0.0f, -0.0f])); + Assert.Equal(1, TensorPrimitives.MaxMagnitude([-1, -0.0f])); + Assert.Equal(1, TensorPrimitives.MaxMagnitude([-1, -0.0f, 1])); + Assert.Equal(0.0f, TensorPrimitives.MaxMagnitude([-0.0f, -0.0f, -0.0f, -0.0f, -0.0f, 0.0f])); + Assert.Equal(1, TensorPrimitives.MaxMagnitude([-0.0f, -0.0f, -0.0f, -0.0f, -1, -0.0f, 0.0f, 1])); + } + + [Fact] + public static void Min_ThrowsForEmpty() + { + Assert.Throws(() => TensorPrimitives.Min(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Min(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + + Assert.Equal(Enumerable.Min(x), TensorPrimitives.Min(x)); + + float min = float.PositiveInfinity; + foreach (float f in x) + { + min = Math.Min(min, f); + } + Assert.Equal(min, TensorPrimitives.Min(x)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Min_NanReturned(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + x[expected] = float.NaN; + Assert.Equal(float.NaN, TensorPrimitives.Min(x)); + } + } + + [Fact] + public static void Min_Negative0LesserThanPositive0() + { + Assert.Equal(-0.0f, TensorPrimitives.Min([-0.0f, +0.0f])); + Assert.Equal(-0.0f, TensorPrimitives.Min([+0.0f, -0.0f])); + Assert.Equal(-1, TensorPrimitives.Min([-1, -0.0f])); + Assert.Equal(-1, TensorPrimitives.Min([-1, -0.0f, 1])); + } + + [Fact] + public static void MinMagnitude_ThrowsForEmpty() + { + Assert.Throws(() => TensorPrimitives.MinMagnitude(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MinMagnitude(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + + Assert.Equal(x.Min(MathF.Abs), TensorPrimitives.MinMagnitude(x)); + + float min = float.PositiveInfinity; + foreach (float f in x) + { + min = Math.Min(min, MathF.Abs(f)); + } + Assert.Equal(min, TensorPrimitives.MinMagnitude(x)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MinMagnitude_NanReturned(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + x[expected] = float.NaN; + Assert.Equal(float.NaN, TensorPrimitives.MinMagnitude(x)); + } + } + + [Fact] + public static void MinMagnitude_Negative0LesserThanPositive0() + { + Assert.Equal(0, TensorPrimitives.MinMagnitude([-0.0f, +0.0f])); + Assert.Equal(0, TensorPrimitives.MinMagnitude([+0.0f, -0.0f])); + Assert.Equal(0, TensorPrimitives.MinMagnitude([-1, -0.0f])); + Assert.Equal(0, TensorPrimitives.MinMagnitude([-1, -0.0f, 1])); + } + + [Fact] + public static void Product_ThrowsForEmpty() + { + Assert.Throws(() => TensorPrimitives.Product(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Product(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + + float f = x[0]; + for (int i = 1; i < x.Length; i++) + { + f *= x[i]; + } + + Assert.Equal(f, TensorPrimitives.Product(x), Tolerance); + } + + [Fact] + public static void Product_KnownValues() + { + Assert.Equal(1, TensorPrimitives.Product([1])); + Assert.Equal(-2, TensorPrimitives.Product([1, -2])); + Assert.Equal(-6, TensorPrimitives.Product([1, -2, 3])); + Assert.Equal(24, TensorPrimitives.Product([1, -2, 3, -4])); + Assert.Equal(120, TensorPrimitives.Product([1, -2, 3, -4, 5])); + Assert.Equal(-720, TensorPrimitives.Product([1, -2, 3, -4, 5, -6])); + Assert.Equal(0, TensorPrimitives.Product([1, -2, 3, -4, 5, -6, 0])); + Assert.Equal(0, TensorPrimitives.Product([0, 1, -2, 3, -4, 5, -6])); + Assert.Equal(0, TensorPrimitives.Product([1, -2, 3, 0, -4, 5, -6])); + Assert.Equal(float.NaN, TensorPrimitives.Product([1, -2, 3, float.NaN, -4, 5, -6])); + } + + [Fact] + public static void ProductOfDifferences_ThrowsForEmptyAndMismatchedLengths() + { + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(ReadOnlySpan.Empty, ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(ReadOnlySpan.Empty, new float[1])); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(new float[1], ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(new float[44], new float[43])); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(new float[43], new float[44])); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void ProductOfDifferences(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + float[] y = CreateAndFillTensor(tensorLength); + + float f = x[0] - y[0]; + for (int i = 1; i < x.Length; i++) + { + f *= x[i] - y[i]; + } + Assert.Equal(f, TensorPrimitives.ProductOfDifferences(x, y), Tolerance); + } + + [Fact] + public static void ProductOfDifferences_KnownValues() + { + Assert.Equal(0, TensorPrimitives.ProductOfDifferences([0], [0])); + Assert.Equal(0, TensorPrimitives.ProductOfDifferences([1], [1])); + Assert.Equal(1, TensorPrimitives.ProductOfDifferences([1], [0])); + Assert.Equal(-1, TensorPrimitives.ProductOfDifferences([0], [1])); + Assert.Equal(-1, TensorPrimitives.ProductOfDifferences([1, 2, 3, 4, 5], [2, 3, 4, 5, 6])); + Assert.Equal(120, TensorPrimitives.ProductOfDifferences([1, 2, 3, 4, 5], [0, 0, 0, 0, 0])); + Assert.Equal(-120, TensorPrimitives.ProductOfDifferences([0, 0, 0, 0, 0], [1, 2, 3, 4, 5])); + Assert.Equal(float.NaN, TensorPrimitives.ProductOfDifferences([1, 2, float.NaN, 4, 5], [0, 0, 0, 0, 0])); + } + + [Fact] + public static void ProductOfSums_ThrowsForEmptyAndMismatchedLengths() + { + Assert.Throws(() => TensorPrimitives.ProductOfSums(ReadOnlySpan.Empty, ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.ProductOfSums(ReadOnlySpan.Empty, new float[1])); + Assert.Throws(() => TensorPrimitives.ProductOfSums(new float[1], ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.ProductOfSums(new float[44], new float[43])); + Assert.Throws(() => TensorPrimitives.ProductOfSums(new float[43], new float[44])); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void ProductOfSums(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + float[] y = CreateAndFillTensor(tensorLength); + + float f = x[0] + y[0]; + for (int i = 1; i < x.Length; i++) + { + f *= x[i] + y[i]; + } + Assert.Equal(f, TensorPrimitives.ProductOfSums(x, y), Tolerance); + } + + [Fact] + public static void ProductOfSums_KnownValues() + { + Assert.Equal(0, TensorPrimitives.ProductOfSums([0], [0])); + Assert.Equal(1, TensorPrimitives.ProductOfSums([0], [1])); + Assert.Equal(1, TensorPrimitives.ProductOfSums([1], [0])); + Assert.Equal(2, TensorPrimitives.ProductOfSums([1], [1])); + Assert.Equal(10395, TensorPrimitives.ProductOfSums([1, 2, 3, 4, 5], [2, 3, 4, 5, 6])); + Assert.Equal(120, TensorPrimitives.ProductOfSums([1, 2, 3, 4, 5], [0, 0, 0, 0, 0])); + Assert.Equal(120, TensorPrimitives.ProductOfSums([0, 0, 0, 0, 0], [1, 2, 3, 4, 5])); + Assert.Equal(float.NaN, TensorPrimitives.ProductOfSums([1, 2, float.NaN, 4, 5], [0, 0, 0, 0, 0])); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Sum(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + + Assert.Equal(Enumerable.Sum(x), TensorPrimitives.Sum(x), Tolerance); + + float sum = 0; + foreach (float f in x) + { + sum += f; + } + Assert.Equal(sum, TensorPrimitives.Sum(x), Tolerance); + } + + [Fact] + public static void Sum_KnownValues() + { + Assert.Equal(0, TensorPrimitives.Sum([0])); + Assert.Equal(1, TensorPrimitives.Sum([0, 1])); + Assert.Equal(6, TensorPrimitives.Sum([1, 2, 3])); + Assert.Equal(0, TensorPrimitives.Sum([-3, 0, 3])); + Assert.Equal(float.NaN, TensorPrimitives.Sum([-3, float.NaN, 3])); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void SumOfSquares(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + + Assert.Equal(Enumerable.Sum(x, v => v * v), TensorPrimitives.SumOfSquares(x), Tolerance); + + float sum = 0; + foreach (float f in x) + { + sum += f * f; + } + Assert.Equal(sum, TensorPrimitives.SumOfSquares(x), Tolerance); + } + + [Fact] + public static void SumOfSquares_KnownValues() + { + Assert.Equal(0, TensorPrimitives.SumOfSquares([0])); + Assert.Equal(1, TensorPrimitives.SumOfSquares([0, 1])); + Assert.Equal(14, TensorPrimitives.SumOfSquares([1, 2, 3])); + Assert.Equal(18, TensorPrimitives.SumOfSquares([-3, 0, 3])); + Assert.Equal(float.NaN, TensorPrimitives.SumOfSquares([-3, float.NaN, 3])); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void SumOfMagnitudes(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + + Assert.Equal(Enumerable.Sum(x, MathF.Abs), TensorPrimitives.SumOfMagnitudes(x), Tolerance); + + float sum = 0; + foreach (float f in x) + { + sum += MathF.Abs(f); + } + Assert.Equal(sum, TensorPrimitives.SumOfMagnitudes(x), Tolerance); + } + + [Fact] + public static void SumOfMagnitudes_KnownValues() + { + Assert.Equal(0, TensorPrimitives.SumOfMagnitudes([0])); + Assert.Equal(1, TensorPrimitives.SumOfMagnitudes([0, 1])); + Assert.Equal(6, TensorPrimitives.SumOfMagnitudes([1, 2, 3])); + Assert.Equal(6, TensorPrimitives.SumOfMagnitudes([-3, 0, 3])); + Assert.Equal(float.NaN, TensorPrimitives.SumOfMagnitudes([-3, float.NaN, 3])); + } } } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs new file mode 100644 index 00000000000000..b8516942f5baa9 --- /dev/null +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Numerics.Tensors.Tests +{ + public static partial class TensorPrimitivesTests + { + [Theory] + [InlineData(0)] + [MemberData(nameof(TensorLengths))] + public static void ConvertToHalf(int tensorLength) + { + float[] source = CreateAndFillTensor(tensorLength); + foreach (int destLength in new[] { source.Length, source.Length + 1 }) + { + Half[] destination = new Half[destLength]; + + TensorPrimitives.ConvertToHalf(source, destination); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal((Half)source[i], destination[i]); + } + + if (destination.Length > source.Length) + { + for (int i = source.Length; i < destination.Length; i++) + { + Assert.Equal(Half.Zero, destination[i]); + } + } + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void ConvertToHalf_ThrowsForTooShortDestination(int tensorLength) + { + float[] source = CreateAndFillTensor(tensorLength); + Half[] destination = new Half[source.Length - 1]; + + AssertExtensions.Throws("destination", () => TensorPrimitives.ConvertToHalf(source, destination)); + } + + [Theory] + [InlineData(0)] + [MemberData(nameof(TensorLengths))] + public static void ConvertToSingle(int tensorLength) + { + Half[] source = new Half[tensorLength]; + for (int i = 0; i < source.Length; i++) + { + source[i] = (Half)s_random.NextSingle(); + } + + foreach (int destLength in new[] { source.Length, source.Length + 1 }) + { + float[] destination = new float[destLength]; + + TensorPrimitives.ConvertToSingle(source, destination); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal((float)source[i], destination[i]); + } + + if (destination.Length > source.Length) + { + for (int i = source.Length; i < destination.Length; i++) + { + Assert.Equal(0f, destination[i]); + } + } + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void ConvertToSingle_ThrowsForTooShortDestination(int tensorLength) + { + Half[] source = new Half[tensorLength]; + float[] destination = new float[source.Length - 1]; + + AssertExtensions.Throws("destination", () => TensorPrimitives.ConvertToSingle(source, destination)); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 7d20cc72202dd1..266e49fc39dd94 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1033,7 +1033,7 @@ public static double Min(double val1, double val2) // // It propagates NaN inputs back to the caller and // otherwise returns the lesser of the inputs. It - // treats +0 as lesser than -0 as per the specification. + // treats +0 as greater than -0 as per the specification. if (val1 != val2) { @@ -1091,7 +1091,7 @@ public static float Min(float val1, float val2) // // It propagates NaN inputs back to the caller and // otherwise returns the lesser of the inputs. It - // treats +0 as lesser than -0 as per the specification. + // treats +0 as greater than -0 as per the specification. if (val1 != val2) { @@ -1145,7 +1145,7 @@ public static double MinMagnitude(double x, double y) // // It propagates NaN inputs back to the caller and // otherwise returns the input with a lesser magnitude. - // It treats +0 as lesser than -0 as per the specification. + // It treats +0 as greater than -0 as per the specification. double ax = Abs(x); double ay = Abs(y); diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 2726d14492f6ab..de0efc14f0ac4f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -285,7 +285,7 @@ public static float MinMagnitude(float x, float y) // // It propagates NaN inputs back to the caller and // otherwise returns the input with a lesser magnitude. - // It treats +0 as lesser than -0 as per the specification. + // It treats +0 as greater than -0 as per the specification. float ax = Abs(x); float ay = Abs(y); From a1cc4074fc849a518670451b555ee660067857b8 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 15 Sep 2023 21:06:34 -0400 Subject: [PATCH 2/3] Cleanup after previous PR, vectorize CosineSimilarity/Dot/L2Normalize/Distance, add tests --- .../Numerics/Tensors/TensorPrimitives.cs | 79 +++--- .../Tensors/TensorPrimitives.netcore.cs | 154 ++++++++++- .../Tensors/TensorPrimitives.netstandard.cs | 76 +++++- .../tests/TensorPrimitivesTests.cs | 247 +++++++++++------- 4 files changed, 406 insertions(+), 150 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs index 3dd7814dca9908..e24246249812cc 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs @@ -262,13 +262,13 @@ public static void Tanh(ReadOnlySpan x, Span destination) /// '' and '' must not be empty. public static float CosineSimilarity(ReadOnlySpan x, ReadOnlySpan y) { - if (x.Length != y.Length) + if (x.IsEmpty) { - ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - if (x.Length == 0 || y.Length == 0) + if (x.Length != y.Length) { - ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); } float dotprod = 0f; @@ -295,24 +295,16 @@ public static float CosineSimilarity(ReadOnlySpan x, ReadOnlySpan /// '' and '' must not be empty. public static float Distance(ReadOnlySpan x, ReadOnlySpan y) { - if (x.Length != y.Length) - { - ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); - } - if (x.Length == 0 || y.Length == 0) + if (x.IsEmpty) { ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - - float distance = 0f; - - for (int i = 0; i < x.Length; i++) + if (x.Length != y.Length) { - float dist = x[i] - y[i]; - distance += dist * dist; + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); } - return MathF.Sqrt(distance); + return MathF.Sqrt(Aggregate(0f, x, y)); } /// @@ -329,14 +321,7 @@ public static float Dot(ReadOnlySpan x, ReadOnlySpan y) // BLAS1: ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); } - float dotprod = 0f; - - for (int i = 0; i < x.Length; i++) - { - dotprod += x[i] * y[i]; - } - - return dotprod; + return Aggregate(0f, x, y); } /// @@ -346,14 +331,7 @@ public static float Dot(ReadOnlySpan x, ReadOnlySpan y) // BLAS1: /// The L2 norm. public static float L2Normalize(ReadOnlySpan x) // BLAS1: nrm2 { - float magx = 0f; - - for (int i = 0; i < x.Length; i++) - { - magx += x[i] * x[i]; - } - - return MathF.Sqrt(magx); + return MathF.Sqrt(Aggregate(0f, x)); } /// @@ -365,13 +343,13 @@ public static float L2Normalize(ReadOnlySpan x) // BLAS1: nrm2 /// '' must not be empty. public static void SoftMax(ReadOnlySpan x, Span destination) { - if (x.Length > destination.Length) + if (x.IsEmpty) { - ThrowHelper.ThrowArgument_DestinationTooShort(); + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - if (x.Length == 0) + if (x.Length > destination.Length) { - ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + ThrowHelper.ThrowArgument_DestinationTooShort(); } float expSum = 0f; @@ -396,13 +374,13 @@ public static void SoftMax(ReadOnlySpan x, Span destination) /// '' must not be empty. public static void Sigmoid(ReadOnlySpan x, Span destination) { - if (x.Length > destination.Length) + if (x.IsEmpty) { - ThrowHelper.ThrowArgument_DestinationTooShort(); + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - if (x.Length == 0) + if (x.Length > destination.Length) { - ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + ThrowHelper.ThrowArgument_DestinationTooShort(); } for (int i = 0; i < x.Length; i++) @@ -497,7 +475,7 @@ public static float Min(ReadOnlySpan x) return result; } - /// Computes the maximum magntude of any element in . + /// Computes the maximum magnitude of any element in . /// The tensor, represented as a span. /// The maximum magnitude of any element in . /// Length of '' must be greater than zero. @@ -544,7 +522,7 @@ public static float MaxMagnitude(ReadOnlySpan x) return resultMag; } - /// Computes the minimum magntude of any element in . + /// Computes the minimum magnitude of any element in . /// The tensor, represented as a span. /// The minimum magnitude of any element in . /// Length of '' must be greater than zero. @@ -624,7 +602,7 @@ public static unsafe int IndexOfMax(ReadOnlySpan x) max = current; } } - else if (IsNegative(max)) + else if (IsNegative(max) && !IsNegative(current)) { result = i; max = current; @@ -668,7 +646,7 @@ public static unsafe int IndexOfMin(ReadOnlySpan x) min = current; } } - else if (IsNegative(current)) + else if (IsNegative(current) && !IsNegative(min)) { result = i; min = current; @@ -716,7 +694,7 @@ public static unsafe int IndexOfMaxMagnitude(ReadOnlySpan x) maxMag = currentMag; } } - else if (IsNegative(max)) + else if (IsNegative(max) && !IsNegative(current)) { result = i; max = current; @@ -737,6 +715,7 @@ public static unsafe int IndexOfMinMagnitude(ReadOnlySpan x) if (!x.IsEmpty) { + float min = float.PositiveInfinity; float minMag = float.PositiveInfinity; for (int i = 0; i < x.Length; i++) @@ -759,12 +738,14 @@ public static unsafe int IndexOfMinMagnitude(ReadOnlySpan x) if (currentMag < minMag) { result = i; + min = current; minMag = currentMag; } } - else if (IsNegative(current)) + else if (IsNegative(current) && !IsNegative(min)) { result = i; + min = current; minMag = currentMag; } } @@ -777,14 +758,14 @@ public static unsafe int IndexOfMinMagnitude(ReadOnlySpan x) /// The tensor, represented as a span. /// The result of adding all elements in , or zero if is empty. public static float Sum(ReadOnlySpan x) => - Aggregate(0.0f, x); + Aggregate(0f, x); /// Computes the sum of the squares of every element in . /// The tensor, represented as a span. /// The result of adding every element in multiplied by itself, or zero if is empty. /// This method effectively does .Sum(.Multiply(, )). public static float SumOfSquares(ReadOnlySpan x) => - Aggregate(0.0f, x); + Aggregate(0f, x); /// Computes the sum of the absolute values of every element in . /// The tensor, represented as a span. @@ -794,7 +775,7 @@ public static float SumOfSquares(ReadOnlySpan x) => /// This method corresponds to the asum method defined by BLAS1. /// public static float SumOfMagnitudes(ReadOnlySpan x) => - Aggregate(0.0f, x); + Aggregate(0f, x); /// Computes the product of all elements in . /// The tensor, represented as a span. diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs index 54cb82795eba2a..34f61199ace159 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -52,6 +51,121 @@ public static void ConvertToSingle(ReadOnlySpan source, Span destin private static bool IsNegative(float f) => float.IsNegative(f); + private static float CosineSimilarityCore(ReadOnlySpan x, ReadOnlySpan y) + { + // Compute the same as: + // TensorPrimitives.Dot(x, y) / (Math.Sqrt(TensorPrimitives.SumOfSquares(x)) * Math.Sqrt(TensorPrimitives.SumOfSquares(y))) + // but only looping over each span once. + + float dotProduct = 0f; + float xSumOfSquares = 0f; + float ySumOfSquares = 0f; + + int i = 0; + +#if NET8_0_OR_GREATER + if (Vector512.IsHardwareAccelerated && x.Length >= Vector512.Count) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + Vector512 dotProductVector = Vector512.Zero; + Vector512 xSumOfSquaresVector = Vector512.Zero; + Vector512 ySumOfSquaresVector = Vector512.Zero; + + // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. + int oneVectorFromEnd = x.Length - Vector.Count; + do + { + Vector512 xVec = Vector512.LoadUnsafe(ref xRef, (uint)i); + Vector512 yVec = Vector512.LoadUnsafe(ref yRef, (uint)i); + + dotProductVector += xVec * yVec; + xSumOfSquaresVector += xVec * xVec; + ySumOfSquaresVector += yVec * yVec; + + i += Vector512.Count; + } + while (i <= oneVectorFromEnd); + + // Sum the vector lanes into the scalar result. + dotProduct += Vector512.Sum(dotProductVector); + xSumOfSquares += Vector512.Sum(xSumOfSquaresVector); + ySumOfSquares += Vector512.Sum(ySumOfSquaresVector); + } + else +#endif + if (Vector256.IsHardwareAccelerated && x.Length >= Vector256.Count) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + Vector256 dotProductVector = Vector256.Zero; + Vector256 xSumOfSquaresVector = Vector256.Zero; + Vector256 ySumOfSquaresVector = Vector256.Zero; + + // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. + int oneVectorFromEnd = x.Length - Vector.Count; + do + { + Vector256 xVec = Vector256.LoadUnsafe(ref xRef, (uint)i); + Vector256 yVec = Vector256.LoadUnsafe(ref yRef, (uint)i); + + dotProductVector += (xVec * yVec); + xSumOfSquaresVector += (xVec * xVec); + ySumOfSquaresVector += (yVec * yVec); + + i += Vector256.Count; + } + while (i <= oneVectorFromEnd); + + // Sum the vector lanes into the scalar result. + dotProduct += Vector256.Sum(dotProductVector); + xSumOfSquares += Vector256.Sum(xSumOfSquaresVector); + ySumOfSquares += Vector256.Sum(ySumOfSquaresVector); + } + else if (Vector128.IsHardwareAccelerated && x.Length >= Vector128.Count) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + Vector128 dotProductVector = Vector128.Zero; + Vector128 xSumOfSquaresVector = Vector128.Zero; + Vector128 ySumOfSquaresVector = Vector128.Zero; + + // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. + int oneVectorFromEnd = x.Length - Vector.Count; + do + { + Vector128 xVec = Vector128.LoadUnsafe(ref xRef, (uint)i); + Vector128 yVec = Vector128.LoadUnsafe(ref yRef, (uint)i); + + dotProductVector += (xVec * yVec); + xSumOfSquaresVector += (xVec * xVec); + ySumOfSquaresVector += (yVec * yVec); + + i += Vector128.Count; + } + while (i <= oneVectorFromEnd); + + // Sum the vector lanes into the scalar result. + dotProduct += Vector128.Sum(dotProductVector); + xSumOfSquares += Vector128.Sum(xSumOfSquaresVector); + ySumOfSquares += Vector128.Sum(ySumOfSquaresVector); + } + + // Process any remaining elements past the last vector. + for (; (uint)i < (uint)x.Length; i++) + { + dotProduct += x[i] * y[i]; + xSumOfSquares += x[i] * x[i]; + ySumOfSquares += y[i] * y[i]; + } + + // Sum(X * Y) / (|X| * |Y|) + return dotProduct / (MathF.Sqrt(xSumOfSquares) * MathF.Sqrt(ySumOfSquares)); + } + private static float Aggregate( float identityValue, ReadOnlySpan x) where TLoad : IUnaryOperator @@ -142,9 +256,6 @@ private static float Aggregate( where TBinary : IBinaryOperator where TAggregate : IBinaryOperator { - Debug.Assert(!x.IsEmpty); - Debug.Assert(x.Length == y.Length); - // Initialize the result to the identity value float result = identityValue; int i = 0; @@ -947,6 +1058,41 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( #endif } + private readonly struct SubtractSquaredOperator : IBinaryOperator + { + public static float Invoke(float x, float y) + { + float tmp = x - y; + return tmp * tmp; + } + + public static Vector128 Invoke(Vector128 x, Vector128 y) + { + Vector128 tmp = x - y; + return tmp * tmp; + } + + public static Vector256 Invoke(Vector256 x, Vector256 y) + { + Vector256 tmp = x - y; + return tmp * tmp; + } + +#if NET8_0_OR_GREATER + public static Vector512 Invoke(Vector512 x, Vector512 y) + { + Vector512 tmp = x - y; + return tmp * tmp; + } +#endif + + public static float Invoke(Vector128 x) => throw new NotSupportedException(); + public static float Invoke(Vector256 x) => throw new NotSupportedException(); +#if NET8_0_OR_GREATER + public static float Invoke(Vector512 x) => throw new NotSupportedException(); +#endif + } + private readonly struct MultiplyOperator : IBinaryOperator { public static float Invoke(float x, float y) => x * y; diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs index 39c20aeaa5996a..134364708b8e5d 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -11,6 +10,63 @@ public static partial class TensorPrimitives { private static unsafe bool IsNegative(float f) => *(int*)&f < 0; + private static float CosineSimilarityCore(ReadOnlySpan x, ReadOnlySpan y) + { + // Compute the same as: + // TensorPrimitives.Dot(x, y) / (Math.Sqrt(TensorPrimitives.SumOfSquares(x)) * Math.Sqrt(TensorPrimitives.SumOfSquares(y))) + // but only looping over each span once. + + float dotProduct = 0f; + float xSumOfSquares = 0f; + float ySumOfSquares = 0f; + + int i = 0; + + if (Vector.IsHardwareAccelerated && x.Length >= Vector.Count) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + Vector dotProductVector = Vector.Zero; + Vector xSumOfSquaresVector = Vector.Zero; + Vector ySumOfSquaresVector = Vector.Zero; + + // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. + int oneVectorFromEnd = x.Length - Vector.Count; + do + { + Vector xVec = AsVector(ref xRef, i); + Vector yVec = AsVector(ref yRef, i); + + dotProductVector += xVec * yVec; + xSumOfSquaresVector += xVec * xVec; + ySumOfSquaresVector += yVec * yVec; + + i += Vector.Count; + } + while (i <= oneVectorFromEnd); + + // Sum the vector lanes into the scalar result. + for (int e = 0; e < Vector.Count; e++) + { + dotProduct += dotProductVector[e]; + xSumOfSquares += xSumOfSquaresVector[e]; + ySumOfSquares += ySumOfSquaresVector[e]; + } + } + + // Process any remaining elements past the last vector. + for (; (uint)i < (uint)x.Length; i++) + { + dotProduct += x[i] * y[i]; + xSumOfSquares += x[i] * x[i]; + ySumOfSquares += y[i] * y[i]; + } + + // Sum(X * Y) / (|X| * |Y|) + return dotProduct / (MathF.Sqrt(xSumOfSquares) * MathF.Sqrt(ySumOfSquares)); + } + private static float Aggregate( float identityValue, ReadOnlySpan x, TLoad load = default, TAggregate aggregate = default) where TLoad : IUnaryOperator @@ -59,9 +115,6 @@ private static float Aggregate( where TBinary : IBinaryOperator where TAggregate : IBinaryOperator { - Debug.Assert(!x.IsEmpty); - Debug.Assert(x.Length == y.Length); - // Initialize the result to the identity value float result = identityValue; int i = 0; @@ -453,6 +506,21 @@ ref Unsafe.As>( public Vector Invoke(Vector x, Vector y) => x - y; } + private readonly struct SubtractSquaredOperator : IBinaryOperator + { + public float Invoke(float x, float y) + { + float tmp = x - y; + return tmp * tmp; + } + + public Vector Invoke(Vector x, Vector y) + { + Vector tmp = x - y; + return tmp * tmp; + } + } + private readonly struct MultiplyOperator : IBinaryOperator { public float Invoke(float x, float y) => x * y; diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs index 5b4ba59eecf5c8..fc98bf53105e29 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs @@ -4,8 +4,6 @@ using Xunit; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -using System.Diagnostics; #pragma warning disable xUnit1025 // reporting duplicate test cases due to not distinguishing 0.0 from -0.0 @@ -13,18 +11,15 @@ namespace System.Numerics.Tensors.Tests { public static partial class TensorPrimitivesTests { - private const double Tolerance = 0.00001; + private const double Tolerance = 0.0001; public static IEnumerable TensorLengths => - from length in new[] { 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 17, 31, 32, 33, 100 } + from length in Enumerable.Range(1, 128) select new object[] { length }; private static readonly Random s_random = new Random(20230828); - private static float[] CreateTensor(int size) - { - return new float[size]; - } + private static float[] CreateTensor(int size) => new float[size]; private static float[] CreateAndFillTensor(int size) { @@ -719,27 +714,43 @@ public static void CosineSimilarity_ThrowsForMismatchedLengths_x_y(int tensorLen [Fact] public static void CosineSimilarity_ThrowsForEmpty_x_y() { - float[] x = []; - float[] y = []; - - Assert.Throws(() => TensorPrimitives.CosineSimilarity(x, y)); + Assert.Throws(() => TensorPrimitives.CosineSimilarity(ReadOnlySpan.Empty, ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.CosineSimilarity(ReadOnlySpan.Empty, CreateTensor(1))); + Assert.Throws(() => TensorPrimitives.CosineSimilarity(CreateTensor(1), ReadOnlySpan.Empty)); } [Theory] - [InlineData(new float[] { 3, 2, 0, 5 }, new float[] { 1, 0, 0, 0 }, 0.49f)] + [InlineData(new float[] { 3, 2, 0, 5 }, new float[] { 1, 0, 0, 0 }, 0.48666f)] [InlineData(new float[] { 1, 1, 1, 1, 1, 0 }, new float[] { 1, 1, 1, 1, 0, 1 }, 0.80f)] - public static void CosineSimilarity(float[] x, float[] y, float expectedResult) + public static void CosineSimilarity_KnownValues(float[] x, float[] y, float expectedResult) + { + Assert.Equal(expectedResult, TensorPrimitives.CosineSimilarity(x, y), Tolerance); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void CosineSimilarity(int tensorLength) { - Assert.Equal(expectedResult, TensorPrimitives.CosineSimilarity(x, y), .01f); + float[] x = CreateAndFillTensor(tensorLength); + float[] y = CreateAndFillTensor(tensorLength); + + float dot = 0f, squareX = 0f, squareY = 0f; + for (int i = 0; i < x.Length; i++) + { + dot += x[i] * y[i]; + squareX += x[i] * x[i]; + squareY += y[i] * y[i]; + } + + Assert.Equal(dot / (Math.Sqrt(squareX) * Math.Sqrt(squareY)), TensorPrimitives.CosineSimilarity(x, y), Tolerance); } [Fact] public static void Distance_ThrowsForEmpty_x_y() { - float[] x = []; - float[] y = []; - - Assert.Throws(() => TensorPrimitives.Distance(x, y)); + Assert.Throws(() => TensorPrimitives.Distance(ReadOnlySpan.Empty, ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.Distance(ReadOnlySpan.Empty, CreateTensor(1))); + Assert.Throws(() => TensorPrimitives.Distance(CreateTensor(1), ReadOnlySpan.Empty)); } [Theory] @@ -757,9 +768,25 @@ public static void Distance_ThrowsForMismatchedLengths_x_y(int tensorLength) [InlineData(new float[] { 0, 4 }, new float[] { 6, 2 }, 6.3245f)] [InlineData(new float[] { 1, 2, 3 }, new float[] { 4, 5, 6 }, 5.1961f)] [InlineData(new float[] { 5, 1, 6, 10 }, new float[] { 7, 2, 8, 4 }, 6.7082f)] - public static void Distance(float[] x, float[] y, float expectedResult) + public static void Distance_KnownValues(float[] x, float[] y, float expectedResult) + { + Assert.Equal(expectedResult, TensorPrimitives.Distance(x, y), Tolerance); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Distance(int tensorLength) { - Assert.Equal(expectedResult, TensorPrimitives.Distance(x, y), .001f); + float[] x = CreateAndFillTensor(tensorLength); + float[] y = CreateAndFillTensor(tensorLength); + + float distance = 0f; + for (int i = 0; i < x.Length; i++) + { + distance += (x[i] - y[i]) * (x[i] - y[i]); + } + + Assert.Equal(Math.Sqrt(distance), TensorPrimitives.Distance(x, y), Tolerance); } [Theory] @@ -777,20 +804,51 @@ public static void Dot_ThrowsForMismatchedLengths_x_y(int tensorLength) [InlineData(new float[] { 1, 2, 3 }, new float[] { 4, 5, 6 }, 32)] [InlineData(new float[] { 1, 2, 3, 10, 8 }, new float[] { 4, 5, 6, -2, 7 }, 68)] [InlineData(new float[] { }, new float[] { }, 0)] - public static void Dot(float[] x, float[] y, float expectedResult) + public static void Dot_KnownValues(float[] x, float[] y, float expectedResult) + { + Assert.Equal(expectedResult, TensorPrimitives.Dot(x, y), Tolerance); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Dot(int tensorLength) { - Assert.Equal(expectedResult, TensorPrimitives.Dot(x, y), .001f); + float[] x = CreateAndFillTensor(tensorLength); + float[] y = CreateAndFillTensor(tensorLength); + + float dot = 0f; + for (int i = 0; i < x.Length; i++) + { + dot += x[i] * y[i]; + } + + Assert.Equal(dot, TensorPrimitives.Dot(x, y), Tolerance); } [Theory] - [InlineData(new float[] { 1, 2, 3 }, 3.7416)] + [InlineData(new float[] { 1, 2, 3 }, 3.7416575f)] [InlineData(new float[] { 3, 4 }, 5)] [InlineData(new float[] { 3 }, 3)] - [InlineData(new float[] { 3, 4, 1, 2 }, 5.477)] + [InlineData(new float[] { 3, 4, 1, 2 }, 5.477226)] [InlineData(new float[] { }, 0f)] - public static void L2Normalize(float[] x, float expectedResult) + public static void L2Normalize_KnownValues(float[] x, float expectedResult) { - Assert.Equal(expectedResult, TensorPrimitives.L2Normalize(x), .001f); + Assert.Equal(expectedResult, TensorPrimitives.L2Normalize(x), Tolerance); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void L2Normalize(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + + float sumOfSquares = 0f; + for (int i = 0; i < x.Length; i++) + { + sumOfSquares += x[i] * x[i]; + } + + Assert.Equal(Math.Sqrt(sumOfSquares), TensorPrimitives.L2Normalize(x), Tolerance); } [Theory] @@ -805,41 +863,38 @@ public static void SoftMax_ThrowsForTooShortDestination(int tensorLength) [Theory] [InlineData(new float[] { 3, 1, .2f }, new float[] { 0.8360188f, 0.11314284f, 0.05083836f })] - [InlineData(new float[] { 3, 4, 1 }, new float[] { 0.2594f, 0.7052f, 0.0351f })] + [InlineData(new float[] { 3, 4, 1 }, new float[] { 0.2594f, 0.705384f, 0.0351f })] [InlineData(new float[] { 5, 3 }, new float[] { 0.8807f, 0.1192f })] [InlineData(new float[] { 4, 2, 1, 9 }, new float[] { 0.0066f, 9.04658e-4f, 3.32805e-4f, 0.9920f})] public static void SoftMax(float[] x, float[] expectedResult) { - var dest = new float[x.Length]; + float[] dest = CreateTensor(x.Length); TensorPrimitives.SoftMax(x, dest); for (int i = 0; i < x.Length; i++) { - Assert.Equal(expectedResult[i], dest[i], .001f); + Assert.Equal(expectedResult[i], dest[i], Tolerance); } } [Fact] public static void SoftMax_DestinationLongerThanSource() { - var x = new float[] { 3, 1, .2f }; - var expectedResult = new float[] { 0.8360188f, 0.11314284f, 0.05083836f }; - var dest = new float[x.Length + 1]; + float[] x = [3, 1, .2f]; + float[] expectedResult = [0.8360188f, 0.11314284f, 0.05083836f]; + float[] dest = CreateTensor(x.Length + 1); TensorPrimitives.SoftMax(x, dest); for (int i = 0; i < x.Length; i++) { - Assert.Equal(expectedResult[i], dest[i], .001f); + Assert.Equal(expectedResult[i], dest[i], Tolerance); } } [Fact] - public static void SoftMax_ThrowsForEmpty_x_y() + public static void SoftMax_ThrowsForEmptyInput() { - var x = new float[] { }; - var dest = new float[x.Length]; - - AssertExtensions.Throws(() => TensorPrimitives.SoftMax(x, dest)); + AssertExtensions.Throws(() => TensorPrimitives.SoftMax(ReadOnlySpan.Empty, CreateTensor(1))); } [Theory] @@ -858,36 +913,35 @@ public static void Sigmoid_ThrowsForTooShortDestination(int tensorLength) [InlineData(new float[] { 0, -3, 3, .5f }, new float[] { 0.5f, 0.0474f, 0.9525f, 0.6224f })] public static void Sigmoid(float[] x, float[] expectedResult) { - var dest = new float[x.Length]; + float[] dest = CreateTensor(x.Length); TensorPrimitives.Sigmoid(x, dest); for (int i = 0; i < x.Length; i++) { - Assert.Equal(expectedResult[i], dest[i], .001f); + Assert.Equal(expectedResult[i], dest[i], Tolerance); } } [Fact] public static void Sigmoid_DestinationLongerThanSource() { - var x = new float[] { -5, -4.5f, -4 }; - var expectedResult = new float[] { 0.0066f, 0.0109f, 0.0179f }; - var dest = new float[x.Length + 1]; + float[] x = [-5, -4.5f, -4]; + float[] expectedResult = [0.0066f, 0.0109f, 0.0179f]; + float[] dest = CreateTensor(x.Length + 1); + TensorPrimitives.Sigmoid(x, dest); for (int i = 0; i < x.Length; i++) { - Assert.Equal(expectedResult[i], dest[i], .001f); + Assert.Equal(expectedResult[i], dest[i], Tolerance); } + Assert.Equal(0f, dest[dest.Length - 1]); } [Fact] - public static void Sigmoid_ThrowsForEmpty_x_y() + public static void Sigmoid_ThrowsForEmptyInput() { - var x = new float[] { }; - var dest = new float[x.Length]; - - AssertExtensions.Throws(() => TensorPrimitives.Sigmoid(x, dest)); + AssertExtensions.Throws(() => TensorPrimitives.Sigmoid(ReadOnlySpan.Empty, CreateTensor(1))); } [Fact] @@ -924,10 +978,12 @@ public static void IndexOfMax_FirstNaNReturned(int tensorLength) [Fact] public static void IndexOfMax_Negative0LesserThanPositive0() { - Assert.Equal(1, TensorPrimitives.IndexOfMax([-0.0f, +0.0f])); - Assert.Equal(0, TensorPrimitives.IndexOfMax([+0.0f, -0.0f])); - Assert.Equal(1, TensorPrimitives.IndexOfMax([-1, -0.0f])); - Assert.Equal(2, TensorPrimitives.IndexOfMax([-1, -0.0f, 1])); + Assert.Equal(1, TensorPrimitives.IndexOfMax([-0f, +0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMax([-0f, -0f, -0f, -0f])); + Assert.Equal(4, TensorPrimitives.IndexOfMax([-0f, -0f, -0f, -0f, +0f, +0f, +0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMax([+0f, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMax([-1, -0f])); + Assert.Equal(2, TensorPrimitives.IndexOfMax([-1, -0f, 1])); } [Fact] @@ -964,10 +1020,11 @@ public static void IndexOfMin_FirstNaNReturned(int tensorLength) [Fact] public static void IndexOfMin_Negative0LesserThanPositive0() { - Assert.Equal(0, TensorPrimitives.IndexOfMin([-0.0f, +0.0f])); - Assert.Equal(1, TensorPrimitives.IndexOfMin([+0.0f, -0.0f])); - Assert.Equal(0, TensorPrimitives.IndexOfMin([-1, -0.0f])); - Assert.Equal(0, TensorPrimitives.IndexOfMin([-1, -0.0f, 1])); + Assert.Equal(0, TensorPrimitives.IndexOfMin([-0f, +0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMin([+0f, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMin([+0f, -0f, -0f, -0f, -0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMin([-1, -0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMin([-1, -0f, 1])); } [Fact] @@ -1004,10 +1061,12 @@ public static void IndexOfMaxMagnitude_FirstNaNReturned(int tensorLength) [Fact] public static void IndexOfMaxMagnitude_Negative0LesserThanPositive0() { - Assert.Equal(1, TensorPrimitives.IndexOfMaxMagnitude([-0.0f, +0.0f])); - Assert.Equal(0, TensorPrimitives.IndexOfMaxMagnitude([+0.0f, -0.0f])); - Assert.Equal(0, TensorPrimitives.IndexOfMaxMagnitude([-1, -0.0f])); - Assert.Equal(2, TensorPrimitives.IndexOfMaxMagnitude([-1, -0.0f, 1])); + Assert.Equal(0, TensorPrimitives.IndexOfMaxMagnitude([-0f, -0f, -0f, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMaxMagnitude([-0f, +0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMaxMagnitude([-0f, +0f, +0f, +0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMaxMagnitude([+0f, -0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMaxMagnitude([-1, -0f])); + Assert.Equal(2, TensorPrimitives.IndexOfMaxMagnitude([-1, -0f, 1])); } [Fact] @@ -1022,7 +1081,7 @@ public static void IndexOfMinMagnitude(int tensorLength) { foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { - float[] x = new float[tensorLength]; + float[] x = CreateTensor(tensorLength); for (int i = 0; i < x.Length; i++) { x[i] = i % 2 == 0 ? 42 : -42; @@ -1050,10 +1109,12 @@ public static void IndexOfMinMagnitude_FirstNaNReturned(int tensorLength) [Fact] public static void IndexOfMinMagnitude_Negative0LesserThanPositive0() { - Assert.Equal(0, TensorPrimitives.IndexOfMinMagnitude([-0.0f, +0.0f])); - Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([+0.0f, -0.0f])); - Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([-1, -0.0f])); - Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([-1, -0.0f, 1])); + Assert.Equal(0, TensorPrimitives.IndexOfMinMagnitude([-0f, -0f, -0f, -0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMinMagnitude([-0f, +0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([+0f, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([+0f, -0f, -0f, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([-1, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([-1, -0f, 1])); } [Fact] @@ -1093,10 +1154,10 @@ public static void Max_NanReturned(int tensorLength) [Fact] public static void Max_Negative0LesserThanPositive0() { - Assert.Equal(+0.0f, TensorPrimitives.Max([-0.0f, +0.0f])); - Assert.Equal(+0.0f, TensorPrimitives.Max([+0.0f, -0.0f])); - Assert.Equal(-0.0f, TensorPrimitives.Max([-1, -0.0f])); - Assert.Equal(1, TensorPrimitives.Max([-1, -0.0f, 1])); + Assert.Equal(+0f, TensorPrimitives.Max([-0f, +0f])); + Assert.Equal(+0f, TensorPrimitives.Max([+0f, -0f])); + Assert.Equal(-0f, TensorPrimitives.Max([-1, -0f])); + Assert.Equal(1, TensorPrimitives.Max([-1, -0f, 1])); } [Fact] @@ -1136,12 +1197,12 @@ public static void MaxMagnitude_NanReturned(int tensorLength) [Fact] public static void MaxMagnitude_Negative0LesserThanPositive0() { - Assert.Equal(+0.0f, TensorPrimitives.MaxMagnitude([-0.0f, +0.0f])); - Assert.Equal(+0.0f, TensorPrimitives.MaxMagnitude([+0.0f, -0.0f])); - Assert.Equal(1, TensorPrimitives.MaxMagnitude([-1, -0.0f])); - Assert.Equal(1, TensorPrimitives.MaxMagnitude([-1, -0.0f, 1])); - Assert.Equal(0.0f, TensorPrimitives.MaxMagnitude([-0.0f, -0.0f, -0.0f, -0.0f, -0.0f, 0.0f])); - Assert.Equal(1, TensorPrimitives.MaxMagnitude([-0.0f, -0.0f, -0.0f, -0.0f, -1, -0.0f, 0.0f, 1])); + Assert.Equal(+0f, TensorPrimitives.MaxMagnitude([-0f, +0f])); + Assert.Equal(+0f, TensorPrimitives.MaxMagnitude([+0f, -0f])); + Assert.Equal(1, TensorPrimitives.MaxMagnitude([-1, -0f])); + Assert.Equal(1, TensorPrimitives.MaxMagnitude([-1, -0f, 1])); + Assert.Equal(0f, TensorPrimitives.MaxMagnitude([-0f, -0f, -0f, -0f, -0f, 0f])); + Assert.Equal(1, TensorPrimitives.MaxMagnitude([-0f, -0f, -0f, -0f, -1, -0f, 0f, 1])); } [Fact] @@ -1181,10 +1242,10 @@ public static void Min_NanReturned(int tensorLength) [Fact] public static void Min_Negative0LesserThanPositive0() { - Assert.Equal(-0.0f, TensorPrimitives.Min([-0.0f, +0.0f])); - Assert.Equal(-0.0f, TensorPrimitives.Min([+0.0f, -0.0f])); - Assert.Equal(-1, TensorPrimitives.Min([-1, -0.0f])); - Assert.Equal(-1, TensorPrimitives.Min([-1, -0.0f, 1])); + Assert.Equal(-0f, TensorPrimitives.Min([-0f, +0f])); + Assert.Equal(-0f, TensorPrimitives.Min([+0f, -0f])); + Assert.Equal(-1, TensorPrimitives.Min([-1, -0f])); + Assert.Equal(-1, TensorPrimitives.Min([-1, -0f, 1])); } [Fact] @@ -1224,10 +1285,10 @@ public static void MinMagnitude_NanReturned(int tensorLength) [Fact] public static void MinMagnitude_Negative0LesserThanPositive0() { - Assert.Equal(0, TensorPrimitives.MinMagnitude([-0.0f, +0.0f])); - Assert.Equal(0, TensorPrimitives.MinMagnitude([+0.0f, -0.0f])); - Assert.Equal(0, TensorPrimitives.MinMagnitude([-1, -0.0f])); - Assert.Equal(0, TensorPrimitives.MinMagnitude([-1, -0.0f, 1])); + Assert.Equal(0, TensorPrimitives.MinMagnitude([-0f, +0f])); + Assert.Equal(0, TensorPrimitives.MinMagnitude([+0f, -0f])); + Assert.Equal(0, TensorPrimitives.MinMagnitude([-1, -0f])); + Assert.Equal(0, TensorPrimitives.MinMagnitude([-1, -0f, 1])); } [Fact] @@ -1270,10 +1331,10 @@ public static void Product_KnownValues() public static void ProductOfDifferences_ThrowsForEmptyAndMismatchedLengths() { Assert.Throws(() => TensorPrimitives.ProductOfDifferences(ReadOnlySpan.Empty, ReadOnlySpan.Empty)); - Assert.Throws(() => TensorPrimitives.ProductOfDifferences(ReadOnlySpan.Empty, new float[1])); - Assert.Throws(() => TensorPrimitives.ProductOfDifferences(new float[1], ReadOnlySpan.Empty)); - Assert.Throws(() => TensorPrimitives.ProductOfDifferences(new float[44], new float[43])); - Assert.Throws(() => TensorPrimitives.ProductOfDifferences(new float[43], new float[44])); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(ReadOnlySpan.Empty, CreateTensor(1))); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(CreateTensor(1), ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(CreateTensor(44), CreateTensor(43))); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(CreateTensor(43), CreateTensor(44))); } [Theory] @@ -1308,10 +1369,10 @@ public static void ProductOfDifferences_KnownValues() public static void ProductOfSums_ThrowsForEmptyAndMismatchedLengths() { Assert.Throws(() => TensorPrimitives.ProductOfSums(ReadOnlySpan.Empty, ReadOnlySpan.Empty)); - Assert.Throws(() => TensorPrimitives.ProductOfSums(ReadOnlySpan.Empty, new float[1])); - Assert.Throws(() => TensorPrimitives.ProductOfSums(new float[1], ReadOnlySpan.Empty)); - Assert.Throws(() => TensorPrimitives.ProductOfSums(new float[44], new float[43])); - Assert.Throws(() => TensorPrimitives.ProductOfSums(new float[43], new float[44])); + Assert.Throws(() => TensorPrimitives.ProductOfSums(ReadOnlySpan.Empty, CreateTensor(1))); + Assert.Throws(() => TensorPrimitives.ProductOfSums(CreateTensor(1), ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.ProductOfSums(CreateTensor(44), CreateTensor(43))); + Assert.Throws(() => TensorPrimitives.ProductOfSums(CreateTensor(43), CreateTensor(44))); } [Theory] From 8a367958c75bbdde8ec8f3cafd87b680a2fe7a49 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sat, 16 Sep 2023 11:50:35 -0400 Subject: [PATCH 3/3] Address PR feedback, and fix a few other issues --- .../System/Buffers/BoundedMemory.Unix.cs | 7 +- .../System/Buffers/BoundedMemory.Windows.cs | 7 +- .../System/Buffers/BoundedMemory.cs | 21 + .../Numerics/Tensors/TensorPrimitives.cs | 5 +- .../Tensors/TensorPrimitives.netcore.cs | 18 +- .../tests/TensorPrimitivesTests.cs | 440 +++++++++--------- .../tests/TensorPrimitivesTests.netcore.cs | 10 +- 7 files changed, 263 insertions(+), 245 deletions(-) diff --git a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Unix.cs b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Unix.cs index b127a2a72491e9..c8197b0055092f 100644 --- a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Unix.cs +++ b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Unix.cs @@ -30,6 +30,8 @@ public UnixImplementation(int elementCount) public override bool IsReadonly => false; + public override int Length => _elementCount; + public override Memory Memory => _memoryManager.Memory; public override Span Span @@ -83,10 +85,7 @@ protected override void Dispose(bool disposing) // no-op; the handle will be disposed separately } - public override Span GetSpan() - { - throw new NotImplementedException(); - } + public override Span GetSpan() => _impl.Span; public override MemoryHandle Pin(int elementIndex) { diff --git a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs index 7b1cbbafc72dbc..96f40d61492e52 100644 --- a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs +++ b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs @@ -81,6 +81,8 @@ internal WindowsImplementation(VirtualAllocHandle handle, int byteOffsetIntoHand public override bool IsReadonly => (Protection != VirtualAllocProtection.PAGE_READWRITE); + public override int Length => _elementCount; + internal VirtualAllocProtection Protection { get @@ -189,10 +191,7 @@ protected override void Dispose(bool disposing) // no-op; the handle will be disposed separately } - public override Span GetSpan() - { - throw new NotImplementedException(); - } + public override Span GetSpan() => _impl.Span; public override MemoryHandle Pin(int elementIndex) { diff --git a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.cs b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.cs index d70b30721fdbd9..81d359cc80e926 100644 --- a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.cs +++ b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.cs @@ -14,6 +14,9 @@ public abstract class BoundedMemory : IDisposable where T : unmanaged /// public abstract bool IsReadonly { get; } + /// Gets the length of the instance. + public abstract int Length { get; } + /// /// Gets the which represents this native memory. /// This instance must be kept alive while working with the . @@ -44,5 +47,23 @@ public abstract class BoundedMemory : IDisposable where T : unmanaged /// OS does not support marking the memory block as read+write. /// public abstract void MakeWriteable(); + + /// + /// Gets the which represents this native memory. + /// This instance must be kept alive while working with the . + /// + public static implicit operator Span(BoundedMemory boundedMemory) => boundedMemory.Span; + + /// + /// Gets the which represents this native memory. + /// This instance must be kept alive while working with the . + /// + public static implicit operator ReadOnlySpan(BoundedMemory boundedMemory) => boundedMemory.Span; + + /// + /// Gets a reference to the element at the specified index. + /// This instance must be kept alive while working with the reference. + /// + public ref T this[int index] => ref Span[index]; } } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs index e24246249812cc..f9191108f1d671 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs @@ -533,7 +533,6 @@ public static float MinMagnitude(ReadOnlySpan x) ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - float result = float.PositiveInfinity; float resultMag = float.PositiveInfinity; for (int i = 0; i < x.Length; i++) @@ -555,18 +554,16 @@ public static float MinMagnitude(ReadOnlySpan x) if (currentMag < resultMag) { - result = current; resultMag = currentMag; } } else if (IsNegative(current)) { - result = current; resultMag = currentMag; } } - return result; + return resultMag; } /// Computes the index of the maximum element in . diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs index 34f61199ace159..4f40aa31494c97 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs @@ -74,7 +74,7 @@ private static float CosineSimilarityCore(ReadOnlySpan x, ReadOnlySpan ySumOfSquaresVector = Vector512.Zero; // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. - int oneVectorFromEnd = x.Length - Vector.Count; + int oneVectorFromEnd = x.Length - Vector512.Count; do { Vector512 xVec = Vector512.LoadUnsafe(ref xRef, (uint)i); @@ -105,15 +105,15 @@ private static float CosineSimilarityCore(ReadOnlySpan x, ReadOnlySpan ySumOfSquaresVector = Vector256.Zero; // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. - int oneVectorFromEnd = x.Length - Vector.Count; + int oneVectorFromEnd = x.Length - Vector256.Count; do { Vector256 xVec = Vector256.LoadUnsafe(ref xRef, (uint)i); Vector256 yVec = Vector256.LoadUnsafe(ref yRef, (uint)i); - dotProductVector += (xVec * yVec); - xSumOfSquaresVector += (xVec * xVec); - ySumOfSquaresVector += (yVec * yVec); + dotProductVector += xVec * yVec; + xSumOfSquaresVector += xVec * xVec; + ySumOfSquaresVector += yVec * yVec; i += Vector256.Count; } @@ -134,15 +134,15 @@ private static float CosineSimilarityCore(ReadOnlySpan x, ReadOnlySpan ySumOfSquaresVector = Vector128.Zero; // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. - int oneVectorFromEnd = x.Length - Vector.Count; + int oneVectorFromEnd = x.Length - Vector128.Count; do { Vector128 xVec = Vector128.LoadUnsafe(ref xRef, (uint)i); Vector128 yVec = Vector128.LoadUnsafe(ref yRef, (uint)i); - dotProductVector += (xVec * yVec); - xSumOfSquaresVector += (xVec * xVec); - ySumOfSquaresVector += (yVec * yVec); + dotProductVector += xVec * yVec; + xSumOfSquaresVector += xVec * xVec; + ySumOfSquaresVector += yVec * yVec; i += Vector128.Count; } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs index fc98bf53105e29..181d152e6ae979 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Xunit; +using System.Buffers; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; +using Xunit; #pragma warning disable xUnit1025 // reporting duplicate test cases due to not distinguishing 0.0 from -0.0 @@ -19,16 +21,16 @@ from length in Enumerable.Range(1, 128) private static readonly Random s_random = new Random(20230828); - private static float[] CreateTensor(int size) => new float[size]; + private static BoundedMemory CreateTensor(int size) => BoundedMemory.Allocate(size); - private static float[] CreateAndFillTensor(int size) + private static BoundedMemory CreateAndFillTensor(int size) { - float[] tensor = CreateTensor(size); - FillTensor(tensor); + BoundedMemory tensor = CreateTensor(size); + FillTensor(tensor.Span); return tensor; } - private static void FillTensor(float[] tensor) + private static void FillTensor(Span tensor) { for (int i = 0; i < tensor.Length; i++) { @@ -38,26 +40,23 @@ private static void FillTensor(float[] tensor) private static float NextSingle() { -#if NETCOREAPP - return s_random.NextSingle(); -#else - return (float)s_random.NextDouble(); -#endif + // For testing purposes, get a mix of negative and positive values. + return (float)((s_random.NextDouble() * 2) - 1); } [Theory] [MemberData(nameof(TensorLengths))] public static void AddTwoTensors(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Add(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] + y[i]), destination[i]); + Assert.Equal(x[i] + y[i], destination[i]); } } @@ -65,9 +64,9 @@ public static void AddTwoTensors(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTwoTensors_ThrowsForMismatchedLengths(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Add(x, y, destination)); } @@ -76,9 +75,9 @@ public static void AddTwoTensors_ThrowsForMismatchedLengths(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTwoTensors_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Add(x, y, destination)); } @@ -87,15 +86,15 @@ public static void AddTwoTensors_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTensorAndScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Add(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] + y), destination[i]); + Assert.Equal(x[i] + y, destination[i]); } } @@ -103,9 +102,9 @@ public static void AddTensorAndScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Add(x, y, destination)); } @@ -114,15 +113,15 @@ public static void AddTensorAndScalar_ThrowsForTooShortDestination(int tensorLen [MemberData(nameof(TensorLengths))] public static void SubtractTwoTensors(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Subtract(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] - y[i]), destination[i]); + Assert.Equal(x[i] - y[i], destination[i]); } } @@ -130,9 +129,9 @@ public static void SubtractTwoTensors(int tensorLength) [MemberData(nameof(TensorLengths))] public static void SubtractTwoTensors_ThrowsForMismatchedLengths(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Subtract(x, y, destination)); } @@ -141,9 +140,9 @@ public static void SubtractTwoTensors_ThrowsForMismatchedLengths(int tensorLengt [MemberData(nameof(TensorLengths))] public static void SubtractTwoTensors_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Subtract(x, y, destination)); } @@ -152,15 +151,15 @@ public static void SubtractTwoTensors_ThrowsForTooShortDestination(int tensorLen [MemberData(nameof(TensorLengths))] public static void SubtractTensorAndScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Subtract(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] - y), destination[i]); + Assert.Equal(x[i] - y, destination[i]); } } @@ -168,9 +167,9 @@ public static void SubtractTensorAndScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void SubtractTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Subtract(x, y, destination)); } @@ -179,15 +178,15 @@ public static void SubtractTensorAndScalar_ThrowsForTooShortDestination(int tens [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensors(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Multiply(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] * y[i]), destination[i]); + Assert.Equal(x[i] * y[i], destination[i]); } } @@ -195,9 +194,9 @@ public static void MultiplyTwoTensors(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensors_ThrowsForMismatchedLengths(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Multiply(x, y, destination)); } @@ -206,9 +205,9 @@ public static void MultiplyTwoTensors_ThrowsForMismatchedLengths(int tensorLengt [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensors_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Multiply(x, y, destination)); } @@ -217,15 +216,15 @@ public static void MultiplyTwoTensors_ThrowsForTooShortDestination(int tensorLen [MemberData(nameof(TensorLengths))] public static void MultiplyTensorAndScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Multiply(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] * y), destination[i]); + Assert.Equal(x[i] * y, destination[i]); } } @@ -233,9 +232,9 @@ public static void MultiplyTensorAndScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MultiplyTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Multiply(x, y, destination)); } @@ -244,15 +243,15 @@ public static void MultiplyTensorAndScalar_ThrowsForTooShortDestination(int tens [MemberData(nameof(TensorLengths))] public static void DivideTwoTensors(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Divide(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] / y[i]), destination[i]); + Assert.Equal(x[i] / y[i], destination[i]); } } @@ -260,9 +259,9 @@ public static void DivideTwoTensors(int tensorLength) [MemberData(nameof(TensorLengths))] public static void DivideTwoTensors_ThrowsForMismatchedLengths(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Divide(x, y, destination)); } @@ -271,9 +270,9 @@ public static void DivideTwoTensors_ThrowsForMismatchedLengths(int tensorLength) [MemberData(nameof(TensorLengths))] public static void DivideTwoTensors_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Divide(x, y, destination)); } @@ -282,15 +281,15 @@ public static void DivideTwoTensors_ThrowsForTooShortDestination(int tensorLengt [MemberData(nameof(TensorLengths))] public static void DivideTensorAndScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Divide(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] / y), destination[i]); + Assert.Equal(x[i] / y, destination[i]); } } @@ -298,9 +297,9 @@ public static void DivideTensorAndScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void DivideTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Divide(x, y, destination)); } @@ -309,8 +308,8 @@ public static void DivideTensorAndScalar_ThrowsForTooShortDestination(int tensor [MemberData(nameof(TensorLengths))] public static void NegateTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Negate(x, destination); @@ -324,8 +323,8 @@ public static void NegateTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void NegateTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Negate(x, destination)); } @@ -334,10 +333,10 @@ public static void NegateTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithThirdTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] multiplier = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.AddMultiply(x, y, multiplier, destination); @@ -351,10 +350,10 @@ public static void AddTwoTensorsAndMultiplyWithThirdTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] multiplier = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -363,10 +362,10 @@ public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForMismatchedLe [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForMismatchedLengths_x_multiplier(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] multiplier = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -375,10 +374,10 @@ public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForMismatchedLe [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] multiplier = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -387,10 +386,10 @@ public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForTooShortDest [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float multiplier = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.AddMultiply(x, y, multiplier, destination); @@ -404,10 +403,10 @@ public static void AddTwoTensorsAndMultiplyWithScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); float multiplier = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -416,10 +415,10 @@ public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForMismatchedLengths [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float multiplier = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -428,10 +427,10 @@ public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForTooShortDestinati [MemberData(nameof(TensorLengths))] public static void AddTensorAndScalarAndMultiplyWithTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] multiplier = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.AddMultiply(x, y, multiplier, destination); @@ -445,10 +444,10 @@ public static void AddTensorAndScalarAndMultiplyWithTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForMismatchedLengths_x_z(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] multiplier = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -457,10 +456,10 @@ public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForMismatchedLe [MemberData(nameof(TensorLengths))] public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] multiplier = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -469,10 +468,10 @@ public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForTooShortDest [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithThirdTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] addend = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.MultiplyAdd(x, y, addend, destination); @@ -486,10 +485,10 @@ public static void MultiplyTwoTensorsAndAddWithThirdTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] addend = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); } @@ -498,10 +497,10 @@ public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForMismatchedLe [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForMismatchedLengths_x_multiplier(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] addend = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory addend = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); } @@ -510,10 +509,10 @@ public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForMismatchedLe [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] addend = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); } @@ -522,10 +521,10 @@ public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForTooShortDest [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float addend = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.MultiplyAdd(x, y, addend, destination); @@ -539,10 +538,10 @@ public static void MultiplyTwoTensorsAndAddWithScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float addend = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); } @@ -551,10 +550,10 @@ public static void MultiplyTwoTensorsAndAddWithScalar_ThrowsForTooShortDestinati [MemberData(nameof(TensorLengths))] public static void MultiplyTensorAndScalarAndAddWithTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] addend = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.MultiplyAdd(x, y, addend, destination); @@ -568,10 +567,10 @@ public static void MultiplyTensorAndScalarAndAddWithTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MultiplyTensorAndScalarAndAddWithTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] addend = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); } @@ -580,8 +579,8 @@ public static void MultiplyTensorAndScalarAndAddWithTensor_ThrowsForTooShortDest [MemberData(nameof(TensorLengths))] public static void ExpTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Exp(x, destination); @@ -595,8 +594,8 @@ public static void ExpTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void ExpTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Exp(x, destination)); } @@ -605,8 +604,8 @@ public static void ExpTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void LogTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Log(x, destination); @@ -620,8 +619,8 @@ public static void LogTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void LogTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Log(x, destination)); } @@ -630,8 +629,8 @@ public static void LogTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void CoshTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Cosh(x, destination); @@ -645,8 +644,8 @@ public static void CoshTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void CoshTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Cosh(x, destination)); } @@ -655,8 +654,8 @@ public static void CoshTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void SinhTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Sinh(x, destination); @@ -670,8 +669,8 @@ public static void SinhTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void SinhTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Sinh(x, destination)); } @@ -680,8 +679,8 @@ public static void SinhTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void TanhTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Tanh(x, destination); @@ -695,8 +694,8 @@ public static void TanhTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void TanhTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Tanh(x, destination)); } @@ -705,8 +704,8 @@ public static void TanhTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void CosineSimilarity_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); Assert.Throws(() => TensorPrimitives.CosineSimilarity(x, y)); } @@ -731,8 +730,8 @@ public static void CosineSimilarity_KnownValues(float[] x, float[] y, float expe [MemberData(nameof(TensorLengths))] public static void CosineSimilarity(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float dot = 0f, squareX = 0f, squareY = 0f; for (int i = 0; i < x.Length; i++) @@ -757,8 +756,8 @@ public static void Distance_ThrowsForEmpty_x_y() [MemberData(nameof(TensorLengths))] public static void Distance_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); Assert.Throws(() => TensorPrimitives.Distance(x, y)); } @@ -777,8 +776,8 @@ public static void Distance_KnownValues(float[] x, float[] y, float expectedResu [MemberData(nameof(TensorLengths))] public static void Distance(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float distance = 0f; for (int i = 0; i < x.Length; i++) @@ -793,8 +792,8 @@ public static void Distance(int tensorLength) [MemberData(nameof(TensorLengths))] public static void Dot_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); Assert.Throws(() => TensorPrimitives.Dot(x, y)); } @@ -813,8 +812,8 @@ public static void Dot_KnownValues(float[] x, float[] y, float expectedResult) [MemberData(nameof(TensorLengths))] public static void Dot(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float dot = 0f; for (int i = 0; i < x.Length; i++) @@ -840,7 +839,7 @@ public static void L2Normalize_KnownValues(float[] x, float expectedResult) [MemberData(nameof(TensorLengths))] public static void L2Normalize(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float sumOfSquares = 0f; for (int i = 0; i < x.Length; i++) @@ -855,8 +854,8 @@ public static void L2Normalize(int tensorLength) [MemberData(nameof(TensorLengths))] public static void SoftMax_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.SoftMax(x, destination)); } @@ -868,7 +867,7 @@ public static void SoftMax_ThrowsForTooShortDestination(int tensorLength) [InlineData(new float[] { 4, 2, 1, 9 }, new float[] { 0.0066f, 9.04658e-4f, 3.32805e-4f, 0.9920f})] public static void SoftMax(float[] x, float[] expectedResult) { - float[] dest = CreateTensor(x.Length); + using BoundedMemory dest = CreateTensor(x.Length); TensorPrimitives.SoftMax(x, dest); for (int i = 0; i < x.Length; i++) @@ -882,7 +881,7 @@ public static void SoftMax_DestinationLongerThanSource() { float[] x = [3, 1, .2f]; float[] expectedResult = [0.8360188f, 0.11314284f, 0.05083836f]; - float[] dest = CreateTensor(x.Length + 1); + using BoundedMemory dest = CreateTensor(x.Length + 1); TensorPrimitives.SoftMax(x, dest); for (int i = 0; i < x.Length; i++) @@ -901,8 +900,8 @@ public static void SoftMax_ThrowsForEmptyInput() [MemberData(nameof(TensorLengths))] public static void Sigmoid_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Sigmoid(x, destination)); } @@ -913,7 +912,7 @@ public static void Sigmoid_ThrowsForTooShortDestination(int tensorLength) [InlineData(new float[] { 0, -3, 3, .5f }, new float[] { 0.5f, 0.0474f, 0.9525f, 0.6224f })] public static void Sigmoid(float[] x, float[] expectedResult) { - float[] dest = CreateTensor(x.Length); + using BoundedMemory dest = CreateTensor(x.Length); TensorPrimitives.Sigmoid(x, dest); for (int i = 0; i < x.Length; i++) @@ -927,15 +926,16 @@ public static void Sigmoid_DestinationLongerThanSource() { float[] x = [-5, -4.5f, -4]; float[] expectedResult = [0.0066f, 0.0109f, 0.0179f]; - float[] dest = CreateTensor(x.Length + 1); + using BoundedMemory dest = CreateTensor(x.Length + 1); TensorPrimitives.Sigmoid(x, dest); + float originalLast = dest[dest.Length - 1]; for (int i = 0; i < x.Length; i++) { Assert.Equal(expectedResult[i], dest[i], Tolerance); } - Assert.Equal(0f, dest[dest.Length - 1]); + Assert.Equal(originalLast, dest[dest.Length - 1]); } [Fact] @@ -956,8 +956,8 @@ public static void IndexOfMax(int tensorLength) { foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { - float[] x = CreateAndFillTensor(tensorLength); - x[expected] = Enumerable.Max(x) + 1; + using BoundedMemory x = CreateAndFillTensor(tensorLength); + x[expected] = Enumerable.Max(MemoryMarshal.ToEnumerable(x.Memory)) + 1; Assert.Equal(expected, TensorPrimitives.IndexOfMax(x)); } } @@ -968,7 +968,7 @@ public static void IndexOfMax_FirstNaNReturned(int tensorLength) { foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); x[expected] = float.NaN; x[tensorLength - 1] = float.NaN; Assert.Equal(expected, TensorPrimitives.IndexOfMax(x)); @@ -998,8 +998,8 @@ public static void IndexOfMin(int tensorLength) { foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { - float[] x = CreateAndFillTensor(tensorLength); - x[expected] = Enumerable.Min(x) - 1; + using BoundedMemory x = CreateAndFillTensor(tensorLength); + x[expected] = Enumerable.Min(MemoryMarshal.ToEnumerable(x.Memory)) - 1; Assert.Equal(expected, TensorPrimitives.IndexOfMin(x)); } } @@ -1010,7 +1010,7 @@ public static void IndexOfMin_FirstNaNReturned(int tensorLength) { foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); x[expected] = float.NaN; x[tensorLength - 1] = float.NaN; Assert.Equal(expected, TensorPrimitives.IndexOfMin(x)); @@ -1039,8 +1039,8 @@ public static void IndexOfMaxMagnitude(int tensorLength) { foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { - float[] x = CreateAndFillTensor(tensorLength); - x[expected] = x.Max(Math.Abs) + 1; + using BoundedMemory x = CreateAndFillTensor(tensorLength); + x[expected] = Enumerable.Max(MemoryMarshal.ToEnumerable(x.Memory), Math.Abs) + 1; Assert.Equal(expected, TensorPrimitives.IndexOfMaxMagnitude(x)); } } @@ -1051,7 +1051,7 @@ public static void IndexOfMaxMagnitude_FirstNaNReturned(int tensorLength) { foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); x[expected] = float.NaN; x[tensorLength - 1] = float.NaN; Assert.Equal(expected, TensorPrimitives.IndexOfMaxMagnitude(x)); @@ -1081,7 +1081,7 @@ public static void IndexOfMinMagnitude(int tensorLength) { foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { - float[] x = CreateTensor(tensorLength); + using BoundedMemory x = CreateTensor(tensorLength); for (int i = 0; i < x.Length; i++) { x[i] = i % 2 == 0 ? 42 : -42; @@ -1099,7 +1099,7 @@ public static void IndexOfMinMagnitude_FirstNaNReturned(int tensorLength) { foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); x[expected] = float.NaN; x[tensorLength - 1] = float.NaN; Assert.Equal(expected, TensorPrimitives.IndexOfMinMagnitude(x)); @@ -1127,12 +1127,12 @@ public static void Max_ThrowsForEmpty() [MemberData(nameof(TensorLengths))] public static void Max(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(Enumerable.Max(x), TensorPrimitives.Max(x)); + Assert.Equal(Enumerable.Max(MemoryMarshal.ToEnumerable(x.Memory)), TensorPrimitives.Max(x)); float max = float.NegativeInfinity; - foreach (float f in x) + foreach (float f in x.Span) { max = Math.Max(max, f); } @@ -1143,7 +1143,7 @@ public static void Max(int tensorLength) [MemberData(nameof(TensorLengths))] public static void Max_NanReturned(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { x[expected] = float.NaN; @@ -1170,12 +1170,12 @@ public static void MaxMagnitude_ThrowsForEmpty() [MemberData(nameof(TensorLengths))] public static void MaxMagnitude(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(x.Max(MathF.Abs), TensorPrimitives.MaxMagnitude(x)); + Assert.Equal(Enumerable.Max(MemoryMarshal.ToEnumerable(x.Memory), MathF.Abs), TensorPrimitives.MaxMagnitude(x)); float max = 0; - foreach (float f in x) + foreach (float f in x.Span) { max = Math.Max(max, MathF.Abs(f)); } @@ -1186,7 +1186,7 @@ public static void MaxMagnitude(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MaxMagnitude_NanReturned(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { x[expected] = float.NaN; @@ -1215,12 +1215,12 @@ public static void Min_ThrowsForEmpty() [MemberData(nameof(TensorLengths))] public static void Min(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(Enumerable.Min(x), TensorPrimitives.Min(x)); + Assert.Equal(Enumerable.Min(MemoryMarshal.ToEnumerable(x.Memory)), TensorPrimitives.Min(x)); float min = float.PositiveInfinity; - foreach (float f in x) + foreach (float f in x.Span) { min = Math.Min(min, f); } @@ -1231,7 +1231,7 @@ public static void Min(int tensorLength) [MemberData(nameof(TensorLengths))] public static void Min_NanReturned(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { x[expected] = float.NaN; @@ -1258,12 +1258,12 @@ public static void MinMagnitude_ThrowsForEmpty() [MemberData(nameof(TensorLengths))] public static void MinMagnitude(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(x.Min(MathF.Abs), TensorPrimitives.MinMagnitude(x)); + Assert.Equal(Enumerable.Min(MemoryMarshal.ToEnumerable(x.Memory), MathF.Abs), TensorPrimitives.MinMagnitude(x)); float min = float.PositiveInfinity; - foreach (float f in x) + foreach (float f in x.Span) { min = Math.Min(min, MathF.Abs(f)); } @@ -1274,7 +1274,7 @@ public static void MinMagnitude(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MinMagnitude_NanReturned(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) { x[expected] = float.NaN; @@ -1301,7 +1301,7 @@ public static void Product_ThrowsForEmpty() [MemberData(nameof(TensorLengths))] public static void Product(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float f = x[0]; for (int i = 1; i < x.Length; i++) @@ -1341,8 +1341,8 @@ public static void ProductOfDifferences_ThrowsForEmptyAndMismatchedLengths() [MemberData(nameof(TensorLengths))] public static void ProductOfDifferences(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float f = x[0] - y[0]; for (int i = 1; i < x.Length; i++) @@ -1379,8 +1379,8 @@ public static void ProductOfSums_ThrowsForEmptyAndMismatchedLengths() [MemberData(nameof(TensorLengths))] public static void ProductOfSums(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float f = x[0] + y[0]; for (int i = 1; i < x.Length; i++) @@ -1407,12 +1407,12 @@ public static void ProductOfSums_KnownValues() [MemberData(nameof(TensorLengths))] public static void Sum(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(Enumerable.Sum(x), TensorPrimitives.Sum(x), Tolerance); + Assert.Equal(Enumerable.Sum(MemoryMarshal.ToEnumerable(x.Memory)), TensorPrimitives.Sum(x), Tolerance); float sum = 0; - foreach (float f in x) + foreach (float f in x.Span) { sum += f; } @@ -1433,12 +1433,12 @@ public static void Sum_KnownValues() [MemberData(nameof(TensorLengths))] public static void SumOfSquares(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(Enumerable.Sum(x, v => v * v), TensorPrimitives.SumOfSquares(x), Tolerance); + Assert.Equal(Enumerable.Sum(MemoryMarshal.ToEnumerable(x.Memory), v => v * v), TensorPrimitives.SumOfSquares(x), Tolerance); float sum = 0; - foreach (float f in x) + foreach (float f in x.Span) { sum += f * f; } @@ -1459,12 +1459,12 @@ public static void SumOfSquares_KnownValues() [MemberData(nameof(TensorLengths))] public static void SumOfMagnitudes(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(Enumerable.Sum(x, MathF.Abs), TensorPrimitives.SumOfMagnitudes(x), Tolerance); + Assert.Equal(Enumerable.Sum(MemoryMarshal.ToEnumerable(x.Memory), MathF.Abs), TensorPrimitives.SumOfMagnitudes(x), Tolerance); float sum = 0; - foreach (float f in x) + foreach (float f in x.Span) { sum += MathF.Abs(f); } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs index b8516942f5baa9..113f26048d352c 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using Xunit; namespace System.Numerics.Tensors.Tests @@ -12,7 +13,7 @@ public static partial class TensorPrimitivesTests [MemberData(nameof(TensorLengths))] public static void ConvertToHalf(int tensorLength) { - float[] source = CreateAndFillTensor(tensorLength); + using BoundedMemory source = CreateAndFillTensor(tensorLength); foreach (int destLength in new[] { source.Length, source.Length + 1 }) { Half[] destination = new Half[destLength]; @@ -38,7 +39,7 @@ public static void ConvertToHalf(int tensorLength) [MemberData(nameof(TensorLengths))] public static void ConvertToHalf_ThrowsForTooShortDestination(int tensorLength) { - float[] source = CreateAndFillTensor(tensorLength); + using BoundedMemory source = CreateAndFillTensor(tensorLength); Half[] destination = new Half[source.Length - 1]; AssertExtensions.Throws("destination", () => TensorPrimitives.ConvertToHalf(source, destination)); @@ -57,7 +58,8 @@ public static void ConvertToSingle(int tensorLength) foreach (int destLength in new[] { source.Length, source.Length + 1 }) { - float[] destination = new float[destLength]; + using BoundedMemory destination = CreateTensor(destLength); + destination.Span.Fill(0f); TensorPrimitives.ConvertToSingle(source, destination); @@ -81,7 +83,7 @@ public static void ConvertToSingle(int tensorLength) public static void ConvertToSingle_ThrowsForTooShortDestination(int tensorLength) { Half[] source = new Half[tensorLength]; - float[] destination = new float[source.Length - 1]; + using BoundedMemory destination = CreateTensor(source.Length - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.ConvertToSingle(source, destination)); }