onnxruntime/winml/test/api/LearningModelBindingAPITest.cpp
raoanag 424107a82a
Merge main to WindowsAI (#18122)
### Description
Merge main to WindowsAI



### 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. -->

---------

Signed-off-by: Nash <george.nash@intel.com>
Signed-off-by: Yiming Hu <yiming.hu@amd.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Co-authored-by: Kaz Nishimura <kazssym@linuxfront.com>
Co-authored-by: Tianlei Wu <tlwu@microsoft.com>
Co-authored-by: Nat Kershaw (MSFT) <nakersha@microsoft.com>
Co-authored-by: Yulong Wang <7679871+fs-eire@users.noreply.github.com>
Co-authored-by: Changming Sun <chasun@microsoft.com>
Co-authored-by: zesongw <zesong.wang@intel.com>
Co-authored-by: Yi Zhang <zhanyi@microsoft.com>
Co-authored-by: Dmitri Smirnov <yuslepukhin@users.noreply.github.com>
Co-authored-by: Yifan Li <109183385+yf711@users.noreply.github.com>
Co-authored-by: simonjub <78098752+simonjub@users.noreply.github.com>
Co-authored-by: PeixuanZuo <94887879+PeixuanZuo@users.noreply.github.com>
Co-authored-by: Adrian Lizarraga <adlizarraga@microsoft.com>
Co-authored-by: Edward Chen <18449977+edgchen1@users.noreply.github.com>
Co-authored-by: Arthur Islamov <arthur@islamov.ai>
Co-authored-by: Jambay Kinley <jambaykinley@microsoft.com>
Co-authored-by: Justin Chu <justinchuby@users.noreply.github.com>
Co-authored-by: Wei-Sheng Chin <wschin@outlook.com>
Co-authored-by: Bowen Bao <bowbao@microsoft.com>
Co-authored-by: Hariharan Seshadri <shariharan91@gmail.com>
Co-authored-by: Numfor Tiapo <numsmt2@gmail.com>
Co-authored-by: Vincent Wang <wangwchpku@outlook.com>
Co-authored-by: Pranav Sharma <prs@microsoft.com>
Co-authored-by: George Nash <george.nash@intel.com>
Co-authored-by: Abhishek Jindal <abjindal@microsoft.com>
Co-authored-by: pengwa <pengwa@microsoft.com>
Co-authored-by: Yiming Hu <woinck@users.noreply.github.com>
Co-authored-by: Jiajia Qin <jiajia.qin@intel.com>
Co-authored-by: Lukas Berbuer <36054362+lukasberbuer@users.noreply.github.com>
Co-authored-by: Wanming Lin <wanming.lin@intel.com>
Co-authored-by: Xavier Dupré <xadupre@users.noreply.github.com>
Co-authored-by: aimilefth <60664743+aimilefth@users.noreply.github.com>
Co-authored-by: Baiju Meswani <bmeswani@microsoft.com>
Co-authored-by: Adam Pocock <adam.pocock@oracle.com>
Co-authored-by: Chi Lo <54722500+chilo-ms@users.noreply.github.com>
Co-authored-by: RandySheriffH <48490400+RandySheriffH@users.noreply.github.com>
Co-authored-by: Randy Shuai <rashuai@microsoft.com>
Co-authored-by: Vadym Stupakov <vadim.stupakov@gmail.com>
Co-authored-by: Jian Chen <cjian@microsoft.com>
Co-authored-by: Brian Lambert <98757707+brian-pieces@users.noreply.github.com>
Co-authored-by: Nicolò Lucchesi <nicolo.lucchesi@gmail.com>
Co-authored-by: liqun Fu <liqfu@microsoft.com>
Co-authored-by: trajep <trajepl@gmail.com>
Co-authored-by: Scott McKay <skottmckay@gmail.com>
Co-authored-by: Mustafa Ateş Uzun <mustafauzun0@gmail.com>
Co-authored-by: MistEO <mistereo@hotmail.com>
Co-authored-by: satyajandhyala <satya.k.jandhyala@gmail.com>
Co-authored-by: shaahji <96227573+shaahji@users.noreply.github.com>
Co-authored-by: Rachel Guo <35738743+YUNQIUGUO@users.noreply.github.com>
Co-authored-by: rachguo <rachguo@rachguos-Mini.attlocal.net>
Co-authored-by: Caroline Zhu <wolfivyaura@gmail.com>
Co-authored-by: Caroline Zhu <carolinezhu@microsoft.com>
Co-authored-by: Guenther Schmuelling <guschmue@microsoft.com>
Co-authored-by: xhcao <xinghua.cao@intel.com>
Co-authored-by: Ella Charlaix <80481427+echarlaix@users.noreply.github.com>
Co-authored-by: Xu Xing <xing.xu@intel.com>
Co-authored-by: Hector Li <hecli@microsoft.com>
Co-authored-by: Ye Wang <52801275+wangyems@users.noreply.github.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: Benedikt Hilmes <benedikt.hilmes@rwth-aachen.de>
Co-authored-by: rachguo <rachguo@rachguos-Mac-mini.local>
Co-authored-by: George Wu <jywu@microsoft.com>
Co-authored-by: JiCheng <wejoncy@163.com>
Co-authored-by: Sheil Kumar <smk2007@gmail.com>
Co-authored-by: Sheil Kumar <sheilk@microsoft.com>
Co-authored-by: cloudhan <guangyunhan@microsoft.com>
Co-authored-by: kyoshisuki <143475866+kyoshisuki@users.noreply.github.com>
Co-authored-by: aciddelgado <139922440+aciddelgado@users.noreply.github.com>
Co-authored-by: tlwu@microsoft.com <tlwu@a100.crj0ad2y1kku1j4yxl4sj10o4e.gx.internal.cloudapp.net>
Co-authored-by: Maximilian Müller <44298237+gedoensmax@users.noreply.github.com>
Co-authored-by: Tang, Cheng <souptc@gmail.com>
Co-authored-by: Cheng Tang <chenta@microsoft.com@orttrainingdev9.d32nl1ml4oruzj4qz3bqlggovf.px.internal.cloudapp.net>
Co-authored-by: Cheng Tang <chenta@microsoft.com>
Co-authored-by: Jeff Daily <jeff.daily@amd.com>
Co-authored-by: cloudhan <cloudhan@outlook.com>
Co-authored-by: Yufeng Li <liyufeng1987@gmail.com>
Co-authored-by: Zhang Lei <zhang.huanning@hotmail.com>
Co-authored-by: Dwayne Robinson <fdwr@hotmail.com>
Co-authored-by: Zhipeng Han <zhipeng.han@outlook.com>
Co-authored-by: Thiago Crepaldi <thiago.crepaldi@microsoft.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Patrice Vignola <vignola.patrice@gmail.com>
Co-authored-by: kunal-vaishnavi <115581922+kunal-vaishnavi@users.noreply.github.com>
Co-authored-by: snadampal <87143774+snadampal@users.noreply.github.com>
Co-authored-by: Sumit Agarwal <sumitagarwal330@gmail.com>
Co-authored-by: Ashwini Khade <askhade@microsoft.com>
Co-authored-by: Yang Gu <yang.gu@intel.com>
Co-authored-by: Cheng Tang <chenta@a100.crj0ad2y1kku1j4yxl4sj10o4e.gx.internal.cloudapp.net>
Co-authored-by: mindest <30493312+mindest@users.noreply.github.com>
Co-authored-by: Scott McKay <Scott.McKay@microsoft.com>
Co-authored-by: Xavier Dupre <xadupre@microsoft.com@orttrainingdev9.d32nl1ml4oruzj4qz3bqlggovf.px.internal.cloudapp.net>
Co-authored-by: guyang3532 <62738430+guyang3532@users.noreply.github.com>
Co-authored-by: Carson M <carson@pyke.io>
Co-authored-by: sophies927 <107952697+sophies927@users.noreply.github.com>
2023-10-27 17:08:01 -07: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;
}