onnxruntime/winml/test/api/LearningModelBindingAPITest.cpp
Justin Chu 2575b9aaa1
Improve comments in winml/ (#17163)
Follow up of #17144. Manually fixed indentation in block comments and
replaced all tabs with spaces.
2023-08-15 23:30:56 -04:00

686 lines
29 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "testPch.h"
#include "APITest.h"
#include "LearningModelBindingAPITest.h"
#include "SqueezeNetValidator.h"
#include <sstream>
using namespace winrt;
using namespace winml;
using namespace wfc;
using namespace wgi;
using namespace wm;
using namespace ws;
static void LearningModelBindingAPITestsClassSetup() {
init_apartment();
#ifdef BUILD_INBOX
winrt_activation_handler = WINRT_RoGetActivationFactory;
#endif
}
static void CpuSqueezeNet() {
std::string cpuInstance("CPU");
WINML_EXPECT_NO_THROW(WinML::Engine::Test::ModelValidator::SqueezeNet(
cpuInstance,
LearningModelDeviceKind::Cpu,
/*dataTolerance*/ 0.00001f,
false
));
}
static void CpuSqueezeNetEmptyOutputs() {
std::string cpuInstance("CPU");
WINML_EXPECT_NO_THROW(WinML::Engine::Test::ModelValidator::SqueezeNet(
cpuInstance,
LearningModelDeviceKind::Cpu,
/*dataTolerance*/ 0.00001f,
false,
OutputBindingStrategy::Empty
););
}
static void CpuSqueezeNetUnboundOutputs() {
std::string cpuInstance("CPU");
WINML_EXPECT_NO_THROW(WinML::Engine::Test::ModelValidator::SqueezeNet(
cpuInstance,
LearningModelDeviceKind::Cpu,
/*dataTolerance*/ 0.00001f,
false,
OutputBindingStrategy::Unbound
););
}
static void CpuSqueezeNetBindInputTensorAsInspectable() {
std::string cpuInstance("CPU");
WINML_EXPECT_NO_THROW(WinML::Engine::Test::ModelValidator::SqueezeNet(
cpuInstance,
LearningModelDeviceKind::Cpu,
/*dataTolerance*/ 0.00001f,
false,
OutputBindingStrategy::Bound /* empty outputs */,
true /* bind inputs as inspectables */
););
}
static void CastMapInt64() {
WINML_EXPECT_NO_THROW(LearningModel::LoadFromFilePath(FileHelpers::GetModulePath() + L"castmap-int64.onnx"));
// TODO: Check Descriptor
}
static void DictionaryVectorizerMapInt64() {
LearningModel learningModel = nullptr;
WINML_EXPECT_NO_THROW(APITest::LoadModel(L"dictvectorizer-int64.onnx", learningModel));
auto inputDescriptor = learningModel.InputFeatures().First().Current();
WINML_EXPECT_TRUE(inputDescriptor.Kind() == LearningModelFeatureKind::Map);
auto mapDescriptor = inputDescriptor.as<MapFeatureDescriptor>();
WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::Int64);
WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor);
auto tensorDescriptor = mapDescriptor.ValueDescriptor().as<TensorFeatureDescriptor>();
// empty size means tensor of scalar value
WINML_EXPECT_TRUE(tensorDescriptor.Shape().Size() == 0);
WINML_EXPECT_TRUE(tensorDescriptor.TensorKind() == TensorKind::Float);
LearningModelSession modelSession(learningModel);
LearningModelBinding binding(modelSession);
std::unordered_map<int64_t, float> map;
map[1] = 1.f;
map[10] = 10.f;
map[3] = 3.f;
auto mapInputName = inputDescriptor.Name();
// Bind as IMap
auto abiMap = winrt::single_threaded_map(std::move(map));
binding.Bind(mapInputName, abiMap);
auto mapInputInspectable = abiMap.as<wf::IInspectable>();
auto first = binding.First();
WINML_EXPECT_TRUE(first.Current().Key() == mapInputName);
WINML_EXPECT_TRUE(first.Current().Value() == mapInputInspectable);
WINML_EXPECT_TRUE(binding.Lookup(mapInputName) == mapInputInspectable);
// Bind as IMapView
auto mapView = abiMap.GetView();
binding.Bind(mapInputName, mapView);
mapInputInspectable = mapView.as<wf::IInspectable>();
first = binding.First();
WINML_EXPECT_TRUE(first.Current().Key() == mapInputName);
WINML_EXPECT_TRUE(first.Current().Value() == mapView);
WINML_EXPECT_TRUE(binding.Lookup(mapInputName) == mapView);
}
static void DictionaryVectorizerMapString() {
LearningModel learningModel = nullptr;
WINML_EXPECT_NO_THROW(APITest::LoadModel(L"dictvectorizer-string.onnx", learningModel));
auto inputDescriptor = learningModel.InputFeatures().First().Current();
WINML_EXPECT_TRUE(inputDescriptor.Kind() == LearningModelFeatureKind::Map);
auto mapDescriptor = inputDescriptor.as<MapFeatureDescriptor>();
WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::String);
WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor);
auto tensorDescriptor = mapDescriptor.ValueDescriptor().as<TensorFeatureDescriptor>();
// empty size means tensor of scalar value
WINML_EXPECT_TRUE(tensorDescriptor.Shape().Size() == 0);
WINML_EXPECT_TRUE(tensorDescriptor.TensorKind() == TensorKind::Float);
LearningModelSession modelSession(learningModel);
LearningModelBinding binding(modelSession);
std::unordered_map<winrt::hstring, float> map;
map[L"1"] = 1.f;
map[L"10"] = 10.f;
map[L"2"] = 2.f;
auto mapInputName = inputDescriptor.Name();
auto abiMap = winrt::single_threaded_map(std::move(map));
binding.Bind(mapInputName, abiMap);
auto mapInputInspectable = abiMap.as<wf::IInspectable>();
auto first = binding.First();
WINML_EXPECT_TRUE(first.Current().Key() == mapInputName);
WINML_EXPECT_TRUE(first.Current().Value() == mapInputInspectable);
WINML_EXPECT_TRUE(binding.Lookup(mapInputName) == mapInputInspectable);
modelSession.Evaluate(binding, L"");
}
static void RunZipMapInt64(winml::LearningModel model, OutputBindingStrategy bindingStrategy) {
auto outputFeatures = model.OutputFeatures();
auto outputDescriptor = outputFeatures.First().Current();
WINML_EXPECT_TRUE(outputDescriptor.Kind() == LearningModelFeatureKind::Sequence);
auto seqDescriptor = outputDescriptor.as<SequenceFeatureDescriptor>();
auto mapDescriptor = seqDescriptor.ElementDescriptor().as<MapFeatureDescriptor>();
WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::Int64);
WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor);
auto tensorDescriptor = mapDescriptor.ValueDescriptor().as<TensorFeatureDescriptor>();
WINML_EXPECT_TRUE(tensorDescriptor.TensorKind() == TensorKind::Float);
LearningModelSession session(model);
LearningModelBinding binding(session);
std::vector<float> inputs = {0.5f, 0.25f, 0.125f};
std::vector<int64_t> shape = {1, 3};
// Bind inputs
auto inputTensor = TensorFloat::CreateFromArray(shape, winrt::array_view<const float>(std::move(inputs)));
binding.Bind(winrt::hstring(L"X"), inputTensor);
typedef IMap<int64_t, float> ABIMap;
typedef IVector<ABIMap> ABISequeneceOfMap;
ABISequeneceOfMap abiOutput = nullptr;
// Bind outputs
if (bindingStrategy == OutputBindingStrategy::Bound) {
abiOutput = winrt::single_threaded_vector<ABIMap>();
binding.Bind(winrt::hstring(L"Y"), abiOutput);
}
// Evaluate
auto result = session.Evaluate(binding, L"0").Outputs();
if (bindingStrategy == OutputBindingStrategy::Bound) {
// from output binding
const auto& out1 = abiOutput.GetAt(0);
const auto& out2 = result.Lookup(L"Y").as<IVectorView<ABIMap>>().GetAt(0);
WINML_LOG_COMMENT((std::ostringstream() << "size: " << out1.Size()).str());
// check outputs
auto iter1 = out1.First();
auto iter2 = out2.First();
for (uint32_t i = 0, size = (uint32_t)inputs.size(); i < size; ++i) {
WINML_EXPECT_TRUE(iter1.HasCurrent());
WINML_EXPECT_TRUE(iter2.HasCurrent());
const auto& pair1 = iter1.Current();
const auto& pair2 = iter2.Current();
WINML_LOG_COMMENT((std::ostringstream() << "key: " << pair1.Key() << ", value: " << pair2.Value()).str());
WINML_EXPECT_TRUE(pair1.Key() == i && pair2.Key() == i);
WINML_EXPECT_TRUE(pair1.Value() == inputs[i] && pair2.Value() == inputs[i]);
iter1.MoveNext();
iter2.MoveNext();
}
WINML_EXPECT_TRUE(!iter1.HasCurrent());
WINML_EXPECT_TRUE(!iter2.HasCurrent());
} else {
abiOutput = result.Lookup(L"Y").as<ABISequeneceOfMap>();
WINML_EXPECT_TRUE(abiOutput.Size() == 1);
ABIMap map = abiOutput.GetAt(0);
WINML_EXPECT_TRUE(map.Size() == 3);
WINML_EXPECT_TRUE(map.Lookup(0) == 0.5);
WINML_EXPECT_TRUE(map.Lookup(1) == .25);
WINML_EXPECT_TRUE(map.Lookup(2) == .125);
}
}
static void ZipMapInt64() {
LearningModel learningModel = nullptr;
WINML_EXPECT_NO_THROW(APITest::LoadModel(L"zipmap-int64.onnx", learningModel));
RunZipMapInt64(learningModel, OutputBindingStrategy::Bound);
}
static void ZipMapInt64Unbound() {
LearningModel learningModel = nullptr;
WINML_EXPECT_NO_THROW(APITest::LoadModel(L"zipmap-int64.onnx", learningModel));
RunZipMapInt64(learningModel, OutputBindingStrategy::Unbound);
}
static void ZipMapString() {
// output constraint: "seq(map(string, float))" or "seq(map(int64, float))"
LearningModel learningModel = nullptr;
WINML_EXPECT_NO_THROW(APITest::LoadModel(L"zipmap-string.onnx", learningModel));
auto outputs = learningModel.OutputFeatures();
auto outputDescriptor = outputs.First().Current();
WINML_EXPECT_TRUE(outputDescriptor.Kind() == LearningModelFeatureKind::Sequence);
auto mapDescriptor = outputDescriptor.as<SequenceFeatureDescriptor>().ElementDescriptor().as<MapFeatureDescriptor>();
WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::String);
WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor);
auto tensorDescriptor = mapDescriptor.ValueDescriptor().as<TensorFeatureDescriptor>();
WINML_EXPECT_TRUE(tensorDescriptor.TensorKind() == TensorKind::Float);
LearningModelSession session(learningModel);
LearningModelBinding binding(session);
std::vector<float> inputs = {0.5f, 0.25f, 0.125f};
std::vector<int64_t> shape = {1, 3};
std::vector<winrt::hstring> labels = {L"cat", L"dog", L"lion"};
std::map<winrt::hstring, float> mapData = {
{ L"cat", 0.0f},
{ L"dog", 0.0f},
{L"lion", 0.0f}
};
typedef IMap<winrt::hstring, float> ABIMap;
ABIMap abiMap = winrt::single_threaded_map<winrt::hstring, float>(std::move(mapData));
std::vector<ABIMap> seqOutput = {abiMap};
IVector<ABIMap> ABIOutput = winrt::single_threaded_vector<ABIMap>(std::move(seqOutput));
TensorFloat inputTensor = TensorFloat::CreateFromArray(shape, winrt::array_view<const float>(std::move(inputs)));
binding.Bind(winrt::hstring(L"X"), inputTensor);
binding.Bind(winrt::hstring(L"Y"), ABIOutput);
auto result = session.Evaluate(binding, L"0").Outputs();
// from output binding
const auto& out1 = ABIOutput.GetAt(0);
const auto& out2 = result.Lookup(L"Y").as<IVectorView<ABIMap>>().GetAt(0);
WINML_LOG_COMMENT((std::ostringstream() << "size: " << out1.Size()).str());
// single key,value pair for each map
auto iter1 = out1.First();
auto iter2 = out2.First();
for (uint32_t i = 0, size = (uint32_t)inputs.size(); i < size; ++i) {
WINML_EXPECT_TRUE(iter2.HasCurrent());
const auto& pair1 = iter1.Current();
const auto& pair2 = iter2.Current();
WINML_LOG_COMMENT((std::ostringstream() << "key: " << pair1.Key().c_str() << ", value " << pair2.Value()).str());
WINML_EXPECT_TRUE(std::wstring(pair1.Key().c_str()).compare(labels[i]) == 0);
WINML_EXPECT_TRUE(std::wstring(pair2.Key().c_str()).compare(labels[i]) == 0);
WINML_EXPECT_TRUE(pair1.Value() == inputs[i] && pair2.Value() == inputs[i]);
iter1.MoveNext();
iter2.MoveNext();
}
WINML_EXPECT_TRUE(!iter1.HasCurrent());
WINML_EXPECT_TRUE(!iter2.HasCurrent());
}
static void GpuSqueezeNet() {
std::string gpuInstance("GPU");
WINML_EXPECT_NO_THROW(WinML::Engine::Test::ModelValidator::SqueezeNet(
gpuInstance,
LearningModelDeviceKind::DirectX,
/*dataTolerance*/ 0.00001f
););
}
static void GpuSqueezeNetEmptyOutputs() {
std::string gpuInstance("GPU");
WINML_EXPECT_NO_THROW(WinML::Engine::Test::ModelValidator::SqueezeNet(
gpuInstance,
LearningModelDeviceKind::DirectX,
/*dataTolerance*/ 0.00001f,
false,
OutputBindingStrategy::Empty
););
}
static void GpuSqueezeNetUnboundOutputs() {
std::string gpuInstance("GPU");
WINML_EXPECT_NO_THROW(WinML::Engine::Test::ModelValidator::SqueezeNet(
gpuInstance,
LearningModelDeviceKind::DirectX,
/*dataTolerance*/ 0.00001f,
false,
OutputBindingStrategy::Unbound
););
}
// Validates that when the input image is the same as the model expects, the binding step is executed correctly.
static void ImageBindingDimensions() {
LearningModelBinding learningModelBinding = nullptr;
LearningModel learningModel = nullptr;
LearningModelSession learningModelSession = nullptr;
LearningModelDevice leraningModelDevice = nullptr;
std::wstring filePath = FileHelpers::GetModulePath() + L"model.onnx";
// load a model with expected input size: 224 x 224
WINML_EXPECT_NO_THROW(leraningModelDevice = LearningModelDevice(LearningModelDeviceKind::Default));
WINML_EXPECT_NO_THROW(learningModel = LearningModel::LoadFromFilePath(filePath));
WINML_EXPECT_TRUE(learningModel != nullptr);
WINML_EXPECT_NO_THROW(learningModelSession = LearningModelSession(learningModel, leraningModelDevice));
WINML_EXPECT_NO_THROW(learningModelBinding = LearningModelBinding(learningModelSession));
// Create input images and execute bind
// Test Case 1: both width and height are larger than model expects
VideoFrame inputImage1(BitmapPixelFormat::Rgba8, 1000, 1000);
ImageFeatureValue inputTensor = ImageFeatureValue::CreateFromVideoFrame(inputImage1);
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"data_0", inputTensor));
// Test Case 2: only height is larger, while width is smaller
VideoFrame inputImage2(BitmapPixelFormat::Rgba8, 20, 1000);
inputTensor = ImageFeatureValue::CreateFromVideoFrame(inputImage2);
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"data_0", inputTensor));
// Test Case 3: only width is larger, while height is smaller
VideoFrame inputImage3(BitmapPixelFormat::Rgba8, 1000, 20);
inputTensor = ImageFeatureValue::CreateFromVideoFrame(inputImage3);
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"data_0", inputTensor));
// Test Case 4: both width and height are smaller than model expects
VideoFrame inputImage4(BitmapPixelFormat::Rgba8, 20, 20);
inputTensor = ImageFeatureValue::CreateFromVideoFrame(inputImage4);
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"data_0", inputTensor));
}
static void VerifyInvalidBindExceptions() {
LearningModel learningModel = nullptr;
WINML_EXPECT_NO_THROW(APITest::LoadModel(L"zipmap-int64.onnx", learningModel));
LearningModelSession session(learningModel);
LearningModelBinding binding(session);
std::vector<float> inputs = {0.5f, 0.25f, 0.125f};
std::vector<int64_t> shape = {1, 3};
auto matchException = [](const winrt::hresult_error& e, HRESULT hr) -> bool { return e.code() == hr; };
auto ensureWinmlSizeMismatch = std::bind(matchException, std::placeholders::_1, WINML_ERR_SIZE_MISMATCH);
auto ensureWinmlInvalidBinding = std::bind(matchException, std::placeholders::_1, WINML_ERR_INVALID_BINDING);
/*
Verify tensor bindings throw correct bind exceptions
*/
// Bind invalid image as tensorfloat input
auto image = FileHelpers::LoadImageFeatureValue(L"227x227.png");
WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"X", image), winrt::hresult_error, ensureWinmlSizeMismatch);
// Bind invalid map as tensorfloat input
std::unordered_map<float, float> map;
auto abiMap = winrt::single_threaded_map(std::move(map));
WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"X", abiMap), winrt::hresult_error, ensureWinmlInvalidBinding);
// Bind invalid sequence as tensorfloat input
std::vector<uint32_t> sequence;
auto abiSequence = winrt::single_threaded_vector(std::move(sequence));
WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"X", abiSequence), winrt::hresult_error, ensureWinmlInvalidBinding);
// Bind invalid tensor size as tensorfloat input
auto tensorBoolean = TensorBoolean::Create();
WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"X", tensorBoolean), winrt::hresult_error, ensureWinmlInvalidBinding);
// Bind invalid tensor shape as tensorfloat input
auto tensorInvalidShape = TensorFloat::Create(std::vector<int64_t>{2, 3, 4});
WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"X", tensorInvalidShape), winrt::hresult_error, ensureWinmlInvalidBinding);
/*
Verify sequence bindings throw correct bind exceptions
*/
// Bind invalid image as sequence<map<int, float> output
WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", image), winrt::hresult_error, ensureWinmlInvalidBinding);
// Bind invalid map as sequence<map<int, float> output
WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", abiMap), winrt::hresult_error, ensureWinmlInvalidBinding);
// Bind invalid sequence<int> as sequence<map<int, float> output
WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", abiSequence), winrt::hresult_error, ensureWinmlInvalidBinding);
// Bind invalid tensor as sequence<map<int, float> output
WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", tensorBoolean), winrt::hresult_error, ensureWinmlInvalidBinding);
/*
Verify map bindings throw correct bind exceptions
*/
WINML_EXPECT_NO_THROW(APITest::LoadModel(L"dictvectorizer-int64.onnx", learningModel));
LearningModelSession mapSession(learningModel);
LearningModelBinding mapBinding(mapSession);
auto inputName = learningModel.InputFeatures().First().Current().Name();
// Bind invalid image as image input
auto smallImage = FileHelpers::LoadImageFeatureValue(L"100x100.png");
WINML_EXPECT_THROW_SPECIFIC(mapBinding.Bind(inputName, smallImage), winrt::hresult_error, ensureWinmlInvalidBinding);
// Bind invalid map as image input
WINML_EXPECT_THROW_SPECIFIC(mapBinding.Bind(inputName, abiMap), winrt::hresult_error, ensureWinmlInvalidBinding);
// Bind invalid sequence as image input
WINML_EXPECT_THROW_SPECIFIC(mapBinding.Bind(inputName, abiSequence), winrt::hresult_error, ensureWinmlInvalidBinding);
// Bind invalid tensor type as image input
WINML_EXPECT_THROW_SPECIFIC(
mapBinding.Bind(inputName, tensorBoolean), winrt::hresult_error, ensureWinmlInvalidBinding
);
}
// Verify that it throws an error when binding an invalid name.
static void BindInvalidInputName() {
LearningModel learningModel = nullptr;
LearningModelBinding learningModelBinding = nullptr;
LearningModelDevice learningModelDevice = nullptr;
LearningModelSession learningModelSession = nullptr;
std::wstring modelPath = FileHelpers::GetModulePath() + L"Add_ImageNet1920.onnx";
WINML_EXPECT_NO_THROW(learningModel = LearningModel::LoadFromFilePath(modelPath));
WINML_EXPECT_TRUE(learningModel != nullptr);
WINML_EXPECT_NO_THROW(learningModelDevice = LearningModelDevice(LearningModelDeviceKind::Default));
WINML_EXPECT_NO_THROW(learningModelSession = LearningModelSession(learningModel, learningModelDevice));
WINML_EXPECT_NO_THROW(learningModelBinding = LearningModelBinding(learningModelSession));
VideoFrame iuputImage(BitmapPixelFormat::Rgba8, 1920, 1080);
ImageFeatureValue inputTensor = ImageFeatureValue::CreateFromVideoFrame(iuputImage);
auto first = learningModel.InputFeatures().First();
std::wstring testInvalidName = L"0";
// Verify that testInvalidName is not in model's InputFeatures
while (first.HasCurrent()) {
WINML_EXPECT_NOT_EQUAL(testInvalidName, first.Current().Name());
first.MoveNext();
}
// Bind inputTensor to a valid input name
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"input_39:0", inputTensor));
// Bind inputTensor to an invalid input name
WINML_EXPECT_THROW_SPECIFIC(
learningModelBinding.Bind(testInvalidName, inputTensor),
winrt::hresult_error,
[](const winrt::hresult_error& e) -> bool { return e.code() == WINML_ERR_INVALID_BINDING; }
);
}
static void VerifyOutputAfterEvaluateAsyncCalledTwice() {
LearningModel learningModel = nullptr;
LearningModelBinding learningModelBinding = nullptr;
LearningModelDevice learningModelDevice = nullptr;
LearningModelSession learningModelSession = nullptr;
std::wstring filePath = FileHelpers::GetModulePath() + L"relu.onnx";
WINML_EXPECT_NO_THROW(learningModelDevice = LearningModelDevice(LearningModelDeviceKind::Default));
WINML_EXPECT_NO_THROW(learningModel = LearningModel::LoadFromFilePath(filePath));
WINML_EXPECT_TRUE(learningModel != nullptr);
WINML_EXPECT_NO_THROW(learningModelSession = LearningModelSession(learningModel, learningModelDevice));
WINML_EXPECT_NO_THROW(learningModelBinding = LearningModelBinding(learningModelSession));
auto inputShape = std::vector<int64_t>{5};
auto inputData1 = std::vector<float>{-50.f, -25.f, 0.f, 25.f, 50.f};
auto inputValue1 =
TensorFloat::CreateFromIterable(inputShape, single_threaded_vector<float>(std::move(inputData1)).GetView());
auto inputData2 = std::vector<float>{50.f, 25.f, 0.f, -25.f, -50.f};
auto inputValue2 =
TensorFloat::CreateFromIterable(inputShape, single_threaded_vector<float>(std::move(inputData2)).GetView());
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"X", inputValue1));
auto outputValue = TensorFloat::Create();
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"Y", outputValue));
WINML_EXPECT_NO_THROW(learningModelSession.Evaluate(learningModelBinding, L""));
auto buffer1 = outputValue.GetAsVectorView();
WINML_EXPECT_TRUE(buffer1 != nullptr);
// The second evaluation
// If we don't bind output again, the output value will not change
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"X", inputValue2));
WINML_EXPECT_NO_THROW(learningModelSession.Evaluate(learningModelBinding, L""));
auto buffer2 = outputValue.GetAsVectorView();
WINML_EXPECT_EQUAL(buffer1.Size(), buffer2.Size());
bool isSame = true;
for (uint32_t i = 0; i < buffer1.Size(); ++i) {
if (buffer1.GetAt(i) != buffer2.GetAt(i)) {
isSame = false;
break;
}
}
WINML_EXPECT_FALSE(isSame);
}
static VideoFrame CreateVideoFrame(const wchar_t* path) {
auto imagefile = StorageFile::GetFileFromPathAsync(path).get();
auto stream = imagefile.OpenAsync(FileAccessMode::Read).get();
auto decoder = BitmapDecoder::CreateAsync(stream).get();
auto softwareBitmap = decoder.GetSoftwareBitmapAsync().get();
return VideoFrame::CreateWithSoftwareBitmap(softwareBitmap);
}
static void VerifyOutputAfterImageBindCalledTwice() {
std::wstring fullModelPath = FileHelpers::GetModulePath() + L"model.onnx";
std::wstring fullImagePath1 = FileHelpers::GetModulePath() + L"kitten_224.png";
std::wstring fullImagePath2 = FileHelpers::GetModulePath() + L"fish.png";
// winml model creation
LearningModel model = nullptr;
WINML_EXPECT_NO_THROW(model = LearningModel::LoadFromFilePath(fullModelPath));
LearningModelSession modelSession = nullptr;
WINML_EXPECT_NO_THROW(
modelSession = LearningModelSession(model, LearningModelDevice(LearningModelDeviceKind::Default))
);
LearningModelBinding modelBinding(modelSession);
// create the tensor for the actual output
auto output = TensorFloat::Create();
modelBinding.Bind(L"softmaxout_1", output);
// Bind image 1 and evaluate
auto frame = CreateVideoFrame(fullImagePath1.c_str());
auto imageTensor = ImageFeatureValue::CreateFromVideoFrame(frame);
WINML_EXPECT_NO_THROW(modelBinding.Bind(L"data_0", imageTensor));
WINML_EXPECT_NO_THROW(modelSession.Evaluate(modelBinding, L""));
// Store 1st result
auto outputVectorView1 = output.GetAsVectorView();
// Bind image 2 and evaluate
// In this scenario, the backing videoframe is updated, and the imagefeaturevalue is rebound.
// The expected result is that the videoframe will be re-tensorized at bind
auto frame2 = CreateVideoFrame(fullImagePath2.c_str());
frame2.CopyToAsync(frame).get();
WINML_EXPECT_NO_THROW(modelBinding.Bind(L"data_0", imageTensor));
WINML_EXPECT_NO_THROW(modelSession.Evaluate(modelBinding, L""));
// Store 2nd result
auto outputVectorView2 = output.GetAsVectorView();
WINML_EXPECT_EQUAL(outputVectorView1.Size(), outputVectorView2.Size());
bool isSame = true;
for (uint32_t i = 0; i < outputVectorView1.Size(); ++i) {
if (outputVectorView1.GetAt(i) != outputVectorView2.GetAt(i)) {
isSame = false;
break;
}
}
WINML_EXPECT_FALSE(isSame);
}
static void SequenceLengthTensorFloat() {
// Tests sequence of tensor float as an input
LearningModel learningModel = nullptr;
LearningModelBinding learningModelBinding = nullptr;
LearningModelDevice learningModelDevice = nullptr;
LearningModelSession learningModelSession = nullptr;
std::wstring filePath = FileHelpers::GetModulePath() + L"sequence_length.onnx";
WINML_EXPECT_NO_THROW(learningModelDevice = LearningModelDevice(LearningModelDeviceKind::Default));
WINML_EXPECT_NO_THROW(learningModel = LearningModel::LoadFromFilePath(filePath));
WINML_EXPECT_TRUE(learningModel != nullptr);
WINML_EXPECT_NO_THROW(learningModelSession = LearningModelSession(learningModel, learningModelDevice));
WINML_EXPECT_NO_THROW(learningModelBinding = LearningModelBinding(learningModelSession));
auto input = winrt::single_threaded_vector<TensorFloat>();
for (int i = 0; i < 3; i++) {
std::vector<int64_t> shape = {5, 3 * i + 1};
std::vector<float> data(
static_cast<size_t>(std::accumulate(shape.begin(), shape.end(), static_cast<int64_t>(1), std::multiplies()))
);
input.Append(TensorFloat::CreateFromShapeArrayAndDataArray(shape, data));
}
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"X", input));
auto results = learningModelSession.Evaluate(learningModelBinding, L"");
WINML_EXPECT_EQUAL(3, results.Outputs().Lookup(L"Y").as<TensorInt64Bit>().GetAsVectorView().GetAt(0));
}
static void SequenceConstructTensorString() {
LearningModel learningModel = nullptr;
LearningModelBinding learningModelBinding = nullptr;
LearningModelDevice learningModelDevice = nullptr;
LearningModelSession learningModelSession = nullptr;
std::wstring filePath = FileHelpers::GetModulePath() + L"sequence_construct.onnx";
WINML_EXPECT_NO_THROW(learningModelDevice = LearningModelDevice(LearningModelDeviceKind::Default));
WINML_EXPECT_NO_THROW(learningModel = LearningModel::LoadFromFilePath(filePath));
WINML_EXPECT_TRUE(learningModel != nullptr);
WINML_EXPECT_NO_THROW(learningModelSession = LearningModelSession(learningModel, learningModelDevice));
WINML_EXPECT_NO_THROW(learningModelBinding = LearningModelBinding(learningModelSession));
std::vector<int64_t> shape1 = {2, 3};
std::vector<int64_t> data1(
static_cast<size_t>(std::accumulate(shape1.begin(), shape1.end(), static_cast<int64_t>(1), std::multiplies()))
);
auto input1 = TensorInt64Bit::CreateFromShapeArrayAndDataArray(shape1, data1);
std::vector<int64_t> shape2 = {2, 3};
std::vector<int64_t> data2(
static_cast<size_t>(std::accumulate(shape2.begin(), shape2.end(), static_cast<int64_t>(1), std::multiplies()))
);
auto input2 = TensorInt64Bit::CreateFromShapeArrayAndDataArray(shape2, data2);
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"tensor1", input1));
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"tensor2", input2));
auto results = learningModelSession.Evaluate(learningModelBinding, L"");
auto output_sequence = results.Outputs().Lookup(L"output_sequence").as<wfc::IVectorView<TensorInt64Bit>>();
WINML_EXPECT_EQUAL(static_cast<uint32_t>(2), output_sequence.Size());
WINML_EXPECT_EQUAL(2, output_sequence.GetAt(0).Shape().GetAt(0));
WINML_EXPECT_EQUAL(3, output_sequence.GetAt(0).Shape().GetAt(1));
WINML_EXPECT_EQUAL(2, output_sequence.GetAt(1).Shape().GetAt(0));
WINML_EXPECT_EQUAL(3, output_sequence.GetAt(1).Shape().GetAt(1));
auto bound_output_sequence = winrt::single_threaded_vector<TensorInt64Bit>();
WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"output_sequence", bound_output_sequence));
WINML_EXPECT_NO_THROW(learningModelSession.Evaluate(learningModelBinding, L""));
WINML_EXPECT_EQUAL(static_cast<uint32_t>(2), bound_output_sequence.Size());
WINML_EXPECT_EQUAL(2, bound_output_sequence.GetAt(0).Shape().GetAt(0));
WINML_EXPECT_EQUAL(3, bound_output_sequence.GetAt(0).Shape().GetAt(1));
WINML_EXPECT_EQUAL(2, bound_output_sequence.GetAt(1).Shape().GetAt(0));
WINML_EXPECT_EQUAL(3, bound_output_sequence.GetAt(1).Shape().GetAt(1));
}
const LearningModelBindingAPITestsApi& getapi() {
static LearningModelBindingAPITestsApi api = {
LearningModelBindingAPITestsClassSetup,
CpuSqueezeNet,
CpuSqueezeNetEmptyOutputs,
CpuSqueezeNetUnboundOutputs,
CpuSqueezeNetBindInputTensorAsInspectable,
CastMapInt64,
DictionaryVectorizerMapInt64,
DictionaryVectorizerMapString,
ZipMapInt64,
ZipMapInt64Unbound,
ZipMapString,
GpuSqueezeNet,
GpuSqueezeNetEmptyOutputs,
GpuSqueezeNetUnboundOutputs,
ImageBindingDimensions,
VerifyInvalidBindExceptions,
BindInvalidInputName,
VerifyOutputAfterEvaluateAsyncCalledTwice,
VerifyOutputAfterImageBindCalledTwice,
SequenceLengthTensorFloat,
SequenceConstructTensorString};
if (SkipGpuTests()) {
api.GpuSqueezeNet = SkipTest;
api.GpuSqueezeNetEmptyOutputs = SkipTest;
api.GpuSqueezeNetUnboundOutputs = SkipTest;
}
if (RuntimeParameterExists(L"noVideoFrameTests")) {
api.ImageBindingDimensions = SkipTest;
api.BindInvalidInputName = SkipTest;
api.VerifyOutputAfterImageBindCalledTwice = SkipTest;
api.VerifyInvalidBindExceptions = SkipTest;
}
return api;
}