mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-07 17:15:29 +00:00
### Description because of #15618 , the default allocator changed to device allocator, which will be GPU instead of CPU. in transpose optimizer we expect to read data from initializers so a CPU allocator is required here. this change fixes transpose optimizer on GPU EP Fixes the issue referred to in #15869, #15796
832 lines
37 KiB
C++
832 lines
37 KiB
C++
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
#include <iostream>
|
|
|
|
#include "asserts.h"
|
|
#include "core/framework/execution_providers.h"
|
|
#include "core/framework/graph_partitioner.h"
|
|
#include "core/framework/kernel_registry.h"
|
|
#include "core/framework/op_kernel.h"
|
|
#include "core/framework/bfc_arena.h"
|
|
#include "core/framework/session_state.h"
|
|
#include "core/graph/graph_utils.h"
|
|
#include "core/graph/graph_viewer.h"
|
|
#include "core/graph/model.h"
|
|
#include "core/graph/op.h"
|
|
#include "core/providers/cpu/cpu_execution_provider.h"
|
|
#include "core/session/onnxruntime_session_options_config_keys.h"
|
|
#include "core/util/thread_utils.h"
|
|
#include "gtest/gtest.h"
|
|
#include "test/test_environment.h"
|
|
#include "test/util/include/default_providers.h"
|
|
#include "core/optimizer/transpose_optimizer/optimizer_utils.h"
|
|
|
|
using namespace ONNX_NAMESPACE;
|
|
using namespace std;
|
|
namespace onnxruntime {
|
|
|
|
namespace test {
|
|
class TestOpKernel : public OpKernel {
|
|
public:
|
|
TestOpKernel(const OpKernelInfo& p) : OpKernel(p) {
|
|
}
|
|
Status Compute(OpKernelContext* context) const override {
|
|
ORT_UNUSED_PARAMETER(context);
|
|
return Status::OK();
|
|
}
|
|
Status ComputeAsync(OpKernelContext* context, DoneCallback done) const override {
|
|
ORT_UNUSED_PARAMETER(context);
|
|
ORT_UNUSED_PARAMETER(done);
|
|
return Status::OK();
|
|
}
|
|
};
|
|
class SessionStateAddGetKernelTest : public testing::TestWithParam<int> {};
|
|
|
|
TEST_P(SessionStateAddGetKernelTest, AddGetKernelTest) {
|
|
OrtThreadPoolParams to;
|
|
to.thread_pool_size = GetParam();
|
|
auto tp = concurrency::CreateThreadPool(&onnxruntime::Env::Default(), to, concurrency::ThreadPoolType::INTRA_OP);
|
|
ONNX_OPERATOR_SCHEMA(Variable)
|
|
.SetDoc("Input variable.")
|
|
.Output(0, "output_1", "docstr for output_1.", "tensor(int32)");
|
|
|
|
onnxruntime::Model model("graph_1", false, DefaultLoggingManager().DefaultLogger());
|
|
auto& graph = model.MainGraph();
|
|
|
|
ExecutionProviders execution_providers;
|
|
auto tmp_cpu_execution_provider = std::make_unique<CPUExecutionProvider>(CPUExecutionProviderInfo(false));
|
|
auto* cpu_execution_provider = tmp_cpu_execution_provider.get();
|
|
ASSERT_STATUS_OK(execution_providers.Add(kCpuExecutionProvider, std::move(tmp_cpu_execution_provider)));
|
|
|
|
DataTransferManager dtm;
|
|
profiling::Profiler profiler;
|
|
|
|
SessionOptions sess_options;
|
|
sess_options.enable_mem_pattern = true;
|
|
sess_options.execution_mode = ExecutionMode::ORT_SEQUENTIAL;
|
|
sess_options.use_deterministic_compute = false;
|
|
sess_options.enable_mem_reuse = true;
|
|
|
|
SessionState s(graph, execution_providers, tp.get(), nullptr, dtm,
|
|
DefaultLoggingManager().DefaultLogger(), profiler, sess_options);
|
|
|
|
std::vector<onnxruntime::NodeArg*> inputs;
|
|
std::vector<onnxruntime::NodeArg*> outputs;
|
|
TypeProto output_type;
|
|
output_type.mutable_tensor_type()->set_elem_type(TensorProto_DataType_INT32);
|
|
output_type.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(1);
|
|
onnxruntime::NodeArg output_arg("node_1_out_1", &output_type);
|
|
outputs.push_back(&output_arg);
|
|
onnxruntime::Node& node = graph.AddNode("node_1", "Variable", "node 1.", inputs, outputs);
|
|
auto status = graph.Resolve();
|
|
ASSERT_TRUE(status.IsOK());
|
|
auto kernel_def = KernelDefBuilder().SetName("Variable").Provider(kCpuExecutionProvider).SinceVersion(1, 10).Build();
|
|
|
|
OpKernelInfo p_info(node, *kernel_def, *cpu_execution_provider, s.GetConstantInitializedTensors(),
|
|
s.GetOrtValueNameIdxMap(), s.GetDataTransferMgr());
|
|
unique_ptr<TestOpKernel> p_kernel;
|
|
p_kernel.reset(new TestOpKernel(p_info));
|
|
size_t orig_num_outputs = p_kernel->Node().OutputDefs().size();
|
|
std::cout << "node_idx: " << node.Index() << std::endl;
|
|
|
|
KernelRegistryManager kernel_registry_manager;
|
|
status = kernel_registry_manager.RegisterKernels(execution_providers);
|
|
ASSERT_TRUE(status.IsOK()) << status.ErrorMessage();
|
|
node.SetExecutionProviderType(kCpuExecutionProvider);
|
|
std::shared_ptr<KernelRegistry> kernel_registry = std::make_shared<KernelRegistry>();
|
|
ASSERT_STATUS_OK(kernel_registry->Register(KernelCreateInfo(
|
|
std::move(kernel_def), [](FuncManager&, const OpKernelInfo& info, std::unique_ptr<OpKernel>& out) -> Status { out = std::make_unique<TestOpKernel>(info); return Status::OK(); })));
|
|
kernel_registry_manager.RegisterKernelRegistry(kernel_registry);
|
|
ASSERT_STATUS_OK(s.FinalizeSessionState(ORT_TSTR(""), kernel_registry_manager));
|
|
|
|
auto test_kernel = s.GetKernel(node.Index());
|
|
std::cout << "orig: " << orig_num_outputs << " new: " << test_kernel->Node().OutputDefs().size() << std::endl;
|
|
EXPECT_EQ(orig_num_outputs, test_kernel->Node().OutputDefs().size());
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(SessionStateTests, SessionStateAddGetKernelTest, testing::Values(0, 1));
|
|
|
|
class TestParam {
|
|
public:
|
|
int ir_version;
|
|
bool enable_mem_pattern;
|
|
int thread_count;
|
|
};
|
|
TestParam param_list[] = {{3, true, 0}, {4, true, 0}, {3, false, 0}, {4, false, 0}, {3, true, 1}, {4, true, 1}, {3, false, 1}, {4, false, 1}};
|
|
|
|
class SessionStateTestP : public testing::TestWithParam<TestParam> {};
|
|
// Test that we separate out constant and non-constant initializers correctly
|
|
TEST_P(SessionStateTestP, TestInitializerProcessing) {
|
|
const TestParam& param = GetParam();
|
|
OrtThreadPoolParams to;
|
|
to.thread_pool_size = to.thread_pool_size;
|
|
auto tp = concurrency::CreateThreadPool(&onnxruntime::Env::Default(), to, concurrency::ThreadPoolType::INTRA_OP);
|
|
|
|
std::basic_ostringstream<ORTCHAR_T> oss;
|
|
oss << ORT_TSTR("testdata/optional_inputs_ir") << param.ir_version << ORT_TSTR(".onnx");
|
|
Status status;
|
|
std::shared_ptr<Model> model;
|
|
ASSERT_TRUE((status = Model::Load(oss.str(), model, nullptr, DefaultLoggingManager().DefaultLogger())).IsOK())
|
|
<< status;
|
|
Graph& graph = model->MainGraph();
|
|
// take a copy as this gets cleared during session state initialization
|
|
InitializedTensorSet initializers = graph.GetAllInitializedTensors();
|
|
|
|
ExecutionProviders execution_providers;
|
|
CPUExecutionProviderInfo epi{false};
|
|
status =
|
|
execution_providers.Add(onnxruntime::kCpuExecutionProvider, std::make_unique<CPUExecutionProvider>(epi));
|
|
ASSERT_TRUE(status.IsOK()) << status;
|
|
|
|
KernelRegistryManager krm;
|
|
status = krm.RegisterKernels(execution_providers);
|
|
ASSERT_TRUE(status.IsOK()) << status;
|
|
|
|
DataTransferManager dtm;
|
|
profiling::Profiler profiler;
|
|
|
|
SessionOptions sess_options;
|
|
sess_options.enable_mem_pattern = param.enable_mem_pattern;
|
|
sess_options.execution_mode = ExecutionMode::ORT_SEQUENTIAL;
|
|
sess_options.use_deterministic_compute = false;
|
|
sess_options.enable_mem_reuse = true;
|
|
|
|
SessionState session_state(graph, execution_providers, tp.get(), nullptr, dtm,
|
|
DefaultLoggingManager().DefaultLogger(), profiler, sess_options);
|
|
|
|
GraphPartitioner partitioner(krm, execution_providers);
|
|
status = partitioner.Partition(graph, session_state.GetMutableFuncMgr(),
|
|
[&execution_providers](Graph& graph, bool& modified,
|
|
const IExecutionProvider& execution_provider,
|
|
const layout_transformer::DebugGraphFn& debug_graph_fn) -> Status {
|
|
return layout_transformer::TransformLayoutForEP(graph, modified, execution_provider, execution_providers.GetDefaultCpuAllocator(), debug_graph_fn);
|
|
});
|
|
ASSERT_TRUE(status.IsOK()) << status;
|
|
|
|
ASSERT_STATUS_OK(session_state.FinalizeSessionState(oss.str(), krm));
|
|
|
|
const auto& initialized_tensors = session_state.GetInitializedTensors();
|
|
const auto& const_initialized_tensors = session_state.GetConstantInitializedTensors();
|
|
|
|
ASSERT_EQ(initializers.size(), initialized_tensors.size())
|
|
<< "SessionState should have an entry for all initializers in Graph.";
|
|
|
|
if (param.ir_version < 4) {
|
|
ASSERT_EQ(initialized_tensors.size(), const_initialized_tensors.size())
|
|
<< "All initializers should be considered constant if IR version < 4.";
|
|
} else {
|
|
const auto& name_to_idx = session_state.GetOrtValueNameIdxMap();
|
|
|
|
for (const auto& entry : initializers) {
|
|
int idx;
|
|
ASSERT_STATUS_OK(name_to_idx.GetIdx(entry.first, idx));
|
|
|
|
bool found = initialized_tensors.find(idx) != initialized_tensors.cend();
|
|
ASSERT_TRUE(found) << "Missing entry for " << entry.first << " in session state initialized tensors";
|
|
|
|
if (graph_utils::IsConstantInitializer(graph, entry.first, false)) {
|
|
found = const_initialized_tensors.find(idx) != const_initialized_tensors.cend();
|
|
ASSERT_TRUE(found) << "Missing entry for " << entry.first << " in session state const initialized tensors";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test that we allocate memory for an initializer from non-arena memory even if we provide an arena-based allocator
|
|
// if the relevant session option config flag is set
|
|
// For this test we need to enable the arena-based allocator which is not supported on x86 builds, so
|
|
// enable this test only on x64 builds
|
|
#if (defined(__amd64__) || defined(_M_AMD64) || defined(__aarch64__) || defined(_M_ARM64)) && !defined(USE_MIMALLOC)
|
|
TEST(SessionStateTest, TestInitializerMemoryAllocatedUsingNonArenaMemory) {
|
|
// Part 1: Feature turned ON (i.e.) allocate from non-arena memory
|
|
{
|
|
std::basic_ostringstream<ORTCHAR_T> oss;
|
|
oss << ORT_TSTR("testdata/mul_1.onnx");
|
|
Status status;
|
|
std::shared_ptr<Model> model;
|
|
ASSERT_TRUE((status = Model::Load(oss.str(), model, nullptr, DefaultLoggingManager().DefaultLogger())).IsOK())
|
|
<< status;
|
|
Graph& graph = model->MainGraph();
|
|
|
|
ExecutionProviders execution_providers;
|
|
CPUExecutionProviderInfo epi{true}; // use an arena-based allocator for this EP
|
|
status = execution_providers.Add(onnxruntime::kCpuExecutionProvider, std::make_unique<CPUExecutionProvider>(epi));
|
|
ASSERT_TRUE(status.IsOK()) << status;
|
|
|
|
KernelRegistryManager krm;
|
|
status = krm.RegisterKernels(execution_providers);
|
|
ASSERT_TRUE(status.IsOK()) << status;
|
|
|
|
DataTransferManager dtm;
|
|
profiling::Profiler profiler;
|
|
|
|
SessionOptions sess_options;
|
|
sess_options.enable_mem_pattern = false;
|
|
sess_options.execution_mode = ExecutionMode::ORT_SEQUENTIAL;
|
|
sess_options.use_deterministic_compute = false;
|
|
sess_options.enable_mem_reuse = true;
|
|
// disable allocating initialized tensor memory from the arena(by default it will be allocated by the arena)
|
|
ASSERT_STATUS_OK(sess_options.config_options.AddConfigEntry(kOrtSessionOptionsUseDeviceAllocatorForInitializers, "1"));
|
|
|
|
SessionState session_state(graph, execution_providers, nullptr, nullptr, dtm,
|
|
DefaultLoggingManager().DefaultLogger(), profiler, sess_options);
|
|
|
|
// Partition the graph
|
|
GraphPartitioner partitioner(krm, execution_providers);
|
|
status = partitioner.Partition(graph, session_state.GetMutableFuncMgr(),
|
|
[&execution_providers](Graph& graph, bool& modified,
|
|
const IExecutionProvider& execution_provider,
|
|
const layout_transformer::DebugGraphFn& debug_graph_fn) -> Status {
|
|
return layout_transformer::TransformLayoutForEP(graph, modified, execution_provider, execution_providers.GetDefaultCpuAllocator(), debug_graph_fn);
|
|
});
|
|
ASSERT_TRUE(status.IsOK()) << status;
|
|
ASSERT_STATUS_OK(session_state.FinalizeSessionState(oss.str(), krm));
|
|
|
|
// Fetch the CPU arena-allocator from the session state
|
|
OrtMemoryInfo mem_info(CPU, OrtArenaAllocator);
|
|
AllocatorPtr alloc = session_state.GetAllocator(mem_info);
|
|
ASSERT_TRUE(alloc != nullptr);
|
|
|
|
// Get stats for the CPU arena-based allocator
|
|
AllocatorStats alloc_stats;
|
|
static_cast<BFCArena*>(alloc.get())->GetStats(&alloc_stats);
|
|
|
|
// Assert that we have made 1 Reserve() call (for allocating memory for the sole initializer in the model)
|
|
ASSERT_EQ(alloc_stats.num_reserves, 1);
|
|
}
|
|
|
|
// Part 2: Feature turned OFF (i.e.) allocate from arena memory (default behavior)
|
|
{
|
|
std::basic_ostringstream<ORTCHAR_T> oss;
|
|
oss << ORT_TSTR("testdata/mul_1.onnx");
|
|
Status status;
|
|
std::shared_ptr<Model> model;
|
|
ASSERT_TRUE((status = Model::Load(oss.str(), model, nullptr, DefaultLoggingManager().DefaultLogger())).IsOK())
|
|
<< status;
|
|
Graph& graph = model->MainGraph();
|
|
|
|
ExecutionProviders execution_providers;
|
|
CPUExecutionProviderInfo epi{true}; // use an arena-based allocator for this EP
|
|
status = execution_providers.Add(onnxruntime::kCpuExecutionProvider, std::make_unique<CPUExecutionProvider>(epi));
|
|
ASSERT_TRUE(status.IsOK()) << status;
|
|
|
|
KernelRegistryManager krm;
|
|
status = krm.RegisterKernels(execution_providers);
|
|
ASSERT_TRUE(status.IsOK()) << status;
|
|
|
|
DataTransferManager dtm;
|
|
profiling::Profiler profiler;
|
|
|
|
SessionOptions sess_options;
|
|
sess_options.enable_mem_pattern = false;
|
|
sess_options.execution_mode = ExecutionMode::ORT_SEQUENTIAL;
|
|
sess_options.use_deterministic_compute = false;
|
|
sess_options.enable_mem_reuse = true;
|
|
|
|
SessionState session_state(graph, execution_providers, nullptr, nullptr, dtm,
|
|
DefaultLoggingManager().DefaultLogger(), profiler, sess_options);
|
|
|
|
// Partition the graph
|
|
GraphPartitioner partitioner(krm, execution_providers);
|
|
status = partitioner.Partition(graph, session_state.GetMutableFuncMgr(),
|
|
[&execution_providers](Graph& graph, bool& modified,
|
|
const IExecutionProvider& execution_provider,
|
|
const layout_transformer::DebugGraphFn& debug_graph_fn) -> Status {
|
|
return layout_transformer::TransformLayoutForEP(graph, modified, execution_provider, execution_providers.GetDefaultCpuAllocator(), debug_graph_fn);
|
|
});
|
|
ASSERT_TRUE(status.IsOK()) << status;
|
|
|
|
// Finalize the session state
|
|
ASSERT_STATUS_OK(session_state.FinalizeSessionState(oss.str(), krm));
|
|
|
|
// Fetch the CPU arena-allocator from the session state
|
|
OrtMemoryInfo mem_info(CPU, OrtArenaAllocator);
|
|
AllocatorPtr alloc = session_state.GetAllocator(mem_info);
|
|
ASSERT_TRUE(alloc != nullptr);
|
|
|
|
// Get stats for the CPU arena-based allocator
|
|
AllocatorStats alloc_stats;
|
|
static_cast<BFCArena*>(alloc.get())->GetStats(&alloc_stats);
|
|
|
|
// Assert that we have made no Reserve() calls
|
|
ASSERT_EQ(alloc_stats.num_reserves, 0);
|
|
|
|
// Assert to ensure an allocation was made for the initializer through the arena allocator (Alloc() was invoked)
|
|
ASSERT_EQ(alloc_stats.num_allocs, 1);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
INSTANTIATE_TEST_SUITE_P(SessionStateTests, SessionStateTestP, testing::ValuesIn(param_list));
|
|
|
|
#ifndef ENABLE_TRAINING_CORE
|
|
class PrePackingTestOpKernel : public OpKernel {
|
|
public:
|
|
PrePackingTestOpKernel(const OpKernelInfo& info) : OpKernel(info) {}
|
|
Status Compute(OpKernelContext* context) const override {
|
|
ORT_UNUSED_PARAMETER(context);
|
|
return Status::OK();
|
|
}
|
|
|
|
Status UseSharedPrePackedBuffers(std::vector<BufferUniquePtr>& prepacked_buffers,
|
|
int input_idx,
|
|
/*out*/ bool& used_shared_buffers) override {
|
|
ORT_UNUSED_PARAMETER(input_idx);
|
|
|
|
weight_packed_ = std::move(prepacked_buffers[0]);
|
|
used_shared_buffers = true;
|
|
++store_pre_packed_weight_calls_count;
|
|
return Status::OK();
|
|
}
|
|
|
|
Status PrePack(const Tensor& tensor, int input_idx, AllocatorPtr alloc,
|
|
/*out*/ bool& is_packed, /*out*/ PrePackedWeights* prepacked_weights) override {
|
|
ORT_UNUSED_PARAMETER(tensor);
|
|
ORT_UNUSED_PARAMETER(input_idx);
|
|
|
|
weight_packed_ = BufferUniquePtr(alloc->Alloc(8), BufferDeleter(alloc));
|
|
float* data_weights_packed = reinterpret_cast<float*>(weight_packed_.get());
|
|
data_weights_packed[0] = 1.2345f;
|
|
data_weights_packed[1] = data_weights_packed[0] * 2.f;
|
|
|
|
if (prepacked_weights != nullptr) {
|
|
prepacked_weights->buffers_.push_back(std::move(weight_packed_));
|
|
prepacked_weights->buffer_sizes_.push_back(8);
|
|
}
|
|
|
|
is_packed = true;
|
|
++prepack_calls_count;
|
|
return Status::OK();
|
|
}
|
|
|
|
int prepack_calls_count = 0;
|
|
int store_pre_packed_weight_calls_count = 0;
|
|
BufferUniquePtr weight_packed_;
|
|
};
|
|
|
|
static void CreateSimpleGraph(Graph& graph) {
|
|
// node creation and placement
|
|
TypeProto type;
|
|
type.mutable_tensor_type()->set_elem_type(TensorProto_DataType_FLOAT);
|
|
type.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(1);
|
|
|
|
std::vector<onnxruntime::NodeArg*> inputs;
|
|
onnxruntime::NodeArg input_0_arg("node_0_input_0", &type);
|
|
onnxruntime::NodeArg input_1_arg("node_0_input_1", &type);
|
|
inputs.push_back(&input_0_arg);
|
|
inputs.push_back(&input_1_arg);
|
|
|
|
std::vector<onnxruntime::NodeArg*> outputs;
|
|
onnxruntime::NodeArg output_arg("node_0_output_0", &type);
|
|
outputs.push_back(&output_arg);
|
|
|
|
graph.AddNode("node_0", "PrePackingTest", "node 0", inputs, outputs);
|
|
|
|
// add an initializer
|
|
ONNX_NAMESPACE::TensorProto tensor;
|
|
tensor.add_dims(1);
|
|
tensor.add_float_data(1.0f);
|
|
tensor.set_data_type(TensorProto_DataType_FLOAT);
|
|
tensor.set_name("node_0_input_1");
|
|
graph.AddInitializedTensor(tensor);
|
|
|
|
auto status = graph.Resolve();
|
|
ASSERT_TRUE(status.IsOK());
|
|
}
|
|
|
|
static const ONNX_NAMESPACE::GraphProto CreateSubgraph(bool then_branch) {
|
|
Model model(then_branch ? "If_then" : "If_else", false, DefaultLoggingManager().DefaultLogger());
|
|
auto& graph = model.MainGraph();
|
|
|
|
std::vector<NodeArg*> inputs;
|
|
std::vector<NodeArg*> outputs;
|
|
|
|
const std::string suffix = then_branch ? "0" : "1";
|
|
|
|
// graph input has to have type and rank even though it's an outer scope value.
|
|
TypeProto type_float;
|
|
type_float.mutable_tensor_type()->set_elem_type(TensorProto_DataType_FLOAT);
|
|
type_float.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(1);
|
|
|
|
// outer scope values
|
|
auto& if_shared = graph.GetOrCreateNodeArg("if_shared", &type_float);
|
|
auto& if_input = graph.GetOrCreateNodeArg("if_input_" + suffix, &type_float);
|
|
|
|
// add so that we don't end up with it being considered a graph input
|
|
graph.AddOuterScopeNodeArg("if_shared");
|
|
graph.AddOuterScopeNodeArg("if_input_" + suffix);
|
|
|
|
auto& if_out = graph.GetOrCreateNodeArg("if_output_" + suffix, &type_float);
|
|
|
|
inputs = {&if_shared, &if_input};
|
|
outputs = {&if_out};
|
|
|
|
graph.AddNode("if_node_" + suffix, "PrePackingTest", "if node " + suffix, inputs, outputs);
|
|
|
|
auto status = graph.Resolve();
|
|
EXPECT_EQ(status, Status::OK());
|
|
|
|
auto& proto = graph.ToGraphProto();
|
|
|
|
return proto;
|
|
}
|
|
|
|
static void CreateGraphWithSubgraph(Graph& graph) {
|
|
TypeProto type_float;
|
|
type_float.mutable_tensor_type()->set_elem_type(TensorProto_DataType_FLOAT);
|
|
type_float.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(1);
|
|
|
|
{
|
|
std::vector<onnxruntime::NodeArg*> inputs;
|
|
onnxruntime::NodeArg input_0_arg("if_input_0", &type_float);
|
|
onnxruntime::NodeArg input_1_arg("if_input_1", &type_float);
|
|
inputs.push_back(&input_0_arg);
|
|
inputs.push_back(&input_1_arg);
|
|
|
|
std::vector<onnxruntime::NodeArg*> outputs;
|
|
onnxruntime::NodeArg output_arg("node_0_output_0", &type_float);
|
|
outputs.push_back(&output_arg);
|
|
|
|
graph.AddNode("node_0", "PrePackingTest", "node 0", inputs, outputs);
|
|
}
|
|
|
|
{
|
|
TypeProto type_bool;
|
|
type_bool.mutable_tensor_type()->set_elem_type(TensorProto_DataType_BOOL);
|
|
type_bool.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(1);
|
|
|
|
onnxruntime::NodeArg bool_arg("bool_arg", &type_bool);
|
|
|
|
std::vector<onnxruntime::NodeArg*> outputs;
|
|
onnxruntime::NodeArg output_arg("output_arg", &type_float);
|
|
outputs.push_back(&output_arg);
|
|
|
|
auto& if_node = graph.AddNode("if", "If", "If node", {&bool_arg}, outputs);
|
|
|
|
auto then_proto = CreateSubgraph(true);
|
|
auto else_proto = CreateSubgraph(false);
|
|
if_node.AddAttribute("then_branch", then_proto);
|
|
if_node.AddAttribute("else_branch", else_proto);
|
|
}
|
|
|
|
// add an initializer
|
|
ONNX_NAMESPACE::TensorProto tensor;
|
|
tensor.add_dims(1);
|
|
tensor.add_float_data(1.0f);
|
|
tensor.set_data_type(TensorProto_DataType_FLOAT);
|
|
tensor.set_name("if_shared");
|
|
graph.AddInitializedTensor(tensor);
|
|
|
|
auto status = graph.Resolve();
|
|
ASSERT_TRUE(status.IsOK());
|
|
}
|
|
|
|
static void PlaceAllNodesToCPUEP(Graph& graph) {
|
|
for (auto& node : graph.Nodes()) {
|
|
node.SetExecutionProviderType(kCpuExecutionProvider);
|
|
if (node.ContainsSubgraph()) {
|
|
for (auto& entry : node.GetAttributeNameToMutableSubgraphMap()) {
|
|
Graph* subgraph = entry.second;
|
|
PlaceAllNodesToCPUEP(*subgraph);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PrepackingTestParam {
|
|
bool test_subgraph;
|
|
bool test_prepacking;
|
|
};
|
|
|
|
class SessionStatePrepackingTest : public testing::TestWithParam<PrepackingTestParam> {};
|
|
TEST_P(SessionStatePrepackingTest, PrePackingTest) {
|
|
PrepackingTestParam test_param = GetParam();
|
|
|
|
OrtThreadPoolParams to;
|
|
auto tp = concurrency::CreateThreadPool(&onnxruntime::Env::Default(), to, concurrency::ThreadPoolType::INTRA_OP);
|
|
ONNX_OPERATOR_SCHEMA(PrePackingTest)
|
|
.SetDoc("Faking Node for PrePacking")
|
|
.Input(0, "Input_0", "input 0", "tensor(float)")
|
|
.Input(1, "Input_1", "input 1", "tensor(float)")
|
|
.Output(0, "output_0", "docstr for output_0.", "tensor(float)");
|
|
|
|
ExecutionProviders execution_providers;
|
|
auto cpu_execution_provider = std::make_unique<CPUExecutionProvider>(CPUExecutionProviderInfo(false));
|
|
ASSERT_STATUS_OK(execution_providers.Add(kCpuExecutionProvider, std::move(cpu_execution_provider)));
|
|
|
|
DataTransferManager dtm;
|
|
profiling::Profiler profiler;
|
|
|
|
std::unordered_map<std::string, int> domain_to_version;
|
|
domain_to_version[kOnnxDomain] = 11;
|
|
Model model("graph_main", false, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(),
|
|
domain_to_version, std::vector<ONNX_NAMESPACE::FunctionProto>(),
|
|
DefaultLoggingManager().DefaultLogger());
|
|
|
|
// onnxruntime::Model model("graph_main", false, DefaultLoggingManager().DefaultLogger());
|
|
if (test_param.test_subgraph) {
|
|
CreateGraphWithSubgraph(model.MainGraph());
|
|
} else {
|
|
CreateSimpleGraph(model.MainGraph());
|
|
}
|
|
|
|
SessionOptions sess_options;
|
|
sess_options.enable_mem_pattern = true;
|
|
sess_options.execution_mode = ExecutionMode::ORT_SEQUENTIAL;
|
|
sess_options.use_deterministic_compute = false;
|
|
sess_options.enable_mem_reuse = true;
|
|
sess_options.config_options.configurations[kOrtSessionOptionsConfigDisablePrepacking] = test_param.test_prepacking ? "0" : "1";
|
|
|
|
SessionState session_state(model.MainGraph(),
|
|
execution_providers,
|
|
tp.get(),
|
|
nullptr, /*inter_op_thread_pool*/
|
|
dtm,
|
|
DefaultLoggingManager().DefaultLogger(),
|
|
profiler,
|
|
sess_options);
|
|
|
|
KernelRegistryManager kernel_registry_manager;
|
|
Status status = kernel_registry_manager.RegisterKernels(execution_providers);
|
|
ASSERT_TRUE(status.IsOK()) << status.ErrorMessage();
|
|
std::shared_ptr<KernelRegistry> kernel_registry = std::make_shared<KernelRegistry>();
|
|
auto kernel_def = KernelDefBuilder().SetName("PrePackingTest").Provider(kCpuExecutionProvider).SinceVersion(1).Build();
|
|
ASSERT_STATUS_OK(kernel_registry->Register(
|
|
KernelCreateInfo(std::move(kernel_def),
|
|
[](FuncManager&, const OpKernelInfo& info, std::unique_ptr<OpKernel>& out) -> Status { out = std::make_unique<PrePackingTestOpKernel>(info); return Status::OK(); })));
|
|
kernel_registry_manager.RegisterKernelRegistry(kernel_registry);
|
|
|
|
PlaceAllNodesToCPUEP(model.MainGraph());
|
|
ASSERT_STATUS_OK(session_state.FinalizeSessionState(std::basic_string<PATH_CHAR_TYPE>(),
|
|
kernel_registry_manager));
|
|
|
|
const auto& const_initialized_tensors = session_state.GetConstantInitializedTensors();
|
|
// check prepacking
|
|
ASSERT_EQ(const_initialized_tensors.size(), size_t(test_param.test_prepacking ? 0 : 1));
|
|
}
|
|
|
|
class SessionStateTestSharedInitalizersWithPrePacking : public ::testing::Test {
|
|
protected:
|
|
ExecutionProviders execution_providers;
|
|
std::unordered_map<std::string, int> domain_to_version;
|
|
DataTransferManager dtm;
|
|
profiling::Profiler profiler;
|
|
KernelRegistryManager kernel_registry_manager;
|
|
std::unique_ptr<concurrency::ThreadPool> tp;
|
|
|
|
void SetUp() override {
|
|
OrtThreadPoolParams to;
|
|
tp = concurrency::CreateThreadPool(&onnxruntime::Env::Default(), to, concurrency::ThreadPoolType::INTRA_OP);
|
|
ONNX_OPERATOR_SCHEMA(PrePackingTest)
|
|
.SetDoc("Faking Node for PrePacking")
|
|
.Input(0, "Input_0", "input 0", "tensor(float)")
|
|
.Input(1, "Input_1", "input 1", "tensor(float)")
|
|
.Output(0, "output_0", "docstr for output_0.", "tensor(float)");
|
|
|
|
auto cpu_execution_provider = std::make_unique<CPUExecutionProvider>(CPUExecutionProviderInfo(false));
|
|
ASSERT_STATUS_OK(execution_providers.Add(kCpuExecutionProvider, std::move(cpu_execution_provider)));
|
|
|
|
domain_to_version[kOnnxDomain] = 11;
|
|
|
|
Status status = kernel_registry_manager.RegisterKernels(execution_providers);
|
|
ASSERT_TRUE(status.IsOK()) << status.ErrorMessage();
|
|
std::shared_ptr<KernelRegistry> kernel_registry = std::make_shared<KernelRegistry>();
|
|
auto kernel_def = KernelDefBuilder().SetName("PrePackingTest").Provider(kCpuExecutionProvider).SinceVersion(1).Build();
|
|
ASSERT_STATUS_OK(kernel_registry->Register(
|
|
KernelCreateInfo(std::move(kernel_def),
|
|
[](FuncManager&, const OpKernelInfo& info, std::unique_ptr<OpKernel>& out) -> Status { out = std::make_unique<PrePackingTestOpKernel>(info); return Status::OK(); })));
|
|
kernel_registry_manager.RegisterKernelRegistry(kernel_registry);
|
|
}
|
|
};
|
|
|
|
// Pre-packing enabled + no shared initializers = no pre-packed weights caching
|
|
TEST_F(SessionStateTestSharedInitalizersWithPrePacking, test1) {
|
|
SessionOptions sess_options;
|
|
sess_options.enable_mem_pattern = true;
|
|
sess_options.execution_mode = ExecutionMode::ORT_SEQUENTIAL;
|
|
sess_options.use_deterministic_compute = false;
|
|
sess_options.enable_mem_reuse = true;
|
|
// Enable pre-packing
|
|
sess_options.config_options.configurations[kOrtSessionOptionsConfigDisablePrepacking] = "0";
|
|
|
|
// First session/model
|
|
Model model_1("graph_main", false, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(),
|
|
domain_to_version, std::vector<ONNX_NAMESPACE::FunctionProto>(),
|
|
DefaultLoggingManager().DefaultLogger());
|
|
|
|
CreateSimpleGraph(model_1.MainGraph());
|
|
PlaceAllNodesToCPUEP(model_1.MainGraph());
|
|
SessionState session_state_1(model_1.MainGraph(),
|
|
execution_providers,
|
|
tp.get(),
|
|
nullptr, /*inter_op_thread_pool*/
|
|
dtm,
|
|
DefaultLoggingManager().DefaultLogger(),
|
|
profiler,
|
|
sess_options);
|
|
|
|
ASSERT_STATUS_OK(session_state_1.FinalizeSessionState(std::basic_string<PATH_CHAR_TYPE>(),
|
|
kernel_registry_manager));
|
|
|
|
const auto* kernel = reinterpret_cast<const PrePackingTestOpKernel*>(session_state_1.GetKernel(0));
|
|
|
|
// Assert that a pre-pack call was made and that no mechanism to store weight from shared container was invoked
|
|
ASSERT_EQ(session_state_1.GetNumberOfPrepacksCounter(), static_cast<size_t>(1));
|
|
ASSERT_EQ(kernel->prepack_calls_count, 1);
|
|
ASSERT_EQ(kernel->store_pre_packed_weight_calls_count, 0);
|
|
|
|
// Second session/model
|
|
Model model_2("graph_main", false, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(),
|
|
domain_to_version, std::vector<ONNX_NAMESPACE::FunctionProto>(),
|
|
DefaultLoggingManager().DefaultLogger());
|
|
|
|
CreateSimpleGraph(model_2.MainGraph());
|
|
PlaceAllNodesToCPUEP(model_2.MainGraph());
|
|
SessionState session_state_2(model_2.MainGraph(),
|
|
execution_providers,
|
|
tp.get(),
|
|
nullptr, /*inter_op_thread_pool*/
|
|
dtm,
|
|
DefaultLoggingManager().DefaultLogger(),
|
|
profiler,
|
|
sess_options);
|
|
|
|
ASSERT_STATUS_OK(session_state_2.FinalizeSessionState(std::basic_string<PATH_CHAR_TYPE>(),
|
|
kernel_registry_manager));
|
|
|
|
kernel = reinterpret_cast<const PrePackingTestOpKernel*>(session_state_2.GetKernel(0));
|
|
|
|
// Assert that a pre-pack call was made and that no mechanism to store weight from shared container was invoked
|
|
ASSERT_EQ(session_state_2.GetNumberOfPrepacksCounter(), static_cast<size_t>(1));
|
|
ASSERT_EQ(kernel->prepack_calls_count, 1);
|
|
ASSERT_EQ(kernel->store_pre_packed_weight_calls_count, 0);
|
|
}
|
|
|
|
// Pre-packing enabled + shared initializers + no pre-packed weights container = no pre-packed weights caching
|
|
TEST_F(SessionStateTestSharedInitalizersWithPrePacking, test2) {
|
|
SessionOptions sess_options;
|
|
sess_options.enable_mem_pattern = true;
|
|
sess_options.execution_mode = ExecutionMode::ORT_SEQUENTIAL;
|
|
sess_options.use_deterministic_compute = false;
|
|
sess_options.enable_mem_reuse = true;
|
|
// Enable pre-packing
|
|
sess_options.config_options.configurations[kOrtSessionOptionsConfigDisablePrepacking] = "0";
|
|
|
|
// Enable shared initializer
|
|
OrtMemoryInfo mem_info(CPU, OrtDeviceAllocator);
|
|
std::vector<float> float_data(1, 1);
|
|
auto value = std::make_unique<OrtValue>();
|
|
Tensor::InitOrtValue(DataTypeImpl::GetType<float>(),
|
|
TensorShape(std::vector<int64_t>{1}), reinterpret_cast<void*>(float_data.data()), mem_info, *value);
|
|
|
|
ASSERT_STATUS_OK(sess_options.AddInitializer("node_0_input_1", value.get()));
|
|
|
|
// First session/model
|
|
Model model_1("graph_main", false, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(),
|
|
domain_to_version, std::vector<ONNX_NAMESPACE::FunctionProto>(),
|
|
DefaultLoggingManager().DefaultLogger());
|
|
|
|
CreateSimpleGraph(model_1.MainGraph());
|
|
PlaceAllNodesToCPUEP(model_1.MainGraph());
|
|
SessionState session_state_1(model_1.MainGraph(),
|
|
execution_providers,
|
|
tp.get(),
|
|
nullptr, /*inter_op_thread_pool*/
|
|
dtm,
|
|
DefaultLoggingManager().DefaultLogger(),
|
|
profiler,
|
|
sess_options);
|
|
|
|
ASSERT_STATUS_OK(session_state_1.FinalizeSessionState(std::basic_string<PATH_CHAR_TYPE>(),
|
|
kernel_registry_manager));
|
|
|
|
const auto* kernel = reinterpret_cast<const PrePackingTestOpKernel*>(session_state_1.GetKernel(0));
|
|
|
|
// Assert that a pre-pack call was made and that no mechanism to store weight from shared container was invoked
|
|
ASSERT_EQ(session_state_1.GetNumberOfPrepacksCounter(), static_cast<size_t>(1));
|
|
ASSERT_EQ(kernel->prepack_calls_count, 1);
|
|
ASSERT_EQ(kernel->store_pre_packed_weight_calls_count, 0);
|
|
|
|
// Second session/model
|
|
Model model_2("graph_main", false, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(),
|
|
domain_to_version, std::vector<ONNX_NAMESPACE::FunctionProto>(),
|
|
DefaultLoggingManager().DefaultLogger());
|
|
|
|
CreateSimpleGraph(model_2.MainGraph());
|
|
PlaceAllNodesToCPUEP(model_2.MainGraph());
|
|
SessionState session_state_2(model_2.MainGraph(),
|
|
execution_providers,
|
|
tp.get(),
|
|
nullptr, /*inter_op_thread_pool*/
|
|
dtm,
|
|
DefaultLoggingManager().DefaultLogger(),
|
|
profiler,
|
|
sess_options);
|
|
|
|
ASSERT_STATUS_OK(session_state_2.FinalizeSessionState(std::basic_string<PATH_CHAR_TYPE>(),
|
|
kernel_registry_manager));
|
|
|
|
kernel = reinterpret_cast<const PrePackingTestOpKernel*>(session_state_2.GetKernel(0));
|
|
|
|
// Assert that a pre-pack call was made and that no mechanism to store weight from shared container was invoked
|
|
ASSERT_EQ(session_state_2.GetNumberOfPrepacksCounter(), static_cast<size_t>(1));
|
|
ASSERT_EQ(kernel->prepack_calls_count, 1);
|
|
ASSERT_EQ(kernel->store_pre_packed_weight_calls_count, 0);
|
|
}
|
|
|
|
// Pre-packing enabled + shared initializers + pre-packed weights container = pre-packed weights caching enabled
|
|
TEST_F(SessionStateTestSharedInitalizersWithPrePacking, test3) {
|
|
SessionOptions sess_options;
|
|
sess_options.enable_mem_pattern = true;
|
|
sess_options.execution_mode = ExecutionMode::ORT_SEQUENTIAL;
|
|
sess_options.use_deterministic_compute = false;
|
|
sess_options.enable_mem_reuse = true;
|
|
// Enable pre-packing
|
|
sess_options.config_options.configurations[kOrtSessionOptionsConfigDisablePrepacking] = "0";
|
|
|
|
// Enable shared initializer
|
|
OrtMemoryInfo mem_info(CPU, OrtDeviceAllocator);
|
|
std::vector<float> float_data(1, 1);
|
|
auto value = std::make_unique<OrtValue>();
|
|
Tensor::InitOrtValue(DataTypeImpl::GetType<float>(), TensorShape(std::vector<int64_t>{1}),
|
|
reinterpret_cast<void*>(float_data.data()), mem_info, *value);
|
|
|
|
ASSERT_STATUS_OK(sess_options.AddInitializer("node_0_input_1", value.get()));
|
|
|
|
// Enable pre-packed weights container
|
|
PrepackedWeightsContainer prepacked_weights_container;
|
|
|
|
// First session/model
|
|
Model model_1("graph_main", false, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(),
|
|
domain_to_version, std::vector<ONNX_NAMESPACE::FunctionProto>(),
|
|
DefaultLoggingManager().DefaultLogger());
|
|
|
|
CreateSimpleGraph(model_1.MainGraph());
|
|
PlaceAllNodesToCPUEP(model_1.MainGraph());
|
|
SessionState session_state_1(model_1.MainGraph(),
|
|
execution_providers,
|
|
tp.get(),
|
|
nullptr, /*inter_op_thread_pool*/
|
|
dtm,
|
|
DefaultLoggingManager().DefaultLogger(),
|
|
profiler,
|
|
sess_options,
|
|
&prepacked_weights_container);
|
|
|
|
ASSERT_STATUS_OK(session_state_1.FinalizeSessionState(std::basic_string<PATH_CHAR_TYPE>(),
|
|
kernel_registry_manager));
|
|
|
|
const auto* kernel = reinterpret_cast<const PrePackingTestOpKernel*>(session_state_1.GetKernel(0));
|
|
// Assert that a pre-pack call was made
|
|
ASSERT_EQ(session_state_1.GetNumberOfPrepacksCounter(), static_cast<size_t>(1));
|
|
ASSERT_EQ(kernel->prepack_calls_count, 1);
|
|
// Assert that we made a call to store pre-packed weight from a shared container
|
|
ASSERT_EQ(kernel->store_pre_packed_weight_calls_count, 1);
|
|
// The weight to be "stored" is the same weight that we got by invoking PrePack() in the step above.
|
|
// Hence, assert that it wasn't a "cached" pre-packed weight (i.e.) pre-packed weight
|
|
// from another instance of the same op_type consuming the same constant initializer.
|
|
ASSERT_EQ(session_state_1.GetUsedSharedPrePackedWeightCounter(), static_cast<size_t>(0));
|
|
|
|
// Second session/model
|
|
Model model_2("graph_main", false, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(),
|
|
domain_to_version, std::vector<ONNX_NAMESPACE::FunctionProto>(),
|
|
DefaultLoggingManager().DefaultLogger());
|
|
|
|
CreateSimpleGraph(model_2.MainGraph());
|
|
PlaceAllNodesToCPUEP(model_2.MainGraph());
|
|
SessionState session_state_2(model_2.MainGraph(),
|
|
execution_providers,
|
|
tp.get(),
|
|
nullptr, /*inter_op_thread_pool*/
|
|
dtm,
|
|
DefaultLoggingManager().DefaultLogger(),
|
|
profiler,
|
|
sess_options,
|
|
&prepacked_weights_container);
|
|
|
|
ASSERT_STATUS_OK(session_state_2.FinalizeSessionState(std::basic_string<PATH_CHAR_TYPE>(),
|
|
kernel_registry_manager));
|
|
|
|
// Assert that a pre-pack call was made
|
|
ASSERT_EQ(session_state_2.GetNumberOfPrepacksCounter(), static_cast<size_t>(1));
|
|
ASSERT_EQ(kernel->prepack_calls_count, 1);
|
|
// Assert that we made a call to store pre-packed weight from a shared container
|
|
ASSERT_EQ(kernel->store_pre_packed_weight_calls_count, 1);
|
|
// The weight to be "stored" is a "cached" weight (i.e.) a pre-packed weight
|
|
// from another instance of the same op_type consuming the same constant initializer.
|
|
// Assert this.
|
|
ASSERT_EQ(session_state_2.GetUsedSharedPrePackedWeightCounter(), static_cast<size_t>(1));
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(SessionStateTests,
|
|
SessionStatePrepackingTest,
|
|
testing::Values(PrepackingTestParam{false, false},
|
|
PrepackingTestParam{false, true},
|
|
PrepackingTestParam{true, false},
|
|
PrepackingTestParam{true, true}));
|
|
#endif
|
|
|
|
} // namespace test
|
|
} // namespace onnxruntime
|