// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #include "core/framework/execution_frame.h" #include "core/framework/op_kernel.h" #include "core/framework/session_state.h" #include "core/graph/model.h" #include "core/providers/cpu/cpu_execution_provider.h" #include "core/session/inference_session.h" #include "test_utils.h" #include "test/test_environment.h" #include "asserts.h" #include "gtest/gtest.h" #include "gmock/gmock.h" using namespace ONNX_NAMESPACE; using namespace std; namespace onnxruntime { namespace test { typedef std::vector ArgMap; std::shared_ptr DummyGraphWithClip() { auto model = std::make_shared("test", false, DefaultLoggingManager().DefaultLogger()); onnxruntime::Graph& graph = model->MainGraph(); TypeProto tensor_float; tensor_float.mutable_tensor_type()->set_elem_type(TensorProto_DataType_FLOAT); onnxruntime::NodeArg input_def("X", &tensor_float), output_def("Y", &tensor_float); graph.AddNode("node1", "Clip", "clip operator", ArgMap{&input_def}, ArgMap{&output_def}); return model; } std::unique_ptr CreateCPUExecutionProvider() { CPUExecutionProviderInfo info; return onnxruntime::make_unique(info); } class ExecutionFrameTest : public ::testing::Test { protected: concurrency::ThreadPool tp_; ExecutionFrameTest() : tp_(&onnxruntime::Env::Default(), ThreadOptions(), ORT_TSTR("ExecutionFrameTest"), 2, true) { } }; TEST_F(ExecutionFrameTest, TensorAllocationTest) { onnxruntime::Model model("test", false, DefaultLoggingManager().DefaultLogger()); onnxruntime::Graph& graph = model.MainGraph(); TypeProto tensor_float; tensor_float.mutable_tensor_type()->set_elem_type(TensorProto_DataType_FLOAT); onnxruntime::NodeArg input_def("X", &tensor_float), output_def("Y", &tensor_float); onnxruntime::Node* node = &graph.AddNode("node1", "Relu", "Relu operator", ArgMap{&input_def}, ArgMap{&output_def}); node->SetExecutionProviderType(kCpuExecutionProvider); ASSERT_STATUS_OK(graph.Resolve()); auto cpu_xp = CreateCPUExecutionProvider(); auto xp_typ = cpu_xp->Type(); ExecutionProviders execution_providers; execution_providers.Add(xp_typ, std::move(cpu_xp)); KernelRegistryManager kernel_registry_manager; ASSERT_STATUS_OK(kernel_registry_manager.RegisterKernels(execution_providers)); SessionState state{execution_providers, true, &tp_, nullptr}; ASSERT_STATUS_OK(state.SetGraphAndCreateKernels(graph, kernel_registry_manager)); node->SetExecutionProviderType(xp_typ); std::unique_ptr p_seq_exec_plan; // TODO below line is for testing only. In production use SequentialPlanner::CreatePlan() SequentialPlannerContext context(ExecutionMode::ORT_SEQUENTIAL); ASSERT_STATUS_OK(SequentialPlanner::CreatePlan(nullptr, GraphViewer(graph), {}, execution_providers, kernel_registry_manager, state.GetOrtValueNameIdxMap(), context, p_seq_exec_plan)); state.SetExecutionPlan(std::move(p_seq_exec_plan)); vector outputs; ExecutionFrame frame({}, {}, {}, outputs, {}, state); int start_index = frame.GetNodeOffset(node->Index()); ASSERT_EQ(start_index, 0); TensorShape shape(std::vector{2, 3}); OrtValue& mlvalue0 = *frame.GetMutableNodeInputOrOutputMLValue(start_index); ASSERT_STATUS_OK(frame.AllocateMLValueTensorSelfOwnBuffer(mlvalue0, start_index, DataTypeImpl::GetType(), execution_providers.Get(xp_typ)->GetAllocator(0, OrtMemTypeDefault)->Info(), shape)); OrtValue* p_ml_value = frame.GetMutableNodeInputOrOutputMLValue(0); ASSERT_TRUE(p_ml_value != nullptr); Tensor* p_tensor = p_ml_value->GetMutable(); ASSERT_TRUE(p_tensor != nullptr); //Use reinterpret_cast to bypass a gcc bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51213 ASSERT_EQ(*reinterpret_cast*>(&p_tensor->Shape()), *reinterpret_cast*>(&shape)); ASSERT_EQ(p_tensor->DataType(), DataTypeImpl::GetType()); //test share memory from tensor TensorShape shape2(std::vector{3, 2}); OrtValue& mlvalue1 = *frame.GetMutableNodeInputOrOutputMLValue(start_index + 1); ASSERT_STATUS_OK(frame.AllocateMLValueTensorPreAllocateBuffer(mlvalue1, start_index, DataTypeImpl::GetType(), p_tensor->Location(), shape2)); const OrtValue* p_ml_value_const = frame.GetNodeInputOrOutputMLValue(1); auto tensor2 = p_ml_value_const ? &(p_ml_value_const->Get()) : nullptr; ASSERT_TRUE(tensor2); //Use reinterpret_cast to bypass a gcc bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51213 ASSERT_EQ(*reinterpret_cast*>(&tensor2->Shape()), *reinterpret_cast*>(&shape2)); ASSERT_EQ(tensor2->template Data(), p_tensor->template Data()); } TEST_F(ExecutionFrameTest, FeedInDataTest) { onnxruntime::Model model("test", false, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(), std::unordered_map{{"", 10}}, {}, DefaultLoggingManager().DefaultLogger()); onnxruntime::Graph& graph = model.MainGraph(); TypeProto tensor_float; tensor_float.mutable_tensor_type()->set_elem_type(TensorProto_DataType_FLOAT); onnxruntime::NodeArg input_def("X", &tensor_float), output_def("Y", &tensor_float); graph.AddNode("node1", "Clip", "Clip operator", ArgMap{&input_def}, ArgMap{&output_def}) .SetExecutionProviderType(kCpuExecutionProvider); graph.Resolve(); auto element_type = DataTypeImpl::GetType(); TensorShape shape({3, 2}); std::vector fdata(static_cast(shape.Size())); //create fake ml value with owned buffer. OrtMemoryInfo cpuinfo(kCpuExecutionProvider, OrtDeviceAllocator); std::unique_ptr p_tensor = onnxruntime::make_unique(element_type, shape, fdata.data(), cpuinfo); OrtValue value; value.Init(p_tensor.release(), DataTypeImpl::GetType(), DataTypeImpl::GetType()->GetDeleteFunc()); auto cpu_xp = CreateCPUExecutionProvider(); auto xp_typ = cpu_xp->Type(); KernelRegistryManager kernel_registry_manager; ExecutionProviders execution_providers; execution_providers.Add(xp_typ, std::move(cpu_xp)); ASSERT_STATUS_OK(kernel_registry_manager.RegisterKernels(execution_providers)); SessionState state{execution_providers, true, &tp_, nullptr}; ASSERT_STATUS_OK(state.SetGraphAndCreateKernels(graph, kernel_registry_manager)); const OrtValueNameIdxMap& mlvalue_name_idx_map = state.GetOrtValueNameIdxMap(); int x_idx = -1, y_idx = -1; ASSERT_TRUE(mlvalue_name_idx_map.GetIdx("X", x_idx).IsOK()); ASSERT_TRUE(mlvalue_name_idx_map.GetIdx("Y", y_idx).IsOK()); vector outputs; ExecutionFrame frame({x_idx}, {value}, {y_idx}, outputs, {}, state); OrtValue* p_ml_value = frame.GetMutableNodeInputOrOutputMLValue(0); Tensor* p_tensor_arg_0 = p_ml_value ? p_ml_value->GetMutable() : nullptr; ASSERT_TRUE(p_tensor_arg_0); //Use reinterpret_cast to bypass a gcc bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51213 ASSERT_EQ(*reinterpret_cast*>(&p_tensor_arg_0->Shape()), *reinterpret_cast*>(&shape)); ASSERT_EQ(p_tensor_arg_0->DataType(), DataTypeImpl::GetType()); ASSERT_EQ(p_tensor_arg_0->MutableData(), value.GetMutable()->MutableData()); } TEST_F(ExecutionFrameTest, MemPatternTest) { auto cpu_xp = CreateCPUExecutionProvider(); auto xp_type = cpu_xp->Type(); std::unordered_map domain_to_version; domain_to_version[onnxruntime::kOnnxDomain] = 7; onnxruntime::Model model("test", true, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(), domain_to_version, {}, DefaultLoggingManager().DefaultLogger()); onnxruntime::Graph& graph = model.MainGraph(); TypeProto tensor_float; tensor_float.mutable_tensor_type()->set_elem_type(TensorProto_DataType_FLOAT); onnxruntime::NodeArg input_def1("X1", &tensor_float), input_def2("X2", &tensor_float), input_def3("X3", &tensor_float), gemm1_out_def("T1", &tensor_float), gemm2_out_def("T2", &tensor_float), clip_out_def("T3", &tensor_float); graph.AddNode("node1", "MatMul", "gemm1", ArgMap{&input_def1, &input_def2}, ArgMap{&gemm1_out_def}) .SetExecutionProviderType(xp_type); graph.AddNode("node2", "MatMul", "gemm2", ArgMap{&gemm1_out_def, &input_def3}, ArgMap{&gemm2_out_def}) .SetExecutionProviderType(xp_type); graph.AddNode("node3", "Clip", "clip1", ArgMap{&gemm2_out_def}, ArgMap{&clip_out_def}) .SetExecutionProviderType(xp_type); ASSERT_STATUS_OK(graph.Resolve()); KernelRegistryManager kernel_registry_manager; ExecutionProviders execution_providers; execution_providers.Add(xp_type, std::move(cpu_xp)); ASSERT_STATUS_OK(kernel_registry_manager.RegisterKernels(execution_providers)); //1. prepare input SessionState state{execution_providers, true, &tp_, nullptr}; ASSERT_STATUS_OK(state.SetGraphAndCreateKernels(graph, kernel_registry_manager)); const OrtValueNameIdxMap& mlvalue_name_idx_map(state.GetOrtValueNameIdxMap()); int x1_idx = -1, x2_idx = -1, x3_idx = -1; int t1_idx = -1, t2_idx = -1, t3_idx = -1; ASSERT_TRUE(mlvalue_name_idx_map.GetIdx("X1", x1_idx).IsOK()); ASSERT_TRUE(mlvalue_name_idx_map.GetIdx("X2", x2_idx).IsOK()); ASSERT_TRUE(mlvalue_name_idx_map.GetIdx("X3", x3_idx).IsOK()); ASSERT_TRUE(mlvalue_name_idx_map.GetIdx("T1", t1_idx).IsOK()); ASSERT_TRUE(mlvalue_name_idx_map.GetIdx("T2", t2_idx).IsOK()); ASSERT_TRUE(mlvalue_name_idx_map.GetIdx("T3", t3_idx).IsOK()); auto cpu_allocator = execution_providers.Get(xp_type)->GetAllocator(0, OrtMemTypeDefault); OrtValue v1, v2, v3; CreateMLValue(cpu_allocator, std::vector{1, 2}, std::vector{1.0f, 1.0f}, &v1); CreateMLValue(cpu_allocator, std::vector{2, 2}, std::vector(4, 1.0f), &v2); CreateMLValue(cpu_allocator, std::vector{2, 3}, std::vector(6, 1.0f), &v3); std::unique_ptr p_seq_exec_plan = onnxruntime::make_unique(); SequentialPlannerContext context(ExecutionMode::ORT_SEQUENTIAL); ASSERT_STATUS_OK(SequentialPlanner::CreatePlan(nullptr, GraphViewer(graph), {}, execution_providers, kernel_registry_manager, mlvalue_name_idx_map, context, p_seq_exec_plan)); state.SetExecutionPlan(std::move(p_seq_exec_plan)); vector outputs; ExecutionFrame frame({x1_idx, x2_idx, x3_idx}, {v1, v2, v3}, {t3_idx}, outputs, {}, state); OrtValue& mlvalue3 = *frame.GetMutableNodeInputOrOutputMLValue(3); OrtValue& mlvalue4 = *frame.GetMutableNodeInputOrOutputMLValue(4); OrtValue& mlvalue5 = *frame.GetMutableNodeInputOrOutputMLValue(5); ASSERT_STATUS_OK(frame.AllocateMLValueTensorSelfOwnBuffer(mlvalue3, 3, DataTypeImpl::GetType(), cpu_allocator->Info(), TensorShape(std::vector{2, 2}))); ASSERT_STATUS_OK(frame.AllocateMLValueTensorSelfOwnBuffer(mlvalue4, 4, DataTypeImpl::GetType(), cpu_allocator->Info(), TensorShape(std::vector{2, 3}))); ASSERT_STATUS_OK(frame.AllocateMLValueTensorSelfOwnBuffer(mlvalue5, 5, DataTypeImpl::GetType(), cpu_allocator->Info(), TensorShape(std::vector{2, 3}))); MemoryPatternGroup pattern; ASSERT_STATUS_OK(frame.GeneratePatterns(&pattern)); ASSERT_EQ(pattern.patterns.size(), pattern.locations.size()); ASSERT_EQ(pattern.patterns.size(), 1u); auto p = pattern.GetPatterns(cpu_allocator->Info()); ASSERT_EQ(p->PeakSize(), 2u * 64u); // each allocation is 64-byte aligned ASSERT_EQ(p->GetBlock(3)->offset_, 0u); ASSERT_EQ(p->GetBlock(4)->offset_, 64u); } TEST(ExecutionFrameTestWithoutSessionState, BadModelInvalidDimParamUsage) { // load model with 2 Scan ops that both incorrectly use shapes of { 'None', 'None' } for their outputs. // as 'None' is not a special value it's treated as a variable name, leading to a runtime error when we // attempt to re-use the output from the first Scan node for the second. validate we detect this and error out. SessionOptions so; so.session_logid = "BadModelInvalidDimParamUsage"; InferenceSession session_object{so, GetEnvironment()}; ASSERT_STATUS_OK(session_object.Load("testdata/invalid_dim_param_value_repetition.onnx")); ASSERT_STATUS_OK(session_object.Initialize()); std::vector dims_X = {10, 6}; std::vector values_X; values_X.reserve(60); for (int i = 0; i < 60; ++i) { values_X.push_back(float(i)); } OrtValue ml_value; CreateMLValue(TestCPUExecutionProvider()->GetAllocator(0, OrtMemTypeDefault), dims_X, values_X, &ml_value); NameMLValMap feeds; feeds.insert(std::make_pair("X", ml_value)); // prepare outputs std::vector output_names; output_names.push_back("Y"); std::vector fetches; // Now run RunOptions run_options; auto st = session_object.Run(run_options, feeds, output_names, &fetches); EXPECT_FALSE(st.IsOK()) << st; EXPECT_THAT(st.ErrorMessage(), testing::HasSubstr("Shape mismatch attempting to re-use buffer.")); } } // namespace test } // namespace onnxruntime