mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-05-23 22:13:38 +00:00
C#: Avoid inefficient DenseTensor ctor in ToTensor extensions (#10240)
* Update extension helpers to avoid inefficient construction of DenseTensor. Add tests for extension helpers.
This commit is contained in:
parent
6ae22d562b
commit
c1c9fa18bf
6 changed files with 241 additions and 26 deletions
|
|
@ -27,7 +27,12 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
|
|||
/// <returns>A 1-dimensional DenseTensor<T> with the same length and content as <paramref name="array"/>.</returns>
|
||||
public static DenseTensor<T> ToTensor<T>(this T[] array)
|
||||
{
|
||||
return new DenseTensor<T>(array);
|
||||
// DenseTensor<T>(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<T>(new Memory<T>(copy), dimensions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -39,7 +44,25 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
|
|||
/// <returns>A 2-dimensional DenseTensor<T> with the same dimensions and content as <paramref name="array"/>.</returns>
|
||||
public static DenseTensor<T> ToTensor<T>(this T[,] array, bool reverseStride = false)
|
||||
{
|
||||
return new DenseTensor<T>(array, reverseStride);
|
||||
if (reverseStride)
|
||||
{
|
||||
// we need logic from the DenseTensor ctor to be applied during copying
|
||||
return new DenseTensor<T>(array, reverseStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's more efficient to copy and flatten to 1D T[] and construct DenseTensor with Memory<T>
|
||||
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<T>(new Memory<T>(copy), dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -51,7 +74,56 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
|
|||
/// <returns>A 3-dimensional DenseTensor<T> with the same dimensions and content as <paramref name="array"/>.</returns>
|
||||
public static DenseTensor<T> ToTensor<T>(this T[,,] array, bool reverseStride = false)
|
||||
{
|
||||
return new DenseTensor<T>(array, reverseStride);
|
||||
if (reverseStride)
|
||||
{
|
||||
// we need logic from the DenseTensor ctor to be applied during copying
|
||||
return new DenseTensor<T>(array, reverseStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's more efficient to copy and flatten to 1D T[] and construct DenseTensor with Memory<T>
|
||||
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<T>(new Memory<T>(copy), dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of this four-dimensional array as a DenseTensor<T>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type contained in the array to copy to the DenseTensor<T>.</typeparam>
|
||||
/// <param name="array">The array to create a DenseTensor<T> from.</param>
|
||||
/// <param name="reverseStride">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.</param>
|
||||
/// <returns>A 4-dimensional DenseTensor<T> with the same dimensions and content as <paramref name="array"/>.</returns>
|
||||
public static DenseTensor<T> ToTensor<T>(this T[,,,] array, bool reverseStride = false)
|
||||
{
|
||||
if (reverseStride)
|
||||
{
|
||||
// we need logic from the DenseTensor ctor to be applied during copying
|
||||
return new DenseTensor<T>(array, reverseStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's more efficient to copy and flatten to 1D T[] and construct DenseTensor with Memory<T>
|
||||
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<T>(new Memory<T>(copy), dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
|
|||
backingArray[index++] = (T)item;
|
||||
}
|
||||
}
|
||||
|
||||
memory = backingArray;
|
||||
}
|
||||
|
||||
|
|
@ -66,26 +67,42 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
|
|||
/// <summary>
|
||||
/// Initializes a rank-n Tensor using the dimensions specified in <paramref name="dimensions"/>.
|
||||
/// </summary>
|
||||
/// <param name="dimensions">An span of integers that represent the size of each dimension of the DenseTensor to create.</param>
|
||||
/// <param name="reverseStride">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.</param>
|
||||
/// <param name="dimensions">
|
||||
/// An span of integers that represent the size of each dimension of the DenseTensor to create.
|
||||
/// </param>
|
||||
/// <param name="reverseStride">
|
||||
/// 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.
|
||||
/// </param>
|
||||
public DenseTensor(ReadOnlySpan<int> dimensions, bool reverseStride = false) : base(dimensions, reverseStride)
|
||||
{
|
||||
memory = new T[Length];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="memory"></param>
|
||||
/// <param name="dimensions">An span of integers that represent the size of each dimension of the DenseTensor to create.</param>
|
||||
/// <param name="reverseStride">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.</param>
|
||||
public DenseTensor(Memory<T> memory, ReadOnlySpan<int> dimensions, bool reverseStride = false) : base(dimensions, reverseStride)
|
||||
/// <param name="dimensions">
|
||||
/// An span of integers that represent the size of each dimension of the DenseTensor to create.</param>
|
||||
/// <param name="reverseStride">
|
||||
/// 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.
|
||||
/// </param>
|
||||
public DenseTensor(Memory<T> memory, ReadOnlySpan<int> 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<T> Buffer => memory;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="index">An integer index computed as a dot-product of indices.</param>
|
||||
/// <returns>The value at the specified position in this Tensor.</returns>
|
||||
|
|
@ -106,8 +123,8 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="index">An integer index computed as a dot-product of indices.</param>
|
||||
/// <param name="value">The new value to set at the specified position in this Tensor.</param>
|
||||
|
|
@ -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
|
|||
/// <returns>A shallow copy of this tensor.</returns>
|
||||
public override Tensor<T> Clone()
|
||||
{
|
||||
return new DenseTensor<T>(Buffer.ToArray(), dimensions, IsReversedStride);
|
||||
// create copy
|
||||
return new DenseTensor<T>(new Memory<T>(memory.ToArray()), dimensions, IsReversedStride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">Type contained in the returned Tensor.</typeparam>
|
||||
/// <param name="dimensions">An span of integers that represent the size of each dimension of the DenseTensor to create.</param>
|
||||
/// <param name="dimensions">
|
||||
/// An span of integers that represent the size of each dimension of the DenseTensor to create.</param>
|
||||
/// <returns>A new tensor with the same layout as this tensor but different type and dimensions.</returns>
|
||||
public override Tensor<TResult> CloneEmpty<TResult>(ReadOnlySpan<int> dimensions)
|
||||
{
|
||||
|
|
@ -182,7 +204,8 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
|
|||
/// <summary>
|
||||
/// Reshapes the current tensor to new dimensions, using the same backing storage.
|
||||
/// </summary>
|
||||
/// <param name="dimensions">An span of integers that represent the size of each dimension of the DenseTensor to create.</param>
|
||||
/// <param name="dimensions">
|
||||
/// An span of integers that represent the size of each dimension of the DenseTensor to create.</param>
|
||||
/// <returns>A new tensor that reinterprets backing Buffer of this tensor with different dimensions.</returns>
|
||||
public override Tensor<T> Reshape(ReadOnlySpan<int> 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<T>(Buffer, dimensions, IsReversedStride);
|
||||
|
|
|
|||
|
|
@ -429,12 +429,16 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">type contained within the Tensor. Typically a value type such as int, double, float, etc.</typeparam>
|
||||
/// <typeparam name="T">
|
||||
/// type contained within the Tensor. Typically a value type such as int, double, float, etc.</typeparam>
|
||||
/// <param name="diagonal">Tensor representing the diagonal to build the new tensor from.</param>
|
||||
/// <param name="offset">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.</param>
|
||||
/// <returns>A new tensor of the same layout and order as <paramref name="diagonal"/> of one higher rank, with the values of <paramref name="diagonal"/> along the specified diagonal and zeros elsewhere.</returns>
|
||||
/// <param name="offset">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.</param>
|
||||
/// <returns>A new tensor of the same layout and order as <paramref name="diagonal"/> of one higher rank,
|
||||
/// with the values of <paramref name="diagonal"/> along the specified diagonal and zeros elsewhere.</returns>
|
||||
public static Tensor<T> CreateFromDiagonal<T>(Tensor<T> diagonal, int offset)
|
||||
{
|
||||
if (diagonal.Rank < 1)
|
||||
|
|
@ -678,10 +682,16 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
/// <param name="fromArray">Array from which to derive dimensions.</param>
|
||||
/// <param name="reverseStride">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.</param>
|
||||
/// <param name="reverseStride">
|
||||
/// 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.</param>
|
||||
protected Tensor(Array fromArray, bool reverseStride) : base(typeof(T))
|
||||
{
|
||||
if (fromArray == null)
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@
|
|||
<None Include="InferenceTest.cs"/>
|
||||
<None Include="OrtIoBindingAllocationTest.cs" Condition=" '$(EnableDefaultCompileItems)' == 'true' " />
|
||||
<None Include="Tensors\TensorTests.cs" Condition=" '$(EnableDefaultCompileItems)' == 'true' " />
|
||||
<None Include="Tensors\ArrayTensorExtensionTests.cs" Condition=" '$(EnableDefaultCompileItems)' == 'true' " />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Tensors\TensorArithmetic.tt">
|
||||
|
|
|
|||
|
|
@ -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<int> expected, DenseTensor<int> 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<int>(), 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<int>(), 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<int>(), 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<int>(), 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<int>(), 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<int>();
|
||||
|
||||
var expectedDims = new int[] { 1, 1, 2, 2, 2 };
|
||||
Assert.Equal(tensor.Length, array.Length);
|
||||
Assert.Equal(expectedDims, tensor.Dimensions.ToArray());
|
||||
CheckValues(array.Cast<int>(), tensor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -89,6 +89,9 @@
|
|||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\Tensors\TensorTests.cs">
|
||||
<Link>TensorTests.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\Tensors\ArrayTensorExtensionsTests.cs">
|
||||
<Link>ArrayTensorExtensionsTests.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
Loading…
Reference in a new issue