diff --git a/csharp/src/Microsoft.ML.OnnxRuntime/DisposableNamedOnnxValue.shared.cs b/csharp/src/Microsoft.ML.OnnxRuntime/DisposableNamedOnnxValue.shared.cs index 691aa59927..6d69f58d20 100644 --- a/csharp/src/Microsoft.ML.OnnxRuntime/DisposableNamedOnnxValue.shared.cs +++ b/csharp/src/Microsoft.ML.OnnxRuntime/DisposableNamedOnnxValue.shared.cs @@ -21,6 +21,7 @@ namespace Microsoft.ML.OnnxRuntime internal class DisposableList : List, IDisposableReadOnlyCollection where T : IDisposable { + private bool _disposed; public DisposableList() { } public DisposableList(int count) : base(count) { } @@ -30,6 +31,11 @@ namespace Microsoft.ML.OnnxRuntime protected virtual void Dispose(bool disposing) { + if (_disposed) + { + return; + } + if (disposing) { // Dispose in the reverse order. @@ -43,6 +49,7 @@ namespace Microsoft.ML.OnnxRuntime this[i]?.Dispose(); } this.Clear(); + _disposed = true; } } diff --git a/csharp/src/Microsoft.ML.OnnxRuntime/FixedBufferOnnxValue.shared.cs b/csharp/src/Microsoft.ML.OnnxRuntime/FixedBufferOnnxValue.shared.cs index 56e0106e9e..3a29eea1bd 100644 --- a/csharp/src/Microsoft.ML.OnnxRuntime/FixedBufferOnnxValue.shared.cs +++ b/csharp/src/Microsoft.ML.OnnxRuntime/FixedBufferOnnxValue.shared.cs @@ -89,7 +89,7 @@ namespace Microsoft.ML.OnnxRuntime /// \endcode /// public static FixedBufferOnnxValue CreateFromMemory(OrtMemoryInfo memoryInfo, Memory memory, - TensorElementType elementType, long[] shape, long bytesSize) + TensorElementType elementType, long[] shape, long bytesSize) where T : unmanaged { if(elementType == TensorElementType.String) { diff --git a/csharp/src/Microsoft.ML.OnnxRuntime/ManagedProjections.shared.cs b/csharp/src/Microsoft.ML.OnnxRuntime/ManagedProjections.shared.cs index 517fcb5683..e512a8c261 100644 --- a/csharp/src/Microsoft.ML.OnnxRuntime/ManagedProjections.shared.cs +++ b/csharp/src/Microsoft.ML.OnnxRuntime/ManagedProjections.shared.cs @@ -5,6 +5,7 @@ using Microsoft.ML.OnnxRuntime.Tensors; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; namespace Microsoft.ML.OnnxRuntime { @@ -24,8 +25,7 @@ namespace Microsoft.ML.OnnxRuntime /// /// /// - /// - /// + /// OrtValye created accoding to the metadata internal static OrtValue CreateProjection(NamedOnnxValue namedOnnxValue, NodeMetadata metadata) { OrtValue result; @@ -67,8 +67,7 @@ namespace Microsoft.ML.OnnxRuntime /// /// NamedOnnxValue containing a IEnumerable /// sequence metadata - /// cleanup list - /// + /// OrtValue that represents a sequence /// private static OrtValue CreateSequenceProjection(NamedOnnxValue namedOnnxValue, NodeMetadata metadata) { @@ -84,8 +83,8 @@ namespace Microsoft.ML.OnnxRuntime capacity = collection.Count; } - // Record all the ortValues belonging to the sequence locally - using (var sequenceOrtValues = new DisposableList(capacity)) + DisposableList sequenceOrtValues = new(capacity); + try { foreach (var element in seqContainer) { @@ -97,7 +96,12 @@ namespace Microsoft.ML.OnnxRuntime sequenceOrtValues.Add(CreateProjection(element, elementMeta)); } - return OrtValue.CreateSequence(sequenceOrtValues); + return OrtValue.CreateSequence(ref sequenceOrtValues); + } + catch(Exception) + { + sequenceOrtValues?.Dispose(); + throw; } } @@ -107,7 +111,6 @@ namespace Microsoft.ML.OnnxRuntime /// /// /// - /// /// OrtValue /// private static OrtValue CreateMapProjection(NamedOnnxValue node, NodeMetadata elementMeta) @@ -123,9 +126,13 @@ namespace Microsoft.ML.OnnxRuntime $"Node: {node.Name} onnxruntime only supports maps with primitive types values"); } - TensorBase keys = node.GetDictionaryKeys(); - using (OrtValue ortValueKeys = OrtValue.CreateFromTensorObject(keys, out TensorElementType elementTypeKeys)) + Span ortValues = new OrtValue[2]; + var disposableGuard = new DisposableArray(ortValues); + try { + TensorBase keys = node.GetDictionaryKeys(); + ortValues[0] = OrtValue.CreateFromTensorObject(keys, out TensorElementType elementTypeKeys); + if (elementTypeKeys != mapMeta.KeyDataType) { throw new OnnxRuntimeException(ErrorCode.InvalidArgument, @@ -133,39 +140,40 @@ namespace Microsoft.ML.OnnxRuntime } TensorBase values = node.GetDictionaryValues(); - using (OrtValue ortValueValues = OrtValue.CreateFromTensorObject(values, out TensorElementType elementTypeValues)) + ortValues[1] = OrtValue.CreateFromTensorObject(values, out TensorElementType elementTypeValues); + if (elementTypeValues != mapValuesMeta.ElementDataType) { - if (elementTypeValues != mapValuesMeta.ElementDataType) - { - throw new OnnxRuntimeException(ErrorCode.InvalidArgument, - $"Map value data type supplied: {elementTypeValues} metadata expected: {mapValuesMeta.ElementDataType}"); - } - - // Create Map OrtValue - return OrtValue.CreateMap(ortValueKeys, ortValueValues); + throw new OnnxRuntimeException(ErrorCode.InvalidArgument, + $"Map value data type supplied: {elementTypeValues} metadata expected: {mapValuesMeta.ElementDataType}"); } + + // Create Map OrtValue + return OrtValue.CreateMap(ref ortValues[0], ref ortValues[1]); + } + catch (Exception) + { + disposableGuard.Dispose(); + throw; } } - /// /// This pins memory that is contained within DenseTensor. /// /// NodeOnnxValue containing DenseTensor /// - /// cleanup list /// /// private static OrtValue CreateTensorProjection(NamedOnnxValue node, NodeMetadata elementMeta) { - if (!(node.Value is TensorBase)) + if (node.Value is not TensorBase) { throw new OnnxRuntimeException(ErrorCode.InvalidArgument, $"NamedOnnxValue contains: {node.Value.GetType()}, expecting a Tensor"); } OrtValue ortValue = OrtValue.CreateFromTensorObject(node.Value as TensorBase, out TensorElementType elementType); - try + try { if (elementType != elementMeta.ElementDataType) { @@ -173,7 +181,7 @@ namespace Microsoft.ML.OnnxRuntime $"Tensor element data type discovered: {elementType} metadata expected: {elementMeta.ElementDataType}"); } } - catch(Exception) + catch (Exception) { ortValue.Dispose(); throw; diff --git a/csharp/src/Microsoft.ML.OnnxRuntime/Microsoft.ML.OnnxRuntime.csproj b/csharp/src/Microsoft.ML.OnnxRuntime/Microsoft.ML.OnnxRuntime.csproj index 981d802f47..3c9f5cf674 100644 --- a/csharp/src/Microsoft.ML.OnnxRuntime/Microsoft.ML.OnnxRuntime.csproj +++ b/csharp/src/Microsoft.ML.OnnxRuntime/Microsoft.ML.OnnxRuntime.csproj @@ -66,7 +66,7 @@ AnyCPU;x86 - 7.3 + default true true ..\..\OnnxRuntime.snk diff --git a/csharp/src/Microsoft.ML.OnnxRuntime/OrtValue.shared.cs b/csharp/src/Microsoft.ML.OnnxRuntime/OrtValue.shared.cs index 2323959456..b3f3ee517d 100644 --- a/csharp/src/Microsoft.ML.OnnxRuntime/OrtValue.shared.cs +++ b/csharp/src/Microsoft.ML.OnnxRuntime/OrtValue.shared.cs @@ -5,6 +5,7 @@ using Microsoft.ML.OnnxRuntime.Tensors; using System; using System.Buffers; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; @@ -39,25 +40,60 @@ namespace Microsoft.ML.OnnxRuntime /// public class OrtValue : IOrtValueOwner, IDisposable { + // OrtValues that are members of Sequences or Maps that map. They potentially map managed memory and we need to keep them around. + // this exists only when we deal with compose ML types. + private DisposableList _compositeMembers; private IntPtr _handle; private MemoryHandle? _memHandle; // Present when the OrtValue is created on top of managed memory private bool _disposed; - /// - /// Constructor. The newly constructed OrtValue takes ownership of the native OrtValue instance - /// and disposes of it when the OrtValue instance is disposed. - /// - /// Pointer to a native instance of OrtValue - /// OnnxValue type if known, otherwise the constructor would interrogate - /// the handle - internal OrtValue(IntPtr handle, OnnxValueType onnxValueType = OnnxValueType.ONNX_TYPE_UNKNOWN) + internal OrtValue(IntPtr handle) { _handle = handle; - OnnxType = onnxValueType; - if (OnnxType == OnnxValueType.ONNX_TYPE_UNKNOWN) + InitOnnxType(); + } + + /// + /// Constructor. The newly constructed OrtValue takes ownership of the native OrtValue instance + /// + /// + /// + /// thrown when onnxValue type is not known + internal OrtValue(IntPtr handle, OnnxValueType onnxValueType) + { + if (onnxValueType == OnnxValueType.ONNX_TYPE_UNKNOWN) { - InitOnnxType(); + throw new ArgumentException("onnxValueType argument is passed as unknown"); } + + _handle = handle; + OnnxType = onnxValueType; + } + + /// + /// Constructor. The newly constructed OrtValue takes ownership of the native OrtValue instance + /// and disposes of it when the OrtValue instance is disposed. The instance will take ownership and will + /// dispose of compositeMembers instances. + /// + /// This constructor can only throw if OnnxType is not specified. + /// + /// native ortValue handle + /// must one of the valid types + /// For composite types this contains dependent ortValues such as members of a sequence + /// or keys/values for the map, that may have been created on top of the managed memory and must be disposed + /// with the new ortValue. This container will be taken the ownership of and the argument will be set to null. + /// throws when onnxValueType is not specified + internal OrtValue(IntPtr handle, OnnxValueType onnxValueType, ref DisposableList compositeMembers) + { + if (onnxValueType == OnnxValueType.ONNX_TYPE_UNKNOWN) + { + throw new ArgumentException("onnxValueType argument is passed as unknown"); + } + + _handle = handle; + OnnxType = onnxValueType; + _compositeMembers = compositeMembers; + compositeMembers = null; } /// @@ -165,7 +201,7 @@ namespace Microsoft.ML.OnnxRuntime /// /// ReadOnlySpan /// - public ReadOnlySpan GetTensorDataAsSpan() where T : struct + public ReadOnlySpan GetTensorDataAsSpan() where T : unmanaged { var byteSpan = GetTensorBufferRawData(typeof(T)); return MemoryMarshal.Cast(byteSpan); @@ -185,7 +221,7 @@ namespace Microsoft.ML.OnnxRuntime /// /// /// Typed Span over the native buffer - public Span GetTensorMutableDataAsSpan() where T : struct + public Span GetTensorMutableDataAsSpan() where T : unmanaged { var byteSpan = GetTensorBufferRawData(typeof(T)); return MemoryMarshal.Cast(byteSpan); @@ -505,6 +541,7 @@ namespace Microsoft.ML.OnnxRuntime /// A disposable OrtValue instance /// public static OrtValue CreateTensorValueFromMemory(OrtMemoryInfo memoryInfo, Memory memory, long[] shape) + where T : unmanaged { var typeInfo = TensorBase.GetTypeInfo(typeof(T)) ?? throw new OnnxRuntimeException(ErrorCode.InvalidArgument, $"Tensor of type: {typeof(T)} is not supported"); @@ -561,7 +598,7 @@ namespace Microsoft.ML.OnnxRuntime /// managed data buffer /// shape that describes the buffer /// A disposable OrtValue instance - public static OrtValue CreateTensorValueFromMemory(T[] data, long[] shape) + public static OrtValue CreateTensorValueFromMemory(T[] data, long[] shape) where T : unmanaged { return OrtValue.CreateTensorValueFromMemory(OrtMemoryInfo.DefaultInstance, new Memory(data), shape); } @@ -847,56 +884,340 @@ namespace Microsoft.ML.OnnxRuntime /// All OrtValues in the collection must be of the same Onnx type. /// I.e. (Tensor, SparseTensor, Map, Sequence, etc.) /// - /// All OrtValues are internally ref-counted and stored within the sequence OrtValue - /// so the input OrtValues can be disposed of after this call. + /// The ortValues that are passed as argument are taken possession of by the newly + /// created OrtValue. The caller should not dispose them, unless this call fails. + /// + /// The ortValues would be empty on successful return. /// - /// a collection of OrtValues + /// a collection of OrtValues. On success the ortValues contained in the list + /// are taken ownership of and the list is cleared. /// A disposable instance of OrtValues /// - public static OrtValue CreateSequence(IReadOnlyCollection ortValues) + public static OrtValue CreateSequence(ICollection ortValues) { if (ortValues is null) { throw new ArgumentNullException(nameof(ortValues)); } - var handles = new IntPtr[ortValues.Count]; - for (int i = 0; i < ortValues.Count; i++) + if (ortValues.IsReadOnly) { - handles[i] = ortValues.ElementAt(i).Handle; + throw new ArgumentException("ortValues argument can not be a readonly collection"); + } + + var compositeMembers = new DisposableList(ortValues); + try + { + var result = CreateSequence(ref compositeMembers); + Debug.Assert(compositeMembers is null, "Must be null on success"); + ortValues.Clear(); + return result; + } + catch (Exception) + { + // The caller is responsible for disposing the ortValues + compositeMembers?.Clear(); + throw; + } + } + + /// + /// Creates a sequence from the values in compositeMembers + /// The argument is taken possession of and is nullified on successful return. + /// + /// sequence ortValues + /// OrtValue instance representing a Sequence + internal static OrtValue CreateSequence(ref DisposableList compositeMembers) + { + var handles = new IntPtr[compositeMembers.Count]; + for (int i = 0; i < compositeMembers.Count; i++) + { + handles[i] = compositeMembers[i].Handle; } NativeApiStatus.VerifySuccess(NativeMethods.OrtCreateValue(handles, - (UIntPtr)ortValues.Count, (IntPtr)OnnxValueType.ONNX_TYPE_SEQUENCE, - out IntPtr sequenceHandle)); - return new OrtValue(sequenceHandle, OnnxValueType.ONNX_TYPE_SEQUENCE); + (UIntPtr)handles.Length, (IntPtr)OnnxValueType.ONNX_TYPE_SEQUENCE, + out IntPtr sequenceHandle)); + + return new OrtValue(sequenceHandle, OnnxValueType.ONNX_TYPE_SEQUENCE, ref compositeMembers); } + /// + /// A delegate type that is expected to process each OrtValue in a sequence. + /// + /// OrtValue that holds sequence element + /// ordinal of the value + public delegate void SequenceElementVisitor(OrtValue ortValue, int index); + + /// + /// Feeds each OrtValue in a sequence to the visitor delegate. + /// This helps users to avoid dealing each value life-span + /// + /// visitor delegate + /// allocator to use for intermediate ort values + /// + public void ProcessSequence(SequenceElementVisitor visitor, OrtAllocator allocator) + { + if (OnnxType != OnnxValueType.ONNX_TYPE_SEQUENCE) + { + throw new OnnxRuntimeException(ErrorCode.InvalidArgument, + $"OrtValue.OnnxType of {OnnxType} is not a sequence"); + } + + int count = GetValueCount(); + for (int i = 0; i < count; i++) + { + using var ortValue = GetValue(i, allocator); + visitor(ortValue, i); + } + } /// /// Creates a map OrtValue with keys and values. - /// ORT supports only a subset of types for keys and values. - /// We are not restricting them here. + /// On a high level the Onnxruntime representation of the map always consists of two + /// OrtValues, keys and values. /// - /// All OrtValues are internally ref-counted and stored within the map OrtValue - /// so the input OrtValues can be disposed of after this call. + /// According to ONNX standard map keys can be unmanaged types only (or strings). + /// Those keys are contained in a single tensor within OrtValue keys. + /// + /// Map values, on the other hand, can be composite types. The values parameter + /// can either contain a single tensor with unmanaged map values with the same number of + /// elements as the keys, or it can be a sequence of OrtValues, + /// each of those can be a composite type (tensor, sequence, map). If it is a sequence, + /// then the number of elements must match the number of elements in keys. + /// + /// Keys and values must be in the same order. + /// + /// ORT supports only a subset of types for keys and values, however, this API does not + /// restrict it. + /// + /// The ortValues that are passed as argument are taken possession of by the newly + /// created OrtValue. The caller should not dispose them, unless this call fails. + /// + /// Keys and values arguments will be set to null on success. /// /// Contains keys /// Contains values /// A disposable OrtValue /// - public static OrtValue CreateMap(OrtValue keys, OrtValue values) + public static OrtValue CreateMap(ref OrtValue keys, ref OrtValue values) { if (keys is null || values is null) { - throw new ArgumentNullException($"keys or/and values are null"); + throw new ArgumentNullException("keys or/and values are null"); } IntPtr[] handles = { keys.Handle, values.Handle }; NativeApiStatus.VerifySuccess( NativeMethods.OrtCreateValue(handles, (UIntPtr)handles.Length, (IntPtr)OnnxValueType.ONNX_TYPE_MAP, out IntPtr mapHandle)); - return new OrtValue(mapHandle, OnnxValueType.ONNX_TYPE_MAP); + + var compositeMembers = new DisposableList + { + keys, + values + }; + + keys = null; + values = null; + + // This constructor will not throw. + return new OrtValue(mapHandle, OnnxValueType.ONNX_TYPE_MAP, ref compositeMembers); + } + + /// + /// This API helps to quickly creates a map OrtValue with unmanaged (primitive) keys and values specified as arrays. + /// This helps the user not to create OrtValues for keys and values separately and deal only with the final result. + /// The map would consist of two tensors, one for keys and one for values. + /// + /// The OrtValues would be created on top of the managed memory arrays and use it directly. + /// The number of elements in keys and values must be the same and they must be in order. + /// + /// The types must be unmanaged. + /// + /// keys type + /// values type + /// array of keys of K type + /// array of values of V type + /// OrtValue instance + /// + /// + public static OrtValue CreateMap(K[] keys, V[] values) where K : unmanaged where V : unmanaged + { + if (keys is null || values is null) + { + throw new ArgumentNullException("Keys or/and values are null"); + } + + if (keys.Length != values.Length) + { + throw new ArgumentException("Expecting keys and values same len. " + + $"Received keys: {keys.Length}, Values: {values.Length}"); + } + + long[] shape = { keys.Length }; + Span ortValues = new OrtValue[2]; + var disposableGuard = new DisposableArray(ortValues); + try + { + ortValues[0] = CreateTensorValueFromMemory(keys, shape); + ortValues[1] = CreateTensorValueFromMemory(values, shape); + return CreateMap(ref ortValues[0], ref ortValues[1]); + } + catch (Exception) + { + disposableGuard.Dispose(); + throw; + } + } + + /// + /// Creates a map OrtValue with string keys and non-string values. + /// This helps the user not to create OrtValues for keys and values separately. + /// The number of elements in keys and values must be the same and they must be in order. + /// The map would consist of two tensors, one for keys and one for values. + /// + /// string keys would be converted to UTF-8 encoding and copied to an allocated native memory. + /// The OrtValue for values would be created on top of the managed memory using it directly. + /// + /// The values type must be unmanaged. + /// + /// + /// Collection of strings + /// + /// OrtValue instance + /// + /// + public static OrtValue CreateMapWithStringKeys(IReadOnlyCollection keys, V[] values) where V : unmanaged + { + if (keys is null || values is null) + { + throw new ArgumentNullException("Keys or/and values are null"); + } + + if (keys.Count != values.Length) + { + throw new ArgumentException("Expecting keys and values same len. " + + $"Received keys: {keys.Count}, Values: {values.Length}"); + } + + long[] shape = { keys.Count }; + + Span ortValues = new OrtValue[2]; + var disposableGuard = new DisposableArray(ortValues); + try + { + ortValues[0] = CreateTensorWithEmptyStrings(OrtAllocator.DefaultInstance, shape); + int count = 0; + foreach (var key in keys) + { + ortValues[0].FillStringTensorElement(key.AsSpan(), count++); + } + + ortValues[1] = CreateTensorValueFromMemory(values, shape); + return CreateMap(ref ortValues[0], ref ortValues[1]); + } + catch (Exception) + { + disposableGuard.Dispose(); + throw; + } + } + + /// + /// Creates a map OrtValue with non-string keys and string values. + /// + /// This helps the user not to create OrtValues for keys and values separately. + /// The number of elements in keys and values must be the same and they must be in order. + /// + /// The OrtValue for keys would be created on top of the managed memory using it directly. + /// string values would be converted to UTF-8 encoding and copied to an allocated native memory. + /// + /// + /// unmanaged type of keys + /// + /// collection of string values + /// Instance of OrtValue + /// + /// + public static OrtValue CreateMapWithStringValues(K[] keys, IReadOnlyCollection values) where K : unmanaged + { + if (keys is null || values is null) + { + throw new ArgumentNullException("Keys or/and values are null"); + } + + if (keys.Length != values.Count) + { + throw new ArgumentException("Expecting keys and values same len. " + + $"Received keys: {keys.Length}, Values: {values.Count}"); + } + + long[] shape = { keys.Length }; + Span ortValues = new OrtValue[2]; + var disposableGuard = new DisposableArray(ortValues); + try + { + ortValues[0] = CreateTensorValueFromMemory(keys, shape); + ortValues[1] = CreateTensorWithEmptyStrings(OrtAllocator.DefaultInstance, shape); + int count = 0; + foreach (var value in values) + { + ortValues[1].FillStringTensorElement(value.AsSpan(), count++); + } + return CreateMap(ref ortValues[0], ref ortValues[1]); + } + catch (Exception) + { + disposableGuard.Dispose(); + throw; + } + } + + /// + /// A public delegate that will be invoked once with map keys and values. + /// The delegate helps not to deal with the lifespan of intermediate OrtValues. + /// Typically, when one uses GetValue() API, it creates a copy of OrtValue + /// that points to the same buffer as keys or values. This API helps to deal with those + /// temporary instances and avoid leaks. + /// + /// According to ONNX standard map keys can be unmanaged types only (or strings). + /// Those keys are contained in a single tensor within OrtValue keys. So you can query those + /// directly from keys argument. + /// + /// Map values, on the other hand, can be composite types. The values parameter + /// can either contain a single tensor with unmanaged map values with the same number of + /// elements as the keys, or it can be a sequence of OrtValues, + /// each of those can be a composite type (tensor, sequence, map). If it is a sequence, + /// then the number of elements must match the number of elements in keys. + /// + /// Depending on the structure of the values, one will either directly query a single tensor + /// from values, or will have to iterate over the sequence of OrtValues and visit each of those + /// resulting in a recursive visitation. + /// + /// This would always represent a tensor + /// Can be any of the Onnx types, but they would all reduce to tensors eventually + public delegate void MapVisitor(OrtValue keys, OrtValue values); + + /// + /// This API helps the user to process a map OrtValue without + /// having to deal with the lifespan of intermediate OrtValues. + /// + /// each API value is fed to the vistor functor. + /// + /// visitor function + /// Allocator to use for intermediate values + /// + public void ProcessMap(MapVisitor visitor, OrtAllocator allocator) + { + if (OnnxType != OnnxValueType.ONNX_TYPE_MAP) + { + throw new OnnxRuntimeException(ErrorCode.InvalidArgument, "This OrtValue does not represent a map"); + } + + using var keys = GetValue(0, allocator); + using var values = GetValue(1, allocator); + visitor(keys, values); } private unsafe void FillStringTensorElement(char* strPtr, int strLength, int index) @@ -973,6 +1294,8 @@ namespace Microsoft.ML.OnnxRuntime { _memHandle?.Dispose(); _memHandle = null; + _compositeMembers?.Dispose(); + _compositeMembers = null; } Debug.Assert(_handle != IntPtr.Zero); diff --git a/csharp/test/Microsoft.ML.OnnxRuntime.EndToEndTests/Microsoft.ML.OnnxRuntime.EndToEndTests.csproj b/csharp/test/Microsoft.ML.OnnxRuntime.EndToEndTests/Microsoft.ML.OnnxRuntime.EndToEndTests.csproj index f44db30afd..1c9827c5ba 100644 --- a/csharp/test/Microsoft.ML.OnnxRuntime.EndToEndTests/Microsoft.ML.OnnxRuntime.EndToEndTests.csproj +++ b/csharp/test/Microsoft.ML.OnnxRuntime.EndToEndTests/Microsoft.ML.OnnxRuntime.EndToEndTests.csproj @@ -6,11 +6,12 @@ AnyCPU bin\$(Configuration)\ - 1.9.0 + 1.15.0 Microsoft.ML.OnnxRuntime true true true + default True true ..\..\OnnxRuntime.snk @@ -47,9 +48,10 @@ - - - + + + + @@ -102,4 +104,5 @@ + 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 12fee9b5db..ee81ab7743 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 @@ -14,7 +14,7 @@ $(OnnxRuntimeCsharpRoot)\..\cmake\external\onnx - 7.2 + default True true ..\..\OnnxRuntime.snk diff --git a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/OrtValueTests.cs b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/OrtValueTests.cs index 94996ed3c8..1b621e2b8e 100644 --- a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/OrtValueTests.cs +++ b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/OrtValueTests.cs @@ -120,7 +120,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests } } static void VerifyTensorCreateWithData(OrtValue tensor, TensorElementType dataType, long[] shape, - ReadOnlySpan originalData) where T : struct + ReadOnlySpan originalData) where T : unmanaged { // Verify invocation var dataTypeInfo = TensorBase.GetTypeInfo(typeof(T)); @@ -172,7 +172,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests // The tensor will be created on top of the managed memory. No copy is made. // The memory should stay pinned until the OrtValue instance is disposed. This means // stayed pinned until the end of Run() method when you are actually running inference. - using(var tensor = OrtValue.CreateTensorValueFromMemory(data, shape)) + using (var tensor = OrtValue.CreateTensorValueFromMemory(data, shape)) { VerifyTensorCreateWithData(tensor, TensorElementType.Int32, shape, data); } @@ -215,7 +215,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests } } - private static void PopulateAndCheck(T[] data) where T : struct + private static void PopulateAndCheck(T[] data) where T : unmanaged { var typeInfo = TensorBase.GetTypeInfo(typeof(T)); Assert.NotNull(typeInfo); @@ -255,80 +255,92 @@ namespace Microsoft.ML.OnnxRuntime.Tests private static readonly long[] ml_data_2 = { 3, 4 }; // Use this utility method to create two tensors for Map and Sequence tests - private static Tuple CreateTwoTensors(IList cleanup) + private static void CreateTwoTensors(out OrtValue val1, out OrtValue val2) { const int ml_data_dim = 2; // For map tensors they must be single dimensional long[] shape = { ml_data_dim }; - unsafe - { - var ortValue_1 = OrtValue.CreateTensorValueFromMemory(ml_data_1, shape); - cleanup.Add(ortValue_1); - var ortValue_2 = OrtValue.CreateTensorValueFromMemory(ml_data_2, shape); - cleanup.Add(ortValue_2); - return Tuple.Create(ortValue_1, ortValue_2); - } + val1 = OrtValue.CreateTensorValueFromMemory(ml_data_1, shape); + val2 = OrtValue.CreateTensorValueFromMemory(ml_data_2, shape); } - [Fact(DisplayName = "CreateMap")] - public void CreateMap() + [Fact(DisplayName = "CreateMapFromValues")] + public void CreateMapFromValues() { - using (var cleanUp = new DisposableListTest()) - { - var valTuple = CreateTwoTensors(cleanUp); - using (var map = OrtValue.CreateMap(valTuple.Item1, valTuple.Item2)) - { - Assert.Equal(OnnxValueType.ONNX_TYPE_MAP, map.OnnxType); - var typeInfo = map.GetTypeInfo(); - var mapInfo = typeInfo.MapTypeInfo; - Assert.Equal(TensorElementType.Int64, mapInfo.KeyType); - Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, mapInfo.ValueType.OnnxType); + CreateTwoTensors(out OrtValue keys, out OrtValue values); + using var map = OrtValue.CreateMap(ref keys, ref values); + Assert.Equal(OnnxValueType.ONNX_TYPE_MAP, map.OnnxType); + var typeInfo = map.GetTypeInfo(); + var mapInfo = typeInfo.MapTypeInfo; + Assert.Equal(TensorElementType.Int64, mapInfo.KeyType); + Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, mapInfo.ValueType.OnnxType); - // Must return always 2 for map since we have two ort values - Assert.Equal(2, map.GetValueCount()); + // Must return always 2 for map since we have two ort values + Assert.Equal(2, map.GetValueCount()); - var keys = map.GetValue(0, OrtAllocator.DefaultInstance); - cleanUp.Add(keys); - Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, keys.OnnxType); - Assert.Equal(ml_data_1, keys.GetTensorDataAsSpan().ToArray()); + map.ProcessMap((keys, values) => { + Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, keys.OnnxType); + Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, values.OnnxType); + Assert.Equal(ml_data_1, keys.GetTensorDataAsSpan().ToArray()); + Assert.Equal(ml_data_2, values.GetTensorDataAsSpan().ToArray()); - var vals = map.GetValue(1, OrtAllocator.DefaultInstance); - cleanUp.Add(vals); - Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, vals.OnnxType); - Assert.Equal(ml_data_2, vals.GetTensorDataAsSpan().ToArray()); - } - } + }, OrtAllocator.DefaultInstance); + } + + [Fact(DisplayName = "CreateMapFromArraysUnmanaged")] + public void CreateMapFromArraysUnmanaged() + { + long[] keys = { 1, 2, 3 }; + float[] vals = { 1, 2, 3 }; + using var map = OrtValue.CreateMap(keys, vals); + } + + [Fact(DisplayName = "CreateMapWithStringKeys")] + public void CreateMapWithStringKeys() + { + string[] keys = { "one", "two", "three" }; + float[] vals = { 1, 2, 3 }; + using var map = OrtValue.CreateMapWithStringKeys(keys, vals); + } + + [Fact(DisplayName = "CreateMapWithStringValues")] + public void CreateMapWithStringValues() + { + long[] keys = { 1, 2, 3 }; + string[] values = { "one", "two", "three" }; + using var map = OrtValue.CreateMapWithStringValues(keys, values); } [Fact(DisplayName = "CreateSequence")] public void CreateSequence() { - using (var cleanUp = new DisposableListTest()) + CreateTwoTensors(out OrtValue val1, out OrtValue val2); + using var seqVals = new DisposableListTest { val1, val2 }; + using var seq = OrtValue.CreateSequence(seqVals); + + Assert.Equal(OnnxValueType.ONNX_TYPE_SEQUENCE, seq.OnnxType); + var typeInfo = seq.GetTypeInfo(); + var seqInfo = typeInfo.SequenceTypeInfo; + Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, seqInfo.ElementType.OnnxType); + + // Will return 2 because we put 2 values in the sequence + Assert.Equal(2, seq.GetValueCount()); + + // Visit each element in the sequence + seq.ProcessSequence((ortValue, index) => { - var valTuple = CreateTwoTensors(cleanUp); - OrtValue[] seqVals = { valTuple.Item1, valTuple.Item2 }; - using (var seq = OrtValue.CreateSequence(seqVals)) + // We know both elements are tensors of long + Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, ortValue.OnnxType); + if (index == 0) { - Assert.Equal(OnnxValueType.ONNX_TYPE_SEQUENCE, seq.OnnxType); - var typeInfo = seq.GetTypeInfo(); - var seqInfo = typeInfo.SequenceTypeInfo; - Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, seqInfo.ElementType.OnnxType); - - // Will return 2 because we put 2 values in the sequence - Assert.Equal(2, seq.GetValueCount()); - - var item_0 = seq.GetValue(0, OrtAllocator.DefaultInstance); - cleanUp.Add(item_0); - Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, item_0.OnnxType); - Assert.Equal(ml_data_1, item_0.GetTensorDataAsSpan().ToArray()); - - var item_1 = seq.GetValue(1, OrtAllocator.DefaultInstance); - cleanUp.Add(item_1); - Assert.Equal(OnnxValueType.ONNX_TYPE_TENSOR, item_1.OnnxType); - Assert.Equal(ml_data_2, item_1.GetTensorDataAsSpan().ToArray()); + Assert.Equal(ml_data_1, ortValue.GetTensorDataAsSpan().ToArray()); } - } + else + { + Assert.Equal(ml_data_2, ortValue.GetTensorDataAsSpan().ToArray()); + } + }, OrtAllocator.DefaultInstance); } } } \ No newline at end of file diff --git a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/TestDataLoader.cs b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/TestDataLoader.cs index 548f7cf238..d9843f1788 100644 --- a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/TestDataLoader.cs +++ b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/TestDataLoader.cs @@ -16,12 +16,16 @@ namespace Microsoft.ML.OnnxRuntime.Tests where T : IDisposable { public DisposableListTest() - {} + { } + + public DisposableListTest(IEnumerable enumerable) : base(enumerable) + { } + public DisposableListTest(int count) : base(count) - {} + { } -#region IDisposable Support + #region IDisposable Support private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) @@ -54,7 +58,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests Dispose(true); GC.SuppressFinalize(this); } -#endregion + #endregion } internal struct DisposableTestPair : IDisposable @@ -218,26 +222,26 @@ namespace Microsoft.ML.OnnxRuntime.Tests switch (nodeMeta.OnnxValueType) { case OnnxValueType.ONNX_TYPE_TENSOR: - { - var tensor = Onnx.TensorProto.Parser.ParseFrom(file); - return LoadTensorPb(tensor, nodeName, nodeMeta); - } + { + var tensor = Onnx.TensorProto.Parser.ParseFrom(file); + return LoadTensorPb(tensor, nodeName, nodeMeta); + } case OnnxValueType.ONNX_TYPE_SEQUENCE: - { - var sequence = Onnx.SequenceProto.Parser.ParseFrom(file); - return CreateNamedOnnxValueFromSequence(sequence, nodeName, nodeMeta); - } + { + var sequence = Onnx.SequenceProto.Parser.ParseFrom(file); + return CreateNamedOnnxValueFromSequence(sequence, nodeName, nodeMeta); + } case OnnxValueType.ONNX_TYPE_MAP: - { - throw new NotImplementedException( - "Map test data format requires clarification: https://github.com/onnx/onnx/issues/5072"); - } + { + throw new NotImplementedException( + "Map test data format requires clarification: https://github.com/onnx/onnx/issues/5072"); + } case OnnxValueType.ONNX_TYPE_OPTIONAL: - { - var opt = Onnx.OptionalProto.Parser.ParseFrom(file); - return CreateNamedOnnxValueFromOptional(opt, nodeName, nodeMeta); - } + { + var opt = Onnx.OptionalProto.Parser.ParseFrom(file); + return CreateNamedOnnxValueFromOptional(opt, nodeName, nodeMeta); + } default: throw new NotImplementedException($"Unable to load value type: {nodeMeta.OnnxValueType} not implemented"); } @@ -254,26 +258,26 @@ namespace Microsoft.ML.OnnxRuntime.Tests switch (nodeMeta.OnnxValueType) { case OnnxValueType.ONNX_TYPE_TENSOR: - { - var tensor = Onnx.TensorProto.Parser.ParseFrom(file); - return new DisposableTestPair(nodeName, LoadOrValueTensorPb(tensor, nodeName, nodeMeta)); - } + { + var tensor = Onnx.TensorProto.Parser.ParseFrom(file); + return new DisposableTestPair(nodeName, LoadOrValueTensorPb(tensor, nodeName, nodeMeta)); + } case OnnxValueType.ONNX_TYPE_SEQUENCE: - { - var sequence = Onnx.SequenceProto.Parser.ParseFrom(file); - return new DisposableTestPair(nodeName, CreateOrtValueFromSequence(sequence, nodeName, nodeMeta)); - } + { + var sequence = Onnx.SequenceProto.Parser.ParseFrom(file); + return new DisposableTestPair(nodeName, CreateOrtValueFromSequence(sequence, nodeName, nodeMeta)); + } case OnnxValueType.ONNX_TYPE_MAP: - { - throw new NotImplementedException( - "Map test data format requires clarification: https://github.com/onnx/onnx/issues/5072"); - } + { + throw new NotImplementedException( + "Map test data format requires clarification: https://github.com/onnx/onnx/issues/5072"); + } case OnnxValueType.ONNX_TYPE_OPTIONAL: - { - var opt = Onnx.OptionalProto.Parser.ParseFrom(file); - return new DisposableTestPair(nodeName, CreateOrtValueFromOptional(opt, nodeName, nodeMeta)); - } + { + var opt = Onnx.OptionalProto.Parser.ParseFrom(file); + return new DisposableTestPair(nodeName, CreateOrtValueFromOptional(opt, nodeName, nodeMeta)); + } default: throw new NotImplementedException($"Unable to load value type: {nodeMeta.OnnxValueType} not implemented"); } @@ -309,50 +313,50 @@ namespace Microsoft.ML.OnnxRuntime.Tests switch (seqElemType) { case Onnx.SequenceProto.Types.DataType.Tensor: - { - SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_TENSOR); - var sequenceOfTensors = new List(sequence.TensorValues.Count); - foreach (var tensor in sequence.TensorValues) { - var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++); - var namedOnnxValue = LoadTensorPb(tensor, elemName, elemMeta); - sequenceOfTensors.Add(namedOnnxValue); + SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_TENSOR); + var sequenceOfTensors = new List(sequence.TensorValues.Count); + foreach (var tensor in sequence.TensorValues) + { + var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++); + var namedOnnxValue = LoadTensorPb(tensor, elemName, elemMeta); + sequenceOfTensors.Add(namedOnnxValue); + } + return NamedOnnxValue.CreateFromSequence(nodeName, sequenceOfTensors); } - return NamedOnnxValue.CreateFromSequence(nodeName, sequenceOfTensors); - } case Onnx.SequenceProto.Types.DataType.Sequence: - { - SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_SEQUENCE); - var seqOfSequences = new List(sequence.SequenceValues.Count); - foreach (var s in sequence.SequenceValues) { - var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++); - seqOfSequences.Add(CreateNamedOnnxValueFromSequence(s, elemName, elemMeta)); + SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_SEQUENCE); + var seqOfSequences = new List(sequence.SequenceValues.Count); + foreach (var s in sequence.SequenceValues) + { + var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++); + seqOfSequences.Add(CreateNamedOnnxValueFromSequence(s, elemName, elemMeta)); + } + return NamedOnnxValue.CreateFromSequence(nodeName, seqOfSequences); } - return NamedOnnxValue.CreateFromSequence(nodeName, seqOfSequences); - } case Onnx.SequenceProto.Types.DataType.Map: - { - SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_MAP); - var seqOfMaps = new List(sequence.MapValues.Count); - foreach (var m in sequence.MapValues) { - var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++); - seqOfMaps.Add(CreateNamedOnnxValueFromMap(m, elemName, elemMeta)); + SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_MAP); + var seqOfMaps = new List(sequence.MapValues.Count); + foreach (var m in sequence.MapValues) + { + var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++); + seqOfMaps.Add(CreateNamedOnnxValueFromMap(m, elemName, elemMeta)); + } + return NamedOnnxValue.CreateFromSequence(nodeName, seqOfMaps); } - return NamedOnnxValue.CreateFromSequence(nodeName, seqOfMaps); - } case Onnx.SequenceProto.Types.DataType.Optional: - { - SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_OPTIONAL); - var seqOfOpts = new List(sequence.OptionalValues.Count); - foreach (var opt in sequence.OptionalValues) { - var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++); - seqOfOpts.Add(CreateNamedOnnxValueFromOptional(opt, elemName, elemMeta)); + SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_OPTIONAL); + var seqOfOpts = new List(sequence.OptionalValues.Count); + foreach (var opt in sequence.OptionalValues) + { + var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++); + seqOfOpts.Add(CreateNamedOnnxValueFromOptional(opt, elemName, elemMeta)); + } + return NamedOnnxValue.CreateFromSequence(nodeName, seqOfOpts); } - return NamedOnnxValue.CreateFromSequence(nodeName, seqOfOpts); - } default: throw new NotImplementedException($"Sequence test data loading does not support element type: " + $"'{seqElemType}'"); @@ -370,20 +374,20 @@ namespace Microsoft.ML.OnnxRuntime.Tests switch ((Onnx.OptionalProto.Types.DataType)optional.ElemType) { case Onnx.OptionalProto.Types.DataType.Tensor: - { - var tensor = optional.TensorValue; - return LoadTensorPb(tensor, nodeName, meta); - } + { + var tensor = optional.TensorValue; + return LoadTensorPb(tensor, nodeName, meta); + } case Onnx.OptionalProto.Types.DataType.Sequence: - { - var sequence = optional.SequenceValue; - return CreateNamedOnnxValueFromSequence(sequence, nodeName, meta); - } + { + var sequence = optional.SequenceValue; + return CreateNamedOnnxValueFromSequence(sequence, nodeName, meta); + } case Onnx.OptionalProto.Types.DataType.Map: - { - var map = optional.MapValue; - return CreateNamedOnnxValueFromMap(map, nodeName, meta); - } + { + var map = optional.MapValue; + return CreateNamedOnnxValueFromMap(map, nodeName, meta); + } case Onnx.OptionalProto.Types.DataType.Optional: throw new NotImplementedException($"Unable to load '{nodeName}' optional contained within optional"); default: @@ -454,23 +458,21 @@ namespace Microsoft.ML.OnnxRuntime.Tests switch (seqElemType) { case Onnx.SequenceProto.Types.DataType.Tensor: - { - SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_TENSOR); - using (var sequenceOfTensors = new DisposableListTest(sequence.TensorValues.Count)) { + SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_TENSOR); + using DisposableListTest sequenceOfTensors = new(sequence.TensorValues.Count); foreach (var tensor in sequence.TensorValues) { var element = LoadOrValueTensorPb(tensor, sequence.Name, elemMeta); sequenceOfTensors.Add(element); } + // Will take possession of ortValues in the sequence and will clear this container return OrtValue.CreateSequence(sequenceOfTensors); } - } case Onnx.SequenceProto.Types.DataType.Sequence: // Sequence of sequences - { - SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_SEQUENCE); - using (var seqOfSequences = new DisposableListTest(sequence.TensorValues.Count)) { + SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_SEQUENCE); + using DisposableListTest seqOfSequences = new(sequence.TensorValues.Count); foreach (var s in sequence.SequenceValues) { var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++); @@ -479,17 +481,15 @@ namespace Microsoft.ML.OnnxRuntime.Tests } return OrtValue.CreateSequence(seqOfSequences); } - } case Onnx.SequenceProto.Types.DataType.Map: - { - throw new NotImplementedException( - "Test data format for maps is under investigation"); - } - case Onnx.SequenceProto.Types.DataType.Optional: - { - SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_OPTIONAL); - using (var seqOfSequences = new DisposableListTest(sequence.TensorValues.Count)) { + throw new NotImplementedException( + "Test data format for maps is under investigation"); + } + case Onnx.SequenceProto.Types.DataType.Optional: + { + SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_OPTIONAL); + using DisposableListTest seqOfSequences = new(sequence.TensorValues.Count); foreach (var opt in sequence.OptionalValues) { var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++); @@ -498,7 +498,6 @@ namespace Microsoft.ML.OnnxRuntime.Tests } return OrtValue.CreateSequence(seqOfSequences); } - } default: throw new NotImplementedException($"Sequence test data loading does not support element type: " + $"'{seqElemType}'"); @@ -511,20 +510,20 @@ namespace Microsoft.ML.OnnxRuntime.Tests switch ((Onnx.OptionalProto.Types.DataType)optional.ElemType) { case Onnx.OptionalProto.Types.DataType.Tensor: - { - var tensor = optional.TensorValue; - return LoadOrValueTensorPb(tensor, nodeName, meta); - } + { + var tensor = optional.TensorValue; + return LoadOrValueTensorPb(tensor, nodeName, meta); + } case Onnx.OptionalProto.Types.DataType.Sequence: - { - var sequence = optional.SequenceValue; - return CreateOrtValueFromSequence(sequence, nodeName, meta); - } + { + var sequence = optional.SequenceValue; + return CreateOrtValueFromSequence(sequence, nodeName, meta); + } case Onnx.OptionalProto.Types.DataType.Map: - { - throw new NotImplementedException( - "Test data format for maps is under investigation"); - } + { + throw new NotImplementedException( + "Test data format for maps is under investigation"); + } case Onnx.OptionalProto.Types.DataType.Optional: throw new NotImplementedException($"Unable to load '{nodeName}' optional contained within optional"); default: 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 61c3b1079f..9886f050fb 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 @@ -11,7 +11,7 @@ true $(OnnxSourceDirectory)\onnx - 7.2 + default True true ..\..\OnnxRuntime.snk