From c875707999bafd95b48b0d9a1f890fdcfd775145 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 9 May 2025 13:20:08 -0700 Subject: [PATCH 1/9] Expose IsDense, HasAnyDenseDimensions, and ToDenseTensor --- .../ref/System.Numerics.Tensors.netcore.cs | 10 ++ .../Tensors/netcore/IReadOnlyTensor.cs | 14 ++ .../Tensors/netcore/IReadOnlyTensor_1.cs | 8 + .../Tensors/netcore/ReadOnlyTensorSpan_1.cs | 6 +- .../System/Numerics/Tensors/netcore/Tensor.cs | 4 +- .../Numerics/Tensors/netcore/TensorShape.cs | 144 ++++++++++++++++-- .../Numerics/Tensors/netcore/TensorSpan.cs | 6 +- .../Numerics/Tensors/netcore/Tensor_1.cs | 17 ++- .../tests/TensorTests.cs | 117 ++++++++++++++ 9 files changed, 305 insertions(+), 21 deletions(-) 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 index 241a65c5da5067..1e21bcb6d743c2 100644 --- a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs @@ -59,6 +59,8 @@ namespace System.Numerics.Tensors public partial interface IReadOnlyTensor { nint FlattenedLength { get; } + bool HasAnyDenseDimensions { get; } + bool IsDense { get; } bool IsEmpty { get; } bool IsPinned { get; } object this[params scoped System.ReadOnlySpan indexes] { get; } @@ -87,6 +89,7 @@ public partial interface IReadOnlyTensor : System.Collections.Generic. TSelf Slice(params scoped System.ReadOnlySpan startIndexes); TSelf Slice(params scoped System.ReadOnlySpan ranges); TSelf Slice(params scoped System.ReadOnlySpan startIndexes); + TSelf ToDenseTensor(); bool TryCopyTo(scoped System.Numerics.Tensors.TensorSpan destination); bool TryFlattenTo(scoped System.Span destination); } @@ -138,6 +141,8 @@ public readonly ref partial struct ReadOnlyTensorSpan public ReadOnlyTensorSpan(T[]? array, int start, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public static System.Numerics.Tensors.ReadOnlyTensorSpan Empty { get { throw null; } } public nint FlattenedLength { get { throw null; } } + public bool HasAnyDenseDimensions { get { throw null; } } + public bool IsDense { get { throw null; } } public bool IsEmpty { get { throw null; } } public ref readonly T this[params scoped System.ReadOnlySpan indexes] { get { throw null; } } public System.Numerics.Tensors.ReadOnlyTensorSpan this[params scoped System.ReadOnlySpan ranges] { get { throw null; } } @@ -805,6 +810,8 @@ public readonly ref partial struct TensorSpan public TensorSpan(T[]? array, int start, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public static System.Numerics.Tensors.TensorSpan Empty { get { throw null; } } public nint FlattenedLength { get { throw null; } } + public bool HasAnyDenseDimensions { get { throw null; } } + public bool IsDense { get { throw null; } } public bool IsEmpty { get { throw null; } } public ref T this[params scoped System.ReadOnlySpan indexes] { get { throw null; } } public System.Numerics.Tensors.TensorSpan this[params scoped System.ReadOnlySpan ranges] { get { throw null; } set { } } @@ -863,6 +870,8 @@ public sealed partial class Tensor : System.Collections.Generic.IEnumerable Empty { get { throw null; } } public nint FlattenedLength { get { throw null; } } + public bool HasAnyDenseDimensions { get { throw null; } } + public bool IsDense { get { throw null; } } public bool IsEmpty { get { throw null; } } public bool IsPinned { get { throw null; } } public ref T this[params scoped System.ReadOnlySpan indexes] { get { throw null; } } @@ -915,6 +924,7 @@ void System.Numerics.Tensors.ITensor.Fill(object value) { } static System.Numerics.Tensors.Tensor System.Numerics.Tensors.ITensor, T>.Create(scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides, bool pinned) { throw null; } static System.Numerics.Tensors.Tensor System.Numerics.Tensors.ITensor, T>.CreateUninitialized(scoped System.ReadOnlySpan lengths, bool pinned) { throw null; } static System.Numerics.Tensors.Tensor System.Numerics.Tensors.ITensor, T>.CreateUninitialized(scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides, bool pinned) { throw null; } + public Tensor ToDenseTensor() { throw null; } public string ToString(scoped System.ReadOnlySpan maximumLengths) { throw null; } public bool TryCopyTo(scoped System.Numerics.Tensors.TensorSpan destination) { throw null; } public bool TryFlattenTo(scoped System.Span destination) { throw null; } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor.cs index 9a90a17cb1640c..5472f389b96c3a 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor.cs @@ -26,6 +26,20 @@ public interface IReadOnlyTensor /// Gets the total number of items in the tensor. nint FlattenedLength { get; } + /// Determines if the current tensor has any dimension where GetDimensionSpan(int) will iterate through tensors that would have be true. + /// + /// This does not include the last dimension, GetDimensionSpan(Rank - 1), as it always iterates 1 element at a time and would mean this property always returns true. + /// An example of a tensor which is not dense but which would have a dense dimension is a 2x2 Tensor where FlattenedLength: 4; Lengths: [2, 2]; Strides: [4, 1]. In such a scenario, the overall tensor is not dense because the backing storage has a length of at least 6 and so has 2 used elements, 2 unused elements, followed by the last 2 used elements. However, the two slices representing [0..1, ..] and [1..2, ..] would themselves be dense; thus GetDimension(0).GetSlice(n) will iterate dense tensors: FlattenedLength: 2, Length: [2], Strides: [1]. + /// + bool HasAnyDenseDimensions { get; } + + /// Determines if the current tensor is dense. + /// + /// A dense tensor is one where the elements are ordered sequentially in memory and where no gaps exist between the elements. + /// For a 2x2 Tensor, this would mean it has FlattenedLength: 4; Lengths: [2, 2]; Strides: [2, 1]. The elements would be sequentially accessed via indexes: [0, 0]; [0, 1]; [1, 0]; [1, 1] + /// + bool IsDense { get; } + /// Gets a value indicating whether this tensor is empty. /// if this tensor is empty; otherwise, . bool IsEmpty { get; } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor_1.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor_1.cs index 022140ed33aa72..c7159b33b05fcd 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor_1.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor_1.cs @@ -82,6 +82,14 @@ public interface IReadOnlyTensor : IReadOnlyTensor, IEnumerable /// is larger than the tensor. TSelf Slice(params scoped ReadOnlySpan ranges); + /// Creates a dense tensor from the elements of the current tensor. + /// The current tensor if it is already dense; otherwise, a new tensor that contains the elements of this tensor. + /// + /// A dense tensor is one where the elements are ordered sequentially in memory and where no gaps exist between the elements. + /// For a 2x2 Tensor, this would mean it has FlattendLength: 4; Lengths: [2, 2]; Strides: [4, 1]. The elements would be sequentially accessed via indexes: [0, 0]; [0, 1]; [1, 0]; [1, 1] + /// + TSelf ToDenseTensor(); + /// Attempts to copy the contents of this tensor into a destination tensor span and returns a value to indicate whether or not the operation succeeded. /// The target of the copy operation. /// if the copy operation succeeded; otherwise, false. diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs index 36452121c96ebd..fd4e77cbfce192 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs @@ -299,7 +299,11 @@ public ReadOnlyTensorSpan this[params scoped ReadOnlySpan ranges] /// public nint FlattenedLength => _shape.FlattenedLength; - internal bool IsContiguousAndDense => _shape.IsContiguousAndDense; + /// + public bool HasAnyDenseDimension => _shape.HasAnyDenseDimension; + + /// + public bool IsDense => _shape.IsDense; /// public bool IsEmpty => _shape.IsEmpty; diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs index 8d4a670a1bd9b6..8ea980fcf7730b 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs @@ -1440,7 +1440,7 @@ public static Tensor Reshape(this Tensor tensor, ReadOnlySpan len if (tensor.Lengths.SequenceEqual(lengths)) return tensor; - if (!tensor.IsContiguousAndDense && !tensor.Strides.Contains(0)) + if (!tensor.IsDense && !tensor.Strides.Contains(0)) { ThrowHelper.ThrowArgument_CannotReshapeNonContiguousOrDense(); } @@ -1513,7 +1513,7 @@ public static TensorSpan Reshape(in this TensorSpan tensor, scoped Read if (tensor.Lengths.SequenceEqual(lengths)) return tensor; - if (!tensor.IsContiguousAndDense && !tensor.Strides.Contains(0)) + if (!tensor.IsDense && !tensor.Strides.Contains(0)) { ThrowHelper.ThrowArgument_CannotReshapeNonContiguousOrDense(); } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs index 9cf04855e93d5d..62c112f68c649f 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs @@ -27,7 +27,7 @@ namespace System.Numerics.Tensors // backing storage size that is present. We can also have arbitrary strides, such as // lengths: [4], strides: [2]. In this case the flattenedLength = 4, while the // linearLength: 8. We can also have a slice of a tensor, such as lengths: [2, 2], - // strides: [3, 1] (which could have been taken from a 3x3 contiguous and dense tensor). + // strides: [3, 1] (which could have been taken from a 3x3 dense tensor). // // These invariants allow us to safely and efficiently index into the backing storage // as we know that memory functionally wraps around after linearLength elements. It @@ -39,8 +39,9 @@ namespace System.Numerics.Tensors internal enum TensorFlags : uint { None = 0, - IsContiguousAndDense = (1 << 0), + IsDense = (1 << 0), IsBroadcast = (1 << 1), + HasDenseDimension = (1 << 2), } [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] @@ -188,6 +189,9 @@ ref Unsafe.As(ref metadata[rank * 2]), flattenedLength = checked(flattenedLength * length); } + + // When the strides are automatically computed, then we must be dense + _flags |= (TensorFlags.IsDense | TensorFlags.HasDenseDimension); } else if (strides.Length != lengths.Length) { @@ -215,11 +219,19 @@ ref Unsafe.As(ref metadata[rank * 2]), .ToArray(); int[] sortedOrder = sortedWithIndex.Select(x => x.Index).ToArray(); + bool isDense = true; + for (int i = 0; i < destinationLinearRankOrder.Length; i++) { int rankIndex = destinationLinearRankOrder.Length - (i + 1); int linearRankIndex = sortedOrder[rankIndex]; + if (linearRankIndex != rankIndex) + { + // We cannot be dense if we aren't following linear order + isDense = false; + } + nint length = lengths[linearRankIndex]; if (length < 0) @@ -243,6 +255,10 @@ ref Unsafe.As(ref metadata[rank * 2]), // the linear rank ordering is incorrect ThrowHelper.ThrowArgument_InvalidTensorShape(); } + else if (stride != minimumNonZeroStride) + { + isDense = false; + } if (length <= 1) { @@ -258,6 +274,12 @@ ref Unsafe.As(ref metadata[rank * 2]), else { _flags |= TensorFlags.IsBroadcast; + + if (length != 1) + { + // We only cannot be dense if the broadcast is for more than 1 element + isDense = false; + } } destinationLengths[linearRankIndex] = length; @@ -265,6 +287,43 @@ ref Unsafe.As(ref metadata[rank * 2]), flattenedLength = checked(flattenedLength * length); } + + if (isDense) + { + _flags |= (TensorFlags.IsDense | TensorFlags.HasDenseDimension); + } + else + { + // We aren't dense, but we might still have some dense dimension if + // the least significant non 0 stride is 1 and all least significant + // 0 strides have length 1. We cannot have a dense dimension for a + // non-zero stride with more than 1 element since we cannot get a + // single span for all elements in that index of the dimension. + + for (int i = 0; i < strides.Length; i++) + { + int index = strides.Length - (i + 1); + nint stride = strides[index]; + + if (stride == 1) + { + _flags |= TensorFlags.HasDenseDimension; + break; + } + + if (stride != 0) + { + break; + } + + nint length = lengths[index]; + + if (length != 1) + { + break; + } + } + } } // Once we've finished computing everything physically present in the input @@ -279,19 +338,13 @@ ref Unsafe.As(ref metadata[rank * 2]), ArgumentOutOfRangeException.ThrowIfLessThan(linearLength, minimumLinearLength); } + Debug.Assert((flattenedLength == minimumLinearLength) || (strides.Length != 0)); + _flattenedLength = flattenedLength; _linearLength = minimumLinearLength; _rank = rank; - if (flattenedLength == minimumLinearLength) - { - // When the flattenedLength and minimumLinearLength are the same, then the - // underlying buffer is "contiguous and dense" which allows various other - // optimizations to be utilized when processing the tensor. - _flags |= TensorFlags.IsContiguousAndDense; - } - ValidateState(); } @@ -338,9 +391,11 @@ ref Unsafe.As(ref metadata[rank * 2]), ValidateState(); } + public bool HasDenseElements => (_flags & TensorFlags.HasDenseDimension) != 0; + public bool IsBroadcast => (_flags & TensorFlags.IsBroadcast) != 0; - public bool IsContiguousAndDense => (_flags & TensorFlags.IsContiguousAndDense) != 0; + public bool IsDense => (_flags & TensorFlags.IsDense) != 0; public nint FlattenedLength => _flattenedLength; @@ -892,7 +947,7 @@ public static TensorShape Create(T[]? array) strides: [1], linearRankOrder: [0], rank: 1, - TensorFlags.IsContiguousAndDense + TensorFlags.IsDense | TensorFlags.HasDenseDimension ); } return default; @@ -991,7 +1046,7 @@ public static TensorShape Create(ref T reference, nint linearLength) strides: [1], linearRankOrder: [0], rank: 1, - TensorFlags.IsContiguousAndDense + TensorFlags.IsDense | TensorFlags.HasDenseDimension ); } else if (linearLength != 0) @@ -1044,7 +1099,43 @@ public override bool Equals([NotNullWhen(true)] object? obj) [Conditional("DEBUG")] private void ValidateState() { - Debug.Assert(IsContiguousAndDense == (FlattenedLength == LinearLength)); + if (IsDense) + { + Debug.Assert(FlattenedLength == LinearLength); + Debug.Assert(HasDenseElements); + } + else + { + Debug.Assert(FlattenedLength != LinearLength); + + bool hasDenseDimension = false; + + for (int i = 0; i < Strides.Length; i++) + { + int index = Strides.Length - (i + 1); + nint stride = Strides[index]; + + if (stride == 1) + { + hasDenseDimension = true; + break; + } + + if (stride != 0) + { + break; + } + + nint length = Lengths[index]; + + if (length != 1) + { + break; + } + } + + Debug.Assert(HasDenseElements == hasDenseDimension); + } Debug.Assert(IsBroadcast == Strides.Contains(0)); } @@ -1126,14 +1217,22 @@ public TensorShape Slice(ReadOnlySpan state, out nint nint flattenedLength = 1; nint maximumLinearIndex = 0; + nint minimumNonZeroStride = 0; nint computedOffset = 0; + bool isDense = true; for (int i = 0; i < linearRankOrder.Length; i++) { int rankIndex = linearRankOrder.Length - (i + 1); int linearRankIndex = linearRankOrder[rankIndex]; + if (rankIndex != linearRankIndex) + { + // We cannot be dense if we aren't following linear order + isDense = false; + } + nint previousLength = previousLengths[linearRankIndex]; nint previousStride = previousStrides[linearRankIndex]; @@ -1143,15 +1242,28 @@ public TensorShape Slice(ReadOnlySpan state, out nint if (stride != 0) { maximumLinearIndex += ((length - 1) * stride); + + if (stride != minimumNonZeroStride) + { + isDense = false; + } } else { flags |= TensorFlags.IsBroadcast; + + if (length != 1) + { + // We only cannot be dense if the broadcast is for more than 1 element + isDense = false; + } } intermediateLengths[linearRankIndex] = length; intermediateStrides[linearRankIndex] = stride; + minimumNonZeroStride = stride * length; + computedOffset += (offset * previousStride); flattenedLength *= length; } @@ -1162,9 +1274,9 @@ public TensorShape Slice(ReadOnlySpan state, out nint nint minimumLinearLength = (flattenedLength != 0) ? (maximumLinearIndex + 1) : flattenedLength; Debug.Assert(minimumLinearLength <= _linearLength); - if (flattenedLength == minimumLinearLength) + if (isDense) { - flags |= TensorFlags.IsContiguousAndDense; + flags |= (TensorFlags.IsDense | TensorFlags.HasDenseDimension); } TensorShape result = new TensorShape( diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs index 093a922f2f3789..3a7ddb2eaeaf3a 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs @@ -201,7 +201,11 @@ public TensorSpan this[params scoped ReadOnlySpan ranges] /// public nint FlattenedLength => _shape.FlattenedLength; - internal bool IsContiguousAndDense => _shape.IsContiguousAndDense; + /// + public bool HasAnyDenseDimension => _shape.HasAnyDenseDimension; + + /// + public bool IsDense => _shape.IsDense; /// public bool IsEmpty => _shape.IsEmpty; diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs index a2417ae57f1afe..9dbc8a193714e8 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs @@ -144,7 +144,8 @@ public Tensor this[params ReadOnlySpan ranges] /// public nint FlattenedLength => _shape.FlattenedLength; - internal bool IsContiguousAndDense => _shape.IsContiguousAndDense; + /// + public bool IsDense => _shape.IsDense; /// public bool IsEmpty => _shape.IsEmpty; @@ -290,6 +291,20 @@ public Tensor Slice(params ReadOnlySpan ranges) ); } + /// + public Tensor ToDenseTensor() + { + Tensor result = this; + + if (!IsDense) + { + result = Tensor.Create(Lengths, IsPinned); + CopyTo(result); + } + + return result; + } + /// public bool TryCopyTo(scoped in TensorSpan destination) => AsReadOnlyTensorSpan().TryCopyTo(destination); diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs index a2e0f203f2b6b6..9f6826debdb063 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using Xunit; @@ -2786,5 +2787,121 @@ public void TensorGetPinnedHandleTests() Assert.Equal(4, ptr[3]); } } + + [Fact] + public void IsDenseTests() + { + // Dense + + Assert.True(Tensor.Create([1]).IsDense); + Assert.True(Tensor.Create([1], [0]).IsDense); + + Assert.True(Tensor.Create([2]).IsDense); + Assert.True(Tensor.Create([2], [1]).IsDense); + + Assert.True(Tensor.Create([1, 2]).IsDense); + Assert.True(Tensor.Create([1, 2], [2, 1]).IsDense); + + Assert.True(Tensor.Create([2, 2]).IsDense); + Assert.True(Tensor.Create([2, 2], [2, 1]).IsDense); + + Assert.True(Tensor.Create([4, 3]).IsDense); + Assert.True(Tensor.Create([4, 3], [12, 1]).IsDense); + + // Non-dense + + Assert.False(Tensor.Create([2], [0]).IsDense); + Assert.False(Tensor.Create([2], [2]).IsDense); + + Assert.False(Tensor.Create([2, 2], [1, 2]).IsDense); + Assert.False(Tensor.Create([2, 2], [1, 0]).IsDense); + + Assert.False(Tensor.Create([3, 4], [1, 12]).IsDense); + Assert.False(Tensor.Create([3, 4], [1, 0]).IsDense); + } + + [Fact] + public void HasAnyDenseDimensionTests() + { + // Dense + + Assert.True(Tensor.Create([1]).HasAnyDenseDimension); + Assert.True(Tensor.Create([1], [0]).HasAnyDenseDimension); + + Assert.True(Tensor.Create([2]).HasAnyDenseDimension); + Assert.True(Tensor.Create([2], [1]).HasAnyDenseDimension); + + Assert.True(Tensor.Create([1, 2]).HasAnyDenseDimension); + Assert.True(Tensor.Create([1, 2], [2, 1]).HasAnyDenseDimension); + + Assert.True(Tensor.Create([2, 2]).HasAnyDenseDimension); + Assert.True(Tensor.Create([2, 2], [2, 1]).HasAnyDenseDimension); + + Assert.True(Tensor.Create([4, 3]).HasAnyDenseDimension); + Assert.True(Tensor.Create([4, 3], [12, 1]).HasAnyDenseDimension); + + // Non-dense w/ Dense Dimension + + Assert.True(Tensor.Create([2], [0]).HasAnyDenseDimension); + + Assert.True(Tensor.Create([2, 2], [1, 0]).HasAnyDenseDimension); + + Assert.True(Tensor.Create([3, 4], [1, 0]).HasAnyDenseDimension); + + // Non-dense w/ Non-dense Dimension + + Assert.False(Tensor.Create([2], [2]).HasAnyDenseDimension); + + Assert.False(Tensor.Create([2, 2], [1, 2]).HasAnyDenseDimension); + + Assert.False(Tensor.Create([3, 4], [1, 12]).HasAnyDenseDimension); + } + + [Fact] + public void ToDenseTensorTests() + { + // Dense + + AssertReturnsSelf(Tensor.Create([1])); + AssertReturnsSelf(Tensor.Create([1], [0])); + + AssertReturnsSelf(Tensor.Create([2])); + AssertReturnsSelf(Tensor.Create([2], [1])); + + AssertReturnsSelf(Tensor.Create([1, 2])); + AssertReturnsSelf(Tensor.Create([1, 2], [2, 1])); + + AssertReturnsSelf(Tensor.Create([2, 2])); + AssertReturnsSelf(Tensor.Create([2, 2], [2, 1])); + + AssertReturnsSelf(Tensor.Create([4, 3])); + AssertReturnsSelf(Tensor.Create([4, 3], [12, 1])); + + // Non-dense + + AssertReturnsNewTensor(Tensor.Create([2], [0])); + AssertReturnsNewTensor(Tensor.Create([2], [2])); + + AssertReturnsNewTensor(Tensor.Create([2, 2], [1, 2])); + AssertReturnsNewTensor(Tensor.Create([2, 2], [1, 0])); + + AssertReturnsNewTensor(Tensor.Create([3, 4], [1, 12])); + AssertReturnsNewTensor(Tensor.Create([3, 4], [1, 0])); + + static void AssertReturnsSelf(Tensor tensor) + { + Assert.Same(tensor, tensor.ToDenseTensor()); + } + + static void AssertReturnsNewTensor(Tensor tensor) + where T : IEqualityOperators + { + Tensor denseTensor = tensor.ToDenseTensor(); + Assert.NotSame(tensor, denseTensor); + + Assert.Equal(tensor.FlattenedLength, denseTensor.FlattenedLength); + Assert.True(Tensor.EqualsAll(tensor, denseTensor)); + } + } } } From add084b03561ede9f013c98883aba3d1c9343df7 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 9 May 2025 17:51:44 -0700 Subject: [PATCH 2/9] Ensure HasAnyDenseDimensions is exposed by Tensor --- .../Numerics/Tensors/netcore/TensorShape.cs | 24 +++++++++---------- .../Numerics/Tensors/netcore/Tensor_1.cs | 3 +++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs index 62c112f68c649f..218cd1e548f498 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs @@ -41,7 +41,7 @@ internal enum TensorFlags : uint None = 0, IsDense = (1 << 0), IsBroadcast = (1 << 1), - HasDenseDimension = (1 << 2), + HasAnyDenseDimensions = (1 << 2), } [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] @@ -191,7 +191,7 @@ ref Unsafe.As(ref metadata[rank * 2]), } // When the strides are automatically computed, then we must be dense - _flags |= (TensorFlags.IsDense | TensorFlags.HasDenseDimension); + _flags |= (TensorFlags.IsDense | TensorFlags.HasAnyDenseDimensions); } else if (strides.Length != lengths.Length) { @@ -290,7 +290,7 @@ ref Unsafe.As(ref metadata[rank * 2]), if (isDense) { - _flags |= (TensorFlags.IsDense | TensorFlags.HasDenseDimension); + _flags |= (TensorFlags.IsDense | TensorFlags.HasAnyDenseDimensions); } else { @@ -307,7 +307,7 @@ ref Unsafe.As(ref metadata[rank * 2]), if (stride == 1) { - _flags |= TensorFlags.HasDenseDimension; + _flags |= TensorFlags.HasAnyDenseDimensions; break; } @@ -391,7 +391,7 @@ ref Unsafe.As(ref metadata[rank * 2]), ValidateState(); } - public bool HasDenseElements => (_flags & TensorFlags.HasDenseDimension) != 0; + public bool HasAnyDenseDimensions => (_flags & TensorFlags.HasAnyDenseDimensions) != 0; public bool IsBroadcast => (_flags & TensorFlags.IsBroadcast) != 0; @@ -947,7 +947,7 @@ public static TensorShape Create(T[]? array) strides: [1], linearRankOrder: [0], rank: 1, - TensorFlags.IsDense | TensorFlags.HasDenseDimension + TensorFlags.IsDense | TensorFlags.HasAnyDenseDimensions ); } return default; @@ -1046,7 +1046,7 @@ public static TensorShape Create(ref T reference, nint linearLength) strides: [1], linearRankOrder: [0], rank: 1, - TensorFlags.IsDense | TensorFlags.HasDenseDimension + TensorFlags.IsDense | TensorFlags.HasAnyDenseDimensions ); } else if (linearLength != 0) @@ -1102,13 +1102,13 @@ private void ValidateState() if (IsDense) { Debug.Assert(FlattenedLength == LinearLength); - Debug.Assert(HasDenseElements); + Debug.Assert(HasAnyDenseDimensions); } else { Debug.Assert(FlattenedLength != LinearLength); - bool hasDenseDimension = false; + bool hasAnyDenseDimensions = false; for (int i = 0; i < Strides.Length; i++) { @@ -1117,7 +1117,7 @@ private void ValidateState() if (stride == 1) { - hasDenseDimension = true; + hasAnyDenseDimensions = true; break; } @@ -1134,7 +1134,7 @@ private void ValidateState() } } - Debug.Assert(HasDenseElements == hasDenseDimension); + Debug.Assert(HasAnyDenseDimensions == hasAnyDenseDimensions); } Debug.Assert(IsBroadcast == Strides.Contains(0)); } @@ -1276,7 +1276,7 @@ public TensorShape Slice(ReadOnlySpan state, out nint if (isDense) { - flags |= (TensorFlags.IsDense | TensorFlags.HasDenseDimension); + flags |= (TensorFlags.IsDense | TensorFlags.HasAnyDenseDimensions); } TensorShape result = new TensorShape( diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs index 9dbc8a193714e8..b4841b44557ab8 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs @@ -144,6 +144,9 @@ public Tensor this[params ReadOnlySpan ranges] /// public nint FlattenedLength => _shape.FlattenedLength; + /// + public bool HasAnyDenseDimensions => _shape.HasAnyDenseDimensions; + /// public bool IsDense => _shape.IsDense; From e223a66787503960308c42c1b89987f2d921d2c2 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sun, 11 May 2025 17:08:38 -0700 Subject: [PATCH 3/9] Fix spelling --- .../Tensors/netcore/ReadOnlyTensorSpan_1.cs | 4 +-- .../System/Numerics/Tensors/netcore/Tensor.cs | 2 +- .../Numerics/Tensors/netcore/TensorSpan.cs | 4 +-- .../tests/TensorTests.cs | 32 +++++++++---------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs index fd4e77cbfce192..b070c5242da062 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs @@ -299,8 +299,8 @@ public ReadOnlyTensorSpan this[params scoped ReadOnlySpan ranges] /// public nint FlattenedLength => _shape.FlattenedLength; - /// - public bool HasAnyDenseDimension => _shape.HasAnyDenseDimension; + /// + public bool HasAnyDenseDimensions => _shape.HasAnyDenseDimensions; /// public bool IsDense => _shape.IsDense; diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs index 8ea980fcf7730b..ee2fa5a382f44a 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs @@ -1589,7 +1589,7 @@ public static ReadOnlyTensorSpan Reshape(in this ReadOnlyTensorSpan ten if (tensor.Lengths.SequenceEqual(lengths)) return tensor; - if (!tensor.IsContiguousAndDense && !tensor.Strides.Contains(0)) + if (!tensor.IsDense && !tensor.Strides.Contains(0)) { ThrowHelper.ThrowArgument_CannotReshapeNonContiguousOrDense(); } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs index 3a7ddb2eaeaf3a..55663994c96e7a 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs @@ -201,8 +201,8 @@ public TensorSpan this[params scoped ReadOnlySpan ranges] /// public nint FlattenedLength => _shape.FlattenedLength; - /// - public bool HasAnyDenseDimension => _shape.HasAnyDenseDimension; + /// + public bool HasAnyDenseDimensions => _shape.HasAnyDenseDimensions; /// public bool IsDense => _shape.IsDense; diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs index 9f6826debdb063..509c6255e546fa 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs @@ -2825,36 +2825,36 @@ public void HasAnyDenseDimensionTests() { // Dense - Assert.True(Tensor.Create([1]).HasAnyDenseDimension); - Assert.True(Tensor.Create([1], [0]).HasAnyDenseDimension); + Assert.True(Tensor.Create([1]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([1], [0]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([2]).HasAnyDenseDimension); - Assert.True(Tensor.Create([2], [1]).HasAnyDenseDimension); + Assert.True(Tensor.Create([2]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([2], [1]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([1, 2]).HasAnyDenseDimension); - Assert.True(Tensor.Create([1, 2], [2, 1]).HasAnyDenseDimension); + Assert.True(Tensor.Create([1, 2]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([1, 2], [2, 1]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([2, 2]).HasAnyDenseDimension); - Assert.True(Tensor.Create([2, 2], [2, 1]).HasAnyDenseDimension); + Assert.True(Tensor.Create([2, 2]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([2, 2], [2, 1]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([4, 3]).HasAnyDenseDimension); - Assert.True(Tensor.Create([4, 3], [12, 1]).HasAnyDenseDimension); + Assert.True(Tensor.Create([4, 3]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([4, 3], [12, 1]).HasAnyDenseDimensions); // Non-dense w/ Dense Dimension - Assert.True(Tensor.Create([2], [0]).HasAnyDenseDimension); + Assert.True(Tensor.Create([2], [0]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([2, 2], [1, 0]).HasAnyDenseDimension); + Assert.True(Tensor.Create([2, 2], [1, 0]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([3, 4], [1, 0]).HasAnyDenseDimension); + Assert.True(Tensor.Create([3, 4], [1, 0]).HasAnyDenseDimensions); // Non-dense w/ Non-dense Dimension - Assert.False(Tensor.Create([2], [2]).HasAnyDenseDimension); + Assert.False(Tensor.Create([2], [2]).HasAnyDenseDimensions); - Assert.False(Tensor.Create([2, 2], [1, 2]).HasAnyDenseDimension); + Assert.False(Tensor.Create([2, 2], [1, 2]).HasAnyDenseDimensions); - Assert.False(Tensor.Create([3, 4], [1, 12]).HasAnyDenseDimension); + Assert.False(Tensor.Create([3, 4], [1, 12]).HasAnyDenseDimensions); } [Fact] From 4521c68bdd4ff65e09c468a740eb27eaa7db9da5 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sun, 11 May 2025 18:22:14 -0700 Subject: [PATCH 4/9] Update CompatibilitySuppressions for S.N.Tensors as its experimental --- .../src/CompatibilitySuppressions.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libraries/System.Numerics.Tensors/src/CompatibilitySuppressions.xml b/src/libraries/System.Numerics.Tensors/src/CompatibilitySuppressions.xml index 9fda67ddf4a768..f488813caf6cda 100644 --- a/src/libraries/System.Numerics.Tensors/src/CompatibilitySuppressions.xml +++ b/src/libraries/System.Numerics.Tensors/src/CompatibilitySuppressions.xml @@ -337,6 +337,20 @@ lib/net9.0/System.Numerics.Tensors.dll true + + CP0006 + M:System.Numerics.Tensors.IReadOnlyTensor`2.ToDenseTensor + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0006 + M:System.Numerics.Tensors.IReadOnlyTensor`2.ToDenseTensor + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + CP0017 M:System.Numerics.Tensors.IReadOnlyTensor`2.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.Buffers.NIndex})$0 From f6f3481b2924975f8e0129401dbc43000339423b Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sun, 11 May 2025 19:54:32 -0700 Subject: [PATCH 5/9] Ensure minimumNonZeroStride is handled when 0 --- .../System.Numerics.Tensors.sln | 26 +++-- .../Numerics/Tensors/netcore/TensorShape.cs | 4 +- .../tests/TensorTests.cs | 102 +++++++++--------- 3 files changed, 73 insertions(+), 59 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/System.Numerics.Tensors.sln b/src/libraries/System.Numerics.Tensors/System.Numerics.Tensors.sln index cb5cf60398d544..2a1a4f598abdd8 100644 --- a/src/libraries/System.Numerics.Tensors/System.Numerics.Tensors.sln +++ b/src/libraries/System.Numerics.Tensors/System.Numerics.Tensors.sln @@ -1,4 +1,8 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36105.17 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{9F20CEA1-2216-4432-BBBD-F01E05D17F23}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.Numerics", "..\Microsoft.Bcl.Numerics\src\Microsoft.Bcl.Numerics.csproj", "{1578185F-C4FA-4866-936B-E62AAEDD03B7}" @@ -33,11 +37,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{7AC4B2C7-A55 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{841A2FA4-A95F-4612-A8B9-AD2EF769BC71}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "tools\gen", "{A21C99E7-E22B-470E-BF48-56B00AFE3D34}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{A21C99E7-E22B-470E-BF48-56B00AFE3D34}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "tools\src", "{25B37C75-C737-4AE8-9260-74A79870C8B8}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{25B37C75-C737-4AE8-9260-74A79870C8B8}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{9482D7C5-F37C-40FC-B057-A16C1ED1C121}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{9482D7C5-F37C-40FC-B057-A16C1ED1C121}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{F9C2AAB1-C7B0-4E43-BB18-4FB16F6E272B}" EndProject @@ -105,23 +109,27 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {9F20CEA1-2216-4432-BBBD-F01E05D17F23} = {DE94CA7D-BB10-4865-85A6-6B694631247F} - {46AD9423-D8C3-44BB-A201-1CCCAB4C6DAF} = {DE94CA7D-BB10-4865-85A6-6B694631247F} - {4AF6A02D-82C8-4898-9EDF-01F107C25061} = {DE94CA7D-BB10-4865-85A6-6B694631247F} {1578185F-C4FA-4866-936B-E62AAEDD03B7} = {DF0561A1-3AB8-4B51-AFB4-392EE1DD6247} - {848DD000-3D22-4A25-A9D9-05AFF857A116} = {DF0561A1-3AB8-4B51-AFB4-392EE1DD6247} {21CB448A-3882-4337-B416-D1A3E0BCFFC5} = {7AC4B2C7-A55C-4C4F-9B02-77F5CBFFF4AB} + {848DD000-3D22-4A25-A9D9-05AFF857A116} = {DF0561A1-3AB8-4B51-AFB4-392EE1DD6247} + {46AD9423-D8C3-44BB-A201-1CCCAB4C6DAF} = {DE94CA7D-BB10-4865-85A6-6B694631247F} + {4AF6A02D-82C8-4898-9EDF-01F107C25061} = {DE94CA7D-BB10-4865-85A6-6B694631247F} {D9283CC0-07E1-417A-B73C-223F3EB7A277} = {841A2FA4-A95F-4612-A8B9-AD2EF769BC71} {DB954E01-898A-4FE2-A3AA-180D041AB08F} = {841A2FA4-A95F-4612-A8B9-AD2EF769BC71} {04FC0651-B9D0-448A-A28B-11B1D4A897F4} = {A21C99E7-E22B-470E-BF48-56B00AFE3D34} {683A7D28-CC55-4375-848D-E659075ECEE4} = {A21C99E7-E22B-470E-BF48-56B00AFE3D34} - {A21C99E7-E22B-470E-BF48-56B00AFE3D34} = {F9C2AAB1-C7B0-4E43-BB18-4FB16F6E272B} {1CBEAEA8-2CA1-4B07-9930-35A785205852} = {25B37C75-C737-4AE8-9260-74A79870C8B8} {BA7828B1-7953-47A0-AE5A-E22B501C4BD0} = {25B37C75-C737-4AE8-9260-74A79870C8B8} - {25B37C75-C737-4AE8-9260-74A79870C8B8} = {F9C2AAB1-C7B0-4E43-BB18-4FB16F6E272B} {57E57290-3A6A-43F8-8764-D4DC8151F89C} = {9482D7C5-F37C-40FC-B057-A16C1ED1C121} + {A21C99E7-E22B-470E-BF48-56B00AFE3D34} = {F9C2AAB1-C7B0-4E43-BB18-4FB16F6E272B} + {25B37C75-C737-4AE8-9260-74A79870C8B8} = {F9C2AAB1-C7B0-4E43-BB18-4FB16F6E272B} {9482D7C5-F37C-40FC-B057-A16C1ED1C121} = {F9C2AAB1-C7B0-4E43-BB18-4FB16F6E272B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {10A5F2C3-5230-4916-9D4D-BBDB94851037} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{683a7d28-cc55-4375-848d-e659075ecee4}*SharedItemsImports = 5 + ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{ba7828b1-7953-47a0-ae5a-e22b501c4bd0}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs index 218cd1e548f498..dafcb1ad2ebaa4 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs @@ -212,7 +212,7 @@ ref Unsafe.As(ref metadata[rank * 2]), // n+1. This makes it convenient to support implicit broadcasting where higher dimensions // aren't actually stored in memory. - nint minimumNonZeroStride = 0; + nint minimumNonZeroStride = 1; var sortedWithIndex = destinationLinearRankOrder.ToArray() .Select((value, index) => new { Value = value, Index = index }) .OrderBy(x => x.Value) @@ -1217,7 +1217,7 @@ public TensorShape Slice(ReadOnlySpan state, out nint nint flattenedLength = 1; nint maximumLinearIndex = 0; - nint minimumNonZeroStride = 0; + nint minimumNonZeroStride = 1; nint computedOffset = 0; bool isDense = true; diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs index 509c6255e546fa..3f1ff1ff0e25f5 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs @@ -2793,31 +2793,31 @@ public void IsDenseTests() { // Dense - Assert.True(Tensor.Create([1]).IsDense); - Assert.True(Tensor.Create([1], [0]).IsDense); + Assert.True(Tensor.Create([(nint)1]).IsDense); + Assert.True(Tensor.Create([(nint)1], [0]).IsDense); - Assert.True(Tensor.Create([2]).IsDense); - Assert.True(Tensor.Create([2], [1]).IsDense); + Assert.True(Tensor.Create([(nint)2]).IsDense); + Assert.True(Tensor.Create([(nint)2], [1]).IsDense); - Assert.True(Tensor.Create([1, 2]).IsDense); - Assert.True(Tensor.Create([1, 2], [2, 1]).IsDense); + Assert.True(Tensor.Create([(nint)1, 2]).IsDense); + Assert.True(Tensor.Create([(nint)1, 2], [0, 1]).IsDense); - Assert.True(Tensor.Create([2, 2]).IsDense); - Assert.True(Tensor.Create([2, 2], [2, 1]).IsDense); + Assert.True(Tensor.Create([(nint)2, 2]).IsDense); + Assert.True(Tensor.Create([(nint)2, 2], [2, 1]).IsDense); - Assert.True(Tensor.Create([4, 3]).IsDense); - Assert.True(Tensor.Create([4, 3], [12, 1]).IsDense); + Assert.True(Tensor.Create([(nint)4, 3]).IsDense); + Assert.True(Tensor.Create([(nint)4, 3], [3, 1]).IsDense); // Non-dense - Assert.False(Tensor.Create([2], [0]).IsDense); - Assert.False(Tensor.Create([2], [2]).IsDense); + Assert.False(Tensor.Create([(nint)2], [0]).IsDense); + Assert.False(Tensor.Create([(nint)2], [2]).IsDense); - Assert.False(Tensor.Create([2, 2], [1, 2]).IsDense); - Assert.False(Tensor.Create([2, 2], [1, 0]).IsDense); + Assert.False(Tensor.Transpose(Tensor.Create([(nint)2, 2], [2, 1])).IsDense); + Assert.False(Tensor.Create([(nint)2, 2], [1, 0]).IsDense); - Assert.False(Tensor.Create([3, 4], [1, 12]).IsDense); - Assert.False(Tensor.Create([3, 4], [1, 0]).IsDense); + Assert.False(Tensor.Transpose(Tensor.Create([(nint)3, 4], [8, 1])).IsDense); + Assert.False(Tensor.Create([(nint)3, 4], [1, 0]).IsDense); } [Fact] @@ -2825,36 +2825,42 @@ public void HasAnyDenseDimensionTests() { // Dense - Assert.True(Tensor.Create([1]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([1], [0]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)1]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)1], [0]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([2]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([2], [1]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)2]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)2], [1]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([1, 2]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([1, 2], [2, 1]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)1, 2]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)1, 2], [0, 1]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([2, 2]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([2, 2], [2, 1]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)2, 2]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)2, 2], [2, 1]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([4, 3]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([4, 3], [12, 1]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)4, 3]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)4, 3], [3, 1]).HasAnyDenseDimensions); // Non-dense w/ Dense Dimension - Assert.True(Tensor.Create([2], [0]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)1], [0]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([2, 2], [1, 0]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)2, 1], [1, 0]).HasAnyDenseDimensions); - Assert.True(Tensor.Create([3, 4], [1, 0]).HasAnyDenseDimensions); + Assert.True(Tensor.Create([(nint)3, 1], [1, 0]).HasAnyDenseDimensions); // Non-dense w/ Non-dense Dimension - Assert.False(Tensor.Create([2], [2]).HasAnyDenseDimensions); + Assert.False(Tensor.Create([(nint)2], [0]).HasAnyDenseDimensions); - Assert.False(Tensor.Create([2, 2], [1, 2]).HasAnyDenseDimensions); + Assert.False(Tensor.Create([(nint)2, 2], [1, 0]).HasAnyDenseDimensions); - Assert.False(Tensor.Create([3, 4], [1, 12]).HasAnyDenseDimensions); + Assert.False(Tensor.Create([(nint)3, 4], [1, 0]).HasAnyDenseDimensions); + + Assert.False(Tensor.Create([(nint)2], [2]).HasAnyDenseDimensions); + + Assert.False(Tensor.Transpose(Tensor.Create([(nint)2, 2], [2, 1])).HasAnyDenseDimensions); + + Assert.False(Tensor.Transpose(Tensor.Create([(nint)3, 4], [8, 1])).HasAnyDenseDimensions); } [Fact] @@ -2862,31 +2868,31 @@ public void ToDenseTensorTests() { // Dense - AssertReturnsSelf(Tensor.Create([1])); - AssertReturnsSelf(Tensor.Create([1], [0])); + AssertReturnsSelf(Tensor.Create([(nint)1])); + AssertReturnsSelf(Tensor.Create([(nint)1], [0])); - AssertReturnsSelf(Tensor.Create([2])); - AssertReturnsSelf(Tensor.Create([2], [1])); + AssertReturnsSelf(Tensor.Create([(nint)2])); + AssertReturnsSelf(Tensor.Create([(nint)2], [1])); - AssertReturnsSelf(Tensor.Create([1, 2])); - AssertReturnsSelf(Tensor.Create([1, 2], [2, 1])); + AssertReturnsSelf(Tensor.Create([(nint)1, 2])); + AssertReturnsSelf(Tensor.Create([(nint)1, 2], [0, 1])); - AssertReturnsSelf(Tensor.Create([2, 2])); - AssertReturnsSelf(Tensor.Create([2, 2], [2, 1])); + AssertReturnsSelf(Tensor.Create([(nint)2, 2])); + AssertReturnsSelf(Tensor.Create([(nint)2, 2], [2, 1])); - AssertReturnsSelf(Tensor.Create([4, 3])); - AssertReturnsSelf(Tensor.Create([4, 3], [12, 1])); + AssertReturnsSelf(Tensor.Create([(nint)4, 3])); + AssertReturnsSelf(Tensor.Create([(nint)4, 3], [3, 1])); // Non-dense - AssertReturnsNewTensor(Tensor.Create([2], [0])); - AssertReturnsNewTensor(Tensor.Create([2], [2])); + AssertReturnsNewTensor(Tensor.Create([(nint)2], [0])); + AssertReturnsNewTensor(Tensor.Create([(nint)2], [2])); - AssertReturnsNewTensor(Tensor.Create([2, 2], [1, 2])); - AssertReturnsNewTensor(Tensor.Create([2, 2], [1, 0])); + AssertReturnsNewTensor(Tensor.Transpose(Tensor.Create([(nint)2, 2], [2, 1]))); + AssertReturnsNewTensor(Tensor.Create([(nint)2, 2], [1, 0])); - AssertReturnsNewTensor(Tensor.Create([3, 4], [1, 12])); - AssertReturnsNewTensor(Tensor.Create([3, 4], [1, 0])); + AssertReturnsNewTensor(Tensor.Transpose(Tensor.Create([(nint)3, 4], [8, 1]))); + AssertReturnsNewTensor(Tensor.Create([(nint)3, 4], [1, 0])); static void AssertReturnsSelf(Tensor tensor) { From 6c6d61da7b8f8bc152cf4c5295e9a0671f603661 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 12 May 2025 07:01:28 -0700 Subject: [PATCH 6/9] Ensure the assert handles IsEmpty not being dense --- .../src/System/Numerics/Tensors/netcore/TensorShape.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs index dafcb1ad2ebaa4..ca672a7435b80f 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs @@ -1101,12 +1101,13 @@ private void ValidateState() { if (IsDense) { + Debug.Assert(!IsEmpty); Debug.Assert(FlattenedLength == LinearLength); Debug.Assert(HasAnyDenseDimensions); } else { - Debug.Assert(FlattenedLength != LinearLength); + Debug.Assert((FlattenedLength != LinearLength) || IsEmpty); bool hasAnyDenseDimensions = false; From 6abd7ef6f49d72faa444b8da9139e93a92a526a7 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 12 May 2025 08:08:33 -0700 Subject: [PATCH 7/9] Don't assert that dense spans aren't empty --- .../src/System/Numerics/Tensors/netcore/TensorShape.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs index ca672a7435b80f..63ba4fc933e963 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs @@ -1101,7 +1101,11 @@ private void ValidateState() { if (IsDense) { - Debug.Assert(!IsEmpty); + // We don't assert !IsEmpty as a zero lengthed slice that + // tracks a byref to physical memory will still be marked + // as dense. This is in contrast to the default empty span + // which is not. + Debug.Assert(FlattenedLength == LinearLength); Debug.Assert(HasAnyDenseDimensions); } From a1e0eac11a2a0abf8d06da27db0b0b5a3d5eae18 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 12 May 2025 10:28:55 -0700 Subject: [PATCH 8/9] Ensure slice sets HasAnyDenseDimensions --- .../Numerics/Tensors/netcore/TensorShape.cs | 102 ++++++++---------- 1 file changed, 44 insertions(+), 58 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs index 63ba4fc933e963..26a3245592b743 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs @@ -292,37 +292,9 @@ ref Unsafe.As(ref metadata[rank * 2]), { _flags |= (TensorFlags.IsDense | TensorFlags.HasAnyDenseDimensions); } - else + else if (CalculateHasAnyDenseDimensions(lengths, strides)) { - // We aren't dense, but we might still have some dense dimension if - // the least significant non 0 stride is 1 and all least significant - // 0 strides have length 1. We cannot have a dense dimension for a - // non-zero stride with more than 1 element since we cannot get a - // single span for all elements in that index of the dimension. - - for (int i = 0; i < strides.Length; i++) - { - int index = strides.Length - (i + 1); - nint stride = strides[index]; - - if (stride == 1) - { - _flags |= TensorFlags.HasAnyDenseDimensions; - break; - } - - if (stride != 0) - { - break; - } - - nint length = lengths[index]; - - if (length != 1) - { - break; - } - } + _flags |= TensorFlags.HasAnyDenseDimensions; } } @@ -1112,34 +1084,7 @@ private void ValidateState() else { Debug.Assert((FlattenedLength != LinearLength) || IsEmpty); - - bool hasAnyDenseDimensions = false; - - for (int i = 0; i < Strides.Length; i++) - { - int index = Strides.Length - (i + 1); - nint stride = Strides[index]; - - if (stride == 1) - { - hasAnyDenseDimensions = true; - break; - } - - if (stride != 0) - { - break; - } - - nint length = Lengths[index]; - - if (length != 1) - { - break; - } - } - - Debug.Assert(HasAnyDenseDimensions == hasAnyDenseDimensions); + Debug.Assert(HasAnyDenseDimensions == CalculateHasAnyDenseDimensions(Lengths, Strides)); } Debug.Assert(IsBroadcast == Strides.Contains(0)); } @@ -1283,6 +1228,10 @@ public TensorShape Slice(ReadOnlySpan state, out nint { flags |= (TensorFlags.IsDense | TensorFlags.HasAnyDenseDimensions); } + else if (CalculateHasAnyDenseDimensions(intermediateLengths, intermediateStrides)) + { + flags |= TensorFlags.HasAnyDenseDimensions; + } TensorShape result = new TensorShape( flattenedLength, @@ -1310,6 +1259,43 @@ public TensorShape Slice(ReadOnlySpan state, out nint return result; } + private static bool CalculateHasAnyDenseDimensions(ReadOnlySpan lengths, ReadOnlySpan strides) + { + // We aren't dense, but we might still have some dense dimension if + // the least significant non 0 stride is 1 and all least significant + // 0 strides have length 1. We cannot have a dense dimension for a + // non-zero stride with more than 1 element since we cannot get a + // single span for all elements in that index of the dimension. + + bool hasAnyDenseDimensions = false; + + for (int i = 0; i < strides.Length; i++) + { + int index = strides.Length - (i + 1); + nint stride = strides[index]; + + if (stride == 1) + { + hasAnyDenseDimensions = true; + break; + } + + if (stride != 0) + { + break; + } + + nint length = lengths[index]; + + if (length != 1) + { + break; + } + } + + return hasAnyDenseDimensions; + } + public interface IGetOffsetAndLength { static abstract nint GetOffset(ReadOnlySpan state, int rankIndex, nint previousLength); From f99a2d7e5c60edb441e49474e11224553c13f81d Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 12 May 2025 12:49:41 -0700 Subject: [PATCH 9/9] Don't assert that a non-dense array cannot have equal flattened and linear lengths --- .../src/System/Numerics/Tensors/netcore/TensorShape.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs index 26a3245592b743..7d0e39571d350d 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs @@ -1083,7 +1083,6 @@ private void ValidateState() } else { - Debug.Assert((FlattenedLength != LinearLength) || IsEmpty); Debug.Assert(HasAnyDenseDimensions == CalculateHasAnyDenseDimensions(Lengths, Strides)); } Debug.Assert(IsBroadcast == Strides.Contains(0));