// 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 #include #include "winrt/Windows.Storage.h" #include using namespace winrt; using namespace winrt::Windows::AI::MachineLearning; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Graphics::Imaging; using namespace winrt::Windows::Media; using namespace winrt::Windows::Storage; static void LearningModelBindingAPITestsClassSetup() { init_apartment(); } static void LearningModelBindingAPITestsGpuMethodSetup() { GPUTEST; } 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(); WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::Int64); WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor); auto tensorDescriptor = mapDescriptor.ValueDescriptor().as(); // 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 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(); 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(); 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(); WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::String); WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor); auto tensorDescriptor = mapDescriptor.ValueDescriptor().as(); // 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 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(); 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( winrt::Windows::AI::MachineLearning::LearningModel model, OutputBindingStrategy bindingStrategy) { auto outputFeatures = model.OutputFeatures(); auto outputDescriptor = outputFeatures.First().Current(); WINML_EXPECT_TRUE(outputDescriptor.Kind() == LearningModelFeatureKind::Sequence); auto seqDescriptor = outputDescriptor.as(); auto mapDescriptor = seqDescriptor.ElementDescriptor().as(); WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::Int64); WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor); auto tensorDescriptor = mapDescriptor.ValueDescriptor().as(); WINML_EXPECT_TRUE(tensorDescriptor.TensorKind() == TensorKind::Float); LearningModelSession session(model); LearningModelBinding binding(session); std::vector inputs = { 0.5f, 0.25f, 0.125f }; std::vector shape = { 1, 3 }; // Bind inputs auto inputTensor = TensorFloat::CreateFromArray( shape, winrt::array_view(std::move(inputs))); binding.Bind(winrt::hstring(L"X"), inputTensor); typedef IMap ABIMap; typedef IVector ABISequeneceOfMap; ABISequeneceOfMap abiOutput = nullptr; // Bind outputs if (bindingStrategy == OutputBindingStrategy::Bound) { abiOutput = winrt::single_threaded_vector(); 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>().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(); 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().ElementDescriptor().as(); WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::String); WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor); auto tensorDescriptor = mapDescriptor.ValueDescriptor().as(); WINML_EXPECT_TRUE(tensorDescriptor.TensorKind() == TensorKind::Float); LearningModelSession session(learningModel); LearningModelBinding binding(session); std::vector inputs = { 0.5f, 0.25f, 0.125f }; std::vector shape = { 1, 3 }; std::vector labels = { L"cat", L"dog", L"lion" }; std::map mapData = { { L"cat", 0.0f }, { L"dog", 0.0f }, { L"lion", 0.0f } }; typedef IMap ABIMap; ABIMap abiMap = winrt::single_threaded_map(std::move(mapData)); std::vector seqOutput = { abiMap }; IVector ABIOutput = winrt::single_threaded_vector(std::move(seqOutput)); TensorFloat inputTensor = TensorFloat::CreateFromArray(shape, winrt::array_view(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>().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 inputs = { 0.5f, 0.25f, 0.125f }; std::vector 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 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 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 { 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 output WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", image), winrt::hresult_error, ensureWinmlInvalidBinding); // Bind invalid map as sequence output WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", abiMap), winrt::hresult_error, ensureWinmlInvalidBinding); // Bind invalid sequence as sequence output WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", abiSequence), winrt::hresult_error, ensureWinmlInvalidBinding); // Bind invalid tensor as sequence output WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", tensorBoolean), winrt::hresult_error, ensureWinmlInvalidBinding); /* Verify image bindings throw correct bind exceptions */ // WINML_EXPECT_NO_THROW(LoadModel(L"fns-candy.onnx")); // LearningModelSession imageSession(m_model); // LearningModelBinding imageBinding(imageSession); // auto inputName = m_model.InputFeatures().First().Current().Name(); // // Bind invalid map as image input // WINML_EXPECT_THROW_SPECIFIC(imageBinding.Bind(inputName, abiMap), winrt::hresult_error, ensureWinmlInvalidBinding); // // Bind invalid sequence as image input // WINML_EXPECT_THROW_SPECIFIC(imageBinding.Bind(inputName, abiSequence), winrt::hresult_error, ensureWinmlInvalidBinding); // // Bind invalid tensor type as image input // WINML_EXPECT_THROW_SPECIFIC(imageBinding.Bind(inputName, tensorBoolean), winrt::hresult_error, ensureWinmlInvalidBinding); // // Bind invalid tensor size as image input // auto tensorFloat = TensorFloat::Create(std::vector { 1, 1, 100, 100 }); // WINML_EXPECT_THROW_SPECIFIC(imageBinding.Bind(inputName, tensorFloat), winrt::hresult_error, ensureWinmlInvalidBinding); // // Bind invalid tensor shape as image input // WINML_EXPECT_THROW_SPECIFIC(imageBinding.Bind(inputName, tensorInvalidShape), 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{ 5 }; auto inputData1 = std::vector{ -50.f, -25.f, 0.f, 25.f, 50.f }; auto inputValue1 = TensorFloat::CreateFromIterable( inputShape, single_threaded_vector(std::move(inputData1)).GetView()); auto inputData2 = std::vector{ 50.f, 25.f, 0.f, -25.f, -50.f }; auto inputValue2 = TensorFloat::CreateFromIterable( inputShape, single_threaded_vector(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(); for (int i = 0; i < 3; i++){ std::vector shape = {5, 3*i + 1}; std::vector data(static_cast(std::accumulate(shape.begin(), shape.end(), static_cast(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().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 shape1 = {2, 3}; std::vector data1(static_cast(std::accumulate(shape1.begin(), shape1.end(), static_cast(1), std::multiplies()))); auto input1 = TensorInt64Bit::CreateFromShapeArrayAndDataArray(shape1, data1); std::vector shape2 = {2, 3}; std::vector data2(static_cast(std::accumulate(shape2.begin(), shape2.end(), static_cast(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>(); WINML_EXPECT_EQUAL(static_cast(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 (); 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(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 constexpr LearningModelBindingAPITestsApi api = { LearningModelBindingAPITestsClassSetup, LearningModelBindingAPITestsGpuMethodSetup, CpuSqueezeNet, CpuSqueezeNetEmptyOutputs, CpuSqueezeNetUnboundOutputs, CpuSqueezeNetBindInputTensorAsInspectable, CastMapInt64, DictionaryVectorizerMapInt64, DictionaryVectorizerMapString, ZipMapInt64, ZipMapInt64Unbound, ZipMapString, GpuSqueezeNet, GpuSqueezeNetEmptyOutputs, GpuSqueezeNetUnboundOutputs, ImageBindingDimensions, VerifyInvalidBindExceptions, BindInvalidInputName, VerifyOutputAfterEvaluateAsyncCalledTwice, VerifyOutputAfterImageBindCalledTwice, SequenceLengthTensorFloat, SequenceConstructTensorString }; return api; }