diff --git a/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/ArrayTensorExtensions.shared.cs b/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/ArrayTensorExtensions.shared.cs
index b26d215840..f6f57bd0b9 100644
--- a/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/ArrayTensorExtensions.shared.cs
+++ b/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/ArrayTensorExtensions.shared.cs
@@ -27,7 +27,12 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
/// A 1-dimensional DenseTensor<T> with the same length and content as .
public static DenseTensor ToTensor(this T[] array)
{
- return new DenseTensor(array);
+ // DenseTensor(Array, ...) is not efficient so do the copy here.
+ var dimensions = new int[] { array.Length };
+ T[] copy = new T[array.Length];
+ array.CopyTo(copy, 0);
+
+ return new DenseTensor(new Memory(copy), dimensions);
}
///
@@ -39,7 +44,25 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
/// A 2-dimensional DenseTensor<T> with the same dimensions and content as .
public static DenseTensor ToTensor(this T[,] array, bool reverseStride = false)
{
- return new DenseTensor(array, reverseStride);
+ if (reverseStride)
+ {
+ // we need logic from the DenseTensor ctor to be applied during copying
+ return new DenseTensor(array, reverseStride);
+ }
+ else
+ {
+ // it's more efficient to copy and flatten to 1D T[] and construct DenseTensor with Memory
+ T[] copy = new T[array.Length];
+ var dimensions = new int[] { array.GetLength(0), array.GetLength(1) };
+
+ long idx = 0;
+ foreach (var item in array)
+ {
+ copy[idx++] = item;
+ }
+
+ return new DenseTensor(new Memory(copy), dimensions);
+ }
}
///
@@ -51,7 +74,56 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
/// A 3-dimensional DenseTensor<T> with the same dimensions and content as .
public static DenseTensor ToTensor(this T[,,] array, bool reverseStride = false)
{
- return new DenseTensor(array, reverseStride);
+ if (reverseStride)
+ {
+ // we need logic from the DenseTensor ctor to be applied during copying
+ return new DenseTensor(array, reverseStride);
+ }
+ else
+ {
+ // it's more efficient to copy and flatten to 1D T[] and construct DenseTensor with Memory
+ T[] copy = new T[array.Length];
+ var dimensions = new int[] { array.GetLength(0), array.GetLength(1), array.GetLength(2) };
+
+ long idx = 0;
+ foreach (var item in array)
+ {
+ copy[idx++] = item;
+ }
+
+ return new DenseTensor(new Memory(copy), dimensions);
+ }
+ }
+
+ ///
+ /// Creates a copy of this four-dimensional array as a DenseTensor<T>
+ ///
+ /// Type contained in the array to copy to the DenseTensor<T>.
+ /// The array to create a DenseTensor<T> from.
+ /// False (default) to indicate that the first dimension is most major (farthest apart) and the last dimension is most minor (closest together): akin to row-major in a rank-2 tensor. True to indicate that the last dimension is most major (farthest apart) and the first dimension is most minor (closest together): akin to column-major in a rank-2 tensor.
+ /// A 4-dimensional DenseTensor<T> with the same dimensions and content as .
+ public static DenseTensor ToTensor(this T[,,,] array, bool reverseStride = false)
+ {
+ if (reverseStride)
+ {
+ // we need logic from the DenseTensor ctor to be applied during copying
+ return new DenseTensor(array, reverseStride);
+ }
+ else
+ {
+ // it's more efficient to copy and flatten to 1D T[] and construct DenseTensor with Memory
+ T[] copy = new T[array.Length];
+ var dimensions = new int[] {
+ array.GetLength(0), array.GetLength(1), array.GetLength(2), array.GetLength(3) };
+
+ long idx = 0;
+ foreach (var item in array)
+ {
+ copy[idx++] = item;
+ }
+
+ return new DenseTensor(new Memory(copy), dimensions);
+ }
}
///
diff --git a/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/DenseTensor.shared.cs b/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/DenseTensor.shared.cs
index 7dc3d6bd15..997b5eeb24 100644
--- a/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/DenseTensor.shared.cs
+++ b/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/DenseTensor.shared.cs
@@ -51,6 +51,7 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
backingArray[index++] = (T)item;
}
}
+
memory = backingArray;
}
@@ -66,26 +67,42 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
///
/// Initializes a rank-n Tensor using the dimensions specified in .
///
- /// An span of integers that represent the size of each dimension of the DenseTensor to create.
- /// False (default) to indicate that the first dimension is most major (farthest apart) and the last dimension is most minor (closest together): akin to row-major in a rank-2 tensor. True to indicate that the last dimension is most major (farthest apart) and the first dimension is most minor (closest together): akin to column-major in a rank-2 tensor.
+ ///
+ /// An span of integers that represent the size of each dimension of the DenseTensor to create.
+ ///
+ ///
+ /// False (default) to indicate that the first dimension is most major (farthest apart) and the last dimension
+ /// is most minor (closest together): akin to row-major in a rank-2 tensor.
+ /// True to indicate that the last dimension is most major (farthest apart) and the first dimension is most
+ /// minor (closest together): akin to column-major in a rank-2 tensor.
+ ///
public DenseTensor(ReadOnlySpan dimensions, bool reverseStride = false) : base(dimensions, reverseStride)
{
memory = new T[Length];
}
///
- /// Constructs a new DenseTensor of the specifed dimensions, wrapping existing backing memory for the contents.
+ /// Constructs a new DenseTensor of the specified dimensions, wrapping existing backing memory for the contents.
///
///
- /// An span of integers that represent the size of each dimension of the DenseTensor to create.
- /// False (default) to indicate that the first dimension is most major (farthest apart) and the last dimension is most minor (closest together): akin to row-major in a rank-2 tensor. True to indicate that the last dimension is most major (farthest apart) and the first dimension is most minor (closest together): akin to column-major in a rank-2 tensor.
- public DenseTensor(Memory memory, ReadOnlySpan dimensions, bool reverseStride = false) : base(dimensions, reverseStride)
+ ///
+ /// An span of integers that represent the size of each dimension of the DenseTensor to create.
+ ///
+ /// False (default) to indicate that the first dimension is most major (farthest apart) and the last dimension
+ /// is most minor (closest together): akin to row-major in a rank-2 tensor.
+ /// True to indicate that the last dimension is most major (farthest apart) and the first dimension is most
+ /// minor (closest together): akin to column-major in a rank-2 tensor.
+ ///
+ public DenseTensor(Memory memory, ReadOnlySpan dimensions, bool reverseStride = false)
+ : base(dimensions, reverseStride)
{
this.memory = memory;
if (Length != memory.Length)
{
- throw new ArgumentException($"Length of {nameof(memory)} ({memory.Length}) must match product of {nameof(dimensions)} ({Length}).");
+ throw new ArgumentException(
+ $"Length of {nameof(memory)} ({memory.Length}) must match product of " +
+ $"{nameof(dimensions)} ({Length}).");
}
}
@@ -95,8 +112,8 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
public Memory Buffer => memory;
///
- /// Gets the value at the specied index, where index is a linearized version of n-dimension indices using strides.
- /// For a scalar, use index = 0
+ /// Gets the value at the specified index, where index is a linearized version of n-dimension indices
+ /// using strides. For a scalar, use index = 0
///
/// An integer index computed as a dot-product of indices.
/// The value at the specified position in this Tensor.
@@ -106,8 +123,8 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
}
///
- /// Sets the value at the specied index, where index is a linearized version of n-dimension indices using strides.
- /// For a scalar, use index = 0
+ /// Sets the value at the specified index, where index is a linearized version of n-dimension indices
+ /// using strides. For a scalar, use index = 0
///
/// An integer index computed as a dot-product of indices.
/// The new value to set at the specified position in this Tensor.
@@ -130,7 +147,9 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
}
if (array.Length < arrayIndex + Length)
{
- throw new ArgumentException("The number of elements in the Tensor is greater than the available space from index to the end of the destination array.", nameof(array));
+ throw new ArgumentException(
+ "The number of elements in the Tensor is greater than the available space from index to " +
+ "the end of the destination array.", nameof(array));
}
Buffer.Span.CopyTo(array.AsSpan(arrayIndex));
@@ -165,14 +184,17 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
/// A shallow copy of this tensor.
public override Tensor Clone()
{
- return new DenseTensor(Buffer.ToArray(), dimensions, IsReversedStride);
+ // create copy
+ return new DenseTensor(new Memory(memory.ToArray()), dimensions, IsReversedStride);
}
///
- /// Creates a new Tensor of a different type with the specified dimensions and the same layout as this tensor with elements initialized to their default value.
+ /// Creates a new Tensor of a different type with the specified dimensions and the same layout as this tensor
+ /// with elements initialized to their default value.
///
/// Type contained in the returned Tensor.
- /// An span of integers that represent the size of each dimension of the DenseTensor to create.
+ ///
+ /// An span of integers that represent the size of each dimension of the DenseTensor to create.
/// A new tensor with the same layout as this tensor but different type and dimensions.
public override Tensor CloneEmpty(ReadOnlySpan dimensions)
{
@@ -182,7 +204,8 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
///
/// Reshapes the current tensor to new dimensions, using the same backing storage.
///
- /// An span of integers that represent the size of each dimension of the DenseTensor to create.
+ ///
+ /// An span of integers that represent the size of each dimension of the DenseTensor to create.
/// A new tensor that reinterprets backing Buffer of this tensor with different dimensions.
public override Tensor Reshape(ReadOnlySpan dimensions)
{
@@ -191,7 +214,8 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
if (newSize != Length)
{
- throw new ArgumentException($"Cannot reshape array due to mismatch in lengths, currently {Length} would become {newSize}.", nameof(dimensions));
+ throw new ArgumentException($"Cannot reshape array due to mismatch in lengths, " +
+ "currently {Length} would become {newSize}.", nameof(dimensions));
}
return new DenseTensor(Buffer, dimensions, IsReversedStride);
diff --git a/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/Tensor.shared.cs b/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/Tensor.shared.cs
index 94b48d10c8..bb7eea2ad1 100644
--- a/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/Tensor.shared.cs
+++ b/csharp/src/Microsoft.ML.OnnxRuntime/Tensors/Tensor.shared.cs
@@ -429,12 +429,16 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
}
///
- /// Creates a n+1-dimension tensor using the specified n-dimension diagonal at the specified offset from the center. Values not on the diagonal will be filled with zeros.
+ /// Creates a n+1-dimension tensor using the specified n-dimension diagonal at the specified offset
+ /// from the center. Values not on the diagonal will be filled with zeros.
///
- /// type contained within the Tensor. Typically a value type such as int, double, float, etc.
+ ///
+ /// type contained within the Tensor. Typically a value type such as int, double, float, etc.
/// Tensor representing the diagonal to build the new tensor from.
- /// Offset of diagonal to set in returned tensor. 0 for the main diagonal, less than zero for diagonals below, greater than zero from diagonals above.
- /// A new tensor of the same layout and order as of one higher rank, with the values of along the specified diagonal and zeros elsewhere.
+ /// Offset of diagonal to set in returned tensor. 0 for the main diagonal,
+ /// less than zero for diagonals below, greater than zero from diagonals above.
+ /// A new tensor of the same layout and order as of one higher rank,
+ /// with the values of along the specified diagonal and zeros elsewhere.
public static Tensor CreateFromDiagonal(Tensor diagonal, int offset)
{
if (diagonal.Rank < 1)
@@ -678,10 +682,16 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
}
///
- /// Initializes tensor with same dimensions as array, content of array is ignored. ReverseStride=true gives a stride of 1-element width to the first dimension (0). ReverseStride=false gives a stride of 1-element width to the last dimension (n-1).
+ /// Initializes tensor with same dimensions as array, content of array is ignored.
+ /// ReverseStride=true gives a stride of 1-element width to the first dimension (0).
+ /// ReverseStride=false gives a stride of 1-element width to the last dimension (n-1).
///
/// Array from which to derive dimensions.
- /// False (default) to indicate that the first dimension is most major (farthest apart) and the last dimension is most minor (closest together): akin to row-major in a rank-2 tensor. True to indicate that the last dimension is most major (farthest apart) and the first dimension is most minor (closest together): akin to column-major in a rank-2 tensor.
+ ///
+ /// False (default) to indicate that the first dimension is most major (farthest apart) and the
+ /// last dimension is most minor (closest together): akin to row-major in a rank-2 tensor.
+ /// True to indicate that the last dimension is most major (farthest apart) and the first dimension
+ /// is most minor (closest together): akin to column-major in a rank-2 tensor.
protected Tensor(Array fromArray, bool reverseStride) : base(typeof(T))
{
if (fromArray == null)
diff --git a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/Microsoft.ML.OnnxRuntime.Tests.Common.csproj b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/Microsoft.ML.OnnxRuntime.Tests.Common.csproj
index 706920746b..853b3818ee 100644
--- a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/Microsoft.ML.OnnxRuntime.Tests.Common.csproj
+++ b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/Microsoft.ML.OnnxRuntime.Tests.Common.csproj
@@ -77,6 +77,7 @@
+
diff --git a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/Tensors/ArrayTensorExtensionsTests.cs b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/Tensors/ArrayTensorExtensionsTests.cs
new file mode 100644
index 0000000000..5e1e82f809
--- /dev/null
+++ b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/Tensors/ArrayTensorExtensionsTests.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using Xunit;
+using Microsoft.ML.OnnxRuntime.Tensors;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.ML.OnnxRuntime.Tests.ArrayTensorExtensions
+{
+ public class ArrayTensorExtensionsTests
+ {
+ static void CheckValues(IEnumerable expected, DenseTensor tensor)
+ {
+ foreach (var pair in expected.Zip(tensor.Buffer.ToArray(), Tuple.Create))
+ {
+ Assert.Equal(pair.Item1, pair.Item2);
+ }
+ }
+
+ [Fact]
+ public void ConstructFrom1D()
+ {
+ var array = new int[] { 1, 2, 3, 4 };
+ var tensor = array.ToTensor();
+
+ var expectedDims = new int[] { 4 };
+ Assert.Equal(tensor.Length, array.Length);
+ Assert.Equal(expectedDims, tensor.Dimensions.ToArray());
+ CheckValues(array.Cast(), tensor);
+ }
+
+ [Fact]
+ public void ConstructFrom2D()
+ {
+ var array = new int[,] { { 1, 2 } , { 3, 4 } };
+ var tensor = array.ToTensor();
+
+ var expectedDims = new int[] { 2, 2 };
+ Assert.Equal(tensor.Length, array.Length);
+ Assert.Equal(expectedDims, tensor.Dimensions.ToArray());
+ CheckValues(array.Cast(), tensor);
+ }
+
+ [Fact]
+ public void ConstructFrom3D()
+ {
+ var array = new int[,,] { { { 1, 2 }, { 3, 4 } },
+ { { 5, 6 }, { 7, 8 } } };
+ var tensor = array.ToTensor();
+
+ var expectedDims = new int[] { 2, 2, 2 };
+ Assert.Equal(tensor.Length, array.Length);
+ Assert.Equal(expectedDims, tensor.Dimensions.ToArray());
+ CheckValues(array.Cast(), tensor);
+ }
+
+ [Fact]
+ public void ConstructFrom3DWithDim1()
+ {
+ var array = new int[,,] { { { 1, 2 } },
+ { { 3, 4 } } };
+ var tensor = array.ToTensor();
+
+ var expectedDims = new int[] { 2, 1, 2 };
+ Assert.Equal(tensor.Length, array.Length);
+ Assert.Equal(expectedDims, tensor.Dimensions.ToArray());
+ CheckValues(array.Cast(), tensor);
+ }
+
+ [Fact]
+ public void ConstructFrom4D()
+ {
+ var array = new int[,,,] {
+ { { { 1, 2 }, { 3, 4 } },
+ { { 5, 6 }, { 7, 8 } } }
+ };
+ var tensor = array.ToTensor();
+
+ var expectedDims = new int[] { 1, 2, 2, 2 };
+ Assert.Equal(tensor.Length, array.Length);
+ Assert.Equal(expectedDims, tensor.Dimensions.ToArray());
+ CheckValues(array.Cast(), tensor);
+ }
+
+ [Fact]
+ public void ConstructFrom5D()
+ {
+ var array = new int[,,,,] {
+ { { { { 1, 2 }, { 3, 4 } },
+ { { 5, 6 }, { 7, 8 } } } }
+ };
+
+ // 5D requires cast to Array
+ Array a = (Array)array;
+ var tensor = a.ToTensor();
+
+ var expectedDims = new int[] { 1, 1, 2, 2, 2 };
+ Assert.Equal(tensor.Length, array.Length);
+ Assert.Equal(expectedDims, tensor.Dimensions.ToArray());
+ CheckValues(array.Cast(), tensor);
+ }
+ }
+}
diff --git a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp.csproj b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp.csproj
index 6a3a7ed4c0..49ec3af9e9 100644
--- a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp.csproj
+++ b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp.csproj
@@ -89,6 +89,9 @@
TensorTests.cs
+
+ ArrayTensorExtensionsTests.cs
+