Support scalar tensors in c# (#4849)

This commit is contained in:
Hariharan Seshadri 2020-08-25 11:00:33 -07:00 committed by GitHub
parent d3cddba8f1
commit 26bd8c2085
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 26 deletions

View file

@ -36,11 +36,6 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
public static long GetProduct(ReadOnlySpan<int> dimensions, int startIndex = 0)
{
if (dimensions.Length == 0)
{
return 0;
}
long product = 1;
for (int i = startIndex; i < dimensions.Length; i++)
{
@ -96,6 +91,11 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
{
int[] strides = new int[dimensions.Length];
if (dimensions.Length == 0)
{
return strides;
}
int stride = 1;
if (reverseStride)
{
@ -174,6 +174,12 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
Debug.Assert(reverseStride ? IsAscending(strides) : IsDescending(strides), "Index decomposition requires ordered strides");
Debug.Assert(strides.Length == indices.Length);
// scalar tensor - nothing to process
if (indices.Length == 0)
{
return;
}
int remainder = index;
for (int i = startFromDimension; i < strides.Length; i++)
{
@ -199,6 +205,12 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
Debug.Assert(reverseStride ? IsAscending(strides) : IsDescending(strides), "Index decomposition requires ordered strides");
Debug.Assert(strides.Length == indices.Length);
// scalar tensor - nothing to process
if (indices.Length == 0)
{
return;
}
int remainder = index;
for (int i = startFromDimension; i < strides.Length; i++)
{
@ -220,6 +232,13 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
Debug.Assert(sourceReverseStride ? IsAscending(sourceStrides) : IsDescending(sourceStrides), "Index decomposition requires ordered strides");
Debug.Assert(sourceStrides.Length == transformStrides.Length);
// scalar tensor
if (sourceStrides.Length == 0)
{
Debug.Assert(index == 0, "Index has to be zero for a scalar tensor");
return 0;
}
int transformIndex = 0;
int remainder = index;

View file

@ -93,6 +93,7 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
/// <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
/// </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>
@ -103,6 +104,7 @@ 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
/// </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>
@ -170,11 +172,7 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
/// <returns>A new tensor that reinterprets backing Buffer of this tensor with different dimensions.</returns>
public override Tensor<T> Reshape(ReadOnlySpan<int> dimensions)
{
if (dimensions.Length == 0)
{
throw new ArgumentException("Dimensions must contain elements.", nameof(dimensions));
}
var newSize = ArrayUtilities.GetProduct(dimensions);
if (newSize != Length)

View file

@ -397,15 +397,15 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
}
/// <summary>
/// Initialize an n-dimensional tensor with the specified dimensions and layout. ReverseStride=true gives a stride of 1-element witdth to the first dimension (0). ReverseStride=false gives a stride of 1-element width to the last dimension (n-1).
/// Initialize an n-dimensional tensor with the specified dimensions and layout. 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="dimensions">An span of integers that represent the size of each dimension of the Tensor 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>
protected Tensor(ReadOnlySpan<int> dimensions, bool reverseStride) : base(typeof(T))
{
if (dimensions.Length == 0)
if (dimensions == null)
{
throw new ArgumentException("Dimensions must contain elements.", nameof(dimensions));
throw new ArgumentNullException(nameof(dimensions));
}
this.dimensions = new int[dimensions.Length];
@ -427,7 +427,7 @@ 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 witdth 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>
@ -438,11 +438,6 @@ namespace Microsoft.ML.OnnxRuntime.Tensors
throw new ArgumentNullException(nameof(fromArray));
}
if (fromArray.Rank == 0)
{
throw new ArgumentException("Array must contain elements.", nameof(fromArray));
}
dimensions = new int[fromArray.Rank];
long size = 1;
for (int i = 0; i < dimensions.Length; i++)

View file

@ -159,9 +159,7 @@ namespace Microsoft.ML.OnnxRuntime.Tensors.Tests
Assert.Equal(24, tensor.Length);
Assert.Equal(tensorConstructor.IsReversedStride, tensor.IsReversedStride);
//Assert.Throws<ArgumentNullException>("dimensions", () => tensorConstructor.CreateFromDimensions<int>(dimensions: null));
Assert.Throws<ArgumentException>("dimensions", () => tensorConstructor.CreateFromDimensions<int>(dimensions: new int[0]));
Assert.Throws<ArgumentNullException>("dimensions", () => tensorConstructor.CreateFromDimensions<int>(dimensions: null));
Assert.Throws<ArgumentOutOfRangeException>("dimensions", () => tensorConstructor.CreateFromDimensions<int>(dimensions: new[] { 1, -1 }));
// ensure dimensions are immutable
@ -224,12 +222,88 @@ namespace Microsoft.ML.OnnxRuntime.Tensors.Tests
Assert.True(StructuralComparisons.StructuralEqualityComparer.Equals(emptyTensor1, emptyTensor4));
Assert.True(StructuralComparisons.StructuralEqualityComparer.Equals(emptyTensor4, emptyTensor1));
// create an empty DenseTensor
// create an empty DenseTensor from dimensions
dimensions = new[] { 0, 2, 1 };
var emptyDenseTensor = new DenseTensor<int>(new Span<int>(dimensions));
var emptyDenseTensor1 = new DenseTensor<int>(new Span<int>(dimensions));
Assert.Equal(0, emptyDenseTensor1.Length);
// accessing any index in the underlying buffer should result in an IndexOutOfRangeException
Assert.Throws<IndexOutOfRangeException>(() => emptyDenseTensor.GetValue(0));
Assert.Throws<IndexOutOfRangeException>(() => emptyDenseTensor.GetValue(5));
Assert.Throws<IndexOutOfRangeException>(() => emptyDenseTensor1.GetValue(0));
Assert.Throws<IndexOutOfRangeException>(() => emptyDenseTensor1.GetValue(5));
// create an empty DenseTensor from memory
var memory = new Memory<int>(new int[] { });
var emptyDenseTensor2 = new DenseTensor<int>(memory, new int[] {2, 0, 2 });
Assert.Equal(0, emptyDenseTensor2.Length);
// accessing any index in the underlying buffer should result in an IndexOutOfRangeException
Assert.Throws<IndexOutOfRangeException>(() => emptyDenseTensor2.GetValue(0));
Assert.Throws<IndexOutOfRangeException>(() => emptyDenseTensor2.GetValue(5));
}
[Theory()]
[MemberData(nameof(GetSingleTensorConstructors))]
public void ConstructScalarTensors(TensorConstructor tensorConstructor)
{
// tests associated with scalar tensors (i.e.) tensors with no dimensions
// test creation of scalar tensors (from dimensions)
var dimensions = new int[] { };
var scalarTensor1 = tensorConstructor.CreateFromDimensions<int>(dimensions: dimensions);
Assert.Equal(0, scalarTensor1.Dimensions.Length);
Assert.Equal(0, scalarTensor1.Rank);
dimensions = new int[0];
var scalarTensor2 = tensorConstructor.CreateFromDimensions<int>(dimensions: dimensions);
Assert.Equal(0, scalarTensor2.Dimensions.Length);
Assert.Equal(0, scalarTensor2.Rank);
// TODO: Create from Array ?
// ensure the lengths of the scalar tensors is 1
Assert.Equal(1, scalarTensor1.Length);
Assert.Equal(1, scalarTensor2.Length);
// equality comparison of scalar tensors with non-scalar tensors throws an ArgumentException
dimensions = new int[] { 2, 2 };
var nonScalarTensor1 = tensorConstructor.CreateFromDimensions<int>(dimensions: dimensions);
dimensions = new int[] { 0, 2, 4 };
var nonScalarTensor2 = tensorConstructor.CreateFromDimensions<int>(dimensions: dimensions);
Assert.Throws<ArgumentException>("other", () => StructuralComparisons.StructuralComparer.Compare(nonScalarTensor1, scalarTensor1));
Assert.Throws<ArgumentException>("other", () => StructuralComparisons.StructuralComparer.Compare(scalarTensor1, nonScalarTensor1));
Assert.Throws<ArgumentException>("other", () => StructuralComparisons.StructuralComparer.Compare(nonScalarTensor2, scalarTensor1));
Assert.Throws<ArgumentException>("other", () => StructuralComparisons.StructuralComparer.Compare(scalarTensor1, nonScalarTensor2));
// equality comparison of scalar tensors is true
// equality comparison of a scalar tensor with itself
Assert.Equal(0, StructuralComparisons.StructuralComparer.Compare(scalarTensor1, scalarTensor1));
Assert.True(StructuralComparisons.StructuralEqualityComparer.Equals(scalarTensor1, scalarTensor1));
// equality comparison of a scalar tensor with another scalar tensor
Assert.Equal(0, StructuralComparisons.StructuralComparer.Compare(scalarTensor1, scalarTensor2));
Assert.Equal(0, StructuralComparisons.StructuralComparer.Compare(scalarTensor2, scalarTensor1));
Assert.True(StructuralComparisons.StructuralEqualityComparer.Equals(scalarTensor1, scalarTensor2));
Assert.True(StructuralComparisons.StructuralEqualityComparer.Equals(scalarTensor2, scalarTensor1));
// create a scalar DenseTensor from dimensions
dimensions = new int[] { };
var scalarDenseTensor1 = new DenseTensor<int>(new Span<int>(dimensions));
Assert.Equal(1, scalarDenseTensor1.Length);
// set and get values in the scalar tensor
scalarDenseTensor1.SetValue(0, 100);
Assert.Equal(100, scalarDenseTensor1.GetValue(0));
// setting a non-zero in the underlying buffer should result in an IndexOutOfRangeException
Assert.Throws<IndexOutOfRangeException>(() => scalarDenseTensor1.SetValue(6, 100));
// accessing a non-zero index in the underlying buffer should result in an IndexOutOfRangeException
Assert.Throws<IndexOutOfRangeException>(() => scalarDenseTensor1.GetValue(5));
// create a scalar DenseTensor from memory
var memory = new Memory<int>(new int[] { 1 });
var scalarDenseTensor2 = new DenseTensor<int>(memory, new int[] { });
Assert.Equal(1, scalarDenseTensor2.Length);
Assert.Equal(1, scalarDenseTensor2.GetValue(0));
}
[Theory()]