mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-02 03:55:34 +00:00
Add MAUI test app that can be used to test model loading and performance (#16658)
### Description <!-- Describe your changes. --> MAUI test app with tooling to add model and generated or provided input test data. The app will load the model and validate the output. It can also run a specified number of iterations to provide basic performance information. <img width="401" alt="image" src="https://github.com/microsoft/onnxruntime/assets/979079/daf3af13-fb22-4cbb-9159-486b483a7485"> ### Motivation and Context <!-- - Why is this change required? What problem does it solve? - If it fixes an open issue, please link to the issue here. --> Primarily to make it easier to test an arbitrary model on iOS. A MAUI app allows testing on all platforms. --------- Co-authored-by: Edward Chen <18449977+edgchen1@users.noreply.github.com>
This commit is contained in:
parent
a45b834722
commit
ad90352a68
44 changed files with 61316 additions and 262 deletions
|
|
@ -1,15 +1,22 @@
|
|||
---
|
||||
# clang-format settings for the C# code
|
||||
BasedOnStyle: Microsoft
|
||||
BasedOnStyle: Microsoft
|
||||
|
||||
# Setting ColumnLimit to 0 so developer choices about where to break lines are maintained.
|
||||
# Developers are responsible for adhering to the 120 character maximum.
|
||||
ColumnLimit: 0
|
||||
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
BeforeWhile: true
|
||||
AfterCaseLabel: true
|
||||
BeforeWhile: true
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
# unfortunately there's no config option for handling the 'get' or 'set' of properties
|
||||
# unfortunately there's no config option for handling the 'get' or 'set' of properties
|
||||
|
||||
IndentCaseLabels: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
SpacesInContainerLiterals: false
|
||||
NamespaceIndentation: All
|
||||
SpacesInContainerLiterals: false
|
||||
SortIncludes: CaseSensitive
|
||||
SortUsingDeclarations: LexicographicNumeric
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.ML.OnnxRuntime.Tests
|
||||
{
|
||||
internal class FloatComparer : IEqualityComparer<float>
|
||||
{
|
||||
private float atol = 1e-3f;
|
||||
private float rtol = 1.7e-2f;
|
||||
|
||||
public bool Equals(float x, float y)
|
||||
{
|
||||
return Math.Abs(x - y) <= (atol + rtol * Math.Abs(y));
|
||||
}
|
||||
public int GetHashCode(float x)
|
||||
{
|
||||
return x.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
internal class DoubleComparer : IEqualityComparer<double>
|
||||
{
|
||||
private double atol = 1e-3;
|
||||
private double rtol = 1.7e-2;
|
||||
|
||||
public bool Equals(double x, double y)
|
||||
{
|
||||
return Math.Abs(x - y) <= (atol + rtol * Math.Abs(y));
|
||||
}
|
||||
public int GetHashCode(double x)
|
||||
{
|
||||
return x.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
internal class ExactComparer<T> : IEqualityComparer<T>
|
||||
{
|
||||
public bool Equals(T x, T y)
|
||||
{
|
||||
return x.Equals(y);
|
||||
}
|
||||
public int GetHashCode(T x)
|
||||
{
|
||||
return x.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use it to compare Float16
|
||||
/// </summary>
|
||||
internal class Float16Comparer : IEqualityComparer<Float16>
|
||||
{
|
||||
public ushort tolerance = 0;
|
||||
public bool Equals(Float16 x, Float16 y)
|
||||
{
|
||||
return Math.Abs(x.value - y.value) <= (tolerance + y.value);
|
||||
}
|
||||
public int GetHashCode(Float16 x)
|
||||
{
|
||||
return x.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use it to compare Bloat16
|
||||
/// </summary>
|
||||
internal class BFloat16Comparer : IEqualityComparer<BFloat16>
|
||||
{
|
||||
public ushort tolerance = 0;
|
||||
public bool Equals(BFloat16 x, BFloat16 y)
|
||||
{
|
||||
return Math.Abs(x.value - y.value) <= (tolerance + y.value);
|
||||
}
|
||||
public int GetHashCode(BFloat16 x)
|
||||
{
|
||||
return x.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1993,7 +1993,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
#endif
|
||||
var session = (deviceId.HasValue)
|
||||
? new InferenceSession(model, option)
|
||||
: new InferenceSession(model);
|
||||
: new InferenceSession(model);
|
||||
float[] inputData = TestDataLoader.LoadTensorFromEmbeddedResource("bench.in");
|
||||
float[] expectedOutput = TestDataLoader.LoadTensorFromEmbeddedResource("bench.expected_out");
|
||||
var inputMeta = session.InputMetadata;
|
||||
|
|
|
|||
|
|
@ -15,10 +15,13 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
public class DisposableListTest<T> : List<T>, IDisposableReadOnlyCollection<T>
|
||||
where T : IDisposable
|
||||
{
|
||||
public DisposableListTest() { }
|
||||
public DisposableListTest(int count) : base(count) { }
|
||||
public DisposableListTest()
|
||||
{}
|
||||
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)
|
||||
|
|
@ -51,7 +54,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal struct DisposableTestPair<TValue> : IDisposable
|
||||
|
|
@ -90,7 +93,6 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
return model;
|
||||
}
|
||||
|
||||
|
||||
internal static float[] LoadTensorFromEmbeddedResource(string path)
|
||||
{
|
||||
var tensorData = new List<float>();
|
||||
|
|
@ -99,7 +101,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
var resourceName = assembly.GetManifestResourceNames().Single(p => p.EndsWith("." + path));
|
||||
using (StreamReader inputFile = new StreamReader(assembly.GetManifestResourceStream(resourceName)))
|
||||
{
|
||||
inputFile.ReadLine(); //skip the input name
|
||||
inputFile.ReadLine(); // skip the input name
|
||||
string[] dataStr = inputFile.ReadLine().Split(new char[] { ',', '[', ']' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < dataStr.Length; i++)
|
||||
{
|
||||
|
|
@ -115,14 +117,14 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
if (nodeMeta.OnnxValueType != OnnxValueType.ONNX_TYPE_TENSOR)
|
||||
{
|
||||
throw new InvalidDataException($"Metadata for: '{nodeName}' has a type: '{nodeMeta.OnnxValueType}'" +
|
||||
$" but loading as tensor: '{tensor.Name}'");
|
||||
$" but loading as tensor: '{tensor.Name}'");
|
||||
}
|
||||
|
||||
var protoDt = (Tensors.TensorElementType)tensor.DataType;
|
||||
var metaElementType = nodeMeta.ElementDataType;
|
||||
if (!((protoDt == metaElementType) ||
|
||||
(protoDt == TensorElementType.UInt16 &&
|
||||
(metaElementType == TensorElementType.BFloat16 || metaElementType == TensorElementType.Float16))))
|
||||
(protoDt == TensorElementType.UInt16 &&
|
||||
(metaElementType == TensorElementType.BFloat16 || metaElementType == TensorElementType.Float16))))
|
||||
throw new InvalidDataException($"For node: '{nodeName}' metadata expects: '{metaElementType}' but loaded loaded tensor type: '{protoDt}'");
|
||||
|
||||
// Tensors within Sequences may have no dimensions as the standard allows
|
||||
|
|
@ -130,7 +132,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
if (nodeMeta.Dimensions.Length > 0 && nodeMeta.Dimensions.Length != tensor.Dims.Count)
|
||||
{
|
||||
throw new InvalidDataException($"node: '{nodeName}' nodeMeta.Dim.Length: {nodeMeta.Dimensions.Length} " +
|
||||
$"is expected to be equal to tensor.Dims.Count {tensor.Dims.Count}");
|
||||
$"is expected to be equal to tensor.Dims.Count {tensor.Dims.Count}");
|
||||
}
|
||||
|
||||
var intDims = new int[tensor.Dims.Count];
|
||||
|
|
@ -143,7 +145,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
{
|
||||
if ((nodeMeta.Dimensions[i] != -1) && (nodeMeta.Dimensions[i] != tensor.Dims[i]))
|
||||
throw new InvalidDataException($"Node: '{nodeName}' dimension at idx {i} is {nodeMeta.Dimensions}[{i}] " +
|
||||
$"is expected to either be -1 or {tensor.Dims[i]}");
|
||||
$"is expected to either be -1 or {tensor.Dims[i]}");
|
||||
}
|
||||
|
||||
// element type for Float16 and BFloat16 in the loaded tensor would always be uint16, so
|
||||
|
|
@ -155,7 +157,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
}
|
||||
|
||||
internal static NamedOnnxValue CreateNamedOnnxValueFromTensorRawData(string nodeName, ReadOnlySpan<byte> rawData,
|
||||
TensorElementType elementType, int[] intDims)
|
||||
TensorElementType elementType, int[] intDims)
|
||||
{
|
||||
switch (elementType)
|
||||
{
|
||||
|
|
@ -209,33 +211,33 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
internal static NamedOnnxValue LoadOnnxValueFromFilePb(string fullFilename, string nodeName, NodeMetadata nodeMeta)
|
||||
{
|
||||
// No sparse tensor support yet
|
||||
//Set buffer size to 4MB
|
||||
// Set buffer size to 4MB
|
||||
const int readBufferSize = 4194304;
|
||||
using (var file = new FileStream(fullFilename, FileMode.Open, FileAccess.Read, FileShare.Read, readBufferSize))
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
|
@ -245,33 +247,33 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
internal static DisposableTestPair<OrtValue> LoadOrtValueFromFilePb(string fullFilename, string nodeName, NodeMetadata nodeMeta)
|
||||
{
|
||||
// No sparse tensor support yet
|
||||
//Set buffer size to 4MB
|
||||
// Set buffer size to 4MB
|
||||
const int readBufferSize = 4194304;
|
||||
using (var file = new FileStream(fullFilename, FileMode.Open, FileAccess.Read, FileShare.Read, readBufferSize))
|
||||
{
|
||||
switch (nodeMeta.OnnxValueType)
|
||||
{
|
||||
case OnnxValueType.ONNX_TYPE_TENSOR:
|
||||
{
|
||||
var tensor = Onnx.TensorProto.Parser.ParseFrom(file);
|
||||
return new DisposableTestPair<OrtValue>(nodeName, LoadOrValueTensorPb(tensor, nodeName, nodeMeta));
|
||||
}
|
||||
{
|
||||
var tensor = Onnx.TensorProto.Parser.ParseFrom(file);
|
||||
return new DisposableTestPair<OrtValue>(nodeName, LoadOrValueTensorPb(tensor, nodeName, nodeMeta));
|
||||
}
|
||||
case OnnxValueType.ONNX_TYPE_SEQUENCE:
|
||||
{
|
||||
var sequence = Onnx.SequenceProto.Parser.ParseFrom(file);
|
||||
return new DisposableTestPair<OrtValue>(nodeName, CreateOrtValueFromSequence(sequence, nodeName, nodeMeta));
|
||||
}
|
||||
{
|
||||
var sequence = Onnx.SequenceProto.Parser.ParseFrom(file);
|
||||
return new DisposableTestPair<OrtValue>(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<OrtValue>(nodeName, CreateOrtValueFromOptional(opt, nodeName, nodeMeta));
|
||||
}
|
||||
{
|
||||
var opt = Onnx.OptionalProto.Parser.ParseFrom(file);
|
||||
return new DisposableTestPair<OrtValue>(nodeName, CreateOrtValueFromOptional(opt, nodeName, nodeMeta));
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException($"Unable to load value type: {nodeMeta.OnnxValueType} not implemented");
|
||||
}
|
||||
|
|
@ -279,14 +281,14 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
}
|
||||
|
||||
private static void SequenceCheckMatchOnnxType(string nodeName, SequenceMetadata meta,
|
||||
OnnxValueType onnxType)
|
||||
OnnxValueType onnxType)
|
||||
{
|
||||
if (meta.ElementMeta.OnnxValueType == onnxType)
|
||||
return;
|
||||
|
||||
throw new InvalidDataException($"Sequence node: '{nodeName}' " +
|
||||
$"has element type: '{onnxType}'" +
|
||||
$" expected: '{meta.ElementMeta.OnnxValueType}'");
|
||||
$"has element type: '{onnxType}'" +
|
||||
$" expected: '{meta.ElementMeta.OnnxValueType}'");
|
||||
}
|
||||
|
||||
private static string MakeSequenceElementName(string nodeName, string seqName, int seqNum)
|
||||
|
|
@ -307,55 +309,54 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
switch (seqElemType)
|
||||
{
|
||||
case Onnx.SequenceProto.Types.DataType.Tensor:
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_TENSOR);
|
||||
var sequenceOfTensors = new List<NamedOnnxValue>(sequence.TensorValues.Count);
|
||||
foreach (var tensor in sequence.TensorValues)
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_TENSOR);
|
||||
var sequenceOfTensors = new List<NamedOnnxValue>(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);
|
||||
var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++);
|
||||
var namedOnnxValue = LoadTensorPb(tensor, elemName, elemMeta);
|
||||
sequenceOfTensors.Add(namedOnnxValue);
|
||||
}
|
||||
return NamedOnnxValue.CreateFromSequence(nodeName, sequenceOfTensors);
|
||||
}
|
||||
case Onnx.SequenceProto.Types.DataType.Sequence:
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_SEQUENCE);
|
||||
var seqOfSequences = new List<NamedOnnxValue>(sequence.SequenceValues.Count);
|
||||
foreach (var s in sequence.SequenceValues)
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_SEQUENCE);
|
||||
var seqOfSequences = new List<NamedOnnxValue>(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);
|
||||
var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++);
|
||||
seqOfSequences.Add(CreateNamedOnnxValueFromSequence(s, elemName, elemMeta));
|
||||
}
|
||||
return NamedOnnxValue.CreateFromSequence(nodeName, seqOfSequences);
|
||||
}
|
||||
case Onnx.SequenceProto.Types.DataType.Map:
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_MAP);
|
||||
var seqOfMaps = new List<NamedOnnxValue>(sequence.MapValues.Count);
|
||||
foreach (var m in sequence.MapValues)
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_MAP);
|
||||
var seqOfMaps = new List<NamedOnnxValue>(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);
|
||||
var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++);
|
||||
seqOfMaps.Add(CreateNamedOnnxValueFromMap(m, elemName, elemMeta));
|
||||
}
|
||||
return NamedOnnxValue.CreateFromSequence(nodeName, seqOfMaps);
|
||||
}
|
||||
case Onnx.SequenceProto.Types.DataType.Optional:
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_OPTIONAL);
|
||||
var seqOfOpts = new List<NamedOnnxValue>(sequence.OptionalValues.Count);
|
||||
foreach (var opt in sequence.OptionalValues)
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_OPTIONAL);
|
||||
var seqOfOpts = new List<NamedOnnxValue>(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);
|
||||
var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++);
|
||||
seqOfOpts.Add(CreateNamedOnnxValueFromOptional(opt, elemName, elemMeta));
|
||||
}
|
||||
return NamedOnnxValue.CreateFromSequence(nodeName, seqOfOpts);
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException($"Sequence test data loading does not support element type: " +
|
||||
$"'{seqElemType}'");
|
||||
$"'{seqElemType}'");
|
||||
}
|
||||
|
||||
}
|
||||
internal static NamedOnnxValue CreateNamedOnnxValueFromMap(Onnx.MapProto map, string nodeName, NodeMetadata nodeMetadata)
|
||||
{
|
||||
|
|
@ -369,20 +370,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:
|
||||
|
|
@ -395,7 +396,8 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
}
|
||||
|
||||
internal static NamedOnnxValue CreateNamedOnnxValueFromRawData<T>(string name, ReadOnlySpan<byte> rawData,
|
||||
int[] dimensions) where T : struct
|
||||
int[] dimensions)
|
||||
where T : struct
|
||||
{
|
||||
var typedSrcSpan = MemoryMarshal.Cast<byte, T>(rawData);
|
||||
var dt = new DenseTensor<T>(typedSrcSpan.ToArray(), dimensions);
|
||||
|
|
@ -407,14 +409,14 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
if (nodeMeta.OnnxValueType != OnnxValueType.ONNX_TYPE_TENSOR)
|
||||
{
|
||||
throw new InvalidDataException($"Metadata for: '{nodeName}' has a type: '{nodeMeta.OnnxValueType}'" +
|
||||
$" but loading as tensor: {tensor.Name}");
|
||||
$" but loading as tensor: {tensor.Name}");
|
||||
}
|
||||
|
||||
var protoDt = (Tensors.TensorElementType)tensor.DataType;
|
||||
var metaElementType = nodeMeta.ElementDataType;
|
||||
if (!((protoDt == metaElementType) ||
|
||||
(protoDt == TensorElementType.UInt16 &&
|
||||
(metaElementType == TensorElementType.BFloat16 || metaElementType == TensorElementType.Float16))))
|
||||
(protoDt == TensorElementType.UInt16 &&
|
||||
(metaElementType == TensorElementType.BFloat16 || metaElementType == TensorElementType.Float16))))
|
||||
throw new InvalidDataException($"For node: '{nodeName}' metadata expects: '{metaElementType}' but loaded loaded tensor type: '{protoDt}'");
|
||||
|
||||
// Tensors within Sequences may have no dimensions as the standard allows
|
||||
|
|
@ -422,7 +424,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
if (nodeMeta.Dimensions.Length > 0 && nodeMeta.Dimensions.Length != tensor.Dims.Count)
|
||||
{
|
||||
throw new InvalidDataException($"node: '{nodeName}' nodeMeta.Dim.Length: {nodeMeta.Dimensions.Length} " +
|
||||
$"is expected to be equal to tensor.Dims.Count {tensor.Dims.Count}");
|
||||
$"is expected to be equal to tensor.Dims.Count {tensor.Dims.Count}");
|
||||
}
|
||||
|
||||
var shape = tensor.Dims.ToArray();
|
||||
|
|
@ -431,7 +433,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
{
|
||||
if ((nodeMeta.Dimensions[i] != -1) && (nodeMeta.Dimensions[i] != shape[i]))
|
||||
throw new InvalidDataException($"Node: '{nodeName}' dimension at idx {i} is {nodeMeta.Dimensions}[{i}] " +
|
||||
$"is expected to either be -1 or {shape[i]}");
|
||||
$"is expected to either be -1 or {shape[i]}");
|
||||
}
|
||||
|
||||
// element type for Float16 and BFloat16 in the loaded tensor would always be uint16, so
|
||||
|
|
@ -452,56 +454,55 @@ 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<OrtValue>(sequence.TensorValues.Count))
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_TENSOR);
|
||||
using (var sequenceOfTensors = new DisposableListTest<OrtValue>(sequence.TensorValues.Count))
|
||||
foreach (var tensor in sequence.TensorValues)
|
||||
{
|
||||
foreach (var tensor in sequence.TensorValues)
|
||||
{
|
||||
var element = LoadOrValueTensorPb(tensor, sequence.Name, elemMeta);
|
||||
sequenceOfTensors.Add(element);
|
||||
}
|
||||
return OrtValue.CreateSequence(sequenceOfTensors);
|
||||
var element = LoadOrValueTensorPb(tensor, sequence.Name, elemMeta);
|
||||
sequenceOfTensors.Add(element);
|
||||
}
|
||||
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<OrtValue>(sequence.TensorValues.Count))
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_SEQUENCE);
|
||||
using (var seqOfSequences = new DisposableListTest<OrtValue>(sequence.TensorValues.Count))
|
||||
foreach (var s in sequence.SequenceValues)
|
||||
{
|
||||
foreach (var s in sequence.SequenceValues)
|
||||
{
|
||||
var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++);
|
||||
var ortValue = CreateOrtValueFromSequence(s, elemName, elemMeta);
|
||||
seqOfSequences.Add(ortValue);
|
||||
}
|
||||
return OrtValue.CreateSequence(seqOfSequences);
|
||||
var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++);
|
||||
var ortValue = CreateOrtValueFromSequence(s, elemName, elemMeta);
|
||||
seqOfSequences.Add(ortValue);
|
||||
}
|
||||
return OrtValue.CreateSequence(seqOfSequences);
|
||||
}
|
||||
}
|
||||
case Onnx.SequenceProto.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.SequenceProto.Types.DataType.Optional:
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_OPTIONAL);
|
||||
using (var seqOfSequences = new DisposableListTest<OrtValue>(sequence.TensorValues.Count))
|
||||
{
|
||||
SequenceCheckMatchOnnxType(nodeName, sequenceMeta, OnnxValueType.ONNX_TYPE_OPTIONAL);
|
||||
using (var seqOfSequences = new DisposableListTest<OrtValue>(sequence.TensorValues.Count))
|
||||
foreach (var opt in sequence.OptionalValues)
|
||||
{
|
||||
foreach (var opt in sequence.OptionalValues)
|
||||
{
|
||||
var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++);
|
||||
var ortValue = CreateOrtValueFromOptional(opt, elemName, elemMeta);
|
||||
seqOfSequences.Add(ortValue);
|
||||
}
|
||||
return OrtValue.CreateSequence(seqOfSequences);
|
||||
var elemName = MakeSequenceElementName(nodeName, sequence.Name, seqNum++);
|
||||
var ortValue = CreateOrtValueFromOptional(opt, elemName, elemMeta);
|
||||
seqOfSequences.Add(ortValue);
|
||||
}
|
||||
return OrtValue.CreateSequence(seqOfSequences);
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException($"Sequence test data loading does not support element type: " +
|
||||
$"'{seqElemType}'");
|
||||
$"'{seqElemType}'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal static OrtValue CreateOrtValueFromOptional(Onnx.OptionalProto optional, string nodeName, NodeMetadata nodeMetadata)
|
||||
|
|
@ -510,21 +511,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:
|
||||
|
|
@ -566,7 +566,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
}
|
||||
|
||||
internal static NamedOnnxValue CreateNamedOnnxValueFromStringTensor(IList<Google.Protobuf.ByteString> strings,
|
||||
string nodeName, int[] dimensions)
|
||||
string nodeName, int[] dimensions)
|
||||
{
|
||||
string[] strArray = new string[strings.Count];
|
||||
for (int i = 0; i < strings.Count; ++i)
|
||||
|
|
@ -582,7 +582,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
return NamedOnnxValue.CreateFromTensor<string>(nodeName, dt);
|
||||
}
|
||||
internal static OrtValue CreateOrtValueFromStringTensor(IList<Google.Protobuf.ByteString> strings,
|
||||
long[] shape)
|
||||
long[] shape)
|
||||
{
|
||||
var ortValue = OrtValue.CreateTensorWithEmptyStrings(OrtAllocator.DefaultInstance, shape);
|
||||
try
|
||||
|
|
@ -608,7 +608,7 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
using (var inputFile = new System.IO.StreamReader(filename))
|
||||
{
|
||||
if (skipheader)
|
||||
inputFile.ReadLine(); //skip the input name
|
||||
inputFile.ReadLine(); // skip the input name
|
||||
string[] dataStr = inputFile.ReadLine().Split(new char[] { ',', '[', ']', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < dataStr.Length; i++)
|
||||
{
|
||||
|
|
@ -619,78 +619,4 @@ namespace Microsoft.ML.OnnxRuntime.Tests
|
|||
return tensorData.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal class FloatComparer : IEqualityComparer<float>
|
||||
{
|
||||
private float atol = 1e-3f;
|
||||
private float rtol = 1.7e-2f;
|
||||
|
||||
public bool Equals(float x, float y)
|
||||
{
|
||||
return Math.Abs(x - y) <= (atol + rtol * Math.Abs(y));
|
||||
}
|
||||
public int GetHashCode(float x)
|
||||
{
|
||||
return x.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
internal class DoubleComparer : IEqualityComparer<double>
|
||||
{
|
||||
private double atol = 1e-3;
|
||||
private double rtol = 1.7e-2;
|
||||
|
||||
public bool Equals(double x, double y)
|
||||
{
|
||||
return Math.Abs(x - y) <= (atol + rtol * Math.Abs(y));
|
||||
}
|
||||
public int GetHashCode(double x)
|
||||
{
|
||||
return x.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
class ExactComparer<T> : IEqualityComparer<T>
|
||||
{
|
||||
public bool Equals(T x, T y)
|
||||
{
|
||||
return x.Equals(y);
|
||||
}
|
||||
public int GetHashCode(T x)
|
||||
{
|
||||
return x.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use it to compare Float16
|
||||
/// </summary>
|
||||
internal class Float16Comparer : IEqualityComparer<Float16>
|
||||
{
|
||||
public ushort tolerance = 0;
|
||||
public bool Equals(Float16 x, Float16 y)
|
||||
{
|
||||
return Math.Abs(x.value - y.value) <= (tolerance + y.value);
|
||||
}
|
||||
public int GetHashCode(Float16 x)
|
||||
{
|
||||
return x.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use it to compare Bloat16
|
||||
/// </summary>
|
||||
internal class BFloat16Comparer : IEqualityComparer<BFloat16>
|
||||
{
|
||||
public ushort tolerance = 0;
|
||||
public bool Equals(BFloat16 x, BFloat16 y)
|
||||
{
|
||||
return Math.Abs(x.value - y.value) <= (tolerance + y.value);
|
||||
}
|
||||
public int GetHashCode(BFloat16 x)
|
||||
{
|
||||
return x.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,30 +119,8 @@
|
|||
|
||||
<!-- NOTE: The xUnit framework doesn't pickup the tests defined within the referenced Microsoft.ML.OnnxRuntime.Tests.Common project -->
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\OrtFloat16Tests.cs">
|
||||
<Link>OrtFloat16Tests.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\OrtEnvTests.cs">
|
||||
<Link>OrtEnvTests.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\InferenceTest.cs">
|
||||
<Link>InferenceTest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\OrtIoBindingAllocationTest.cs">
|
||||
<Link>OrtIoBindingAllocationTest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\Tensors\TensorTests.cs">
|
||||
<Link>TensorTests.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\OrtValueTests.cs">
|
||||
<Link>OrtValueTests.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\Tensors\ArrayTensorExtensionsTests.cs">
|
||||
<Link>ArrayTensorExtensionsTests.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\TrainingTest.cs">
|
||||
<Link>TrainingTest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\**\*Test.cs" />
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.Tests.Common\**\*Tests.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
14
csharp/tools/MauiModelTester/App.xaml
Normal file
14
csharp/tools/MauiModelTester/App.xaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version = "1.0" encoding = "UTF-8" ?>
|
||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:MauiModelTester"
|
||||
x:Class="MauiModelTester.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
11
csharp/tools/MauiModelTester/App.xaml.cs
Normal file
11
csharp/tools/MauiModelTester/App.xaml.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
namespace MauiModelTester;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MainPage = new AppShell();
|
||||
}
|
||||
}
|
||||
14
csharp/tools/MauiModelTester/AppShell.xaml
Normal file
14
csharp/tools/MauiModelTester/AppShell.xaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Shell
|
||||
x:Class="MauiModelTester.AppShell"
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:MauiModelTester"
|
||||
Shell.FlyoutBehavior="Disabled">
|
||||
|
||||
<ShellContent
|
||||
Title="Home"
|
||||
ContentTemplate="{DataTemplate local:MainPage}"
|
||||
Route="MainPage" />
|
||||
|
||||
</Shell>
|
||||
9
csharp/tools/MauiModelTester/AppShell.xaml.cs
Normal file
9
csharp/tools/MauiModelTester/AppShell.xaml.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace MauiModelTester;
|
||||
|
||||
public partial class AppShell : Shell
|
||||
{
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
65
csharp/tools/MauiModelTester/MainPage.xaml
Normal file
65
csharp/tools/MauiModelTester/MainPage.xaml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="MauiModelTester.MainPage" BackgroundColor="Black">
|
||||
|
||||
<ScrollView x:Name="MainScrollView" Margin="10">
|
||||
<Grid RowSpacing="10" ColumnSpacing="0">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0"
|
||||
Text="ONNX Runtime Mobile Model Tester"
|
||||
FontSize="Header"
|
||||
TextColor="DeepSkyBlue"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
<Grid Grid.Row="1" ColumnSpacing="10">
|
||||
<!-- Run params. EP and iterations currently-->
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Text="Execution Provider"
|
||||
TextColor="White" VerticalOptions="Center"/>
|
||||
<Picker Grid.Row="0" Grid.Column="1" x:Name="ExecutionProviderOptions"
|
||||
TextColor="White" Margin="5,5,5,5"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Text="Iterations" TextColor="White" VerticalOptions="Center"/>
|
||||
<Entry Grid.Row="1" Grid.Column="1" x:Name="Iterations" Text="10" TextColor="White"/>
|
||||
</Grid>
|
||||
|
||||
<Button Grid.Row="2"
|
||||
x:Name="RunButton" Text="Run"
|
||||
FontSize="Large"
|
||||
TextColor="White"
|
||||
BackgroundColor="DeepSkyBlue"
|
||||
SemanticProperties.Hint="Runs the model the requested number of times."
|
||||
Clicked="OnRunClicked"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
<Label Grid.Row="3" Text="Performance test results" TextColor="White" FontAttributes="Bold" />
|
||||
|
||||
<ActivityIndicator Grid.Row="4" x:Name="BusyIndicator" IsRunning="False" Color="Orange" />
|
||||
|
||||
<ScrollView Grid.Row="4" x:Name="TestResultsView" Margin="10">
|
||||
<StackLayout x:Name="TestResults">
|
||||
<Label Text="Results will be displayed after the model is run." TextColor="GhostWhite" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
||||
</Grid>
|
||||
</ScrollView>
|
||||
|
||||
</ContentPage>
|
||||
174
csharp/tools/MauiModelTester/MainPage.xaml.cs
Normal file
174
csharp/tools/MauiModelTester/MainPage.xaml.cs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
using System.Diagnostics;
|
||||
|
||||
namespace MauiModelTester;
|
||||
|
||||
public partial class MainPage : ContentPage
|
||||
{
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// See:
|
||||
// ONNX Runtime Execution Providers: https://onnxruntime.ai/docs/execution-providers/
|
||||
// Core ML: https://developer.apple.com/documentation/coreml
|
||||
// NNAPI: https://developer.android.com/ndk/guides/neuralnetworks
|
||||
ExecutionProviderOptions.Items.Add(nameof(ExecutionProviders.CPU));
|
||||
|
||||
if (DeviceInfo.Platform == DevicePlatform.Android)
|
||||
{
|
||||
ExecutionProviderOptions.Items.Add(nameof(ExecutionProviders.NNAPI));
|
||||
}
|
||||
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
ExecutionProviderOptions.Items.Add(nameof(ExecutionProviders.CoreML));
|
||||
}
|
||||
|
||||
// XNNPACK provides optimized CPU execution on ARM64 and ARM platforms for models using float
|
||||
var arch = System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture;
|
||||
if (arch == System.Runtime.InteropServices.Architecture.Arm64 ||
|
||||
arch == System.Runtime.InteropServices.Architecture.Arm)
|
||||
{
|
||||
ExecutionProviderOptions.Items.Add(nameof(ExecutionProviders.XNNPACK));
|
||||
}
|
||||
|
||||
ExecutionProviderOptions.SelectedIndex = 0; // default to CPU
|
||||
ExecutionProviderOptions.SelectedIndexChanged += ExecutionProviderOptions_SelectedIndexChanged;
|
||||
|
||||
_currentExecutionProvider = ExecutionProviders.CPU;
|
||||
|
||||
// start creating session in background.
|
||||
CreateInferenceSession();
|
||||
}
|
||||
|
||||
private async Task CreateInferenceSession()
|
||||
{
|
||||
// wait if we're already creating an inference session.
|
||||
if (_inferenceSessionCreationTask != null)
|
||||
{
|
||||
await _inferenceSessionCreationTask.ConfigureAwait(false);
|
||||
_inferenceSessionCreationTask = null;
|
||||
}
|
||||
|
||||
_inferenceSessionCreationTask = CreateInferenceSessionImpl();
|
||||
}
|
||||
|
||||
private async Task CreateInferenceSessionImpl()
|
||||
{
|
||||
var executionProvider = ExecutionProviderOptions.SelectedItem switch {
|
||||
nameof(ExecutionProviders.NNAPI) => ExecutionProviders.NNAPI,
|
||||
nameof(ExecutionProviders.CoreML) => ExecutionProviders.CoreML,
|
||||
nameof(ExecutionProviders.XNNPACK) => ExecutionProviders.XNNPACK,
|
||||
_ => ExecutionProviders.CPU
|
||||
};
|
||||
|
||||
if (_inferenceSession == null || executionProvider != _currentExecutionProvider)
|
||||
{
|
||||
_currentExecutionProvider = executionProvider;
|
||||
|
||||
// re/create an inference session with the execution provider.
|
||||
// this is an expensive operation as we have to reload the model, and should be avoided in production apps.
|
||||
_inferenceSession = new OrtInferenceSession(_currentExecutionProvider);
|
||||
await _inferenceSession.Create();
|
||||
|
||||
// Display the results which at this point will have the model load time and the warmup Run() time.
|
||||
ShowResults();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecutionProviderOptions_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
// update in background
|
||||
UpdateExecutionProvider();
|
||||
}
|
||||
|
||||
private void OnRunClicked(object sender, EventArgs e)
|
||||
{
|
||||
// run in background
|
||||
RunAsync();
|
||||
}
|
||||
|
||||
private async Task UpdateExecutionProvider()
|
||||
{
|
||||
try
|
||||
{
|
||||
await SetBusy(true);
|
||||
await CreateInferenceSession();
|
||||
await SetBusy(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await SetBusy(false);
|
||||
MainThread.BeginInvokeOnMainThread(() => DisplayAlert("Error", ex.Message, "OK"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await SetBusy(true);
|
||||
|
||||
await ClearResult();
|
||||
|
||||
var iterationsStr = Iterations.Text;
|
||||
int iterations = iterationsStr == string.Empty ? 10 : int.Parse(iterationsStr);
|
||||
|
||||
// create inference session if it doesn't exist or EP has changed
|
||||
await CreateInferenceSession();
|
||||
|
||||
await Task.Run(() => _inferenceSession.Run(iterations));
|
||||
|
||||
await SetBusy(false);
|
||||
|
||||
ShowResults();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await SetBusy(false);
|
||||
MainThread.BeginInvokeOnMainThread(() => DisplayAlert("Error", ex.Message, "OK"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetBusy(bool busy)
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
// disable controls that would create a new session or another
|
||||
// Run call until we're done with the current Run.
|
||||
ExecutionProviderOptions.IsEnabled = !busy;
|
||||
RunButton.IsEnabled = !busy;
|
||||
|
||||
BusyIndicator.IsRunning = busy;
|
||||
BusyIndicator.IsVisible = busy;
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ClearResult()
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{ TestResults.Clear(); });
|
||||
}
|
||||
|
||||
private void ShowResults()
|
||||
{
|
||||
var createResults = () =>
|
||||
{
|
||||
var stats = _inferenceSession.PerfStats;
|
||||
var label = new Label { TextColor = Colors.GhostWhite };
|
||||
|
||||
label.Text = $"Model load time: {stats.LoadTime.TotalMilliseconds:F4} ms\n";
|
||||
label.Text += $"Warmup run time: {stats.WarmupTime.TotalMilliseconds:F4} ms\n\n";
|
||||
label.Text += string.Join('\n', stats.GetRunStatsReport(true));
|
||||
TestResults.Add(label);
|
||||
|
||||
Debug.WriteLine(label.Text);
|
||||
};
|
||||
|
||||
MainThread.BeginInvokeOnMainThread(createResults);
|
||||
}
|
||||
|
||||
private ExecutionProviders _currentExecutionProvider;
|
||||
private OrtInferenceSession _inferenceSession;
|
||||
private Task _inferenceSessionCreationTask;
|
||||
}
|
||||
69
csharp/tools/MauiModelTester/MauiModelTester.csproj
Normal file
69
csharp/tools/MauiModelTester/MauiModelTester.csproj
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0-android;net6.0-ios</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>MauiModelTester</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>MauiModelTester</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>com.microsoft.OnnxRuntime.MauiModelTester</ApplicationId>
|
||||
<ApplicationIdGuid>24eecf93-5fe6-4ea3-a3dd-baf27df7656a</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">12.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<PackageIcon>onnxruntime_icon.png</PackageIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\onnxruntime_icon.png" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\onnxruntime_logo.png" Resize="true" Color="#C76006" />
|
||||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\test\Microsoft.ML.OnnxRuntime.Tests.Common\EqualityComparers.cs" />
|
||||
<Compile Include="..\Microsoft.ML.OnnxRuntime.PerfTool\OnnxMl.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.16.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.16.0-dev-20230707-1222-2a11f29eaa" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.Extensions" Version="0.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Project loaded by MauiModelTester.sln. Use the nightly package. -->
|
||||
<ItemGroup Condition="'$(SolutionName)'=='MauiModelTester'">
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.Managed" Version="1.16.0-dev-20230707-1224-2a11f29eaa" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Project loaded by the ORT C# solution in /csharp. Use the local build of the C# wrapper -->
|
||||
<ItemGroup Condition="'$(SolutionName)'!='MauiModelTester'">
|
||||
<ProjectReference Include="..\..\src\Microsoft.ML.OnnxRuntime\Microsoft.ML.OnnxRuntime.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
27
csharp/tools/MauiModelTester/MauiModelTester.sln
Normal file
27
csharp/tools/MauiModelTester/MauiModelTester.sln
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31611.283
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiModelTester", "MauiModelTester.csproj", "{80517A36-E015-4282-89A7-245B25A963B7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{80517A36-E015-4282-89A7-245B25A963B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{80517A36-E015-4282-89A7-245B25A963B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{80517A36-E015-4282-89A7-245B25A963B7}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{80517A36-E015-4282-89A7-245B25A963B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{80517A36-E015-4282-89A7-245B25A963B7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{80517A36-E015-4282-89A7-245B25A963B7}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
24
csharp/tools/MauiModelTester/MauiProgram.cs
Normal file
24
csharp/tools/MauiModelTester/MauiProgram.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MauiModelTester;
|
||||
|
||||
public static class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder.UseMauiApp<App>().ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||
});
|
||||
|
||||
#if DEBUG
|
||||
// Add the extension debug logger so Debug.WriteLine output shows up in the Output window when running in VS
|
||||
builder.Logging.AddDebug();
|
||||
System.Diagnostics.Debug.WriteLine("Debug output enabled.");
|
||||
#endif
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
14
csharp/tools/MauiModelTester/NuGet.config
Normal file
14
csharp/tools/MauiModelTester/NuGet.config
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<solution>
|
||||
<add key="disableSourceControlIntegration" value="true" />
|
||||
</solution>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="NuGet Official" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="ORT-Nightly" value="https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/ORT-Nightly/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
<disabledPackageSources>
|
||||
<clear />
|
||||
</disabledPackageSources>
|
||||
</configuration>
|
||||
115
csharp/tools/MauiModelTester/OrtInferenceSession.cs
Normal file
115
csharp/tools/MauiModelTester/OrtInferenceSession.cs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
using Microsoft.ML.OnnxRuntime;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MauiModelTester
|
||||
{
|
||||
public enum ExecutionProviders
|
||||
{
|
||||
CPU, // CPU execution provider is always available by default
|
||||
NNAPI, // NNAPI is available on Android
|
||||
CoreML, // CoreML is available on iOS/macOS
|
||||
XNNPACK // XNNPACK is available on ARM/ARM64 platforms and benefits 32-bit float models
|
||||
}
|
||||
|
||||
// An inference session runs an ONNX model
|
||||
internal class OrtInferenceSession
|
||||
{
|
||||
public OrtInferenceSession(ExecutionProviders provider = ExecutionProviders.CPU)
|
||||
{
|
||||
_sessionOptions = new SessionOptions();
|
||||
switch (_executionProvider)
|
||||
{
|
||||
case ExecutionProviders.CPU:
|
||||
break;
|
||||
case ExecutionProviders.NNAPI:
|
||||
_sessionOptions.AppendExecutionProvider_Nnapi();
|
||||
break;
|
||||
case ExecutionProviders.CoreML:
|
||||
_sessionOptions.AppendExecutionProvider_CoreML();
|
||||
break;
|
||||
case ExecutionProviders.XNNPACK:
|
||||
_sessionOptions.AppendExecutionProvider("XNNPACK");
|
||||
break;
|
||||
}
|
||||
|
||||
// enable pre/post processing custom operators from onnxruntime-extensions
|
||||
_sessionOptions.RegisterOrtExtensions();
|
||||
|
||||
_perfStats = new PerfStats();
|
||||
}
|
||||
|
||||
// async task to create the inference session which is an expensive operation.
|
||||
public async Task Create()
|
||||
{
|
||||
// create the InferenceSession. this is an expensive operation so only do this when necessary.
|
||||
// the InferenceSession supports multiple calls to Run, including concurrent calls.
|
||||
var modelBytes = await Utils.LoadResource("test_data/model.onnx");
|
||||
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
_inferenceSession = new InferenceSession(modelBytes, _sessionOptions);
|
||||
stopwatch.Stop();
|
||||
_perfStats.LoadTime = stopwatch.Elapsed;
|
||||
|
||||
(_inputs, _expectedOutputs) = await Utils.LoadTestData();
|
||||
|
||||
// warmup
|
||||
Run(1, true);
|
||||
}
|
||||
|
||||
public void Run(int iterations = 1, bool isWarmup = false)
|
||||
{
|
||||
// do all setup outside of the timing
|
||||
var runOptions = new RunOptions();
|
||||
var outputNames = _inferenceSession.OutputNames;
|
||||
|
||||
_perfStats.ClearRunTimes();
|
||||
|
||||
// var stopwatch = new Stopwatch();
|
||||
|
||||
for (var i = 0; i < iterations; i++)
|
||||
{
|
||||
// stopwatch.Restart();
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
using IDisposableReadOnlyCollection<OrtValue> results =
|
||||
_inferenceSession.Run(runOptions, _inputs, outputNames);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
if (isWarmup)
|
||||
{
|
||||
_perfStats.WarmupTime = stopwatch.Elapsed;
|
||||
|
||||
// validate the expected output on the first Run only
|
||||
if (_expectedOutputs.Count > 0)
|
||||
{
|
||||
// create dictionary of output name to results
|
||||
var actual = outputNames.Zip(results).ToDictionary(x => x.First, x => x.Second);
|
||||
|
||||
foreach (var expectedOutput in _expectedOutputs)
|
||||
{
|
||||
var outputName = expectedOutput.Key;
|
||||
Utils.TensorComparer.VerifyTensorResults(outputName, expectedOutput.Value,
|
||||
actual[outputName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_perfStats.AddRunTime(stopwatch.Elapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PerfStats PerfStats => _perfStats;
|
||||
|
||||
private SessionOptions _sessionOptions;
|
||||
private InferenceSession _inferenceSession;
|
||||
private ExecutionProviders _executionProvider = ExecutionProviders.CPU;
|
||||
private Dictionary<string, OrtValue> _inputs;
|
||||
private Dictionary<string, OrtValue> _expectedOutputs;
|
||||
private PerfStats _perfStats;
|
||||
}
|
||||
}
|
||||
71
csharp/tools/MauiModelTester/PerfStats.cs
Normal file
71
csharp/tools/MauiModelTester/PerfStats.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
namespace MauiModelTester
|
||||
{
|
||||
internal class PerfStats
|
||||
{
|
||||
internal PerfStats()
|
||||
{
|
||||
_runTimes = new List<double>();
|
||||
}
|
||||
|
||||
internal TimeSpan LoadTime { get; set; }
|
||||
internal TimeSpan WarmupTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Add TimeSpan for one call to Run.
|
||||
/// </summary>
|
||||
/// <param name="runTime">Elapsed time</param>
|
||||
internal void AddRunTime(TimeSpan runTime)
|
||||
{
|
||||
_runTimes.Add(runTime.TotalMilliseconds);
|
||||
}
|
||||
|
||||
internal void ClearRunTimes()
|
||||
{
|
||||
_runTimes.Clear();
|
||||
}
|
||||
|
||||
internal List<string> GetRunStatsReport(bool outputRunTimes = false)
|
||||
{
|
||||
List<string> lines = new List<string>();
|
||||
|
||||
if (_runTimes.Count > 0)
|
||||
{
|
||||
// we want unsorted run times if we need to investigate any unexpected latency as that gives a clearer
|
||||
// picture of when in the iterations the latency occurred.
|
||||
List<string> runTimesOutput = null;
|
||||
if (outputRunTimes)
|
||||
{
|
||||
runTimesOutput = new List<string>();
|
||||
runTimesOutput.Add("\nRun times (ms):");
|
||||
runTimesOutput.Add(string.Join(", ", _runTimes.Select(x => x.ToString("F2"))));
|
||||
}
|
||||
|
||||
_runTimes.Sort();
|
||||
|
||||
var totalRunTime = _runTimes.Sum();
|
||||
|
||||
lines.Add($"Total time for {_runTimes.Count} iterations: {totalRunTime:F2} ms\n");
|
||||
lines.Add($"Average run time: {totalRunTime / _runTimes.Count:F2} ms");
|
||||
lines.Add($"Minimum run time: {_runTimes.Min():F2} ms");
|
||||
lines.Add($"Maximum run time: {_runTimes.Max():F2} ms\n");
|
||||
lines.Add($"50th Percentile run time: {_runTimes[(int)(_runTimes.Count * 0.5)]:F2} ms");
|
||||
lines.Add($"90th Percentile run time: {_runTimes[(int)(_runTimes.Count * 0.9)]:F2} ms");
|
||||
|
||||
if (_runTimes.Count >= 100)
|
||||
{
|
||||
lines.Add($"95th Percentile run time: {_runTimes[(int)(_runTimes.Count * 0.95)]:F2} ms");
|
||||
lines.Add($"99th Percentile run time: {_runTimes[(int)(_runTimes.Count * 0.99)]:F2} ms");
|
||||
}
|
||||
|
||||
if (outputRunTimes)
|
||||
{
|
||||
lines.AddRange(runTimesOutput);
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private List<double> _runTimes;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application android:allowBackup="false" android:icon="@mipmap/onnxruntime_icon" android:supportsRtl="true"></application>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
|
||||
namespace MauiModelTester;
|
||||
|
||||
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using Android.App;
|
||||
using Android.Runtime;
|
||||
|
||||
namespace MauiModelTester;
|
||||
|
||||
[Application]
|
||||
public class MainApplication : MauiApplication
|
||||
{
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||
: base(handle, ownership)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#512BD4</color>
|
||||
<color name="colorPrimaryDark">#2B0B98</color>
|
||||
<color name="colorAccent">#2B0B98</color>
|
||||
</resources>
|
||||
8
csharp/tools/MauiModelTester/Platforms/Windows/App.xaml
Normal file
8
csharp/tools/MauiModelTester/Platforms/Windows/App.xaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<maui:MauiWinUIApplication
|
||||
x:Class="MauiModelTester.WinUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:maui="using:Microsoft.Maui"
|
||||
xmlns:local="using:MauiModelTester.WinUI">
|
||||
|
||||
</maui:MauiWinUIApplication>
|
||||
24
csharp/tools/MauiModelTester/Platforms/Windows/App.xaml.cs
Normal file
24
csharp/tools/MauiModelTester/Platforms/Windows/App.xaml.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Microsoft.UI.Xaml;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace MauiModelTester.WinUI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : MauiWinUIApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="796F758E-3487-477A-A1A3-9F8F898A776C" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>$placeholder$</DisplayName>
|
||||
<PublisherDisplayName>User Name</PublisherDisplayName>
|
||||
<Logo>$placeholder$.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate" />
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="$placeholder$"
|
||||
Description="$placeholder$"
|
||||
Square150x150Logo="$placeholder$.png"
|
||||
Square44x44Logo="$placeholder$.png"
|
||||
BackgroundColor="transparent">
|
||||
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
|
||||
<uap:SplashScreen Image="$placeholder$.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
|
||||
</Package>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using Foundation;
|
||||
|
||||
namespace MauiModelTester;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
32
csharp/tools/MauiModelTester/Platforms/iOS/Info.plist
Normal file
32
csharp/tools/MauiModelTester/Platforms/iOS/Info.plist
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|
||||
15
csharp/tools/MauiModelTester/Platforms/iOS/Program.cs
Normal file
15
csharp/tools/MauiModelTester/Platforms/iOS/Program.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace MauiModelTester;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"profiles": {
|
||||
"Windows Machine": {
|
||||
"commandName": "MsixPackage",
|
||||
"nativeDebugging": false
|
||||
}
|
||||
}
|
||||
}
|
||||
80
csharp/tools/MauiModelTester/ReadMe.md
Normal file
80
csharp/tools/MauiModelTester/ReadMe.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# MAUI Model Tester
|
||||
## Usage
|
||||
|
||||
Run create_test_data.py to specify
|
||||
- path to the model you wish to use
|
||||
- symbolic dimension values to use if needed
|
||||
- any specific input if the randomly generated input will not be good enough
|
||||
- you can create specific input with /tools/python/onnx_test_data_utils.py
|
||||
- see the comments in create_test_data.py for more details
|
||||
- expected output if saving the output from running the model locally is not good enough
|
||||
- the model will be executed when creating the test data to validate the input
|
||||
|
||||
This will copy the model to Resources\Raw\test_data\model.onnx and the test data files to
|
||||
Resources\Raw\test_data\test_data_set_0
|
||||
|
||||
The MAUI application will read the model and test data from those locations and should need no other changes to be able
|
||||
to execute the model.
|
||||
|
||||
NOTE: The project uses builds from the nightly feed to keep things simple.
|
||||
|
||||
If it was part of the main ONNX Runtime C# solution we'd have to
|
||||
- add the ORT nightly feed to the top level nuget.config
|
||||
- this potentially adds confusion about where nuget packages come from in unit tests
|
||||
- keep updating the referenced nightly packages so they remain valid so the complete solution builds in the CI
|
||||
|
||||
## Testing C# or native code changes
|
||||
|
||||
If you have new code to test the easiest way is to run the nuget packaging pipeline on
|
||||
https://aiinfra.visualstudio.com/Lotus/_build against your branch. Download the native and managed nuget packages from
|
||||
the CI artifacts and update the nuget.config to point to the directory they are in.
|
||||
This can be used to test both native and C# code changes.
|
||||
|
||||
If you wish to test local changes to the C# code, you can create a local nuget package and add the directory it's in to
|
||||
nuget.config. With the current setup you'd first need to build ORT with the `--build_csharp` param to update the C#
|
||||
Directory.build.props and create the native library for the current platform, as the current packaging infrastructure
|
||||
creates the native and managed packages at the same time and requires a native library to exist.
|
||||
|
||||
Alternatively, you can open /csharp/OnnxRuntime.CSharp.sln and temporarily add
|
||||
/csharp/tools/MauiModelTester/MauiModelTester.csproj to it. The csproj should automatically adjust to use a project
|
||||
reference to /csharp/src/Microsoft.ML.OnnxRuntime/Microsoft.ML.OnnxRuntime.csproj instead of a package reference to
|
||||
the Microsoft.ML.OnnxRuntime.Managed nuget package. Note that you must still run build.bat/build.sh with
|
||||
`--build_csharp` to generate /csharp/Directory.Build.props, but can run with the `--update` parameter so no native build
|
||||
is done. Most likely you should grab the latest nightly native package from the packaging pipeline so the native code is
|
||||
compatible.
|
||||
|
||||
## Local build setup
|
||||
|
||||
The following commands _should_ install the necessary workloads to create the managed package including mobile targets,
|
||||
build the managed library and create the native (local build only) and managed packages. The packages will be in the
|
||||
ORT build output directory (e.g. build/Windows/Debug/Debug). The native package will only contain the runtime for the
|
||||
current platform (e.g. Windows 64-bit if you're building on Windows) so can't be used for testing other platforms. Use
|
||||
the native package from the nightly feed or a packaging CI.
|
||||
|
||||
```
|
||||
dotnet workload install ios android macos
|
||||
dotnet workload restore .\src\Microsoft.ML.OnnxRuntime\Microsoft.ML.OnnxRuntime.csproj -p:SelectedTargets=All
|
||||
msbuild -t:restore .\src\Microsoft.ML.OnnxRuntime\Microsoft.ML.OnnxRuntime.csproj -p:SelectedTargets=All
|
||||
msbuild -t:build .\src\Microsoft.ML.OnnxRuntime\Microsoft.ML.OnnxRuntime.csproj -p:SelectedTargets=All
|
||||
msbuild .\OnnxRuntime.CSharp.proj -t:CreatePackage -p:OrtPackageId=Microsoft.ML.OnnxRuntime -p:Configuration=Debug -p:Platform="Any CPU"
|
||||
```
|
||||
|
||||
## Example test data creation
|
||||
|
||||
Example commands to create test data
|
||||
|
||||
### PyTorch mobilenet V3 with a symbolic dimension called 'batch' that we set to 1.
|
||||
https://pytorch.org/vision/main/models/mobilenetv3.html exported with torch.onnx.export with the batch dimension
|
||||
being dynamic. We provide a value of 1 for the batch size to use in our testing.
|
||||
|
||||
`create_test_data.py -s batch=1 -m pytorch_mobilenet_v3.onnx`
|
||||
|
||||
### SuperResolution with pre-processing in the model.
|
||||
|
||||
As the model has pre-processing in it from onnxruntime-extensions, we want to provide the raw bytes from a jpeg or png.
|
||||
|
||||
Convert image to protobuf file with name of 'image' to match the model input. The output filename doesn't matter.
|
||||
`python ..\..\..\tools\python\onnx_test_data_utils.py --action image_to_pb --raw --input lion.png --output superres_input.pb --name image`
|
||||
|
||||
Create test data using this input.
|
||||
`create_test_data.py --input_data superres_input.pb --model_path RealESRGAN_with_pre_post_processing.onnx`
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
Binary file not shown.
Binary file not shown.
15
csharp/tools/MauiModelTester/Resources/Raw/AboutAssets.txt
Normal file
15
csharp/tools/MauiModelTester/Resources/Raw/AboutAssets.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
Any raw assets you want to be deployed with your application can be placed in
|
||||
this directory (and child directories). Deployment of the asset to your application
|
||||
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
|
||||
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
|
||||
These files will be deployed with you package and will be accessible using Essentials:
|
||||
|
||||
async Task LoadMauiAsset()
|
||||
{
|
||||
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
var contents = reader.ReadToEnd();
|
||||
}
|
||||
59296
csharp/tools/MauiModelTester/Resources/Raw/test_data/model.onnx
Normal file
59296
csharp/tools/MauiModelTester/Resources/Raw/test_data/model.onnx
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1 @@
|
|||
pytorch_mobilenet_v3.onnx
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
44
csharp/tools/MauiModelTester/Resources/Styles/Colors.xaml
Normal file
44
csharp/tools/MauiModelTester/Resources/Styles/Colors.xaml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xaml-comp compile="true" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||
|
||||
<Color x:Key="Primary">#ed7a0e</Color>
|
||||
<Color x:Key="Secondary">#DFD8F7</Color>
|
||||
<Color x:Key="Tertiary">#C76006</Color>
|
||||
<Color x:Key="White">White</Color>
|
||||
<Color x:Key="Black">Black</Color>
|
||||
<Color x:Key="Gray100">#E1E1E1</Color>
|
||||
<Color x:Key="Gray200">#C8C8C8</Color>
|
||||
<Color x:Key="Gray300">#ACACAC</Color>
|
||||
<Color x:Key="Gray400">#919191</Color>
|
||||
<Color x:Key="Gray500">#6E6E6E</Color>
|
||||
<Color x:Key="Gray600">#404040</Color>
|
||||
<Color x:Key="Gray900">#212121</Color>
|
||||
<Color x:Key="Gray950">#141414</Color>
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
|
||||
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
|
||||
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
|
||||
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
|
||||
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
|
||||
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
|
||||
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
|
||||
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
|
||||
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
|
||||
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
|
||||
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
|
||||
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
|
||||
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
|
||||
|
||||
<Color x:Key="Yellow100Accent">#F7B548</Color>
|
||||
<Color x:Key="Yellow200Accent">#FFD590</Color>
|
||||
<Color x:Key="Yellow300Accent">#FFE5B9</Color>
|
||||
<Color x:Key="Cyan100Accent">#28C2D1</Color>
|
||||
<Color x:Key="Cyan200Accent">#7BDDEF</Color>
|
||||
<Color x:Key="Cyan300Accent">#C3F2F4</Color>
|
||||
<Color x:Key="Blue100Accent">#3E8EED</Color>
|
||||
<Color x:Key="Blue200Accent">#72ACF1</Color>
|
||||
<Color x:Key="Blue300Accent">#A7CBF6</Color>
|
||||
|
||||
</ResourceDictionary>
|
||||
405
csharp/tools/MauiModelTester/Resources/Styles/Styles.xaml
Normal file
405
csharp/tools/MauiModelTester/Resources/Styles/Styles.xaml
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xaml-comp compile="true" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||
|
||||
<Style TargetType="ActivityIndicator">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="IndicatorView">
|
||||
<Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
|
||||
<Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="StrokeShape" Value="Rectangle"/>
|
||||
<Setter Property="StrokeThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="BoxView">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Primary}}" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Padding" Value="14,10"/>
|
||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="CheckBox">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DatePicker">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Editor">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Entry">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Frame">
|
||||
<Setter Property="HasShadow" Value="False" />
|
||||
<Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ImageButton">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="BorderColor" Value="Transparent"/>
|
||||
<Setter Property="BorderWidth" Value="0"/>
|
||||
<Setter Property="CornerRadius" Value="0"/>
|
||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ListView">
|
||||
<Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Picker">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ProgressBar">
|
||||
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="RadioButton">
|
||||
<Setter Property="BackgroundColor" Value="Transparent"/>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="RefreshView">
|
||||
<Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="SearchBar">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="SearchHandler">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Shadow">
|
||||
<Setter Property="Radius" Value="15" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
<Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Offset" Value="10,10" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Slider">
|
||||
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="SwipeItem">
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Switch">
|
||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="ThumbColor" Value="{StaticResource White}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="On">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Off">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TimePicker">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent"/>
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Page" ApplyToDerivedTypes="True">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Shell" ApplyToDerivedTypes="True">
|
||||
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="Shell.ForegroundColor" Value="{OnPlatform WinUI={StaticResource Primary}, Default={StaticResource White}}" />
|
||||
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="Shell.NavBarHasShadow" Value="False" />
|
||||
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="NavigationPage">
|
||||
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TabbedPage">
|
||||
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
247
csharp/tools/MauiModelTester/Utils.cs
Normal file
247
csharp/tools/MauiModelTester/Utils.cs
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
using Microsoft.ML.OnnxRuntime;
|
||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
using Microsoft.ML.OnnxRuntime.Tests;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MauiModelTester
|
||||
{
|
||||
internal class Utils
|
||||
{
|
||||
internal static async Task<byte[]> LoadResource(string name)
|
||||
{
|
||||
using Stream fileStream = await FileSystem.Current.OpenAppPackageFileAsync(name);
|
||||
using MemoryStream memoryStream = new MemoryStream();
|
||||
fileStream.CopyTo(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
internal static async Task<(Dictionary<string, OrtValue>, Dictionary<string, OrtValue>)> LoadTestData()
|
||||
{
|
||||
var loadData = async (string prefix) =>
|
||||
{
|
||||
var data = new Dictionary<string, OrtValue>();
|
||||
int idx = 0;
|
||||
|
||||
do
|
||||
{
|
||||
var filename = "test_data/test_data_set_0/" + prefix + idx + ".pb";
|
||||
var exists = await FileSystem.Current.AppPackageFileExistsAsync(filename);
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
// we expect sequentially named files for all inputs so as soon as one is missing we're done
|
||||
break;
|
||||
}
|
||||
|
||||
var tensorProtoData = await LoadResource(filename);
|
||||
|
||||
// get name and tensor data and create OrtValue
|
||||
Onnx.TensorProto tensorProto = null;
|
||||
tensorProto = Onnx.TensorProto.Parser.ParseFrom(tensorProtoData);
|
||||
var ortValue = CreateOrtValueFromTensorProto(tensorProto);
|
||||
|
||||
data[tensorProto.Name] = ortValue;
|
||||
|
||||
idx++;
|
||||
}
|
||||
while (true);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
var inputData = await loadData("input_");
|
||||
var outputData = await loadData("output_");
|
||||
|
||||
return (inputData, outputData);
|
||||
}
|
||||
|
||||
internal static OrtValue CreateOrtValueFromTensorProto(Onnx.TensorProto tensorProto)
|
||||
{
|
||||
Type tensorElementType = GetElementType((TensorElementType)tensorProto.DataType);
|
||||
OrtValue ortValue = null;
|
||||
|
||||
// special case for strings
|
||||
if (tensorElementType == typeof(string))
|
||||
{
|
||||
var numElements = tensorProto.Dims.Aggregate(1L, (x, y) => x * y);
|
||||
ortValue = OrtValue.CreateTensorWithEmptyStrings(OrtAllocator.DefaultInstance,
|
||||
tensorProto.Dims.ToArray());
|
||||
|
||||
int idx = 0;
|
||||
foreach (var str in tensorProto.StringData)
|
||||
{
|
||||
ortValue.FillStringTensorElement(str.Span, idx++);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// use reflection to call generic method
|
||||
var func = typeof(Utils)
|
||||
.GetMethod(nameof(TensorProtoToOrtValue), BindingFlags.Static | BindingFlags.NonPublic)
|
||||
.MakeGenericMethod(tensorElementType);
|
||||
|
||||
ortValue = (OrtValue)func.Invoke(null, new[] { tensorProto });
|
||||
}
|
||||
|
||||
return ortValue;
|
||||
}
|
||||
|
||||
internal static Type GetElementType(TensorElementType elemType)
|
||||
{
|
||||
switch (elemType)
|
||||
{
|
||||
case TensorElementType.Float:
|
||||
return typeof(float);
|
||||
case TensorElementType.Double:
|
||||
return typeof(double);
|
||||
case TensorElementType.Int16:
|
||||
return typeof(short);
|
||||
case TensorElementType.UInt16:
|
||||
return typeof(ushort);
|
||||
case TensorElementType.Int32:
|
||||
return typeof(int);
|
||||
case TensorElementType.UInt32:
|
||||
return typeof(uint);
|
||||
case TensorElementType.Int64:
|
||||
return typeof(long);
|
||||
case TensorElementType.UInt64:
|
||||
return typeof(ulong);
|
||||
case TensorElementType.UInt8:
|
||||
return typeof(byte);
|
||||
case TensorElementType.Int8:
|
||||
return typeof(sbyte);
|
||||
case TensorElementType.String:
|
||||
return typeof(byte);
|
||||
case TensorElementType.Bool:
|
||||
return typeof(bool);
|
||||
default:
|
||||
throw new ArgumentException("Unexpected element type of " + elemType);
|
||||
}
|
||||
}
|
||||
|
||||
static OrtValue TensorProtoToOrtValue<T>(Onnx.TensorProto tensorProto)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var elementSize = sizeof(T);
|
||||
T[] data = new T[tensorProto.RawData.Length / elementSize];
|
||||
|
||||
fixed(byte *bytes = tensorProto.RawData.Span) fixed(void *target = data)
|
||||
{
|
||||
Buffer.MemoryCopy(bytes, target, tensorProto.RawData.Length, tensorProto.RawData.Length);
|
||||
}
|
||||
|
||||
return OrtValue.CreateTensorValueFromMemory(data, tensorProto.Dims.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
internal class TensorComparer
|
||||
{
|
||||
// we need to use a delegate in the checker func to handle string as well as numeric types
|
||||
private delegate ReadOnlySpan<T> GetDataFn<T>(OrtValue ortValue);
|
||||
|
||||
private static ReadOnlySpan<T> GetData<T>(OrtValue ortValue)
|
||||
where T : struct
|
||||
{
|
||||
return ortValue.GetTensorDataAsSpan<T>();
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<string> GetStringData(OrtValue ortValue)
|
||||
{
|
||||
return ortValue.GetStringTensorAsArray();
|
||||
}
|
||||
|
||||
private static void CheckEqual<T>(string name, OrtValue expected, OrtValue actual,
|
||||
IEqualityComparer<T> comparer, GetDataFn<T> getDataFn)
|
||||
{
|
||||
var expectedTypeAndShape = expected.GetTypeInfo().TensorTypeAndShapeInfo;
|
||||
var actualTypeAndShape = actual.GetTypeInfo().TensorTypeAndShapeInfo;
|
||||
|
||||
if (expectedTypeAndShape.ElementCount != actualTypeAndShape.ElementCount)
|
||||
{
|
||||
throw new ArithmeticException(
|
||||
$"Element count mismatch for {name}. " +
|
||||
$"Expected:{expectedTypeAndShape.ElementCount} Actual:{actualTypeAndShape.ElementCount}");
|
||||
}
|
||||
|
||||
var expectedData = getDataFn(expected);
|
||||
var actualData = getDataFn(actual);
|
||||
|
||||
List<string> mismatches = new List<string>();
|
||||
|
||||
for (int i = 0; i < expectedData.Length; i++)
|
||||
{
|
||||
if (!comparer.Equals(expectedData[i], actualData[i]))
|
||||
{
|
||||
mismatches.Add($"[{i}] {expectedData[i]} != {actualData[i]}");
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatches.Count > 0)
|
||||
{
|
||||
throw new ArithmeticException(
|
||||
$"Result mismatch for {name}. Mismatched entries:{string.Join(',', mismatches)}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckEqual<T>(string name, OrtValue expected, OrtValue actual,
|
||||
IEqualityComparer<T> comparer)
|
||||
where T : struct
|
||||
{
|
||||
CheckEqual(name, expected, actual, comparer, GetData<T>);
|
||||
}
|
||||
|
||||
internal static void VerifyTensorResults(string name, OrtValue expected, OrtValue actual)
|
||||
{
|
||||
var tensorElementType = expected.GetTypeInfo().TensorTypeAndShapeInfo.ElementDataType;
|
||||
switch (tensorElementType)
|
||||
{
|
||||
case TensorElementType.Float:
|
||||
CheckEqual(name, expected, actual, new FloatComparer());
|
||||
break;
|
||||
case TensorElementType.Double:
|
||||
CheckEqual(name, expected, actual, new DoubleComparer());
|
||||
break;
|
||||
case TensorElementType.Int32:
|
||||
CheckEqual(name, expected, actual, new ExactComparer<int>());
|
||||
break;
|
||||
case TensorElementType.UInt32:
|
||||
CheckEqual(name, expected, actual, new ExactComparer<uint>());
|
||||
break;
|
||||
case TensorElementType.Int16:
|
||||
CheckEqual(name, expected, actual, new ExactComparer<short>());
|
||||
break;
|
||||
case TensorElementType.UInt16:
|
||||
CheckEqual(name, expected, actual, new ExactComparer<ushort>());
|
||||
break;
|
||||
case TensorElementType.Int64:
|
||||
CheckEqual(name, expected, actual, new ExactComparer<long>());
|
||||
break;
|
||||
case TensorElementType.UInt64:
|
||||
CheckEqual(name, expected, actual, new ExactComparer<ulong>());
|
||||
break;
|
||||
case TensorElementType.UInt8:
|
||||
CheckEqual(name, expected, actual, new ExactComparer<byte>());
|
||||
break;
|
||||
case TensorElementType.Int8:
|
||||
CheckEqual(name, expected, actual, new ExactComparer<sbyte>());
|
||||
break;
|
||||
case TensorElementType.Bool:
|
||||
CheckEqual(name, expected, actual, new ExactComparer<bool>());
|
||||
break;
|
||||
case TensorElementType.Float16:
|
||||
CheckEqual(name, expected, actual, new Float16Comparer { tolerance = 2 });
|
||||
break;
|
||||
case TensorElementType.BFloat16:
|
||||
CheckEqual(name, expected, actual, new BFloat16Comparer { tolerance = 2 });
|
||||
break;
|
||||
case TensorElementType.String:
|
||||
CheckEqual<string>(name, expected, actual, new ExactComparer<string>(), GetStringData);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unexpected data type of {tensorElementType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
csharp/tools/MauiModelTester/create_test_data.py
Normal file
159
csharp/tools/MauiModelTester/create_test_data.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import argparse
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import numpy as np
|
||||
|
||||
# set to the directory the ONNX Runtime repo is in
|
||||
# `git checkout https://github.com/microsoft/onnxruntime.git` if needed.
|
||||
ORT_ROOT_DIR = Path(__file__).parents[3]
|
||||
SOLUTION_DIR = Path(__file__).parent
|
||||
|
||||
# add path for test data/dir generation utils
|
||||
sys.path.append(str(ORT_ROOT_DIR / "tools" / "python"))
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="""Setup the model and test data for usage with the MAUI model tester app.
|
||||
Input data will be randomly generated as needed.
|
||||
The model will be run locally and the output saved as expected output.
|
||||
Explicit input data or expected output data can be specified by providing .pb files with the input/output name
|
||||
and tensor. These can be created with /tools/python/onnx_test_data_utils.py.
|
||||
|
||||
See https://github.com/microsoft/onnxruntime/blob/main/tools/python/PythonTools.md#creating-a-test-directory-for-a-model # noqa
|
||||
for info on creating specific input or expected output""",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--symbolic_dims",
|
||||
"-s",
|
||||
help="Symbolic dimension values if the model inputs have symbolic dimensions and the input data is being "
|
||||
"generated. Format is `name=value {name2=value2 ...}.",
|
||||
type=str,
|
||||
nargs="+",
|
||||
required=False,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--input_data",
|
||||
"-i",
|
||||
help="Input data pb files created with onnx_test_data_utils.py. Multiple can be specified.",
|
||||
type=Path,
|
||||
nargs="+",
|
||||
required=False,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--output_data",
|
||||
"-o",
|
||||
help="Expected output data pb files created with onnx_test_data_utils.py. Multiple can be specified.",
|
||||
type=Path,
|
||||
nargs="+",
|
||||
required=False,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--model_path",
|
||||
"-m",
|
||||
help="Path to ONNX model to use. Model will be copied into the test app",
|
||||
type=Path,
|
||||
Required=True,
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
args.model_path.resolve(strict=True)
|
||||
|
||||
# convert symbolic dims to dictionary
|
||||
symbolic_dims = None
|
||||
if args.symbolic_dims:
|
||||
symbolic_dims = {}
|
||||
for value in args.symbolic_dims:
|
||||
pieces = value.split("=")
|
||||
assert len(pieces) == 2
|
||||
name = pieces[0].strip()
|
||||
dim_value = int(pieces[1].strip())
|
||||
symbolic_dims[name] = dim_value
|
||||
|
||||
args.symbolic_dims = symbolic_dims
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def create_existing_data_map(pb_files: List[Path]):
|
||||
import onnx_test_data_utils as data_utils
|
||||
|
||||
data_map = {}
|
||||
for file in pb_files:
|
||||
file.resolve(strict=True)
|
||||
name, data = data_utils.read_tensorproto_pb_file(str(file))
|
||||
data_map[name] = data
|
||||
|
||||
return data_map
|
||||
|
||||
|
||||
def add_model_and_test_data_to_app(
|
||||
model_path: Path,
|
||||
symbolic_dims: Optional[Dict[str, int]] = None,
|
||||
input_map: Optional[Dict[str, np.ndarray]] = None,
|
||||
output_map: Optional[Dict[str, np.ndarray]] = None,
|
||||
):
|
||||
import ort_test_dir_utils as utils
|
||||
|
||||
output_path = SOLUTION_DIR / "Resources" / "Raw"
|
||||
test_name = "test_data"
|
||||
|
||||
test_path = output_path / test_name
|
||||
# remove existing data
|
||||
if test_path.exists():
|
||||
shutil.rmtree(test_path)
|
||||
|
||||
# If you want to directly create input data without using onnx_test_data_utils you can edit the input map here
|
||||
# if not input_map:
|
||||
# input_map = {}
|
||||
#
|
||||
# input_map['Input3'] = np.random.rand(1, 1, 28, 28).astype(np.float32)
|
||||
|
||||
utils.create_test_dir(
|
||||
str(model_path),
|
||||
str(output_path),
|
||||
test_name,
|
||||
# Explicit input data. Any missing required inputs will have data generated for them.
|
||||
name_input_map=input_map,
|
||||
# Optional map for any symbolic values.
|
||||
symbolic_dim_values_map=symbolic_dims,
|
||||
# Expected output can be provided if you want to validate model output against this.
|
||||
name_output_map=output_map,
|
||||
)
|
||||
|
||||
# create_test_dir will copy the model to the output directory.
|
||||
# rename the copied model to the generic name the app expects.
|
||||
copied_model = output_path / test_name / model_path.name
|
||||
copied_model.rename(copied_model.with_name("model.onnx"))
|
||||
|
||||
# add a text file with the original model path just so there's some info on where it came from
|
||||
with open(test_path / "model_info.txt", "w") as model_info_file:
|
||||
model_info_file.write(str(model_path))
|
||||
|
||||
|
||||
def create_test_data():
|
||||
args = parse_args()
|
||||
|
||||
input_map = None
|
||||
output_map = None
|
||||
|
||||
if args.input_data:
|
||||
input_map = create_existing_data_map(args.input_data)
|
||||
|
||||
if args.output_data:
|
||||
output_map = create_existing_data_map(args.output_data)
|
||||
|
||||
add_model_and_test_data_to_app(args.model_path, args.symbolic_dims, input_map, output_map)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_test_data()
|
||||
|
|
@ -129,6 +129,12 @@ def get_arg_parser():
|
|||
parser.add_argument("--output", help="Filename to serialize the TensorProto to.")
|
||||
|
||||
image_to_pb_group = parser.add_argument_group("image_to_pb", "image_to_pb specific options")
|
||||
image_to_pb_group.add_argument(
|
||||
"--raw",
|
||||
action="store_true",
|
||||
help="Save raw image bytes for usage with a model that has the DecodeImage custom operator "
|
||||
"from onnxruntime-extensions.",
|
||||
)
|
||||
image_to_pb_group.add_argument(
|
||||
"--resize",
|
||||
default=None,
|
||||
|
|
@ -191,7 +197,11 @@ if __name__ == "__main__":
|
|||
print("Missing argument. Need input, output, name to be specified.", file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
img_np = image_to_numpy(args.input, args.resize, args.channels_last, args.add_batch_dim)
|
||||
if args.raw:
|
||||
img_np = np.fromfile(args.input, np.ubyte)
|
||||
else:
|
||||
img_np = image_to_numpy(args.input, args.resize, args.channels_last, args.add_batch_dim)
|
||||
|
||||
numpy_to_pb(args.name, img_np, args.output)
|
||||
elif args.action == "random_to_pb":
|
||||
if not args.output or not args.shape or not args.datatype or not args.name:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import shutil
|
|||
import numpy as np
|
||||
import onnx
|
||||
import onnx_test_data_utils
|
||||
from onnx import numpy_helper
|
||||
from onnx import TensorProto, numpy_helper
|
||||
|
||||
import onnxruntime as ort
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ def _create_missing_input_data(model_inputs, name_input_map, symbolic_dim_values
|
|||
dims.append(dim.dim_value)
|
||||
elif dim_type == "dim_param":
|
||||
if dim.dim_param not in symbolic_dim_values_map:
|
||||
raise ValueError(f"Value for symbolic dim {dim.dim_param} was not provided.")
|
||||
raise ValueError(f"Value for symbolic dim '{dim.dim_param}' was not provided.")
|
||||
|
||||
dims.append(symbolic_dim_values_map[dim.dim_param])
|
||||
else:
|
||||
|
|
@ -57,9 +57,16 @@ def _create_missing_input_data(model_inputs, name_input_map, symbolic_dim_values
|
|||
# shape for the input name instead.
|
||||
raise ValueError("Unsupported model. Unknown dim with no value or symbolic name.")
|
||||
|
||||
np_type = onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[input.type.tensor_type.elem_type]
|
||||
# create random data. give it range -10 to 10 so if we convert to an integer type it's not all 0s and 1s
|
||||
data = (np.random.standard_normal(dims) * 10).astype(np_type)
|
||||
onnx_type = input.type.tensor_type.elem_type
|
||||
# create random data.
|
||||
data = np.random.random_sample(dims)
|
||||
# use range of [0, 1) for floating point data
|
||||
# use range of [0, 256) for other data types
|
||||
if onnx_type not in [TensorProto.FLOAT, TensorProto.BFLOAT16, TensorProto.DOUBLE, TensorProto.FLOAT16]:
|
||||
data *= 256
|
||||
|
||||
np_type = onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[onnx_type]
|
||||
data = data.astype(np_type)
|
||||
|
||||
name_input_map[input.name] = data
|
||||
|
||||
|
|
@ -139,7 +146,20 @@ def create_test_dir(
|
|||
# save expected output data if provided. run model to create if not.
|
||||
if not name_output_map:
|
||||
output_names = [o.name for o in model_outputs]
|
||||
sess = ort.InferenceSession(test_model_filename)
|
||||
so = ort.SessionOptions()
|
||||
|
||||
# try and enable onnxruntime-extensions if present
|
||||
try:
|
||||
import onnxruntime_extensions
|
||||
|
||||
so.register_custom_ops_library(onnxruntime_extensions.get_library_path())
|
||||
|
||||
except ImportError:
|
||||
# ignore if onnxruntime_extensions is not available.
|
||||
# if the model uses custom ops from there it will fail to load.
|
||||
pass
|
||||
|
||||
sess = ort.InferenceSession(test_model_filename, so)
|
||||
outputs = sess.run(output_names, name_input_map)
|
||||
name_output_map = {}
|
||||
for name, data in zip(output_names, outputs):
|
||||
|
|
|
|||
Loading…
Reference in a new issue