mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-29 20:14:01 +00:00
Add scenario tests (#2457)
* Add scenario tests * Remove TODO from model license * Add winml_api test dependency
This commit is contained in:
parent
34b1bf2db7
commit
f40d5d80a0
12 changed files with 2863 additions and 0 deletions
|
|
@ -62,9 +62,22 @@ add_winml_test(
|
|||
TARGET winml_test_api
|
||||
SOURCES ${winml_test_api_src}
|
||||
LIBS winml_test_common
|
||||
DEPENDS winml_api
|
||||
)
|
||||
target_precompiled_header(winml_test_api testPch.h)
|
||||
|
||||
file(GLOB winml_test_scenario_src CONFIGURE_DEPENDS "${WINML_TEST_SRC_DIR}/scenario/cppwinrt/*.cpp")
|
||||
add_winml_test(
|
||||
TARGET winml_test_scenario
|
||||
SOURCES ${winml_test_scenario_src}
|
||||
LIBS winml_test_common onnxruntime_providers_dml
|
||||
DEPENDS winml_api
|
||||
)
|
||||
target_precompiled_header(winml_test_scenario testPch.h)
|
||||
set_target_properties(winml_test_scenario PROPERTIES LINK_FLAGS
|
||||
"/DELAYLOAD:d2d1.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:dxgi.dll"
|
||||
)
|
||||
|
||||
# During build time, copy any modified collaterals.
|
||||
# configure_file(source destination COPYONLY), which configures CMake to copy the file whenever source is modified,
|
||||
# can't be used here because we don't know the destination during configure time (in multi-configuration generators,
|
||||
|
|
@ -88,3 +101,4 @@ add_winml_collateral("${WINML_TEST_SRC_DIR}/collateral/images/*.png")
|
|||
add_winml_collateral("${WINML_TEST_SRC_DIR}/collateral/models/*.onnx")
|
||||
add_winml_collateral("${WINML_TEST_SRC_DIR}/common/testdata/squeezenet/*")
|
||||
add_winml_collateral("${WINML_TEST_SRC_DIR}/scenario/cppwinrt/*.onnx")
|
||||
add_winml_collateral("${WINML_TEST_SRC_DIR}/scenario/models/*.onnx")
|
||||
|
|
|
|||
102
winml/test/scenario/cppwinrt/CustomNullOp.h
Normal file
102
winml/test/scenario/cppwinrt/CustomNullOp.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
//
|
||||
// Implements a custom operator kernel which counts the number of calls to Compute(), but otherwise is a no-op.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
template <typename T>
|
||||
struct NullShapeInferrer : winrt::implements<NullShapeInferrer<T>, IMLOperatorShapeInferrer>
|
||||
{
|
||||
STDMETHOD(InferOutputShapes)(IMLOperatorShapeInferenceContext* context) noexcept
|
||||
{
|
||||
EXPECT_NO_THROW(OperatorHelper::ShapeInferenceFunction<T>(context));
|
||||
return S_OK;
|
||||
}
|
||||
};
|
||||
|
||||
struct NullOperator : winrt::implements<NullOperator, IMLOperatorKernel>
|
||||
{
|
||||
NullOperator(std::atomic<uint32_t>* callCount) : m_callCount(callCount) {}
|
||||
|
||||
STDMETHOD(Compute)(IMLOperatorKernelContext* context)
|
||||
{
|
||||
winrt::com_ptr<IMLOperatorTensor> outputTensor;
|
||||
EXPECT_HRESULT_SUCCEEDED(context->GetOutputTensor(0, outputTensor.put()));
|
||||
|
||||
++(*m_callCount);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<uint32_t>* m_callCount;
|
||||
};
|
||||
|
||||
struct NullOperatorFactory : winrt::implements<NullOperatorFactory, IMLOperatorKernelFactory>
|
||||
{
|
||||
NullOperatorFactory(std::atomic<uint32_t>* callCount) : m_callCount(callCount) {}
|
||||
|
||||
STDMETHOD(CreateKernel)(
|
||||
IMLOperatorKernelCreationContext* context,
|
||||
IMLOperatorKernel** kernel)
|
||||
{
|
||||
auto op = winrt::make<NullOperator>(m_callCount);
|
||||
op.copy_to(kernel);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static MLOperatorEdgeDescription CreateEdgeDescriptor(MLOperatorEdgeType type, MLOperatorTensorDataType dataType)
|
||||
{
|
||||
MLOperatorEdgeDescription desc;
|
||||
desc.edgeType = MLOperatorEdgeType::Tensor;
|
||||
desc.tensorDataType = dataType;
|
||||
return desc;
|
||||
}
|
||||
|
||||
static void RegisterKernel(
|
||||
const char* name,
|
||||
const char* domain,
|
||||
int versionSince,
|
||||
winrt::com_ptr<IMLOperatorRegistry> registry,
|
||||
winrt::com_ptr<IMLOperatorShapeInferrer> shapeInferrer,
|
||||
std::atomic<uint32_t>* callCount)
|
||||
{
|
||||
MLOperatorKernelDescription kernelDescription;
|
||||
kernelDescription.domain = domain;
|
||||
kernelDescription.name = name;
|
||||
kernelDescription.minimumOperatorSetVersion = versionSince;
|
||||
kernelDescription.executionType = MLOperatorExecutionType::D3D12;
|
||||
|
||||
MLOperatorEdgeTypeConstrant typeConstraint;
|
||||
typeConstraint.typeLabel = "T";
|
||||
std::vector<MLOperatorEdgeDescription> allowedEdges
|
||||
{
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Double),
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Float),
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Float16)
|
||||
};
|
||||
typeConstraint.allowedTypes = allowedEdges.data();
|
||||
typeConstraint.allowedTypeCount = static_cast<uint32_t>(allowedEdges.size());
|
||||
|
||||
std::vector<MLOperatorEdgeTypeConstrant> typeConstraints{ typeConstraint };
|
||||
kernelDescription.typeConstraints = typeConstraints.data();
|
||||
kernelDescription.typeConstraintCount = static_cast<uint32_t>(typeConstraints.size());
|
||||
|
||||
kernelDescription.defaultAttributes = nullptr;
|
||||
kernelDescription.defaultAttributeCount = 0;
|
||||
kernelDescription.options = MLOperatorKernelOptions::None;
|
||||
kernelDescription.executionOptions = 0;
|
||||
|
||||
auto factory = winrt::make<NullOperatorFactory>(callCount);
|
||||
|
||||
EXPECT_HRESULT_SUCCEEDED(registry->RegisterOperatorKernel(
|
||||
&kernelDescription,
|
||||
factory.get(),
|
||||
shapeInferrer.get()
|
||||
));
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<uint32_t>* m_callCount;
|
||||
};
|
||||
57
winml/test/scenario/cppwinrt/CustomOperatorProvider.h
Normal file
57
winml/test/scenario/cppwinrt/CustomOperatorProvider.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include "NoisyReluCpu.h"
|
||||
#include "ReluCpu.h"
|
||||
|
||||
struct CustomOperatorProvider :
|
||||
winrt::implements<
|
||||
CustomOperatorProvider,
|
||||
winrt::Windows::AI::MachineLearning::ILearningModelOperatorProvider,
|
||||
ILearningModelOperatorProviderNative>
|
||||
{
|
||||
HMODULE m_library;
|
||||
winrt::com_ptr<IMLOperatorRegistry> m_registry;
|
||||
|
||||
CustomOperatorProvider()
|
||||
{
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
m_library = LoadLibraryW(L"windows.ai.machinelearning.dll");
|
||||
#elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PC_APP)
|
||||
m_library = LoadPackagedLibrary(L"windows.ai.machinelearning.dll", 0 /*Reserved*/);
|
||||
#endif
|
||||
using create_registry_delegate = HRESULT WINAPI (_COM_Outptr_ IMLOperatorRegistry** registry);
|
||||
auto create_registry = reinterpret_cast<create_registry_delegate*>(GetProcAddress(m_library, "MLCreateOperatorRegistry"));
|
||||
if (FAILED(create_registry(m_registry.put())))
|
||||
{
|
||||
__fastfail(0);
|
||||
}
|
||||
|
||||
RegisterSchemas();
|
||||
RegisterKernels();
|
||||
}
|
||||
|
||||
~CustomOperatorProvider()
|
||||
{
|
||||
FreeLibrary(m_library);
|
||||
}
|
||||
|
||||
void RegisterSchemas()
|
||||
{
|
||||
NoisyReluOperatorFactory::RegisterNoisyReluSchema(m_registry);
|
||||
}
|
||||
|
||||
void RegisterKernels()
|
||||
{
|
||||
// Replace the Relu operator kernel
|
||||
ReluOperatorFactory::RegisterReluKernel(m_registry);
|
||||
|
||||
// Add a new operator kernel for Relu
|
||||
NoisyReluOperatorFactory::RegisterNoisyReluKernel(m_registry);
|
||||
}
|
||||
|
||||
STDMETHOD(GetRegistry)(IMLOperatorRegistry** ppOperatorRegistry)
|
||||
{
|
||||
m_registry.copy_to(ppOperatorRegistry);
|
||||
return S_OK;
|
||||
}
|
||||
};
|
||||
727
winml/test/scenario/cppwinrt/CustomOps.cpp
Normal file
727
winml/test/scenario/cppwinrt/CustomOps.cpp
Normal file
|
|
@ -0,0 +1,727 @@
|
|||
#include "testPch.h"
|
||||
#include <wil/result.h>
|
||||
#include <D3d11_4.h>
|
||||
#include <dxgi1_6.h>
|
||||
#include "filehelpers.h"
|
||||
#include <fstream>
|
||||
#include "SqueezeNetValidator.h"
|
||||
#include <winrt/Windows.Graphics.Imaging.h>
|
||||
#include <winrt/Windows.Media.h>
|
||||
#include "winrt/Windows.Storage.h"
|
||||
#include <winrt/Windows.Storage.Streams.h>
|
||||
#include <MemoryBuffer.h>
|
||||
#include <gsl/gsl>
|
||||
// #include "dml/api/DirectML.h"
|
||||
#include "CustomOperatorProvider.h"
|
||||
|
||||
// For custom operator and shape inferencing support
|
||||
#include "core/providers/dml/DmlExecutionProvider/inc/MLOperatorAuthor.h"
|
||||
#include "core/providers/dml/OperatorAuthorHelper/MLOperatorAuthorHelper.h"
|
||||
#include "core/providers/dml/OperatorAuthorHelper/OperatorHelper.h"
|
||||
#include "core/providers/dml/OperatorAuthorHelper/OperatorRegistration.h"
|
||||
#include "core/graph/constants.h"
|
||||
#include "CustomNullOp.h"
|
||||
#include <wil/wrl.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::AI::MachineLearning;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Windows::Media;
|
||||
using namespace winrt::Windows::Graphics::Imaging;
|
||||
using namespace winrt::Windows::Storage;
|
||||
using namespace winrt::Windows::Storage::Streams;
|
||||
|
||||
class CustomOpsScenarioTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
CustomOpsScenarioTest() {
|
||||
init_apartment();
|
||||
}
|
||||
};
|
||||
|
||||
class CustomOpsScenarioGpuTest : public CustomOpsScenarioTest
|
||||
{
|
||||
};
|
||||
|
||||
// Tests that the execution provider correctly fuses operators together when custom ops are involved.
|
||||
TEST_F(CustomOpsScenarioGpuTest, CustomOperatorFusion)
|
||||
{
|
||||
constexpr const wchar_t* c_modelFilename = L"squeezenet_tensor_input.onnx";
|
||||
|
||||
// This particular model has 25 Conv ops and 25 Relu ops, all of which are eligible for fusion so we expect them
|
||||
// all to be fused (removing them from the graph) and replaced with the appropriate fused op instead. The same
|
||||
// goes for the single Gemm+Sigmoid in the model too.
|
||||
constexpr const uint32_t c_expectedConvOps = 0;
|
||||
constexpr const uint32_t c_expectedReluOps = 0;
|
||||
constexpr const uint32_t c_expectedFusedConvOps = 25;
|
||||
constexpr const uint32_t c_expectedGemmOps = 0;
|
||||
constexpr const uint32_t c_expectedSigmoidOps = 0;
|
||||
constexpr const uint32_t c_expectedFusedGemmOps = 1;
|
||||
|
||||
// These ops are also part of the model but shouldn't be fused
|
||||
constexpr const uint32_t c_expectedBatchNormOps = 1;
|
||||
constexpr const uint32_t c_expectedMaxPoolOps = 3;
|
||||
constexpr const uint32_t c_expectedConcatOps = 8;
|
||||
|
||||
struct CallbackOperatorProvider :
|
||||
winrt::implements<
|
||||
CallbackOperatorProvider,
|
||||
winrt::Windows::AI::MachineLearning::ILearningModelOperatorProvider,
|
||||
ILearningModelOperatorProviderNative>
|
||||
{
|
||||
struct CallCounts
|
||||
{
|
||||
std::atomic<uint32_t> conv = 0;
|
||||
std::atomic<uint32_t> relu = 0;
|
||||
std::atomic<uint32_t> fusedConv = 0;
|
||||
std::atomic<uint32_t> gemm = 0;
|
||||
std::atomic<uint32_t> sigmoid = 0;
|
||||
std::atomic<uint32_t> fusedGemm = 0;
|
||||
std::atomic<uint32_t> batchNorm = 0;
|
||||
std::atomic<uint32_t> maxPool = 0;
|
||||
std::atomic<uint32_t> concat = 0;
|
||||
};
|
||||
|
||||
const CallCounts& GetCallCounts()
|
||||
{
|
||||
return m_callCounts;
|
||||
}
|
||||
|
||||
CallbackOperatorProvider()
|
||||
{
|
||||
using namespace OperatorHelper;
|
||||
|
||||
EXPECT_HRESULT_SUCCEEDED(MLCreateOperatorRegistry(m_registry.put()));
|
||||
|
||||
#pragma push_macro("REGISTER_KERNEL")
|
||||
#define REGISTER_KERNEL(_name, _domain, _opSet, _shapeInferrer, _callCount) \
|
||||
NullOperatorFactory::RegisterKernel( \
|
||||
#_name, \
|
||||
(_domain), \
|
||||
_opSet::sc_sinceVer_ ## _name, \
|
||||
m_registry, \
|
||||
winrt::make<NullShapeInferrer<_shapeInferrer>>(), \
|
||||
(_callCount));
|
||||
|
||||
REGISTER_KERNEL(Conv, onnxruntime::kOnnxDomain, OnnxOperatorSet7, ConvHelper, &m_callCounts.conv);
|
||||
REGISTER_KERNEL(Relu, onnxruntime::kOnnxDomain, OnnxOperatorSet7, GetOutputShapeAsInputShapeHelper, &m_callCounts.relu);
|
||||
REGISTER_KERNEL(FusedConv, onnxruntime::kMSDmlDomain, MsftOperatorSet1, ConvHelper, &m_callCounts.fusedConv);
|
||||
|
||||
REGISTER_KERNEL(Gemm, onnxruntime::kOnnxDomain, OnnxOperatorSet7, GemmHelper, &m_callCounts.gemm);
|
||||
REGISTER_KERNEL(Sigmoid, onnxruntime::kOnnxDomain, OnnxOperatorSet7, GetOutputShapeAsInputShapeHelper, &m_callCounts.sigmoid);
|
||||
REGISTER_KERNEL(FusedGemm, onnxruntime::kMSDmlDomain, MsftOperatorSet1, GemmHelper, &m_callCounts.fusedGemm);
|
||||
|
||||
REGISTER_KERNEL(BatchNormalization, onnxruntime::kOnnxDomain, OnnxOperatorSet7, GetOutputShapeAsInputShapeHelper, &m_callCounts.batchNorm);
|
||||
REGISTER_KERNEL(MaxPool, onnxruntime::kOnnxDomain, OnnxOperatorSet7, PoolingHelper, &m_callCounts.maxPool);
|
||||
REGISTER_KERNEL(Concat, onnxruntime::kOnnxDomain, OnnxOperatorSet7, ConcatHelper, &m_callCounts.concat);
|
||||
|
||||
#pragma pop_macro("REGISTER_KERNEL")
|
||||
}
|
||||
|
||||
STDMETHOD(GetRegistry)(IMLOperatorRegistry** ppOperatorRegistry)
|
||||
{
|
||||
if (ppOperatorRegistry == nullptr)
|
||||
{
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
m_registry.copy_to(ppOperatorRegistry);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
winrt::com_ptr<IMLOperatorRegistry> m_registry;
|
||||
CallCounts m_callCounts;
|
||||
};
|
||||
|
||||
auto customOperatorProvider = winrt::make<CallbackOperatorProvider>();
|
||||
auto provider = customOperatorProvider.as<ILearningModelOperatorProvider>();
|
||||
|
||||
LearningModelDevice device = nullptr;
|
||||
EXPECT_NO_THROW(device = LearningModelDevice(LearningModelDeviceKind::DirectX));
|
||||
std::wstring fullPath = FileHelpers::GetModulePath() + c_modelFilename;
|
||||
auto model = LearningModel::LoadFromFilePath(fullPath, provider);
|
||||
|
||||
auto featureValue = FileHelpers::LoadImageFeatureValue(L"227x227.png");
|
||||
|
||||
LearningModelSession session = nullptr;
|
||||
EXPECT_NO_THROW(session = LearningModelSession(model, device));
|
||||
LearningModelBinding modelBinding(session);
|
||||
|
||||
modelBinding.Bind(L"data", featureValue);
|
||||
auto result = session.Evaluate(modelBinding, L"");
|
||||
|
||||
const auto& callCounts = customOperatorProvider.as<CallbackOperatorProvider>()->GetCallCounts();
|
||||
|
||||
// Verify that the correct number of each operator was seen (i.e. that none were dropped / incorrectly fused)
|
||||
EXPECT_EQ(c_expectedConvOps, callCounts.conv);
|
||||
EXPECT_EQ(c_expectedReluOps, callCounts.relu);
|
||||
EXPECT_EQ(c_expectedFusedConvOps, callCounts.fusedConv);
|
||||
EXPECT_EQ(c_expectedGemmOps, callCounts.gemm);
|
||||
EXPECT_EQ(c_expectedSigmoidOps, callCounts.sigmoid);
|
||||
EXPECT_EQ(c_expectedFusedGemmOps, callCounts.fusedGemm);
|
||||
EXPECT_EQ(c_expectedBatchNormOps, callCounts.batchNorm);
|
||||
EXPECT_EQ(c_expectedMaxPoolOps, callCounts.maxPool);
|
||||
EXPECT_EQ(c_expectedConcatOps, callCounts.concat);
|
||||
}
|
||||
|
||||
struct LocalCustomOperatorProvider :
|
||||
winrt::implements<
|
||||
LocalCustomOperatorProvider,
|
||||
winrt::Windows::AI::MachineLearning::ILearningModelOperatorProvider,
|
||||
ILearningModelOperatorProviderNative>
|
||||
{
|
||||
LocalCustomOperatorProvider()
|
||||
{
|
||||
using namespace OperatorHelper;
|
||||
|
||||
EXPECT_HRESULT_SUCCEEDED(MLCreateOperatorRegistry(m_registry.put()));
|
||||
}
|
||||
|
||||
STDMETHOD(GetRegistry)(IMLOperatorRegistry** ppOperatorRegistry)
|
||||
{
|
||||
if (ppOperatorRegistry == nullptr)
|
||||
{
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
m_registry.copy_to(ppOperatorRegistry);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IMLOperatorRegistry* GetRegistry()
|
||||
{
|
||||
return m_registry.get();
|
||||
}
|
||||
|
||||
protected:
|
||||
winrt::com_ptr<IMLOperatorRegistry> m_registry;
|
||||
};
|
||||
|
||||
// Checks test attributes set on ABI kernels can be queried with correct values
|
||||
void VerifyTestAttributes(const MLOperatorAttributes& attrs)
|
||||
{
|
||||
std::string strAttr = attrs.GetAttribute("DefaultedNonRequiredString");
|
||||
EXPECT_EQ(strAttr, "1");
|
||||
|
||||
std::vector<std::string> strArrayAttr = attrs.GetAttributeVector("DefaultedNonRequiredStringArray");
|
||||
std::vector<std::string> expected = std::vector<std::string>({ "1", "2" });
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ(strArrayAttr[i], expected[i]);
|
||||
}
|
||||
|
||||
EXPECT_EQ(1, attrs.GetAttribute<int64_t>("DefaultedNonRequiredInt"));
|
||||
EXPECT_EQ(1.0f, attrs.GetAttribute<float>("DefaultedNonRequiredFloat"));
|
||||
|
||||
EXPECT_EQ(std::vector<int64_t>({ 1, 2 }), attrs.GetAttributeVector<int64_t>("DefaultedNonRequiredIntArray"));
|
||||
EXPECT_EQ(std::vector<float>({ 1.0f, 2.0f }), attrs.GetAttributeVector<float>("DefaultedNonRequiredFloatArray"));
|
||||
}
|
||||
|
||||
// Foo kernel which is doing Add and optionally truncates its output
|
||||
template <typename T, bool VerifyAttributes = false, bool Truncate = false>
|
||||
class FooKernel
|
||||
{
|
||||
public:
|
||||
FooKernel(const MLOperatorKernelCreationContext& info)
|
||||
{
|
||||
if (VerifyAttributes)
|
||||
{
|
||||
VerifyTestAttributes(info);
|
||||
}
|
||||
|
||||
VerifyShapeInfo(info);
|
||||
}
|
||||
|
||||
void VerifyShapeInfo(const MLOperatorKernelCreationContext& info)
|
||||
{
|
||||
if (!Truncate)
|
||||
{
|
||||
com_ptr<IMLOperatorTensorShapeDescription> shapeInfo;
|
||||
EXPECT_EQ(info.GetInterface()->HasTensorShapeDescription(), false);
|
||||
EXPECT_HRESULT_FAILED(info.GetInterface()->GetTensorShapeDescription(shapeInfo.put()));
|
||||
}
|
||||
else
|
||||
{
|
||||
com_ptr<IMLOperatorTensorShapeDescription> shapeInfo;
|
||||
EXPECT_EQ(info.GetInterface()->HasTensorShapeDescription(), true);
|
||||
EXPECT_EQ(info.GetInterface()->GetTensorShapeDescription(shapeInfo.put()), S_OK);
|
||||
}
|
||||
}
|
||||
|
||||
void Compute(const MLOperatorKernelContext& context) const
|
||||
{
|
||||
const auto X = context.GetInputTensor(0);
|
||||
const auto W = context.GetInputTensor(1);
|
||||
|
||||
auto xData = X.GetData<T>();
|
||||
auto wData = W.GetData<T>();
|
||||
|
||||
auto shape = X.GetShape();
|
||||
|
||||
// This is used to test shape inference
|
||||
if (Truncate)
|
||||
{
|
||||
shape[0] -= 1;
|
||||
}
|
||||
|
||||
if (!Truncate)
|
||||
{
|
||||
com_ptr<IMLOperatorTensor> tensor;
|
||||
EXPECT_HRESULT_FAILED(context.GetInterface()->GetOutputTensor(0, tensor.put()));
|
||||
}
|
||||
else
|
||||
{
|
||||
MLOperatorTensor tensor = context.GetOutputTensor(0);
|
||||
}
|
||||
|
||||
auto Y = context.GetOutputTensor(0, shape);
|
||||
auto yData = Y.GetData<T>();
|
||||
|
||||
size_t size = 1;
|
||||
for (size_t i = 0; i < shape.size(); i++)
|
||||
{
|
||||
size *= shape[i];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; i++)
|
||||
{
|
||||
yData[i] = xData[i] + wData[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <bool VerifyTestAttributes = false>
|
||||
void CreateABIFooKernel(IMLOperatorKernelCreationContext* kernelInfo, IMLOperatorKernel** opKernel)
|
||||
{
|
||||
HRESULT hr = MLOperatorKernel<FooKernel<float, VerifyTestAttributes>>::CreateInstance(*kernelInfo, opKernel);
|
||||
THROW_IF_FAILED(hr);
|
||||
}
|
||||
|
||||
void CreateTruncatedABIFooKernel(IMLOperatorKernelCreationContext* kernelInfo, IMLOperatorKernel** opKernel)
|
||||
{
|
||||
HRESULT hr = MLOperatorKernel<FooKernel<float, true, true>>::CreateInstance(*kernelInfo, opKernel);
|
||||
THROW_IF_FAILED(hr);
|
||||
}
|
||||
|
||||
// Test using a foo kernel which is doing Add, but register it as "Mul".
|
||||
TEST_F(CustomOpsScenarioTest, CustomKernelWithBuiltInSchema)
|
||||
{
|
||||
// Create the registry
|
||||
auto operatorProvider = winrt::make<LocalCustomOperatorProvider>();
|
||||
IMLOperatorRegistry* registry = operatorProvider.as<LocalCustomOperatorProvider>()->GetRegistry();
|
||||
|
||||
// Register the kernel
|
||||
MLOperatorEdgeDescription floatTensorType =
|
||||
{
|
||||
MLOperatorEdgeType::Tensor,
|
||||
static_cast<uint64_t>(MLOperatorTensorDataType::Float)
|
||||
};
|
||||
|
||||
MLOperatorEdgeTypeConstrant constraint = { "T", &floatTensorType, 1 };
|
||||
|
||||
MLOperatorKernelDescription kernelDesc =
|
||||
{
|
||||
"",
|
||||
"Mul",
|
||||
7,
|
||||
MLOperatorExecutionType::Cpu,
|
||||
&constraint,
|
||||
1,
|
||||
nullptr,
|
||||
0,
|
||||
MLOperatorKernelOptions::AllowDynamicInputShapes
|
||||
};
|
||||
|
||||
Microsoft::WRL::ComPtr<MLOperatorKernelFactory> factory = wil::MakeOrThrow<MLOperatorKernelFactory>(CreateABIFooKernel<false>);
|
||||
EXPECT_HRESULT_SUCCEEDED(registry->RegisterOperatorKernel(&kernelDesc, factory.Get(), nullptr));
|
||||
|
||||
// Prepare inputs
|
||||
std::vector<int64_t> dimsX = { 3, 2 };
|
||||
std::vector<float> valuesX = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f };
|
||||
|
||||
// Prepare expected inputs and outputs
|
||||
std::vector<int64_t> expectedDimsY = { 3, 2 };
|
||||
|
||||
// The expected value should be Add's result.
|
||||
std::vector<float> expectedValuesY = { 2.0f, 4.0f, 6.0f, 8.0f, 10.0f, 12.0f };
|
||||
|
||||
// Create the model and sessions
|
||||
std::wstring fullPath = FileHelpers::GetModulePath() + L"mul.onnx";
|
||||
LearningModel model = LearningModel::LoadFromFilePath(fullPath, operatorProvider);
|
||||
|
||||
LearningModelSession session(model);
|
||||
LearningModelBinding bindings(session);
|
||||
|
||||
// Bind inputs and outputs
|
||||
TensorFloat inputTensor = TensorFloat::CreateFromArray(dimsX, winrt::array_view<const float>(std::move(valuesX)));
|
||||
bindings.Bind(winrt::hstring(L"X"), inputTensor);
|
||||
|
||||
auto outputValue = TensorFloat::Create();
|
||||
EXPECT_NO_THROW(bindings.Bind(L"Y", outputValue));
|
||||
|
||||
// Evaluate the model
|
||||
hstring correlationId;
|
||||
EXPECT_NO_THROW(session.Evaluate(bindings, correlationId));
|
||||
|
||||
// Check the result shape
|
||||
EXPECT_EQ(expectedDimsY.size(), outputValue.Shape().Size());
|
||||
for (uint32_t j = 0; j < outputValue.Shape().Size(); j++)
|
||||
{
|
||||
EXPECT_EQ(expectedDimsY.at(j), outputValue.Shape().GetAt(j));
|
||||
}
|
||||
|
||||
// Check the results
|
||||
auto buffer = outputValue.GetAsVectorView();
|
||||
EXPECT_TRUE(buffer != nullptr);
|
||||
EXPECT_TRUE(std::equal(expectedValuesY.cbegin(), expectedValuesY.cend(), begin(buffer)));
|
||||
|
||||
// Release the model before operatorProvider goes out of scope
|
||||
model = nullptr;
|
||||
}
|
||||
|
||||
// Similar to MLOperatorShapeInferrer, but using an std::function
|
||||
class MLOperatorShapeInferrerFromFunc : public Microsoft::WRL::RuntimeClass<
|
||||
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IMLOperatorShapeInferrer>
|
||||
{
|
||||
public:
|
||||
MLOperatorShapeInferrerFromFunc(std::function<void(IMLOperatorShapeInferenceContext*)> shapeInferenceFn) :
|
||||
m_func(shapeInferenceFn)
|
||||
{}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE InferOutputShapes(IMLOperatorShapeInferenceContext* context) noexcept override try
|
||||
{
|
||||
m_func(context);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
private:
|
||||
std::function<void(IMLOperatorShapeInferenceContext*)> m_func;
|
||||
};
|
||||
|
||||
// Test using a custom kernel and schema, while verifying attribute defaults, type mapping, and inference methods
|
||||
TEST_F(CustomOpsScenarioTest, CustomKernelWithCustomSchema)
|
||||
{
|
||||
// Test cases
|
||||
struct
|
||||
{
|
||||
// Whether the Foo kernel should truncate its output
|
||||
bool truncateOutput;
|
||||
|
||||
// Whether a type label is used in the schema, versus a type description
|
||||
bool useTypeLabel;
|
||||
|
||||
// Whether the schema provides a type inference function, and uses an output type
|
||||
// of Int32 instead of Float32
|
||||
bool useTypeInference;
|
||||
|
||||
// Whether a shape inference method is provided in the schema
|
||||
bool useShapeInferenceInSchema;
|
||||
|
||||
// Whether a shape inference method is provided in the kernel
|
||||
bool useShapeInferenceInKernel;
|
||||
|
||||
// Whether attribute defaults are provided in the schema, instead of the kernel
|
||||
bool attributeDefaultsInSchema;
|
||||
} testCases[] =
|
||||
{
|
||||
{false, true, false, false, false, false},
|
||||
{false, false, false, false, false, false},
|
||||
{false, true, true, false, false, true},
|
||||
{true, false, false, false, true, false},
|
||||
{true, true, true, true, true, true},
|
||||
};
|
||||
|
||||
for (size_t caseIndex = 0; caseIndex < std::size(testCases); ++caseIndex)
|
||||
{
|
||||
// Create the registry
|
||||
auto operatorProvider = winrt::make<LocalCustomOperatorProvider>();
|
||||
IMLOperatorRegistry* registry = operatorProvider.as<LocalCustomOperatorProvider>()->GetRegistry();
|
||||
|
||||
// Create input and output parameters
|
||||
MLOperatorSchemaEdgeDescription inputParam = {};
|
||||
inputParam.options = MLOperatorParameterOptions::Single;
|
||||
|
||||
if (!testCases[caseIndex].useTypeLabel)
|
||||
{
|
||||
assert(!testCases[caseIndex].useTypeInference);
|
||||
|
||||
MLOperatorEdgeDescription edgeDesc = {};
|
||||
edgeDesc.edgeType = MLOperatorEdgeType::Tensor;
|
||||
edgeDesc.tensorDataType = MLOperatorTensorDataType::Float;
|
||||
|
||||
inputParam.typeFormat = MLOperatorSchemaEdgeTypeFormat::EdgeDescription;
|
||||
inputParam.edgeDescription = edgeDesc;
|
||||
}
|
||||
else
|
||||
{
|
||||
inputParam.typeFormat = MLOperatorSchemaEdgeTypeFormat::Label;
|
||||
inputParam.typeLabel = "T1";
|
||||
}
|
||||
|
||||
MLOperatorSchemaEdgeDescription outputParam = inputParam;
|
||||
|
||||
// Type inference should set this to tensor(float) even though T2 is not matched
|
||||
// on an input label
|
||||
if (testCases[caseIndex].useTypeInference)
|
||||
{
|
||||
if (inputParam.typeFormat == MLOperatorSchemaEdgeTypeFormat::Label)
|
||||
{
|
||||
outputParam.typeLabel = "T2";
|
||||
}
|
||||
else
|
||||
{
|
||||
outputParam.edgeDescription.tensorDataType = MLOperatorTensorDataType::Int32;
|
||||
}
|
||||
}
|
||||
|
||||
MLOperatorSchemaEdgeDescription inputs[] = { inputParam, inputParam };
|
||||
|
||||
MLOperatorEdgeDescription edgeTypes[6] =
|
||||
{
|
||||
{MLOperatorEdgeType::Tensor, static_cast<uint64_t>(MLOperatorTensorDataType::UInt32)},
|
||||
{MLOperatorEdgeType::Tensor, static_cast<uint64_t>(MLOperatorTensorDataType::UInt64)},
|
||||
{MLOperatorEdgeType::Tensor, static_cast<uint64_t>(MLOperatorTensorDataType::Int32)},
|
||||
{MLOperatorEdgeType::Tensor, static_cast<uint64_t>(MLOperatorTensorDataType::Int64)},
|
||||
{MLOperatorEdgeType::Tensor, static_cast<uint64_t>(MLOperatorTensorDataType::Float)},
|
||||
{MLOperatorEdgeType::Tensor, static_cast<uint64_t>(MLOperatorTensorDataType::Double)}
|
||||
};
|
||||
|
||||
// Type constraints. Only the first is used unless type inference is provided and
|
||||
// the kernel emits a different output type as "T2"
|
||||
MLOperatorEdgeTypeConstrant constraints[] =
|
||||
{
|
||||
{"T1", edgeTypes, static_cast<uint32_t>(std::size(edgeTypes))},
|
||||
{"T2", edgeTypes, static_cast<uint32_t>(std::size(edgeTypes))}
|
||||
};
|
||||
|
||||
// Test attributes
|
||||
MLOperatorAttribute attributes[] =
|
||||
{
|
||||
{"DefaultedNonRequiredInt", MLOperatorAttributeType::Int, false},
|
||||
{"DefaultedNonRequiredFloat", MLOperatorAttributeType::Float, false},
|
||||
{"DefaultedNonRequiredString", MLOperatorAttributeType::String, false},
|
||||
{"DefaultedNonRequiredIntArray", MLOperatorAttributeType::IntArray, false},
|
||||
{"DefaultedNonRequiredFloatArray", MLOperatorAttributeType::FloatArray, false},
|
||||
{"DefaultedNonRequiredStringArray", MLOperatorAttributeType::StringArray, false},
|
||||
|
||||
{"NonDefaultedNonRequiredStringArray", MLOperatorAttributeType::StringArray, false},
|
||||
};
|
||||
|
||||
// Defaults. These are queried back during kernel creation, type and shape inference
|
||||
// and tested against the same values
|
||||
MLOperatorAttributeNameValue defaultAttributes[] =
|
||||
{
|
||||
{"DefaultedNonRequiredInt", MLOperatorAttributeType::Int, 1},
|
||||
{"DefaultedNonRequiredFloat", MLOperatorAttributeType::Float, 1},
|
||||
{"DefaultedNonRequiredString", MLOperatorAttributeType::String, 1},
|
||||
{"DefaultedNonRequiredIntArray", MLOperatorAttributeType::IntArray, 2},
|
||||
{"DefaultedNonRequiredFloatArray", MLOperatorAttributeType::FloatArray, 2},
|
||||
{"DefaultedNonRequiredStringArray", MLOperatorAttributeType::StringArray, 2},
|
||||
};
|
||||
|
||||
int64_t defaultInts[] = { 1, 2 };
|
||||
float defaultFloats[] = { 1.0f, 2.0f };
|
||||
const char* defaultStrings[] = { "1", "2" };
|
||||
defaultAttributes[0].ints = defaultInts;
|
||||
defaultAttributes[1].floats = defaultFloats;
|
||||
defaultAttributes[2].strings = defaultStrings;
|
||||
defaultAttributes[3].ints = defaultInts;
|
||||
defaultAttributes[4].floats = defaultFloats;
|
||||
defaultAttributes[5].strings = defaultStrings;
|
||||
|
||||
// Schema definition
|
||||
MLOperatorSchemaDescription schemaDesc = {};
|
||||
schemaDesc.name = "Foo";
|
||||
schemaDesc.operatorSetVersionAtLastChange = 7;
|
||||
schemaDesc.inputs = inputs;
|
||||
schemaDesc.inputCount = 2;
|
||||
schemaDesc.outputs = &outputParam;
|
||||
schemaDesc.outputCount = 1;
|
||||
schemaDesc.typeConstraints = constraints;
|
||||
schemaDesc.typeConstraintCount = testCases[caseIndex].useTypeLabel ? 2 : 0;
|
||||
schemaDesc.attributes = attributes;
|
||||
schemaDesc.attributeCount = static_cast<uint32_t>(std::size(attributes));
|
||||
|
||||
if (testCases[caseIndex].attributeDefaultsInSchema)
|
||||
{
|
||||
schemaDesc.defaultAttributes = defaultAttributes;
|
||||
schemaDesc.defaultAttributeCount = static_cast<uint32_t>(std::size(defaultAttributes));
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<MLOperatorTypeInferrer> typeInferrer;
|
||||
Microsoft::WRL::ComPtr<MLOperatorShapeInferrerFromFunc> shapeInferrer;
|
||||
|
||||
// Type inference function
|
||||
if (testCases[caseIndex].useTypeInference)
|
||||
{
|
||||
typeInferrer = wil::MakeOrThrow<MLOperatorTypeInferrer>([](IMLOperatorTypeInferenceContext* ctx) -> void
|
||||
{
|
||||
VerifyTestAttributes(MLOperatorTypeInferenceContext(ctx));
|
||||
|
||||
MLOperatorEdgeDescription edgeDesc = {};
|
||||
edgeDesc.edgeType = MLOperatorEdgeType::Tensor;
|
||||
edgeDesc.tensorDataType = MLOperatorTensorDataType::Float;
|
||||
|
||||
MLOperatorTypeInferenceContext(ctx).SetOutputEdgeDescription(0, &edgeDesc);
|
||||
});
|
||||
}
|
||||
|
||||
// Store the shape inference context with a reference following the call to InferOutputShapes.
|
||||
// This will be called after loading the model as an isolated test for how ABI context objects
|
||||
// are "closed."
|
||||
Microsoft::WRL::ComPtr<IMLOperatorShapeInferenceContext> shapeInferenceContext;
|
||||
|
||||
// Shape inference is tested by truncating the output size
|
||||
bool truncateOutput = testCases[caseIndex].truncateOutput;
|
||||
if (truncateOutput)
|
||||
{
|
||||
shapeInferrer = wil::MakeOrThrow<MLOperatorShapeInferrerFromFunc>([&shapeInferenceContext](IMLOperatorShapeInferenceContext* ctx) -> void
|
||||
{
|
||||
VerifyTestAttributes(MLShapeInferenceContext(ctx));
|
||||
MLShapeInferenceContext(ctx).SetOutputTensorShape(0, { 2, 2 });
|
||||
shapeInferenceContext = ctx;
|
||||
});
|
||||
}
|
||||
|
||||
// Register the schema
|
||||
MLOperatorSetId opsetId = { "", 7 };
|
||||
MLOperatorSchemaDescription* opSchemaDescs = &schemaDesc;
|
||||
EXPECT_EQ(S_OK, registry->RegisterOperatorSetSchema(
|
||||
&opsetId,
|
||||
1,
|
||||
&opSchemaDescs,
|
||||
1,
|
||||
typeInferrer.Get(),
|
||||
testCases[caseIndex].useShapeInferenceInSchema ? shapeInferrer.Get() : nullptr
|
||||
));
|
||||
|
||||
{
|
||||
// Register a future version of the schema in the same domain, while setting its
|
||||
// input count to zero to ensure it is not being used.
|
||||
auto futureSchemaDesc = schemaDesc;
|
||||
futureSchemaDesc.inputCount = 0;
|
||||
|
||||
MLOperatorSetId id = { "", 9 };
|
||||
MLOperatorSchemaDescription* schemaDescs = &futureSchemaDesc;
|
||||
EXPECT_EQ(S_OK, registry->RegisterOperatorSetSchema(
|
||||
&id,
|
||||
7,
|
||||
&schemaDescs,
|
||||
1,
|
||||
typeInferrer.Get(),
|
||||
testCases[caseIndex].useShapeInferenceInSchema ? shapeInferrer.Get() : nullptr
|
||||
));
|
||||
}
|
||||
{
|
||||
// Register in another (unused) domain to the custom registry
|
||||
auto otherSchemaDesc = schemaDesc;
|
||||
otherSchemaDesc.inputCount = 0;
|
||||
|
||||
MLOperatorSetId id = { "otherDomain", 7 };
|
||||
MLOperatorSchemaDescription* schemaDescs = &otherSchemaDesc;
|
||||
EXPECT_EQ(S_OK, registry->RegisterOperatorSetSchema(
|
||||
&id,
|
||||
1,
|
||||
&schemaDescs,
|
||||
1,
|
||||
typeInferrer.Get(),
|
||||
testCases[caseIndex].useShapeInferenceInSchema ? shapeInferrer.Get() : nullptr
|
||||
));
|
||||
}
|
||||
// Register the Foo kernel
|
||||
MLOperatorEdgeDescription floatTensorEdgeDesc = {};
|
||||
floatTensorEdgeDesc.edgeType = MLOperatorEdgeType::Tensor;
|
||||
floatTensorEdgeDesc.tensorDataType = MLOperatorTensorDataType::Float;
|
||||
|
||||
MLOperatorEdgeTypeConstrant kernelConstraint = { "T", &floatTensorEdgeDesc, 1 };
|
||||
|
||||
MLOperatorKernelDescription kernelDesc =
|
||||
{
|
||||
"",
|
||||
"Foo",
|
||||
7,
|
||||
MLOperatorExecutionType::Cpu,
|
||||
&kernelConstraint,
|
||||
1
|
||||
};
|
||||
|
||||
if (!testCases[caseIndex].attributeDefaultsInSchema)
|
||||
{
|
||||
kernelDesc.defaultAttributes = defaultAttributes;
|
||||
kernelDesc.defaultAttributeCount = static_cast<uint32_t>(std::size(defaultAttributes));
|
||||
}
|
||||
|
||||
if (!truncateOutput)
|
||||
{
|
||||
kernelDesc.options = MLOperatorKernelOptions::AllowDynamicInputShapes;
|
||||
Microsoft::WRL::ComPtr<MLOperatorKernelFactory> factory = wil::MakeOrThrow<MLOperatorKernelFactory>(CreateABIFooKernel<true>);
|
||||
|
||||
EXPECT_EQ(S_OK, registry->RegisterOperatorKernel(&kernelDesc, factory.Get(), nullptr));
|
||||
}
|
||||
else
|
||||
{
|
||||
Microsoft::WRL::ComPtr<MLOperatorKernelFactory> factory = wil::MakeOrThrow<MLOperatorKernelFactory>(CreateTruncatedABIFooKernel);
|
||||
EXPECT_EQ(S_OK, registry->RegisterOperatorKernel(
|
||||
&kernelDesc,
|
||||
factory.Get(),
|
||||
testCases[caseIndex].useShapeInferenceInKernel ? shapeInferrer.Get() : nullptr
|
||||
));
|
||||
}
|
||||
|
||||
// Prepare inputs
|
||||
std::vector<int64_t> dimsX = { 3, 2 };
|
||||
std::vector<float> valuesX = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f };
|
||||
|
||||
// Prepare expected inputs and outputs
|
||||
std::vector<int64_t> expectedDimsY = { truncateOutput ? 2 : 3, 2 };
|
||||
// now the expected value should be Add's result.
|
||||
std::vector<float> expectedValuesY = { 2.0f, 4.0f, 6.0f, 8.0f, 10.0f, 12.0f };
|
||||
if (truncateOutput)
|
||||
{
|
||||
// The leading dimension is truncated, and the second dimension has two elements over that dim
|
||||
expectedValuesY.resize(expectedValuesY.size() - 2);
|
||||
}
|
||||
|
||||
// Load the model and sessions
|
||||
std::wstring fullPath = FileHelpers::GetModulePath() + (truncateOutput ? L"foo_truncated.onnx" : L"foo.onnx");
|
||||
LearningModel model = LearningModel::LoadFromFilePath(fullPath, operatorProvider);
|
||||
LearningModelSession session(model);
|
||||
|
||||
// Bind input and outputs
|
||||
LearningModelBinding bindings(session);
|
||||
|
||||
TensorFloat inputTensor = TensorFloat::CreateFromArray(dimsX, winrt::array_view<const float>(std::move(valuesX)));
|
||||
bindings.Bind(winrt::hstring(L"X"), inputTensor);
|
||||
|
||||
auto outputValue = TensorFloat::Create();
|
||||
EXPECT_NO_THROW(bindings.Bind(L"Y", outputValue));
|
||||
|
||||
// Evaluate the model
|
||||
hstring correlationId;
|
||||
EXPECT_NO_THROW(session.Evaluate(bindings, correlationId));
|
||||
|
||||
// Verify the result shape
|
||||
EXPECT_EQ(expectedDimsY.size(), outputValue.Shape().Size());
|
||||
for (uint32_t j = 0; j < outputValue.Shape().Size(); j++)
|
||||
{
|
||||
EXPECT_EQ(expectedDimsY.at(j), outputValue.Shape().GetAt(j));
|
||||
}
|
||||
|
||||
// Verify the result values
|
||||
auto buffer = outputValue.GetAsVectorView();
|
||||
EXPECT_TRUE(buffer != nullptr);
|
||||
EXPECT_TRUE(std::equal(expectedValuesY.cbegin(), expectedValuesY.cend(), begin(buffer)));
|
||||
|
||||
// Release the model before operatorProvider goes out of scope
|
||||
model = nullptr;
|
||||
|
||||
if (shapeInferenceContext)
|
||||
{
|
||||
// Check that the shape inference context is closed and safely fails
|
||||
MLOperatorEdgeDescription edgeDesc;
|
||||
EXPECT_EQ(E_INVALIDARG, shapeInferenceContext->GetInputEdgeDescription(0, &edgeDesc));
|
||||
}
|
||||
}
|
||||
}
|
||||
293
winml/test/scenario/cppwinrt/NoisyReluCpu.h
Normal file
293
winml/test/scenario/cppwinrt/NoisyReluCpu.h
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
#pragma once
|
||||
|
||||
#include "core/providers/dml/DmlExecutionProvider/inc/MLOperatorAuthor.h"
|
||||
|
||||
struct NoisyReluShapeInferrer : winrt::implements<NoisyReluShapeInferrer, IMLOperatorShapeInferrer>
|
||||
{
|
||||
STDMETHOD(InferOutputShapes)(IMLOperatorShapeInferenceContext* context) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
uint32_t inputDimsSize;
|
||||
context->GetInputTensorDimensionCount(0, &inputDimsSize);
|
||||
|
||||
uint32_t *inputDims = new uint32_t[inputDimsSize];
|
||||
context->GetInputTensorShape(0, inputDimsSize, inputDims);
|
||||
|
||||
context->SetOutputTensorShape(0, inputDimsSize, inputDims);
|
||||
return S_OK;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return winrt::to_hresult();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct NoisyReluOperator: winrt::implements<NoisyReluOperator, IMLOperatorKernel>
|
||||
{
|
||||
float m_mean;
|
||||
float m_variance;
|
||||
|
||||
NoisyReluOperator(float mean, float variance) :
|
||||
m_mean(mean),
|
||||
m_variance(variance)
|
||||
{}
|
||||
|
||||
// Computes the outputs of the kernel. This may be called multiple times
|
||||
// simultaneously within the same instance of the class. Implementations
|
||||
// of this method must be thread-safe.
|
||||
STDMETHOD(Compute)(IMLOperatorKernelContext* context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the input tensor
|
||||
winrt::com_ptr<IMLOperatorTensor> inputTensor;
|
||||
context->GetInputTensor(0, inputTensor.put());
|
||||
|
||||
// Get the output tensor
|
||||
winrt::com_ptr<IMLOperatorTensor> outputTensor;
|
||||
context->GetOutputTensor(0, outputTensor.put());
|
||||
|
||||
// Get the input and output shape sizes
|
||||
uint32_t inputDimsSize = inputTensor->GetDimensionCount();
|
||||
uint32_t outputDimsSize = outputTensor->GetDimensionCount();
|
||||
if (inputDimsSize != outputDimsSize)
|
||||
{
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
// Get the input shape
|
||||
std::vector<uint32_t> inputDims(inputDimsSize);
|
||||
outputTensor->GetShape(inputDimsSize, inputDims.data());
|
||||
|
||||
// Get the output shape
|
||||
std::vector<uint32_t> outputDims(outputDimsSize);
|
||||
outputTensor->GetShape(outputDimsSize, outputDims.data());
|
||||
|
||||
// For the number of total elements in the input and output shapes
|
||||
auto outputDataSize = std::accumulate(outputDims.begin(), outputDims.end(), 1, std::multiplies<uint32_t>());
|
||||
auto inputDataSize = std::accumulate(inputDims.begin(), inputDims.end(), 1, std::multiplies<uint32_t>());
|
||||
if (outputDataSize != inputDataSize)
|
||||
{
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
// If the tensor types are both float type
|
||||
if (outputTensor->GetTensorDataType() == MLOperatorTensorDataType::Float &&
|
||||
inputTensor->GetTensorDataType() == MLOperatorTensorDataType::Float)
|
||||
{
|
||||
// For cpu data
|
||||
if (outputTensor->IsCpuData() && inputTensor->IsCpuData())
|
||||
{
|
||||
ComputeInternal<float>(inputTensor.get(), outputTensor.get(), inputDataSize);
|
||||
}
|
||||
}
|
||||
else if (outputTensor->GetTensorDataType() == MLOperatorTensorDataType::Double &&
|
||||
inputTensor->GetTensorDataType() == MLOperatorTensorDataType::Double)
|
||||
{
|
||||
// For cpu data
|
||||
if (outputTensor->IsCpuData() && inputTensor->IsCpuData())
|
||||
{
|
||||
ComputeInternal<double>(inputTensor.get(), outputTensor.get(), inputDataSize);
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return winrt::to_hresult();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U = T>
|
||||
void ComputeInternal(IMLOperatorTensor* pInputTensor, IMLOperatorTensor* pOutputTensor, uint32_t size)
|
||||
{
|
||||
// Create a normal distribution
|
||||
std::normal_distribution<> dist{ m_mean, m_variance };
|
||||
std::random_device rd{};
|
||||
std::mt19937 gen{ rd() };
|
||||
|
||||
auto inputData = static_cast<T*>(pInputTensor->GetData());
|
||||
auto outputData = static_cast<U*>(pOutputTensor->GetData());
|
||||
|
||||
for (uint32_t i = 0; i < size; i++)
|
||||
{
|
||||
outputData[i] = static_cast<U>(std::max<T>(0, static_cast<T>(inputData[i] + dist(gen))));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct NoisyReluOperatorFactory : winrt::implements<NoisyReluOperatorFactory, IMLOperatorKernelFactory>
|
||||
{
|
||||
STDMETHOD(CreateKernel)(
|
||||
IMLOperatorKernelCreationContext* context,
|
||||
IMLOperatorKernel** kernel)
|
||||
{
|
||||
try
|
||||
{
|
||||
float mean;
|
||||
context->GetAttribute("mean", MLOperatorAttributeType::Float, 1, sizeof(float), reinterpret_cast<void*>(&mean));
|
||||
float variance;
|
||||
context->GetAttribute("variance", MLOperatorAttributeType::Float, 1, sizeof(float), reinterpret_cast<void*>(&variance));
|
||||
|
||||
auto noisyReluOperator = winrt::make<NoisyReluOperator>(mean, variance);
|
||||
noisyReluOperator.copy_to(kernel);
|
||||
return S_OK;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return winrt::to_hresult();
|
||||
}
|
||||
}
|
||||
|
||||
static MLOperatorEdgeDescription CreateEdgeDescriptor(MLOperatorEdgeType type, MLOperatorTensorDataType dataType)
|
||||
{
|
||||
MLOperatorEdgeDescription desc;
|
||||
desc.edgeType = MLOperatorEdgeType::Tensor;
|
||||
desc.tensorDataType = dataType;
|
||||
return desc;
|
||||
}
|
||||
|
||||
static void RegisterNoisyReluSchema(winrt::com_ptr<IMLOperatorRegistry> registry)
|
||||
{
|
||||
MLOperatorSetId operatorSetId;
|
||||
operatorSetId.domain = "";
|
||||
operatorSetId.version = 7;
|
||||
|
||||
MLOperatorSchemaDescription noisyReluSchema;
|
||||
noisyReluSchema.name = "NoisyRelu";
|
||||
noisyReluSchema.operatorSetVersionAtLastChange = 1;
|
||||
|
||||
MLOperatorSchemaEdgeDescription noisyReluXInput;
|
||||
noisyReluXInput.options = MLOperatorParameterOptions::Single;
|
||||
noisyReluXInput.typeFormat = MLOperatorSchemaEdgeTypeFormat::Label;
|
||||
noisyReluXInput.typeLabel = "T";
|
||||
|
||||
std::vector<MLOperatorSchemaEdgeDescription> inputs { noisyReluXInput };
|
||||
noisyReluSchema.inputs = inputs.data();
|
||||
noisyReluSchema.inputCount = static_cast<uint32_t>(inputs.size());
|
||||
|
||||
MLOperatorSchemaEdgeDescription noisyReluXOutput;
|
||||
noisyReluXOutput.options = MLOperatorParameterOptions::Single;
|
||||
noisyReluXOutput.typeFormat = MLOperatorSchemaEdgeTypeFormat::Label;
|
||||
noisyReluXOutput.typeLabel = "T";
|
||||
|
||||
std::vector<MLOperatorSchemaEdgeDescription> outputs{ noisyReluXOutput };
|
||||
noisyReluSchema.outputs = outputs.data();
|
||||
noisyReluSchema.outputCount = static_cast<uint32_t>(outputs.size());
|
||||
|
||||
MLOperatorEdgeTypeConstrant typeConstraint;
|
||||
typeConstraint.typeLabel = "T";
|
||||
std::vector<MLOperatorEdgeDescription> allowedEdges
|
||||
{
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Double),
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Float),
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Float16)
|
||||
};
|
||||
typeConstraint.allowedTypes = allowedEdges.data();
|
||||
typeConstraint.allowedTypeCount = static_cast<uint32_t>(allowedEdges.size());
|
||||
|
||||
std::vector<MLOperatorEdgeTypeConstrant> typeConstraints { typeConstraint };
|
||||
noisyReluSchema.typeConstraints = typeConstraints.data();
|
||||
noisyReluSchema.typeConstraintCount = static_cast<uint32_t>(typeConstraints.size());
|
||||
|
||||
|
||||
MLOperatorAttribute noisyReluMeanAttribute;
|
||||
noisyReluMeanAttribute.name = "mean";
|
||||
noisyReluMeanAttribute.required = false;
|
||||
noisyReluMeanAttribute.type = MLOperatorAttributeType::Float;
|
||||
|
||||
MLOperatorAttribute noisyReluVarianceAttribute;
|
||||
noisyReluVarianceAttribute.name = "variance";
|
||||
noisyReluVarianceAttribute.required = false;
|
||||
noisyReluVarianceAttribute.type = MLOperatorAttributeType::Float;
|
||||
|
||||
std::vector<MLOperatorAttribute> attributes { noisyReluMeanAttribute, noisyReluVarianceAttribute };
|
||||
noisyReluSchema.attributes = attributes.data();
|
||||
noisyReluSchema.attributeCount = static_cast<uint32_t>(attributes.size());
|
||||
|
||||
MLOperatorAttributeNameValue noisyReluMeanAttributeValue;
|
||||
noisyReluMeanAttributeValue.name = "mean";
|
||||
noisyReluMeanAttributeValue.type = MLOperatorAttributeType::Float;
|
||||
noisyReluMeanAttributeValue.valueCount = 1;
|
||||
static float defaultMeans[] = { 0.f };
|
||||
noisyReluMeanAttributeValue.floats = defaultMeans;
|
||||
|
||||
MLOperatorAttributeNameValue noisyReluVarianceAttributeValue;
|
||||
noisyReluVarianceAttributeValue.name = "variance";
|
||||
noisyReluVarianceAttributeValue.type = MLOperatorAttributeType::Float;
|
||||
noisyReluVarianceAttributeValue.valueCount = 1;
|
||||
static float defaultVariance[] = { 1.f };
|
||||
noisyReluVarianceAttributeValue.floats = defaultVariance;
|
||||
|
||||
std::vector<MLOperatorAttributeNameValue> attributeDefaultValues { noisyReluMeanAttributeValue, noisyReluVarianceAttributeValue };
|
||||
noisyReluSchema.defaultAttributes = attributeDefaultValues.data();
|
||||
noisyReluSchema.defaultAttributeCount = static_cast<uint32_t>(attributeDefaultValues.size());
|
||||
|
||||
std::vector<const MLOperatorSchemaDescription*> schemas { &noisyReluSchema };
|
||||
registry->RegisterOperatorSetSchema(
|
||||
&operatorSetId,
|
||||
6 /* baseline version */,
|
||||
schemas.data(),
|
||||
static_cast<uint32_t>(schemas.size()),
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
static void RegisterNoisyReluKernel(winrt::com_ptr<IMLOperatorRegistry> registry)
|
||||
{
|
||||
MLOperatorKernelDescription kernelDescription;
|
||||
kernelDescription.domain = "";
|
||||
kernelDescription.name = "NoisyRelu";
|
||||
kernelDescription.minimumOperatorSetVersion = 1;
|
||||
kernelDescription.executionType = MLOperatorExecutionType::Cpu;
|
||||
|
||||
MLOperatorEdgeTypeConstrant typeConstraint;
|
||||
typeConstraint.typeLabel = "T";
|
||||
std::vector<MLOperatorEdgeDescription> allowedEdges
|
||||
{
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Double),
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Float),
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Float16)
|
||||
};
|
||||
typeConstraint.allowedTypes = allowedEdges.data();
|
||||
typeConstraint.allowedTypeCount = static_cast<uint32_t>(allowedEdges.size());
|
||||
|
||||
std::vector<MLOperatorEdgeTypeConstrant> typeConstraints{ typeConstraint };
|
||||
kernelDescription.typeConstraints = typeConstraints.data();
|
||||
kernelDescription.typeConstraintCount = static_cast<uint32_t>(typeConstraints.size());
|
||||
|
||||
|
||||
MLOperatorAttributeNameValue noisyReluMeanAttributeValue;
|
||||
noisyReluMeanAttributeValue.name = "mean";
|
||||
noisyReluMeanAttributeValue.type = MLOperatorAttributeType::Float;
|
||||
noisyReluMeanAttributeValue.valueCount = 1;
|
||||
static float defaultMeans[] = { 0.f };
|
||||
noisyReluMeanAttributeValue.floats = defaultMeans;
|
||||
|
||||
MLOperatorAttributeNameValue noisyReluVarianceAttributeValue;
|
||||
noisyReluVarianceAttributeValue.name = "variance";
|
||||
noisyReluVarianceAttributeValue.type = MLOperatorAttributeType::Float;
|
||||
noisyReluVarianceAttributeValue.valueCount = 1;
|
||||
static float defaultVariance[] = { 1.f };
|
||||
noisyReluVarianceAttributeValue.floats = defaultVariance;
|
||||
|
||||
std::vector<MLOperatorAttributeNameValue> attributeDefaultValues{ noisyReluMeanAttributeValue, noisyReluVarianceAttributeValue };
|
||||
kernelDescription.defaultAttributes = attributeDefaultValues.data();
|
||||
kernelDescription.defaultAttributeCount = static_cast<uint32_t>(attributeDefaultValues.size());
|
||||
kernelDescription.options = MLOperatorKernelOptions::None;
|
||||
kernelDescription.executionOptions = 0;
|
||||
|
||||
auto factory = winrt::make<NoisyReluOperatorFactory>();
|
||||
auto shareInferrer = winrt::make<NoisyReluShapeInferrer>();
|
||||
|
||||
registry->RegisterOperatorKernel(
|
||||
&kernelDescription,
|
||||
factory.get(),
|
||||
shareInferrer.get()
|
||||
);
|
||||
}
|
||||
};
|
||||
157
winml/test/scenario/cppwinrt/ReluCpu.h
Normal file
157
winml/test/scenario/cppwinrt/ReluCpu.h
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
#pragma once
|
||||
|
||||
#include "core/providers/dml/DmlExecutionProvider/inc/MLOperatorAuthor.h"
|
||||
|
||||
struct ReluShapeInferrer : winrt::implements<ReluShapeInferrer, IMLOperatorShapeInferrer>
|
||||
{
|
||||
STDMETHOD(InferOutputShapes)(IMLOperatorShapeInferenceContext* context) noexcept
|
||||
{
|
||||
uint32_t inputDimsSize;
|
||||
context->GetInputTensorDimensionCount(0, &inputDimsSize);
|
||||
|
||||
uint32_t *inputDims = new uint32_t[inputDimsSize];
|
||||
context->GetInputTensorShape(0, inputDimsSize, inputDims);
|
||||
|
||||
context->SetOutputTensorShape(0, inputDimsSize, inputDims);
|
||||
return S_OK;
|
||||
}
|
||||
};
|
||||
|
||||
struct ReluOperator: winrt::implements<ReluOperator, IMLOperatorKernel>
|
||||
{
|
||||
ReluOperator() {}
|
||||
|
||||
// Computes the outputs of the kernel. In this case, the output will represent
|
||||
// the Rectified Linear Unit (Relu) output.
|
||||
//
|
||||
// Based on the operators location in the model graph this operator may be called multiple times
|
||||
// or simultaneously within the same instance of the class during evaluation. Implementations
|
||||
// of this method must be thread-safe.
|
||||
STDMETHOD(Compute)(IMLOperatorKernelContext* context)
|
||||
{
|
||||
// Get the input tensor
|
||||
winrt::com_ptr<IMLOperatorTensor> inputTensor;
|
||||
context->GetInputTensor(0, inputTensor.put());
|
||||
|
||||
// Get the output tensor
|
||||
winrt::com_ptr<IMLOperatorTensor> outputTensor;
|
||||
context->GetOutputTensor(0, outputTensor.put());
|
||||
|
||||
// Get the input and output shape sizes
|
||||
uint32_t inputDimsSize = inputTensor->GetDimensionCount();
|
||||
uint32_t outputDimsSize = outputTensor->GetDimensionCount();
|
||||
if (inputDimsSize != outputDimsSize)
|
||||
{
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
// Get the input shape
|
||||
std::vector<uint32_t> inputDims(inputDimsSize);
|
||||
outputTensor->GetShape(inputDimsSize, inputDims.data());
|
||||
|
||||
// Get the output shape
|
||||
std::vector<uint32_t> outputDims(outputDimsSize);
|
||||
outputTensor->GetShape(outputDimsSize, outputDims.data());
|
||||
|
||||
// For the number of total elements in the input and output shapes
|
||||
auto outputDataSize = std::accumulate(outputDims.begin(), outputDims.end(), 1, std::multiplies<uint32_t>());
|
||||
auto inputDataSize = std::accumulate(inputDims.begin(), inputDims.end(), 1, std::multiplies<uint32_t>());
|
||||
if (outputDataSize != inputDataSize)
|
||||
{
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
// If the tensor types are both float type
|
||||
if (outputTensor->GetTensorDataType() == MLOperatorTensorDataType::Float &&
|
||||
inputTensor->GetTensorDataType() == MLOperatorTensorDataType::Float)
|
||||
{
|
||||
// For cpu data
|
||||
if (outputTensor->IsCpuData() && inputTensor->IsCpuData())
|
||||
{
|
||||
ComputeInternal<float>(inputTensor.get(), outputTensor.get(), inputDataSize);
|
||||
}
|
||||
}
|
||||
else if (outputTensor->GetTensorDataType() == MLOperatorTensorDataType::Double &&
|
||||
inputTensor->GetTensorDataType() == MLOperatorTensorDataType::Double)
|
||||
{
|
||||
// For cpu data
|
||||
if (outputTensor->IsCpuData() && inputTensor->IsCpuData())
|
||||
{
|
||||
ComputeInternal<double>(inputTensor.get(), outputTensor.get(), inputDataSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
template <typename T, typename U = T>
|
||||
void ComputeInternal(IMLOperatorTensor* pInputTensor, IMLOperatorTensor* pOutputTensor, uint32_t size)
|
||||
{
|
||||
auto inputData = static_cast<T*>(pInputTensor->GetData());
|
||||
auto outputData = static_cast<U*>(pOutputTensor->GetData());
|
||||
|
||||
for (uint32_t i = 0; i < size; i++)
|
||||
{
|
||||
outputData[i] = static_cast<U>(std::max<T>(0, inputData[i]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ReluOperatorFactory : winrt::implements<ReluOperatorFactory, IMLOperatorKernelFactory>
|
||||
{
|
||||
STDMETHOD(CreateKernel)(
|
||||
IMLOperatorKernelCreationContext* context,
|
||||
IMLOperatorKernel** kernel)
|
||||
{
|
||||
auto reluOperator = winrt::make<ReluOperator>();
|
||||
reluOperator.copy_to(kernel);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static MLOperatorEdgeDescription CreateEdgeDescriptor(MLOperatorEdgeType type, MLOperatorTensorDataType dataType)
|
||||
{
|
||||
MLOperatorEdgeDescription desc;
|
||||
desc.edgeType = MLOperatorEdgeType::Tensor;
|
||||
desc.tensorDataType = dataType;
|
||||
return desc;
|
||||
}
|
||||
|
||||
static void RegisterReluKernel(winrt::com_ptr<IMLOperatorRegistry> registry)
|
||||
{
|
||||
MLOperatorKernelDescription kernelDescription;
|
||||
kernelDescription.domain = "";
|
||||
kernelDescription.name = "Relu";
|
||||
kernelDescription.minimumOperatorSetVersion = 1;
|
||||
kernelDescription.executionType = MLOperatorExecutionType::Cpu;
|
||||
|
||||
MLOperatorEdgeTypeConstrant typeConstraint;
|
||||
typeConstraint.typeLabel = "T";
|
||||
std::vector<MLOperatorEdgeDescription> allowedEdges
|
||||
{
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Double),
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Float),
|
||||
CreateEdgeDescriptor(MLOperatorEdgeType::Tensor, MLOperatorTensorDataType::Float16)
|
||||
};
|
||||
typeConstraint.allowedTypes = allowedEdges.data();
|
||||
typeConstraint.allowedTypeCount = static_cast<uint32_t>(allowedEdges.size());
|
||||
|
||||
std::vector<MLOperatorEdgeTypeConstrant> typeConstraints{ typeConstraint };
|
||||
kernelDescription.typeConstraints = typeConstraints.data();
|
||||
kernelDescription.typeConstraintCount = static_cast<uint32_t>(typeConstraints.size());
|
||||
|
||||
kernelDescription.defaultAttributes = nullptr;
|
||||
kernelDescription.defaultAttributeCount = 0;
|
||||
kernelDescription.options = MLOperatorKernelOptions::None;
|
||||
kernelDescription.executionOptions = 0;
|
||||
|
||||
auto factory = winrt::make<ReluOperatorFactory>();
|
||||
auto shareInferrer = winrt::make<ReluShapeInferrer>();
|
||||
|
||||
registry->RegisterOperatorKernel(
|
||||
&kernelDescription,
|
||||
factory.get(),
|
||||
shareInferrer.get()
|
||||
);
|
||||
}
|
||||
};
|
||||
BIN
winml/test/scenario/cppwinrt/noisy_relu.onnx
Normal file
BIN
winml/test/scenario/cppwinrt/noisy_relu.onnx
Normal file
Binary file not shown.
11
winml/test/scenario/cppwinrt/relu.onnx
Normal file
11
winml/test/scenario/cppwinrt/relu.onnx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
justoeck:0
|
||||
|
||||
XY"ReluZ
|
||||
X
|
||||
|
||||
|
||||
b
|
||||
Y
|
||||
|
||||
|
||||
B
|
||||
1496
winml/test/scenario/cppwinrt/scenariotestscppwinrt.cpp
Normal file
1496
winml/test/scenario/cppwinrt/scenariotestscppwinrt.cpp
Normal file
File diff suppressed because it is too large
Load diff
6
winml/test/scenario/models/LICENSE.md
Normal file
6
winml/test/scenario/models/LICENSE.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Licenses
|
||||
|
||||
| Model | Source | License |
|
||||
| ----------- | ---------- | ----------- |
|
||||
| Resnet 50 | https://github.com/keras-team/keras | MIT |
|
||||
| Inception v1 | https://github.com/onnx/models | MIT |
|
||||
BIN
winml/test/scenario/models/coreml_Resnet50_ImageNet-dq.onnx
Normal file
BIN
winml/test/scenario/models/coreml_Resnet50_ImageNet-dq.onnx
Normal file
Binary file not shown.
BIN
winml/test/scenario/models/onnxzoo_lotus_inception_v1-dq.onnx
Normal file
BIN
winml/test/scenario/models/onnxzoo_lotus_inception_v1-dq.onnx
Normal file
Binary file not shown.
Loading…
Reference in a new issue