mirror of
https://github.com/zebrajr/pytorch.git
synced 2025-12-07 12:21:27 +01:00
[SR] Add BlockRunner and handle sub-blocks (#69834)
Summary: Pull Request resolved: https://github.com/pytorch/pytorch/pull/69834 * Modify the `StaticModule` constructor to handle index initialization for sub-blocks. * Add a new class `StaticRuntimeBlockRunner`. This class is almost exactly like what we've been calling `StaticRuntime` up to this point, except that it does not own a `values_` array. All `StaticRuntimeBlockRunners` hold an unowned reference to a `values_` array owned by `StaticRuntime`. This is a useful abstraction for implementing control flow - it gives us a way for sub-blocks to look up values from surrounding scopes! ghstack-source-id: 148086245 Test Plan: `buck test caffe2/benchmarks/static_runtime/...` Reviewed By: d1jang Differential Revision: D33028039 fbshipit-source-id: 4f01417bad51a0cf09b1680a518308da647be1f6
This commit is contained in:
parent
bdcc5f5f47
commit
3a9feffd92
|
|
@ -106,7 +106,8 @@ TEST(StaticModule, ValueGroup) {
|
|||
torch::jit::StaticModule sm(input_graph);
|
||||
const Graph& graph = sm.graph();
|
||||
std::vector<const Node*> nodes(graph.nodes().begin(), graph.nodes().end());
|
||||
const auto& value_group = sm.value_group();
|
||||
auto* root_block = sm.root_block();
|
||||
const auto& value_group = sm.block_info(root_block).value_group();
|
||||
|
||||
std::vector<const Value*> expected_input_aliases{
|
||||
graph.inputs()[0], graph.inputs()[1], nodes[0]->output()};
|
||||
|
|
@ -138,9 +139,11 @@ TEST(StaticModule, IsOptimizableContainerType_NonOptimizableInputs) {
|
|||
|
||||
auto sm = makeStaticModuleFromScript(src);
|
||||
const auto& graph = sm.graph();
|
||||
auto* root_block = sm.root_block();
|
||||
const auto& block_info = sm.block_info(root_block);
|
||||
|
||||
for (const Node* n : graph.nodes()) {
|
||||
EXPECT_FALSE(sm.is_optimizable_container_type(n));
|
||||
EXPECT_FALSE(block_info.node_is_optimizable_container_type(n));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,9 +161,11 @@ TEST(StaticModule, IsOptimizableContainerType_WrongType) {
|
|||
|
||||
auto sm = makeStaticModuleFromScript(src);
|
||||
const auto& graph = sm.graph();
|
||||
auto* root_block = sm.root_block();
|
||||
const auto& block_info = sm.block_info(root_block);
|
||||
|
||||
for (const Node* n : graph.nodes()) {
|
||||
EXPECT_FALSE(sm.is_optimizable_container_type(n));
|
||||
EXPECT_FALSE(block_info.node_is_optimizable_container_type(n));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -175,12 +180,14 @@ TEST(StaticModule, IsOptimizableContainerType_CanUseOutVariant) {
|
|||
)JIT";
|
||||
auto sm = makeStaticModuleFromScript(src);
|
||||
const auto& graph = sm.graph();
|
||||
auto* root_block = sm.root_block();
|
||||
const auto& block_info = sm.block_info(root_block);
|
||||
|
||||
for (const Node* n : graph.nodes()) {
|
||||
if (n->kind() == c10::prim::ListConstruct) {
|
||||
EXPECT_TRUE(sm.is_optimizable_container_type(n));
|
||||
EXPECT_TRUE(block_info.node_is_optimizable_container_type(n));
|
||||
} else {
|
||||
EXPECT_FALSE(sm.is_optimizable_container_type(n));
|
||||
EXPECT_FALSE(block_info.node_is_optimizable_container_type(n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1050,7 +1057,8 @@ TEST(ManagedTensorRanges, NoAliases) {
|
|||
auto* z = vmap["z"];
|
||||
|
||||
FastSet<const Value*> managed_tensors = {y, z};
|
||||
ManagedTensorRanges ranges(graph, managed_tensors);
|
||||
AliasDb alias_db(graph);
|
||||
auto ranges = ManagedTensorRanges(*graph->block(), alias_db, managed_tensors);
|
||||
|
||||
std::vector<Node*> nodes(
|
||||
graph->block()->nodes().begin(), graph->block()->nodes().end());
|
||||
|
|
@ -1089,7 +1097,8 @@ TEST(ManagedTensorRanges, AliasExtendingLifetimes) {
|
|||
auto* z2 = vmap["z2"];
|
||||
|
||||
FastSet<const Value*> managed_tensors = {y, z1, z2};
|
||||
ManagedTensorRanges ranges(graph, managed_tensors);
|
||||
AliasDb alias_db(graph);
|
||||
auto ranges = ManagedTensorRanges(*graph->block(), alias_db, managed_tensors);
|
||||
|
||||
std::vector<Node*> nodes(
|
||||
graph->block()->nodes().begin(), graph->block()->nodes().end());
|
||||
|
|
@ -1135,7 +1144,8 @@ TEST(ManagedTensorRanges, LifetimeOverlap) {
|
|||
auto* d = vmap["d"];
|
||||
auto* e = vmap["e"];
|
||||
|
||||
ManagedTensorRanges ranges(graph, {b, c, d, e});
|
||||
AliasDb alias_db(graph);
|
||||
auto ranges = ManagedTensorRanges(*graph->block(), alias_db, {b, c, d, e});
|
||||
const std::vector<std::pair<Value*, Value*>> overlapping_values{
|
||||
{b, c}, {c, d}, {c, e}};
|
||||
|
||||
|
|
@ -1169,7 +1179,8 @@ TEST(ManagedTensorRanges, OverlappingLifetimesContainers) {
|
|||
auto* c = vmap["c"];
|
||||
auto* d = vmap["d"];
|
||||
|
||||
ManagedTensorRanges ranges(graph, {b, c, d});
|
||||
AliasDb alias_db(graph);
|
||||
auto ranges = ManagedTensorRanges(*graph->block(), alias_db, {b, c, d});
|
||||
|
||||
EXPECT_TRUE(ranges.lifetimesOverlap(b, c));
|
||||
EXPECT_TRUE(ranges.lifetimesOverlap(b, d));
|
||||
|
|
@ -1189,7 +1200,8 @@ TEST(ManagedTensorRanges, OverlappingLifetimesOutputs) {
|
|||
auto* b = vmap["b"];
|
||||
auto* output = vmap["output"];
|
||||
|
||||
ManagedTensorRanges ranges(graph, {b, output});
|
||||
AliasDb alias_db(graph);
|
||||
auto ranges = ManagedTensorRanges(*graph->block(), alias_db, {b, output});
|
||||
|
||||
EXPECT_TRUE(ranges.lifetimesOverlap(b, output));
|
||||
}
|
||||
|
|
@ -1275,7 +1287,9 @@ void testAssignStorageToManagedTensors(
|
|||
}
|
||||
ASSERT_EQ(managed_tensor_values.size(), tensor_value_to_tensor.size());
|
||||
|
||||
auto ranges = ManagedTensorRanges(graph, managed_tensor_values);
|
||||
AliasDb alias_db(graph);
|
||||
auto ranges =
|
||||
ManagedTensorRanges(*graph->block(), alias_db, managed_tensor_values);
|
||||
auto groups = assignStorageToManagedTensors(
|
||||
graph->block()->nodes(), ranges, tensor_value_to_tensor);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <torch/csrc/jit/passes/remove_mutation.h>
|
||||
#include <torch/csrc/jit/passes/subgraph_rewrite.h>
|
||||
#include <torch/csrc/jit/passes/variadic_ops.h>
|
||||
#include <torch/csrc/jit/runtime/graph_iterator.h>
|
||||
#include <torch/csrc/jit/runtime/static/fusion.h>
|
||||
#include <torch/csrc/jit/runtime/static/memory_planner.h>
|
||||
#include <torch/csrc/jit/runtime/static/ops.h>
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
#endif
|
||||
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
|
|
@ -58,8 +60,8 @@ bool isUnsupportedOp(const NodeKind& kind) {
|
|||
return kind == aten::__is__ || kind == aten::__isnot__;
|
||||
}
|
||||
|
||||
// graph must be frozen or canEnableStaticRuntime would return false if there's
|
||||
// any prim::CallMethod op left in the graph
|
||||
// graph must be frozen or canEnableStaticRuntime would return false
|
||||
// if there's any prim::CallMethod op left in the graph
|
||||
bool canEnableStaticRuntime(const std::shared_ptr<torch::jit::Graph>& graph) {
|
||||
// check for sub-blocks
|
||||
bool can_support = true;
|
||||
|
|
@ -181,26 +183,20 @@ std::vector<Value*> valueVecFromFastSet(const FastSet<const Value*>& s) {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool mayContainAlias(AliasDb& db, const Value* a, const Value* b) {
|
||||
bool mayContainAlias(const AliasDb& db, const Value* v1, const Value* v2) {
|
||||
// AliasDb is not const-correct here, so we have to const_cast
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
|
||||
return db.mayContainAlias(const_cast<Value*>(a), const_cast<Value*>(b));
|
||||
return db.mayContainAlias(const_cast<Value*>(v1), const_cast<Value*>(v2));
|
||||
}
|
||||
|
||||
bool mayContainAlias(
|
||||
AliasDb& db,
|
||||
const AliasDb& db,
|
||||
const Value* a,
|
||||
const FastSet<const Value*>& b) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
|
||||
return db.mayContainAlias(const_cast<Value*>(a), valueVecFromFastSet(b));
|
||||
}
|
||||
|
||||
bool mayContainAlias(
|
||||
AliasDb& db,
|
||||
const FastSet<const Value*>& a,
|
||||
const FastSet<const Value*>& b) {
|
||||
return db.mayContainAlias(valueVecFromFastSet(a), valueVecFromFastSet(b));
|
||||
}
|
||||
|
||||
void PrepareGraphForStaticModule(
|
||||
std::shared_ptr<torch::jit::Graph> graph,
|
||||
const StaticModuleOptions& opts,
|
||||
|
|
@ -248,23 +244,21 @@ std::pair<std::shared_ptr<Graph>, c10::optional<Module>> PrepareForStaticModule(
|
|||
|
||||
} // namespace
|
||||
|
||||
void ValueGroup::init(
|
||||
const std::shared_ptr<torch::jit::Graph>& graph,
|
||||
AliasDb& db) {
|
||||
void ValueGroup::init(const Block& block, const AliasDb& db) {
|
||||
external_aliases_.clear();
|
||||
output_aliases_.clear();
|
||||
// Build `external_aliases` as we look through nodes forwardly from
|
||||
// the graph's inputs and add aliases of the inputs being created by the
|
||||
// nodes.
|
||||
external_aliases_.insert(graph->inputs().begin(), graph->inputs().end());
|
||||
for (const auto* node : graph->nodes()) {
|
||||
external_aliases_.insert(block.inputs().begin(), block.inputs().end());
|
||||
for (const auto* node : block.nodes()) {
|
||||
if (node->kind() == prim::Constant) {
|
||||
for (const auto* output : node->outputs()) {
|
||||
external_aliases_.insert(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto* node : graph->nodes()) {
|
||||
for (const auto* node : block.nodes()) {
|
||||
if (node->kind() == prim::Constant) {
|
||||
// Constants are already in `external_aliases`.
|
||||
continue;
|
||||
|
|
@ -278,8 +272,8 @@ void ValueGroup::init(
|
|||
|
||||
// Build `output_aliases` as we look through nodes reversely so that we can
|
||||
// start from the output values, and follow the flows backwardly from there.
|
||||
output_aliases_.insert(graph->outputs().begin(), graph->outputs().end());
|
||||
for (const auto* node : graph->nodes().reverse()) {
|
||||
output_aliases_.insert(block.outputs().begin(), block.outputs().end());
|
||||
for (const auto* node : block.nodes().reverse()) {
|
||||
if (node->kind() == prim::Constant) {
|
||||
// Constants cannot create any aliases.
|
||||
continue;
|
||||
|
|
@ -317,12 +311,6 @@ bool containTensorsOnly(at::ArrayRef<Value*> values) {
|
|||
});
|
||||
}
|
||||
|
||||
bool mayContainAlias(const Value* v1, const Value* v2, const AliasDb& db) {
|
||||
// AliasDb is not const-correct here, so we have to const_cast
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
|
||||
return db.mayContainAlias(const_cast<Value*>(v1), const_cast<Value*>(v2));
|
||||
}
|
||||
|
||||
bool isPureFunction(const Node* node) {
|
||||
auto* schema = node->maybeSchema();
|
||||
return schema &&
|
||||
|
|
@ -332,12 +320,12 @@ bool isPureFunction(const Node* node) {
|
|||
} // namespace
|
||||
|
||||
ManagedTensorRanges::ManagedTensorRanges(
|
||||
const std::shared_ptr<Graph>& graph,
|
||||
Block& block,
|
||||
const AliasDb& alias_db,
|
||||
const FastSet<const Value*>& managed_tensor_values) {
|
||||
AliasDb alias_db(graph);
|
||||
const std::vector<Node*> nodes(graph->nodes().begin(), graph->nodes().end());
|
||||
const std::vector<Node*> nodes(block.nodes().begin(), block.nodes().end());
|
||||
const FastSet<const Value*> graph_inputs(
|
||||
graph->inputs().begin(), graph->inputs().end());
|
||||
block.inputs().begin(), block.inputs().end());
|
||||
|
||||
auto isUntrackedValue = [&alias_db, &graph_inputs](const Value* value) {
|
||||
return !alias_db.isMutableType(value) ||
|
||||
|
|
@ -363,7 +351,7 @@ ManagedTensorRanges::ManagedTensorRanges(
|
|||
value_lifetimes_.emplace(output, Lifetime(i, i));
|
||||
}
|
||||
}
|
||||
for (auto* graph_output : graph->outputs()) {
|
||||
for (auto* graph_output : block.outputs()) {
|
||||
auto* lifetime = getLifetime(graph_output);
|
||||
if (!lifetime) {
|
||||
DCHECK(isUntrackedValue(graph_output));
|
||||
|
|
@ -376,7 +364,7 @@ ManagedTensorRanges::ManagedTensorRanges(
|
|||
// has an input and output that may alias each other, set the input's
|
||||
// lifetime end to max(input.lifetime_end, output.lifetime_end). Iterate
|
||||
// backwards to handle chains of aliases.
|
||||
for (const auto* node : graph->nodes().reverse()) {
|
||||
for (const auto* node : block.nodes().reverse()) {
|
||||
if (isPureFunction(node)) {
|
||||
// If the node is a pure function, it doesn't create any aliases,
|
||||
// so we can safely skip it.
|
||||
|
|
@ -389,7 +377,7 @@ ManagedTensorRanges::ManagedTensorRanges(
|
|||
auto* input_lifetime = getLifetime(input);
|
||||
DCHECK(input_lifetime != nullptr);
|
||||
for (auto* output : outputs) {
|
||||
if (mayContainAlias(input, output, alias_db)) {
|
||||
if (mayContainAlias(alias_db, input, output)) {
|
||||
auto* output_lifetime = getLifetime(output);
|
||||
DCHECK(output_lifetime != nullptr);
|
||||
input_lifetime->end =
|
||||
|
|
@ -404,7 +392,7 @@ ManagedTensorRanges::ManagedTensorRanges(
|
|||
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
|
||||
Node* freeing_node;
|
||||
if (lifetime->end == num_nodes) {
|
||||
freeing_node = graph->return_node();
|
||||
freeing_node = block.return_node();
|
||||
} else {
|
||||
freeing_node = nodes[lifetime->end];
|
||||
}
|
||||
|
|
@ -519,15 +507,6 @@ StaticModule::StaticModule(
|
|||
}
|
||||
}
|
||||
|
||||
// map Value* to its SSA definition IR
|
||||
FastMap<Value*, DefInfo> value_to_ssa_def;
|
||||
|
||||
// N inputs map to the first N entries in storage
|
||||
for (const auto i : c10::irange(graph_->inputs().size())) {
|
||||
Value* input = graph_->inputs()[i];
|
||||
value_to_ssa_def[input] = std::make_pair(INPUT_VALUE, i);
|
||||
}
|
||||
|
||||
{
|
||||
size_t nodes_size = 0, constants_size = 0;
|
||||
for (Node* node : graph_->nodes()) {
|
||||
|
|
@ -536,7 +515,6 @@ StaticModule::StaticModule(
|
|||
|
||||
constants_.reserve(constants_size);
|
||||
functions_.reserve(nodes_size);
|
||||
nodes_.reserve(nodes_size);
|
||||
}
|
||||
|
||||
// Create ProcessedFunction instances first to freeze their addresses to pass
|
||||
|
|
@ -544,13 +522,89 @@ StaticModule::StaticModule(
|
|||
AliasDb alias_db(graph_, /*isFrozen=*/false);
|
||||
GRAPH_DEBUG("AliasDb: ", alias_db.toString());
|
||||
|
||||
// Construct constant and function nodes
|
||||
for (Node* node : graph_->nodes()) {
|
||||
// Maps each Value* in the graph to its index in the values_ array that will
|
||||
// eventually be created by StaticRuntime.
|
||||
FastMap<const Value*, uint32_t> value_to_index;
|
||||
prepareFunctionsAndConstants(graph_->block(), alias_db, value_to_index);
|
||||
|
||||
const auto constants_index_offset = 0;
|
||||
const auto values_index_offset = constants_index_offset + constants().size();
|
||||
value_buffer_size_ = values_index_offset;
|
||||
|
||||
value_buffer_size_ +=
|
||||
prepareBlockInfo(graph_->block(), values_index_offset, value_to_index);
|
||||
|
||||
prepareProcessedNodes(graph_->block(), value_to_index, alias_db);
|
||||
|
||||
for (auto& block_and_info : block_infos_) {
|
||||
auto& block_info = block_and_info.second;
|
||||
block_info.prepare_for_memory_planner(alias_db, opts);
|
||||
}
|
||||
}
|
||||
|
||||
size_t StaticModule::prepareBlockInfo(
|
||||
Block* block,
|
||||
const size_t start_idx,
|
||||
FastMap<const Value*, uint32_t>& value_to_index) {
|
||||
block_infos_.emplace(block, BlockInfo(start_idx, *block));
|
||||
|
||||
const auto num_inputs = block->inputs().size();
|
||||
for (const auto i : c10::irange(num_inputs)) {
|
||||
value_to_index.emplace(block->inputs()[i], start_idx + i);
|
||||
}
|
||||
auto cur_idx = start_idx + num_inputs;
|
||||
|
||||
for (auto* node : block->nodes()) {
|
||||
for (auto* sub_block : node->blocks()) {
|
||||
cur_idx += prepareBlockInfo(sub_block, cur_idx, value_to_index);
|
||||
}
|
||||
|
||||
if (node->kind() == prim::Constant) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TORCH_CHECK(
|
||||
cur_idx < (1 << 16),
|
||||
"outputs offset in values table",
|
||||
cur_idx,
|
||||
" would overflow 2-byte index storage");
|
||||
|
||||
const auto num_outputs = node->outputs().size();
|
||||
for (const auto i : c10::irange(num_outputs)) {
|
||||
value_to_index.emplace(node->outputs()[i], cur_idx + i);
|
||||
}
|
||||
cur_idx += num_outputs;
|
||||
}
|
||||
|
||||
std::vector<uint16_t> output_indices;
|
||||
output_indices.reserve(block->outputs().size());
|
||||
for (auto* output : block->outputs()) {
|
||||
const auto output_idx = value_to_index.at(output);
|
||||
TORCH_CHECK(
|
||||
output_idx < (1 << 16),
|
||||
"outputs offset in values table",
|
||||
output_idx,
|
||||
" would overflow 2-byte index storage");
|
||||
output_indices.push_back(output_idx);
|
||||
}
|
||||
|
||||
block_infos_.at(block).set_output_indices(std::move(output_indices));
|
||||
return cur_idx - start_idx;
|
||||
}
|
||||
|
||||
void StaticModule::prepareFunctionsAndConstants(
|
||||
Block* block,
|
||||
const AliasDb& alias_db,
|
||||
FastMap<const Value*, uint32_t>& value_to_index) {
|
||||
for (auto* node : block->nodes()) {
|
||||
for (auto* sub_block : node->blocks()) {
|
||||
prepareFunctionsAndConstants(sub_block, alias_db, value_to_index);
|
||||
}
|
||||
|
||||
if (node->kind() == prim::Constant) {
|
||||
auto* v = node->output();
|
||||
TORCH_CHECK(v->type()->kind() != FunctionType::Kind);
|
||||
// construct SSA definition for constant nodes
|
||||
value_to_ssa_def[v] = std::make_pair(CONSTANT_VALUE, constants_.size());
|
||||
value_to_index.emplace(v, constants_.size());
|
||||
constants_.emplace_back(toIValue(v).value());
|
||||
continue;
|
||||
}
|
||||
|
|
@ -561,66 +615,34 @@ StaticModule::StaticModule(
|
|||
containTensorsOnly(node->outputs());
|
||||
// new ProcessedFunction
|
||||
functions_.emplace_back(
|
||||
node, opts.enable_out_variant, check_outputs_for_overlap);
|
||||
node, opts_.enable_out_variant, check_outputs_for_overlap);
|
||||
}
|
||||
}
|
||||
|
||||
// construct SSA definition for non-constant nodes
|
||||
int node_idx = 0;
|
||||
size_t StaticModule::prepareProcessedNodes(
|
||||
Block* block,
|
||||
const FastMap<const Value*, uint32_t>& value_to_index,
|
||||
const AliasDb& alias_db,
|
||||
size_t node_idx) {
|
||||
const auto node_start = node_idx;
|
||||
|
||||
auto& block_info = block_infos_.at(block);
|
||||
std::vector<ProcessedNode> nodes;
|
||||
FastMap<Node*, bool> node_has_out_variant;
|
||||
|
||||
const auto inputs_index_offset = inputs_offset();
|
||||
const auto constants_index_offset = constants_offset();
|
||||
const auto values_index_offset = intermediate_values_offset();
|
||||
|
||||
// Map node_idx to index offset in values_. Can't reserve space
|
||||
// because we don't know how many non-constant nodes there are yet.
|
||||
std::vector<uint32_t> node_output_idx_map;
|
||||
uint32_t node_outputs_seen_so_far = 0;
|
||||
for (Node* node : graph_->nodes()) {
|
||||
for (auto* node : block->nodes()) {
|
||||
if (node->kind() == prim::Constant) {
|
||||
continue;
|
||||
}
|
||||
// Assign memory for the outputs
|
||||
const auto outputs_offset_for_node =
|
||||
node_outputs_seen_so_far + values_index_offset;
|
||||
TORCH_CHECK(
|
||||
outputs_offset_for_node < (1 << 16),
|
||||
"outputs offset in values table",
|
||||
outputs_offset_for_node,
|
||||
" would overflow 2-byte index storage");
|
||||
node_output_idx_map.push_back(outputs_offset_for_node);
|
||||
node_outputs_seen_so_far += node->outputs().size();
|
||||
}
|
||||
|
||||
for (Node* node : graph_->nodes()) {
|
||||
if (node->kind() == prim::Constant) {
|
||||
continue;
|
||||
for (auto* sub_block : node->blocks()) {
|
||||
node_idx +=
|
||||
prepareProcessedNodes(sub_block, value_to_index, alias_db, node_idx);
|
||||
}
|
||||
ProcessedNodeInputs input_indices(node->inputs().size());
|
||||
std::vector<DefInfo> input_ssa_defs;
|
||||
for (const auto input_idx : c10::irange(node->inputs().size())) {
|
||||
Value* const input = node->inputs()[input_idx];
|
||||
int inner_node_idx = 0;
|
||||
int out_idx = 0;
|
||||
std::tie(inner_node_idx, out_idx) = value_to_ssa_def.at(input);
|
||||
unsigned int input_ivalue_idx = 0;
|
||||
if (inner_node_idx == StaticModule::INPUT_VALUE) {
|
||||
input_ivalue_idx = out_idx + inputs_index_offset;
|
||||
} else if (inner_node_idx == StaticModule::CONSTANT_VALUE) {
|
||||
input_ivalue_idx = out_idx + constants_index_offset;
|
||||
} else {
|
||||
DCHECK_GE(inner_node_idx, 0);
|
||||
const auto global_value_idx =
|
||||
node_output_idx_map[inner_node_idx] + out_idx;
|
||||
if (inner_node_idx < node_output_idx_map.size() - 1) {
|
||||
DCHECK_LT(global_value_idx, node_output_idx_map[inner_node_idx + 1]);
|
||||
} else {
|
||||
DCHECK_LT(
|
||||
global_value_idx,
|
||||
constants_index_offset + node_outputs_seen_so_far);
|
||||
}
|
||||
input_ivalue_idx = global_value_idx;
|
||||
}
|
||||
auto* input = node->inputs()[input_idx];
|
||||
auto input_ivalue_idx = value_to_index.at(input);
|
||||
TORCH_CHECK(
|
||||
input_ivalue_idx < (1 << 16),
|
||||
"input index in values table ",
|
||||
|
|
@ -630,72 +652,48 @@ StaticModule::StaticModule(
|
|||
}
|
||||
|
||||
ProcessedFunction* fn = &functions_[node_idx];
|
||||
|
||||
// create a new ProcessedNode
|
||||
// see [Check and correct bad schema alias info at runtime]
|
||||
bool check_outputs_for_overlap =
|
||||
!alias_db.mayContainAlias(node->inputs(), node->outputs()) &&
|
||||
containTensorsOnly(node->outputs());
|
||||
nodes_.emplace_back(
|
||||
node, fn, std::move(input_indices), node_output_idx_map[node_idx]);
|
||||
const auto node_output_idx = node->outputs().empty()
|
||||
// The index is unused if there are no outputs, so just create a
|
||||
// placeholder value.
|
||||
? std::numeric_limits<uint16_t>::max()
|
||||
: value_to_index.at(node->output(0));
|
||||
nodes.emplace_back(node, fn, std::move(input_indices), node_output_idx);
|
||||
|
||||
node_has_out_variant.emplace(node, nodes_.back().has_out_variant());
|
||||
for (const auto i : c10::irange(node->outputs().size())) {
|
||||
value_to_ssa_def[node->outputs()[i]] = std::make_pair(node_idx, i);
|
||||
}
|
||||
node_idx++;
|
||||
node_has_out_variant.emplace(node, nodes.back().has_out_variant());
|
||||
++node_idx;
|
||||
}
|
||||
|
||||
num_intermediate_values_ = std::accumulate(
|
||||
nodes_.begin(),
|
||||
nodes_.end(),
|
||||
0,
|
||||
[](uint32_t sum, const ProcessedNode& pnode) {
|
||||
return sum + pnode.num_outputs();
|
||||
});
|
||||
block_info.set_nodes(std::move(nodes), node_has_out_variant);
|
||||
block_info.init_value_group(alias_db);
|
||||
|
||||
for (auto& pnode : nodes_) {
|
||||
if (pnode.num_outputs() == 1 &&
|
||||
isOptimizableContainerType(pnode.node(), node_has_out_variant)) {
|
||||
node_is_optimizable_container_type_.emplace(pnode.node());
|
||||
}
|
||||
}
|
||||
output_indices_.reserve(graph_->outputs().size());
|
||||
for (auto output : graph_->outputs()) {
|
||||
int node_idx = 0;
|
||||
int out_idx = 0;
|
||||
std::tie(node_idx, out_idx) = value_to_ssa_def[output];
|
||||
uint32_t output_index = 0;
|
||||
if (node_idx == StaticModule::INPUT_VALUE) {
|
||||
output_index = out_idx + inputs_index_offset;
|
||||
} else if (node_idx == StaticModule::CONSTANT_VALUE) {
|
||||
output_index = constants_index_offset + out_idx;
|
||||
} else {
|
||||
output_index = nodes_[node_idx].output_ivalue_index(out_idx);
|
||||
}
|
||||
TORCH_CHECK(
|
||||
output_index < (1 << 16),
|
||||
"output index ",
|
||||
output_index,
|
||||
" would overflow 2-byte index storage");
|
||||
output_indices_.emplace_back(output_index);
|
||||
}
|
||||
|
||||
// Prepare for memory planning
|
||||
value_group_.init(graph_, alias_db);
|
||||
GRAPH_DEBUG(value_group_.toString());
|
||||
|
||||
prepareForMemoryPlanner();
|
||||
return node_idx - node_start;
|
||||
}
|
||||
|
||||
void StaticModule::prepareForMemoryPlanner() {
|
||||
if (!opts_.enable_out_variant) {
|
||||
void BlockInfo::set_nodes(
|
||||
std::vector<ProcessedNode> nodes,
|
||||
const FastMap<Node*, bool>& node_has_out_variant) {
|
||||
nodes_ = std::move(nodes);
|
||||
|
||||
for (auto& node : nodes_) {
|
||||
if (node.num_outputs() == 1 &&
|
||||
isOptimizableContainerType(node.node(), node_has_out_variant)) {
|
||||
node_is_optimizable_container_type_.emplace(node.node());
|
||||
}
|
||||
}
|
||||
}
|
||||
void BlockInfo::prepare_for_memory_planner(
|
||||
const AliasDb& alias_db,
|
||||
const StaticModuleOptions& opts) {
|
||||
if (!opts.enable_out_variant) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Never manage graph outputs so that we can do std::move(output_ivalue).
|
||||
// This does not affect performance if the graph returns a collection object.
|
||||
FastSet<const Value*> graph_output_values(
|
||||
graph_->outputs().begin(), graph_->outputs().end());
|
||||
block_.outputs().begin(), block_.outputs().end());
|
||||
|
||||
// collect register indices of outputs of ops with out variant
|
||||
for (ProcessedNode& pnode : nodes_) {
|
||||
|
|
@ -707,7 +705,7 @@ void StaticModule::prepareForMemoryPlanner() {
|
|||
const Value* out_v = outputs[i];
|
||||
// Types are stored in the underlying TorchScript IR
|
||||
bool is_tensor_type = out_v->type()->castRaw<TensorType>();
|
||||
if (opts_.manage_output_tensors && is_tensor_type &&
|
||||
if (opts.manage_output_tensors && is_tensor_type &&
|
||||
graph_output_values.find(out_v) == graph_output_values.end() &&
|
||||
value_group_.isOutputAlias(out_v)) {
|
||||
managed_output_tensor_values_.insert(out_v);
|
||||
|
|
@ -718,7 +716,7 @@ void StaticModule::prepareForMemoryPlanner() {
|
|||
}
|
||||
if (is_tensor_type) {
|
||||
managed_tensor_values_.insert(out_v);
|
||||
} else if (is_optimizable_container_type(pnode.node())) {
|
||||
} else if (node_is_optimizable_container_type(pnode.node())) {
|
||||
// We "leak" certain container types because their allocations
|
||||
// take a long time
|
||||
leaked_values_.insert(out_v);
|
||||
|
|
@ -726,7 +724,7 @@ void StaticModule::prepareForMemoryPlanner() {
|
|||
}
|
||||
}
|
||||
|
||||
for (const Value* output : graph_->outputs()) {
|
||||
for (const Value* output : block_.outputs()) {
|
||||
managed_tensor_values_.erase(output);
|
||||
}
|
||||
GRAPH_DEBUG("managed_tensor_values: ", dumpValueSet(managed_tensor_values_));
|
||||
|
|
@ -734,7 +732,8 @@ void StaticModule::prepareForMemoryPlanner() {
|
|||
"managed_output_tensor_values_: ",
|
||||
dumpValueSet(managed_output_tensor_values_));
|
||||
|
||||
managed_tensor_ranges_ = ManagedTensorRanges(graph_, managed_tensor_values_);
|
||||
managed_tensor_ranges_ =
|
||||
ManagedTensorRanges(block_, alias_db, managed_tensor_values_);
|
||||
}
|
||||
|
||||
const StaticModuleOptions& StaticModule::opts() const {
|
||||
|
|
@ -757,9 +756,12 @@ StaticRuntime& StaticModule::runtime() {
|
|||
}
|
||||
|
||||
Node* StaticModule::findNodeWithKindForTesting(const std::string& kind) const {
|
||||
for (auto& pnode : nodes()) {
|
||||
if (pnode.node()->kind().toQualString() == kind) {
|
||||
return pnode.node();
|
||||
for (auto& block_and_info : block_infos_) {
|
||||
auto& block_info = block_and_info.second;
|
||||
for (auto& pnode : block_info.nodes()) {
|
||||
if (pnode.node()->kind().toQualString() == kind) {
|
||||
return pnode.node();
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
|
@ -777,41 +779,64 @@ c10::IValue StaticModule::operator()(
|
|||
return runtime()(std::move(args), kwargs);
|
||||
}
|
||||
|
||||
StaticRuntime::StaticRuntime(const StaticModule& sm)
|
||||
BlockRunner::BlockRunner(
|
||||
const StaticModule& sm,
|
||||
std::vector<IValue>& values,
|
||||
Block* block,
|
||||
bool is_root_block)
|
||||
: static_module_(sm),
|
||||
first_input_is_self_(static_module_.first_input_is_self()),
|
||||
manage_output_tensors_enabled_(sm.opts().manage_output_tensors),
|
||||
nodes_(sm.nodes()) {
|
||||
values_.resize(sm.total_num_values());
|
||||
const auto constants_index_offset = sm.constants_offset();
|
||||
const auto constants_begin_it = values_.begin() + constants_index_offset;
|
||||
const auto constants_end_it = constants_begin_it + sm.constants().size();
|
||||
std::copy(sm.constants().begin(), sm.constants().end(), constants_begin_it);
|
||||
|
||||
for (const auto idx : c10::irange(sm.nodes().size())) {
|
||||
auto& n = nodes_[idx];
|
||||
block_info_(static_module_.block_info(block)),
|
||||
is_root_block_(is_root_block),
|
||||
first_input_is_self_(
|
||||
is_root_block_ && static_module_.first_input_is_self()),
|
||||
inputs_begin_(block_info_.block_inputs_idx()),
|
||||
// TODO(T108633124): Turn on manage output tensors for sub-blocks.
|
||||
manage_output_tensors_enabled_(
|
||||
is_root_block_ && sm.opts().manage_output_tensors),
|
||||
values_(values),
|
||||
nodes_(block_info_.nodes()) {
|
||||
for (auto& n : nodes_) {
|
||||
n.set_values(values_.data());
|
||||
}
|
||||
|
||||
// TODO: can we convert outputs_ to store indices?
|
||||
for (auto index : sm.output_indices()) {
|
||||
for (auto index : block_info_.block_output_indices()) {
|
||||
outputs_.emplace_back(&values_[index]);
|
||||
}
|
||||
|
||||
for (auto& pnode : nodes_) {
|
||||
auto* node = pnode.node();
|
||||
auto blocks = node->blocks();
|
||||
const auto num_blocks = blocks.size();
|
||||
if (num_blocks == 0) {
|
||||
continue;
|
||||
}
|
||||
DCHECK(node->kind() == prim::If || node->kind() == prim::Loop);
|
||||
auto block_runners = std::make_unique<std::vector<BlockRunner>>();
|
||||
block_runners->reserve(num_blocks);
|
||||
|
||||
for (auto* b : blocks) {
|
||||
block_runners->emplace_back(sm, values, b);
|
||||
}
|
||||
pnode.set_block_runners(std::move(block_runners));
|
||||
}
|
||||
}
|
||||
|
||||
StaticRuntime::~StaticRuntime() = default;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
|
||||
BlockRunner::BlockRunner(BlockRunner&&) noexcept = default;
|
||||
|
||||
void StaticRuntime::set_arg(const size_t idx, std::vector<IValue>&& args) {
|
||||
BlockRunner::~BlockRunner() = default;
|
||||
|
||||
void BlockRunner::set_arg(const size_t idx, std::vector<IValue>&& args) {
|
||||
DCHECK(idx < args.size());
|
||||
Input(idx + first_input_is_self_) = std::move(args[idx]);
|
||||
}
|
||||
|
||||
void StaticRuntime::set_arg(const size_t idx, const std::vector<IValue>& args) {
|
||||
void BlockRunner::set_arg(const size_t idx, const std::vector<IValue>& args) {
|
||||
DCHECK(idx < args.size());
|
||||
Input(idx + first_input_is_self_) = args[idx];
|
||||
}
|
||||
|
||||
void StaticRuntime::set_arg(const size_t idx, const IValue& arg) {
|
||||
void BlockRunner::set_arg(const size_t idx, const IValue& arg) {
|
||||
Input(idx + first_input_is_self_) = arg;
|
||||
}
|
||||
|
||||
|
|
@ -827,24 +852,21 @@ void check_type(const Argument& schema_arg, const IValue& arg) {
|
|||
} // namespace
|
||||
|
||||
template <typename IValueList>
|
||||
void StaticRuntime::set_inputs(
|
||||
void BlockRunner::set_inputs(
|
||||
IValueList&& args,
|
||||
const std::unordered_map<std::string, c10::IValue>& kwargs) {
|
||||
const auto total_num_inputs =
|
||||
args.size() + kwargs.size() + first_input_is_self_;
|
||||
TORCH_CHECK(total_num_inputs == static_module_.num_inputs());
|
||||
TORCH_CHECK(total_num_inputs == block_info_.num_inputs());
|
||||
|
||||
const auto& schema = static_module_.schema();
|
||||
if (first_input_is_self_) {
|
||||
Input(0) = static_module_.module()._ivalue();
|
||||
}
|
||||
|
||||
if (C10_UNLIKELY(!schema)) {
|
||||
if (!is_root_block_ || C10_UNLIKELY(!schema)) {
|
||||
TORCH_CHECK(
|
||||
kwargs.empty(),
|
||||
"Schema is not available, but StaticRuntime got kwargs. "
|
||||
"Consider creating the Static Runtime instance "
|
||||
"with StaticModule(const torch::jit::Module& m) instead.");
|
||||
kwargs.empty(), "Schema is not available, but BlockRunner got kwargs.");
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
set_arg(i, std::forward<IValueList>(args));
|
||||
}
|
||||
|
|
@ -887,15 +909,11 @@ void StaticRuntime::set_inputs(
|
|||
args.size() + consumed_kwargs == schema_args.size() - 1);
|
||||
}
|
||||
|
||||
void StaticRuntime::create_memory_planner() {
|
||||
void BlockRunner::create_memory_planner() {
|
||||
if (!planner_) {
|
||||
planner_ = std::make_unique<MemoryPlanner>(
|
||||
this,
|
||||
static_module_.value_group(),
|
||||
static_module_.managed_tensor_values(),
|
||||
static_module_.managed_output_tensor_values(),
|
||||
static_module_.leaked_values(),
|
||||
static_module_.managed_tensor_ranges(),
|
||||
block_info_,
|
||||
static_module_.opts().enable_out_variant,
|
||||
manage_output_tensors_enabled_,
|
||||
static_module_.opts().optimize_memory);
|
||||
|
|
@ -924,7 +942,7 @@ void destroyNodeOutputs(ProcessedNode& p_node) {
|
|||
|
||||
} // namespace
|
||||
|
||||
void StaticRuntime::clean_up_intermediate_ivalues() noexcept {
|
||||
void BlockRunner::clean_up_intermediate_ivalues() noexcept {
|
||||
// We have to iterate in reverse order here due to borrowed
|
||||
// IValues - we don't want to destroy a value until all of its
|
||||
// borrows are cleaned up!
|
||||
|
|
@ -933,7 +951,7 @@ void StaticRuntime::clean_up_intermediate_ivalues() noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
void StaticRuntime::resetMemory() noexcept {
|
||||
void BlockRunner::resetMemory() noexcept {
|
||||
planner_.reset();
|
||||
// We must clean up intermediate values before inputs in case
|
||||
// there are borrowed inputs and static runtime owns the only
|
||||
|
|
@ -942,7 +960,7 @@ void StaticRuntime::resetMemory() noexcept {
|
|||
clean_up_input_ivalues();
|
||||
}
|
||||
|
||||
c10::IValue StaticRuntime::move_outputs_to_tuple(uint32_t num_outputs) {
|
||||
c10::IValue BlockRunner::move_outputs_to_tuple(uint32_t num_outputs) {
|
||||
switch (num_outputs) {
|
||||
case 1:
|
||||
return c10::ivalue::Tuple::create(IValue(std::move(*outputs_[0])));
|
||||
|
|
@ -1032,7 +1050,7 @@ c10::IValue StaticRuntime::move_outputs_to_tuple(uint32_t num_outputs) {
|
|||
/// buffer) fails. There is still a corner case that fails with the added flag.
|
||||
/// If a resize is triggered at the same time as the op creating an alias at the
|
||||
/// same time, the current checks would fail to detect the alias.
|
||||
void StaticRuntime::verify_and_correct_memory_overlap(ProcessedNode& n) {
|
||||
void BlockRunner::verify_and_correct_memory_overlap(ProcessedNode& n) {
|
||||
// The slow check can be removed once the internal/output buffers are merged
|
||||
if (C10_UNLIKELY(n.check_outputs_for_memory_overlap())) {
|
||||
if (C10_UNLIKELY(!planner_)) {
|
||||
|
|
@ -1065,7 +1083,7 @@ void StaticRuntime::verify_and_correct_memory_overlap(ProcessedNode& n) {
|
|||
}
|
||||
}
|
||||
|
||||
bool StaticRuntime::fast_check_and_correct_overlap_with(
|
||||
bool BlockRunner::fast_check_and_correct_overlap_with(
|
||||
ProcessedNode& n,
|
||||
c10::IValue& tensor_ival) {
|
||||
auto& tensor = tensor_ival.toTensor();
|
||||
|
|
@ -1078,38 +1096,38 @@ bool StaticRuntime::fast_check_and_correct_overlap_with(
|
|||
return false;
|
||||
}
|
||||
|
||||
StaticRuntime::Deallocator::~Deallocator() {
|
||||
BlockRunner::Deallocator::~Deallocator() {
|
||||
// Assume cleanup cannot throw.
|
||||
cleanupImpl();
|
||||
#ifndef NDEBUG
|
||||
runtime_.check_for_memory_leak(/*output_returned*/ false);
|
||||
block_runner_.check_for_memory_leak(/*output_returned*/ false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void StaticRuntime::Deallocator::cleanupImpl() {
|
||||
void BlockRunner::Deallocator::cleanupImpl() {
|
||||
// MemoryPlanner is created after the first invocation of `run()`. This
|
||||
// is done intentionally because MemoryPlanner uses `Tensor` sizes of
|
||||
// the previous `run()` for memory planning of subsequent runs
|
||||
if (C10_LIKELY(finished_)) {
|
||||
runtime_.create_memory_planner();
|
||||
block_runner_.create_memory_planner();
|
||||
}
|
||||
|
||||
if (C10_LIKELY(runtime_.planner_)) {
|
||||
runtime_.planner_->deallocate();
|
||||
if (C10_LIKELY(block_runner_.planner_)) {
|
||||
block_runner_.planner_->deallocate();
|
||||
} else {
|
||||
// This is the first run, and it didn't finish, so we can't use a
|
||||
// `MemoryPlanner` to deallocate stuff. Just reset everything mannually.
|
||||
runtime_.resetMemory();
|
||||
block_runner_.resetMemory();
|
||||
}
|
||||
// clean up owning refs of input tensors
|
||||
runtime_.clean_up_input_ivalues();
|
||||
block_runner_.clean_up_input_ivalues();
|
||||
if (C10_UNLIKELY(!finished_)) {
|
||||
runtime_.deallocateOutputTensors();
|
||||
block_runner_.deallocateOutputTensors();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IValueList>
|
||||
c10::IValue StaticRuntime::run_impl(
|
||||
c10::IValue BlockRunner::run_impl(
|
||||
IValueList&& args,
|
||||
const KeywordArgs& kwargs) {
|
||||
// We assume inference workloads, so we do not need
|
||||
|
|
@ -1138,8 +1156,8 @@ c10::IValue StaticRuntime::run_impl(
|
|||
}
|
||||
|
||||
// no need to keep references of outputs in static runtime anymore
|
||||
if (static_module_.num_outputs() > 1) {
|
||||
return move_outputs_to_tuple(static_module_.num_outputs());
|
||||
if (block_info_.num_outputs() > 1) {
|
||||
return move_outputs_to_tuple(block_info_.num_outputs());
|
||||
}
|
||||
|
||||
DCHECK(check_for_memory_leak(/*output_returned*/ false));
|
||||
|
|
@ -1149,7 +1167,7 @@ c10::IValue StaticRuntime::run_impl(
|
|||
}
|
||||
|
||||
template <typename IValueList>
|
||||
c10::IValue StaticRuntime::run_impl_record_functions(
|
||||
c10::IValue BlockRunner::run_impl_record_functions(
|
||||
IValueList&& args,
|
||||
const KeywordArgs& kwargs) {
|
||||
bool pre_sampled = false;
|
||||
|
|
@ -1168,7 +1186,7 @@ c10::IValue StaticRuntime::run_impl_record_functions(
|
|||
return run_impl(std::forward<IValueList>(args), kwargs);
|
||||
}
|
||||
|
||||
c10::IValue StaticRuntime::operator()(
|
||||
c10::IValue BlockRunner::operator()(
|
||||
const std::vector<c10::IValue>& args,
|
||||
const KeywordArgs& kwargs) {
|
||||
#ifdef PYTORCH_DISABLE_NET_PROFILING
|
||||
|
|
@ -1178,7 +1196,7 @@ c10::IValue StaticRuntime::operator()(
|
|||
#endif
|
||||
}
|
||||
|
||||
c10::IValue StaticRuntime::operator()(
|
||||
c10::IValue BlockRunner::operator()(
|
||||
std::vector<c10::IValue>&& args,
|
||||
const KeywordArgs& kwargs) {
|
||||
#ifdef PYTORCH_DISABLE_NET_PROFILING
|
||||
|
|
@ -1205,7 +1223,7 @@ std::string generate_latency_json(const std::string& label, double millis) {
|
|||
|
||||
} // namespace
|
||||
|
||||
void StaticRuntime::benchmark(
|
||||
void BlockRunner::benchmark(
|
||||
const std::vector<std::vector<c10::IValue>>& args_list,
|
||||
const std::vector<KeywordArgs>& kwargs_list,
|
||||
const int warmup_runs,
|
||||
|
|
@ -1267,7 +1285,7 @@ void StaticRuntime::benchmark(
|
|||
}
|
||||
std::cout << std::setw(15) << results.total_time << " ms. in Total"
|
||||
<< std::endl;
|
||||
std::cout << "StaticRuntime setup time: " << results.setup_time << " ms"
|
||||
std::cout << "BlockRunner setup time: " << results.setup_time << " ms"
|
||||
<< std::endl;
|
||||
std::cout << "Memory allocation time: " << results.memory_alloc_time
|
||||
<< " ms\n";
|
||||
|
|
@ -1312,7 +1330,7 @@ void StaticRuntime::benchmark(
|
|||
#endif
|
||||
}
|
||||
|
||||
float StaticRuntime::benchmark_model(
|
||||
float BlockRunner::benchmark_model(
|
||||
const std::vector<std::vector<c10::IValue>>& args_list,
|
||||
const std::vector<KeywordArgs>& kwargs_list,
|
||||
const int warmup_runs,
|
||||
|
|
@ -1396,7 +1414,7 @@ void display_pnode_info(const ProcessedNode& pnode) {
|
|||
}
|
||||
}
|
||||
|
||||
void StaticRuntime::display_nodes(
|
||||
void BlockRunner::display_nodes(
|
||||
const std::vector<c10::IValue>& args,
|
||||
const KeywordArgs& kwargs) {
|
||||
c10::InferenceMode mode;
|
||||
|
|
@ -1415,7 +1433,7 @@ void StaticRuntime::display_nodes(
|
|||
on_exit.setFinished();
|
||||
}
|
||||
|
||||
StaticRuntime::IndividualMetrics StaticRuntime::benchmark_individual_ops(
|
||||
BlockRunner::IndividualMetrics BlockRunner::benchmark_individual_ops(
|
||||
const std::vector<std::vector<c10::IValue>>& args_list,
|
||||
const std::vector<KeywordArgs>& kwargs_list,
|
||||
const int warmup_runs,
|
||||
|
|
@ -1543,10 +1561,16 @@ StaticRuntime::IndividualMetrics StaticRuntime::benchmark_individual_ops(
|
|||
return results;
|
||||
}
|
||||
|
||||
bool StaticRuntime::check_for_memory_leak(bool output_returned) {
|
||||
bool BlockRunner::check_for_memory_leak(
|
||||
bool output_returned,
|
||||
bool recurse_on_sub_blocks) {
|
||||
// check for inputs
|
||||
for (const auto i : c10::irange(static_module_.num_inputs())) {
|
||||
TORCH_CHECK(values_[i].isNone(), "Input ", i, " was not cleaned up");
|
||||
for (const auto i : c10::irange(block_info_.num_inputs())) {
|
||||
TORCH_CHECK(
|
||||
values_[i + block_info_.block_inputs_idx()].isNone(),
|
||||
"Input ",
|
||||
i,
|
||||
" was not cleaned up");
|
||||
}
|
||||
FastSet<const IValue*> output_ivalues(outputs_.begin(), outputs_.end());
|
||||
for (const auto n : c10::irange(nodes_.size())) {
|
||||
|
|
@ -1561,7 +1585,8 @@ bool StaticRuntime::check_for_memory_leak(bool output_returned) {
|
|||
(isManagedOutputTensor(*ival) || isManagedOutputTensorValue(val))) {
|
||||
// `ival` contains a managed output tensor that the runtime doesn't
|
||||
// reclaim at the end of an iteration, but the client does so
|
||||
// by explicitly calling `StaticRuntime::deallocateOutputTensors`.
|
||||
// by explicitly calling
|
||||
// `BlockRunner::deallocateOutputTensors`.
|
||||
continue;
|
||||
}
|
||||
const std::string error_msg = "Output " + c10::to_string(i) + ", %" +
|
||||
|
|
@ -1573,7 +1598,8 @@ bool StaticRuntime::check_for_memory_leak(bool output_returned) {
|
|||
if (!ival->isNone()) {
|
||||
TORCH_CHECK(
|
||||
ival->isTensor() ||
|
||||
static_module_.is_optimizable_container_type(pnode.node()) ||
|
||||
block_info_.node_is_optimizable_container_type(
|
||||
pnode.node()) ||
|
||||
doesNotHeapAllocateWhenStoredInIValue(*val->type()),
|
||||
error_msg);
|
||||
if (ival->isTensor()) {
|
||||
|
|
@ -1595,12 +1621,20 @@ bool StaticRuntime::check_for_memory_leak(bool output_returned) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto* block_runners = pnode.block_runners();
|
||||
if (recurse_on_sub_blocks && block_runners) {
|
||||
for (auto& block_runner : *block_runners) {
|
||||
block_runner.check_for_memory_leak(
|
||||
output_returned, recurse_on_sub_blocks);
|
||||
}
|
||||
}
|
||||
}
|
||||
VLOG(1) << "Finished checking for memory leak";
|
||||
return true;
|
||||
}
|
||||
|
||||
void StaticRuntime::deallocateOutputTensors() {
|
||||
void BlockRunner::deallocateOutputTensors() {
|
||||
if (!static_module_.opts().manage_output_tensors) {
|
||||
TORCH_CHECK(
|
||||
!planner_ || planner_->numOutputBufferBytes() == 0,
|
||||
|
|
@ -1613,7 +1647,7 @@ void StaticRuntime::deallocateOutputTensors() {
|
|||
}
|
||||
}
|
||||
|
||||
bool StaticRuntime::checkOutputTensorMemoryLeaks() {
|
||||
bool BlockRunner::checkOutputTensorMemoryLeaks() {
|
||||
if (!static_module_.opts().manage_output_tensors || !planner_) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1639,21 +1673,21 @@ bool StaticRuntime::checkOutputTensorMemoryLeaks() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool StaticRuntime::isManagedOutputTensor(const IValue& ivalue) const {
|
||||
bool BlockRunner::isManagedOutputTensor(const IValue& ivalue) const {
|
||||
return planner_ && planner_->isManagedOutputTensor(ivalue);
|
||||
}
|
||||
|
||||
bool StaticRuntime::isManagedOutputTensorValue(const Value* value) const {
|
||||
bool BlockRunner::isManagedOutputTensorValue(const Value* value) const {
|
||||
// It's possible that manage_output_tensors_ was disabled after initializing
|
||||
// managed_output_tensor_values, so we have to check that flag here.
|
||||
if (!planner_ || !manage_output_tensors_enabled_) {
|
||||
return false;
|
||||
}
|
||||
const auto& managed_outputs = static_module_.managed_output_tensor_values();
|
||||
const auto& managed_outputs = block_info_.managed_output_tensor_values();
|
||||
return managed_outputs.find(value) != managed_outputs.end();
|
||||
}
|
||||
|
||||
void StaticRuntime::disableManageOutputTensors() {
|
||||
void BlockRunner::disableManageOutputTensors() {
|
||||
if (!manage_output_tensors_enabled_) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1915,5 +1949,50 @@ void ProcessedNode::verify_and_correct_memory_overlap() {
|
|||
}
|
||||
}
|
||||
|
||||
StaticRuntime::StaticRuntime(const StaticModule& sm) {
|
||||
values_.resize(sm.value_buffer_size());
|
||||
std::copy(sm.constants().begin(), sm.constants().end(), values_.begin());
|
||||
block_ = std::make_unique<BlockRunner>(
|
||||
sm, values_, sm.root_block(), /*is_root_block*/ true);
|
||||
;
|
||||
}
|
||||
|
||||
c10::IValue StaticRuntime::operator()(
|
||||
const std::vector<c10::IValue>& args,
|
||||
const KeywordArgs& kwargs) {
|
||||
return (*block_)(args, kwargs);
|
||||
}
|
||||
|
||||
c10::IValue StaticRuntime::operator()(
|
||||
std::vector<c10::IValue>&& args,
|
||||
const KeywordArgs& kwargs) {
|
||||
return (*block_)(std::move(args), kwargs);
|
||||
}
|
||||
|
||||
bool StaticRuntime::check_for_memory_leak(bool output_returned) {
|
||||
return block_->check_for_memory_leak(
|
||||
output_returned, /* recurse_on_sub_blocks */ true);
|
||||
}
|
||||
|
||||
bool StaticRuntime::checkOutputTensorMemoryLeaks() {
|
||||
return block_->checkOutputTensorMemoryLeaks();
|
||||
}
|
||||
|
||||
void StaticRuntime::deallocateOutputTensors() {
|
||||
block_->deallocateOutputTensors();
|
||||
}
|
||||
|
||||
bool StaticRuntime::isManagedOutputTensor(const IValue& ivalue) const {
|
||||
return block_->isManagedOutputTensor(ivalue);
|
||||
}
|
||||
|
||||
void StaticRuntime::disableManageOutputTensors() {
|
||||
block_->disableManageOutputTensors();
|
||||
}
|
||||
|
||||
const MemoryPlanner* StaticRuntime::get_memory_planner() const {
|
||||
return block_->get_memory_planner();
|
||||
}
|
||||
|
||||
} // namespace jit
|
||||
} // namespace torch
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <torch/csrc/jit/passes/freeze_module.h>
|
||||
#include <torch/csrc/jit/passes/inliner.h>
|
||||
#include <torch/csrc/jit/runtime/static/ProcessedNodeInputs.h>
|
||||
#include <limits>
|
||||
|
||||
#ifdef FBCODE_CAFFE2
|
||||
#include <folly/container/F14Map.h>
|
||||
|
|
@ -82,7 +83,7 @@ TORCH_API inline bool borrowsOutputs(c10::Symbol kind) {
|
|||
class ValueGroup {
|
||||
public:
|
||||
explicit ValueGroup() = default;
|
||||
void init(const std::shared_ptr<torch::jit::Graph>& graph, AliasDb& db);
|
||||
void init(const Block& block, const AliasDb& db);
|
||||
|
||||
bool isExternalAlias(const Value* value) const {
|
||||
return external_aliases_.find(value) != external_aliases_.end();
|
||||
|
|
@ -112,7 +113,8 @@ class TORCH_API ManagedTensorRanges {
|
|||
public:
|
||||
ManagedTensorRanges() = default;
|
||||
ManagedTensorRanges(
|
||||
const std::shared_ptr<Graph>& graph,
|
||||
Block& block,
|
||||
const AliasDb& alias_db,
|
||||
const FastSet<const Value*>& managed_tensor_values);
|
||||
|
||||
// If true, then this node is the last use of at least one
|
||||
|
|
@ -213,11 +215,122 @@ struct TORCH_API StaticModuleOptions {
|
|||
/// pool.push(runtime);
|
||||
/// @endcode
|
||||
///
|
||||
|
||||
class MemoryPlanner;
|
||||
class ProcessedFunction;
|
||||
class ProcessedNode;
|
||||
class StaticRuntime;
|
||||
|
||||
// A `BlockInfo` instance stores all of the shared state that each
|
||||
// `BlockRunner` will need to access. Most of this information is
|
||||
// read-only and shared between threads.
|
||||
// - Each `BlockInfo` corresponds to one block in the graph.
|
||||
// - Each `BlockInfo` may be used by multiple block runners (when there are many
|
||||
// threads).
|
||||
// - All of the `BlockInfo`s are stored in a vector in the `StaticModule` and
|
||||
// are initialized during `StaticModule` construction.
|
||||
// - Most of the information stored is used to initialize the block's memory
|
||||
// planner.
|
||||
class BlockInfo {
|
||||
public:
|
||||
BlockInfo(uint32_t input_idx, Block& block)
|
||||
: input_idx_(input_idx), block_(block) {}
|
||||
|
||||
void set_nodes(
|
||||
std::vector<ProcessedNode> nodes,
|
||||
const FastMap<Node*, bool>& node_has_out_variant);
|
||||
|
||||
const std::vector<ProcessedNode>& nodes() const {
|
||||
return nodes_;
|
||||
}
|
||||
|
||||
size_t num_nodes() const {
|
||||
return nodes_.size();
|
||||
}
|
||||
|
||||
size_t num_inputs() const {
|
||||
return block_.inputs().size();
|
||||
}
|
||||
|
||||
size_t num_outputs() const {
|
||||
return block_.outputs().size();
|
||||
}
|
||||
|
||||
graph_node_list node_ptrs() const {
|
||||
return block_.nodes();
|
||||
}
|
||||
|
||||
void set_output_indices(std::vector<uint16_t> indices) {
|
||||
output_indices_ = std::move(indices);
|
||||
}
|
||||
|
||||
const std::vector<uint16_t>& block_output_indices() const {
|
||||
return output_indices_;
|
||||
}
|
||||
|
||||
auto block_inputs_idx() const {
|
||||
return input_idx_;
|
||||
}
|
||||
|
||||
bool node_is_optimizable_container_type(const Node* node) const {
|
||||
return node_is_optimizable_container_type_.find(node) !=
|
||||
node_is_optimizable_container_type_.end();
|
||||
}
|
||||
|
||||
bool value_is_managed_tensor(const Value* value) const {
|
||||
return managed_tensor_values_.find(value) != managed_tensor_values_.end();
|
||||
}
|
||||
|
||||
bool value_is_leaked_container(const Value* value) const {
|
||||
return leaked_values_.find(value) != leaked_values_.end();
|
||||
}
|
||||
|
||||
const ValueGroup& value_group() const {
|
||||
return value_group_;
|
||||
}
|
||||
|
||||
const ManagedTensorRanges& managed_tensor_ranges() const {
|
||||
return managed_tensor_ranges_;
|
||||
}
|
||||
|
||||
void init_value_group(const AliasDb& alias_db) {
|
||||
value_group_.init(block_, alias_db);
|
||||
}
|
||||
|
||||
void prepare_for_memory_planner(
|
||||
const AliasDb& alias_db,
|
||||
const StaticModuleOptions& opt);
|
||||
|
||||
const auto& managed_output_tensor_values() const {
|
||||
return managed_output_tensor_values_;
|
||||
}
|
||||
|
||||
const auto& managed_tensor_values() const {
|
||||
return managed_tensor_values_;
|
||||
}
|
||||
|
||||
const auto& leaked_values() const {
|
||||
return leaked_values_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ProcessedNode> nodes_;
|
||||
|
||||
ValueGroup value_group_;
|
||||
|
||||
FastSet<const Node*> node_is_optimizable_container_type_;
|
||||
FastSet<const Value*> managed_tensor_values_;
|
||||
FastSet<const Value*> managed_output_tensor_values_;
|
||||
FastSet<const Value*> leaked_values_;
|
||||
|
||||
ManagedTensorRanges managed_tensor_ranges_{};
|
||||
|
||||
// The index of this block's inputs in the shared values_ array.
|
||||
const uint16_t input_idx_;
|
||||
// The indices of this block's outputs in the shared values_ array.
|
||||
std::vector<uint16_t> output_indices_;
|
||||
Block& block_;
|
||||
};
|
||||
|
||||
class TORCH_API StaticModule {
|
||||
public:
|
||||
explicit StaticModule(
|
||||
|
|
@ -231,23 +344,12 @@ class TORCH_API StaticModule {
|
|||
const StaticModuleOptions& opts = StaticModuleOptions(),
|
||||
std::vector<IValue> sample_inputs = {});
|
||||
|
||||
typedef enum {
|
||||
CONSTANT_VALUE = -2, // VALUE nodes defined by prim::Constant
|
||||
INPUT_VALUE = -1, // VALUE nodes representing graph inputs
|
||||
} VALUE_KIND;
|
||||
|
||||
private:
|
||||
explicit StaticModule(
|
||||
std::pair<std::shared_ptr<torch::jit::Graph>, c10::optional<Module>>
|
||||
graph_and_module,
|
||||
const StaticModuleOptions& opts);
|
||||
|
||||
// for <kind, idx>
|
||||
// if kind == CONSTANT_VALUE: map to constants_[idx]
|
||||
// if kind == INPUT_VALUE: map to inputs_[idx]
|
||||
// otherwise: map to nodes_[kind].outputs()[idx]
|
||||
using DefInfo = std::pair<int, int>;
|
||||
|
||||
public:
|
||||
using KeywordArgs = std::unordered_map<std::string, c10::IValue>;
|
||||
c10::IValue operator()(
|
||||
|
|
@ -268,10 +370,6 @@ class TORCH_API StaticModule {
|
|||
|
||||
const StaticModuleOptions& opts() const;
|
||||
|
||||
const ValueGroup& valueGroup() const {
|
||||
return value_group_;
|
||||
}
|
||||
|
||||
size_t num_inputs() const;
|
||||
size_t num_outputs() const;
|
||||
|
||||
|
|
@ -295,74 +393,69 @@ class TORCH_API StaticModule {
|
|||
return constants_;
|
||||
}
|
||||
|
||||
const BlockInfo& block_info(Block* block) const {
|
||||
return block_infos_.at(block);
|
||||
}
|
||||
|
||||
Block* root_block() const {
|
||||
return graph_->block();
|
||||
}
|
||||
|
||||
private:
|
||||
friend class StaticRuntime;
|
||||
|
||||
// Our nodes don't have their inputs & outputs initialized; don't
|
||||
// let anybody but StaticRuntime and tests get them.
|
||||
const std::vector<ProcessedNode>& nodes() const {
|
||||
return nodes_;
|
||||
}
|
||||
friend class BlockRunner;
|
||||
|
||||
public:
|
||||
auto num_nodes() const {
|
||||
return nodes_.size();
|
||||
return std::accumulate(
|
||||
block_infos_.begin(),
|
||||
block_infos_.end(),
|
||||
0,
|
||||
[](size_t sum, const auto& block_and_info) {
|
||||
auto& block_info = block_and_info.second;
|
||||
return sum + block_info.num_nodes();
|
||||
});
|
||||
}
|
||||
|
||||
C10_NODISCARD Node* findNodeWithKindForTesting(const std::string& kind) const;
|
||||
|
||||
graph_node_list node_ptrs() const {
|
||||
return graph_->nodes();
|
||||
}
|
||||
|
||||
bool is_optimizable_container_type(const Node* n) const {
|
||||
auto it = node_is_optimizable_container_type_.find(n);
|
||||
return it != node_is_optimizable_container_type_.end();
|
||||
}
|
||||
|
||||
const c10::optional<c10::FunctionSchema>& schema() const {
|
||||
return schema_;
|
||||
}
|
||||
|
||||
const ValueGroup& value_group() const {
|
||||
return value_group_;
|
||||
}
|
||||
|
||||
const FastSet<const Value*>& managed_tensor_values() const {
|
||||
return managed_tensor_values_;
|
||||
}
|
||||
|
||||
const FastSet<const Value*>& managed_output_tensor_values() const {
|
||||
return managed_output_tensor_values_;
|
||||
}
|
||||
|
||||
const FastSet<const Value*>& leaked_values() const {
|
||||
return leaked_values_;
|
||||
}
|
||||
|
||||
const ManagedTensorRanges& managed_tensor_ranges() const {
|
||||
return managed_tensor_ranges_;
|
||||
}
|
||||
|
||||
bool first_input_is_self() const {
|
||||
return module_.has_value();
|
||||
}
|
||||
|
||||
size_t inputs_offset() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t constants_offset() const {
|
||||
return inputs_offset() + num_inputs();
|
||||
}
|
||||
|
||||
size_t intermediate_values_offset() const {
|
||||
return constants_offset() + num_constants();
|
||||
}
|
||||
|
||||
StaticRuntime& runtime();
|
||||
|
||||
// See [Shared values array]
|
||||
size_t value_buffer_size() const {
|
||||
return value_buffer_size_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Recursively prepares the BlockInfo array.
|
||||
// - Populates `value_to_index` with the indices of each intermediate value
|
||||
// - Returns the number of Value* processed, including sub-blocks.
|
||||
size_t prepareBlockInfo(
|
||||
Block* block,
|
||||
const size_t start_idx,
|
||||
FastMap<const Value*, uint32_t>& value_to_index);
|
||||
|
||||
void prepareFunctionsAndConstants(
|
||||
Block* block,
|
||||
const AliasDb& alias_db,
|
||||
FastMap<const Value*, uint32_t>& value_to_index);
|
||||
|
||||
// Recurses on sub-blocks and populates the array of ProcessedNodes
|
||||
// Returns (number of nodes processed, number of blocks processed)
|
||||
size_t prepareProcessedNodes(
|
||||
Block* block,
|
||||
const FastMap<const Value*, uint32_t>& value_to_index,
|
||||
const AliasDb& alias_db,
|
||||
size_t node_idx = 0);
|
||||
|
||||
// Initialize various attributes that the memory planner will need.
|
||||
// To be called at the tail of the ctor.
|
||||
void prepareForMemoryPlanner();
|
||||
|
|
@ -383,15 +476,6 @@ class TORCH_API StaticModule {
|
|||
// Indices of graph outputs in the single values array.
|
||||
std::vector<uint16_t> output_indices_;
|
||||
|
||||
ValueGroup value_group_;
|
||||
|
||||
FastSet<const Node*> node_is_optimizable_container_type_;
|
||||
|
||||
FastSet<const Value*> managed_tensor_values_{};
|
||||
FastSet<const Value*> managed_output_tensor_values_{};
|
||||
FastSet<const Value*> leaked_values_{};
|
||||
ManagedTensorRanges managed_tensor_ranges_{};
|
||||
|
||||
size_t num_intermediate_values_ = 0;
|
||||
|
||||
// Includes self if module_ != nullopt.
|
||||
|
|
@ -399,16 +483,33 @@ class TORCH_API StaticModule {
|
|||
// argument. In this case, `self` isn't used in the graph, but the schema
|
||||
// includes it anyways to be consistent with the JIT interpreter.
|
||||
size_t num_inputs_;
|
||||
// See `BlockInfo` definition. The blocks are stored in depth-first order.
|
||||
FastMap<Block*, BlockInfo> block_infos_;
|
||||
size_t value_buffer_size_ = 0;
|
||||
};
|
||||
|
||||
class TORCH_API StaticRuntime {
|
||||
// `BlockRunner` contains the core runtime logic. Each block runner
|
||||
// corresponds to one block in the graph and has its own memory planner.
|
||||
// `StaticRuntime` will initialize all `BlockRunner`s
|
||||
// upon construction. Each block runner only directly executes nodes from its
|
||||
// block. Special ops with sub-blocks like `prim::If` may have
|
||||
// `BlockRunner`s stored in their `ProcessedNode`s; these
|
||||
// sub-blocks get executed in the op's implementation.
|
||||
// `StaticRuntime` stores a vector of IValues that all
|
||||
// `BlockRunner`s share. This vector is used to store all
|
||||
// constants, inputs, and intermediate tensors.
|
||||
class TORCH_API BlockRunner {
|
||||
public:
|
||||
explicit StaticRuntime(const StaticModule& sm);
|
||||
StaticRuntime(StaticRuntime&&) = delete;
|
||||
StaticRuntime& operator=(StaticRuntime&&) = delete;
|
||||
~StaticRuntime();
|
||||
BlockRunner(
|
||||
const StaticModule& sm,
|
||||
std::vector<IValue>& values,
|
||||
Block* block,
|
||||
bool is_root_block = false);
|
||||
BlockRunner(BlockRunner&&) noexcept;
|
||||
BlockRunner& operator=(BlockRunner&&) = delete;
|
||||
~BlockRunner();
|
||||
|
||||
C10_DISABLE_COPY_AND_ASSIGN(StaticRuntime);
|
||||
C10_DISABLE_COPY_AND_ASSIGN(BlockRunner);
|
||||
|
||||
using KeywordArgs = std::unordered_map<std::string, c10::IValue>;
|
||||
c10::IValue operator()(
|
||||
|
|
@ -451,11 +552,16 @@ class TORCH_API StaticRuntime {
|
|||
|
||||
// Input is readwrite
|
||||
IValue& Input(uint32_t i) {
|
||||
DCHECK_LT(i, static_module_.num_inputs());
|
||||
DCHECK_LT(i, block_info_.num_inputs());
|
||||
DCHECK_LT(i, values_.size());
|
||||
return values_[i];
|
||||
return values_[i + block_info_.block_inputs_idx()];
|
||||
}
|
||||
|
||||
size_t init_sub_blocks(
|
||||
const StaticModule& sm,
|
||||
std::vector<IValue>& values,
|
||||
size_t block_idx);
|
||||
|
||||
// Output is readonly. The writing process happens inside ProcessedNodes
|
||||
C10_NODISCARD const IValue& Output(uint32_t i) const {
|
||||
DCHECK(i < outputs_.size());
|
||||
|
|
@ -475,7 +581,7 @@ class TORCH_API StaticRuntime {
|
|||
}
|
||||
|
||||
graph_node_list node_ptrs() const {
|
||||
return static_module_.node_ptrs();
|
||||
return block_info_.node_ptrs();
|
||||
}
|
||||
|
||||
const Graph& graph() const {
|
||||
|
|
@ -486,11 +592,9 @@ class TORCH_API StaticRuntime {
|
|||
return planner_.get();
|
||||
}
|
||||
|
||||
bool check_for_memory_leak(bool output_returned = true);
|
||||
|
||||
bool is_optimizable_container_type(Node* n) const {
|
||||
return static_module_.is_optimizable_container_type(n);
|
||||
}
|
||||
bool check_for_memory_leak(
|
||||
bool output_returned = true,
|
||||
bool recurse_on_sub_blocks = false);
|
||||
|
||||
// WARNING: Deallocate managed output tensors. A client receiving Static
|
||||
// Runtime-managed Tensors needs to be very careful to call
|
||||
|
|
@ -521,7 +625,8 @@ class TORCH_API StaticRuntime {
|
|||
// when destructed.
|
||||
class Deallocator {
|
||||
public:
|
||||
explicit Deallocator(StaticRuntime& runtime) : runtime_(runtime) {}
|
||||
explicit Deallocator(BlockRunner& block_runner)
|
||||
: block_runner_(block_runner) {}
|
||||
|
||||
Deallocator(Deallocator&&) = default;
|
||||
Deallocator(const Deallocator&) = default;
|
||||
|
|
@ -537,7 +642,7 @@ class TORCH_API StaticRuntime {
|
|||
void cleanupImpl();
|
||||
|
||||
bool finished_ = false;
|
||||
StaticRuntime& runtime_;
|
||||
BlockRunner& block_runner_;
|
||||
};
|
||||
|
||||
template <typename IValueList>
|
||||
|
|
@ -569,8 +674,8 @@ class TORCH_API StaticRuntime {
|
|||
|
||||
// clean up owning refs of input IValues
|
||||
void clean_up_input_ivalues() noexcept {
|
||||
for (const auto idx : c10::irange(static_module_.num_inputs())) {
|
||||
values_[idx] = IValue();
|
||||
for (const auto idx : c10::irange(block_info_.num_inputs())) {
|
||||
values_[idx + inputs_begin_] = IValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -591,16 +696,29 @@ class TORCH_API StaticRuntime {
|
|||
const KeywordArgs& kwargs);
|
||||
|
||||
const StaticModule& static_module_;
|
||||
const BlockInfo& block_info_;
|
||||
|
||||
const bool is_root_block_;
|
||||
// Cache this so we don't have to call static_module_.first_input_is_self()
|
||||
const bool first_input_is_self_;
|
||||
// Index of the start of this blocks inputs in the shared values_ array.
|
||||
const uint16_t inputs_begin_;
|
||||
|
||||
bool manage_output_tensors_enabled_ = false;
|
||||
std::unique_ptr<MemoryPlanner> planner_;
|
||||
// first static_module_.num_inputs() slots are inputs, next
|
||||
// static_module_.constants().size() slots are a copy of
|
||||
// static_module_.constants(), rest are regular values in the
|
||||
// graph. ProcessedNodes reference their inputs and outputs with
|
||||
// [Shared values array]
|
||||
// ProcessedNodes reference their inputs and outputs with
|
||||
// offsets into this array, which saves memory.
|
||||
std::vector<IValue> values_;
|
||||
// All BlockRunners share the same array. The layout is as
|
||||
// follows:
|
||||
// [constants][block_0][block_1]...[block_N]
|
||||
// Note that constants from all blocks are pooled together at the start.
|
||||
// The block ordering is depth-first.
|
||||
// Each block is further divided into inputs and intermediates:
|
||||
// [block_i] = [inputs_i][intermediates_i]
|
||||
// Each BlockRunner knows where its inputs start. Each ProcessedNode
|
||||
// knows how to find the indices of its outputs/inputs in this array.
|
||||
std::vector<IValue>& values_;
|
||||
std::vector<IValue*> outputs_;
|
||||
std::vector<ProcessedNode> nodes_;
|
||||
};
|
||||
|
|
@ -643,15 +761,55 @@ class TORCH_API ProcessedNode {
|
|||
ProcessedNode() = default;
|
||||
// ProcessedNodes are created within StaticModule and then
|
||||
// associated with a shared values array using set_values() when
|
||||
// they are copied into a StaticRuntime.
|
||||
// they are copied into a StaticRuntime. block_runners_ are also
|
||||
// not initialized until StaticRuntime initialization; see
|
||||
// BlockRunner's ctor.
|
||||
ProcessedNode(
|
||||
Node* n,
|
||||
ProcessedFunction* fn,
|
||||
ProcessedNodeInputs inputs,
|
||||
uint16_t outputs_offset);
|
||||
|
||||
ProcessedNode(const ProcessedNode&) = default;
|
||||
ProcessedNode& operator=(const ProcessedNode&) = default;
|
||||
ProcessedNode(const ProcessedNode& other)
|
||||
: node_(other.node_),
|
||||
fn_(other.fn_),
|
||||
overlap_detected_(other.overlap_detected_),
|
||||
inputs_(other.inputs_),
|
||||
outputs_offset_(other.outputs_offset_),
|
||||
num_outputs_(other.num_outputs_),
|
||||
values_(other.values_),
|
||||
// It doesn't really make sense to copy block runners,
|
||||
// each processed node needs its own. This is OK to do
|
||||
// since ProcessedNodes are copied from StaticModule right before
|
||||
// the block runners are set up.
|
||||
// TODO(T105178680): For this task, we should move
|
||||
// block runners out of ProcessedNode. Then, we don't have to deal
|
||||
// with this caveat.
|
||||
block_runners_(nullptr)
|
||||
#ifndef PYTORCH_DISABLE_PER_OP_PROFILING
|
||||
,
|
||||
op_name_(other.op_name_)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
ProcessedNode& operator=(const ProcessedNode& other) {
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
node_ = other.node_;
|
||||
fn_ = other.fn_;
|
||||
overlap_detected_ = other.overlap_detected_;
|
||||
inputs_ = other.inputs_;
|
||||
outputs_offset_ = other.outputs_offset_;
|
||||
num_outputs_ = other.num_outputs_;
|
||||
values_ = other.values_;
|
||||
block_runners_ = nullptr;
|
||||
#ifndef PYTORCH_DISABLE_PER_OP_PROFILING
|
||||
op_name_ = other.op_name_;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
// These should be noexcept, but some Android build is failing
|
||||
// saying the noexcept specification doesn't match the calculated
|
||||
|
|
@ -732,11 +890,21 @@ class TORCH_API ProcessedNode {
|
|||
}
|
||||
|
||||
C10_NODISCARD uint16_t output_ivalue_index(uint16_t i) const {
|
||||
DCHECK(i < num_outputs_);
|
||||
return outputs_offset_ + i;
|
||||
}
|
||||
// used in debug mode
|
||||
bool verify_no_memory_overlap(bool force_check = false) const;
|
||||
|
||||
std::vector<BlockRunner>* block_runners() {
|
||||
return block_runners_.get();
|
||||
}
|
||||
|
||||
void set_block_runners(
|
||||
std::unique_ptr<std::vector<BlockRunner>> block_runners) {
|
||||
block_runners_ = std::move(block_runners);
|
||||
}
|
||||
|
||||
private:
|
||||
C10_NODISCARD bool verify_outputs_dont_overlap_each_other() const;
|
||||
|
||||
|
|
@ -749,10 +917,74 @@ class TORCH_API ProcessedNode {
|
|||
uint16_t outputs_offset_;
|
||||
uint16_t num_outputs_;
|
||||
IValue* values_ = nullptr; // unowned
|
||||
// For control flow; processed nodes may have sub-blocks which can
|
||||
// be executed by op implementations.
|
||||
std::unique_ptr<std::vector<BlockRunner>> block_runners_;
|
||||
#ifndef PYTORCH_DISABLE_PER_OP_PROFILING
|
||||
const char* op_name_;
|
||||
#endif
|
||||
};
|
||||
|
||||
// `StaticRuntime` is the owner of the array of IValues (used for constants,
|
||||
// inputs, and intermediate tensors) that all `BlockRunner`s share.
|
||||
// Upon construction, it initializes all block runners. `operator()` simply
|
||||
// forwards the inputs to the top-level block runner. Each `StaticRuntime`
|
||||
// instance corresponds to one `StaticModule`. Multiple `StaticRuntime`
|
||||
// instances can be created; this is useful for multi-threaded execution, since
|
||||
// `operator()` is not thread-safe.
|
||||
class TORCH_API StaticRuntime {
|
||||
public:
|
||||
explicit StaticRuntime(const StaticModule& sm);
|
||||
|
||||
using KeywordArgs = std::unordered_map<std::string, c10::IValue>;
|
||||
c10::IValue operator()(
|
||||
const std::vector<c10::IValue>& args,
|
||||
const KeywordArgs& kwargs = KeywordArgs());
|
||||
c10::IValue operator()(
|
||||
std::vector<c10::IValue>&& args,
|
||||
const KeywordArgs& kwargs = KeywordArgs());
|
||||
|
||||
bool check_for_memory_leak(bool output_returned = true);
|
||||
bool checkOutputTensorMemoryLeaks();
|
||||
|
||||
void deallocateOutputTensors();
|
||||
bool isManagedOutputTensor(const IValue& ivalue) const;
|
||||
void disableManageOutputTensors();
|
||||
|
||||
// Gets the top-level memory planner. Used for testing.
|
||||
const MemoryPlanner* get_memory_planner() const;
|
||||
|
||||
void benchmark(
|
||||
const std::vector<std::vector<c10::IValue>>& args_list,
|
||||
const std::vector<KeywordArgs>& kwargs_list,
|
||||
const int warmup_runs,
|
||||
const int main_runs,
|
||||
bool print_per_node_time = false,
|
||||
bool generate_ai_pep_output = false) {
|
||||
block_->benchmark(
|
||||
args_list,
|
||||
kwargs_list,
|
||||
warmup_runs,
|
||||
main_runs,
|
||||
print_per_node_time,
|
||||
generate_ai_pep_output);
|
||||
}
|
||||
|
||||
using IndividualMetrics = BlockRunner::IndividualMetrics;
|
||||
|
||||
IndividualMetrics benchmark_individual_ops(
|
||||
const std::vector<std::vector<c10::IValue>>& args_list,
|
||||
const std::vector<KeywordArgs>& kwargs_list,
|
||||
const int warmup_runs,
|
||||
const int main_runs) {
|
||||
return block_->benchmark_individual_ops(
|
||||
args_list, kwargs_list, warmup_runs, main_runs);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<BlockRunner> block_;
|
||||
std::vector<IValue> values_;
|
||||
};
|
||||
|
||||
} // namespace jit
|
||||
} // namespace torch
|
||||
|
|
|
|||
|
|
@ -129,10 +129,10 @@ bool setIncludes(const FastSet<const Value*>& set, const Value* v) {
|
|||
}
|
||||
|
||||
std::vector<std::pair<size_t, at::Tensor*>> assignStorageToOutputTensors(
|
||||
StaticRuntime* runtime,
|
||||
BlockRunner* block_runner,
|
||||
const FastSet<const Value*>& managed_output_tensor_values) {
|
||||
std::vector<std::pair<size_t, at::Tensor*>> managed_output_tensors;
|
||||
for (auto& pnode : runtime->nodes()) {
|
||||
for (auto& pnode : block_runner->nodes()) {
|
||||
for (const auto i : c10::irange(pnode.outputs().size())) {
|
||||
auto& ival = pnode.Output(i);
|
||||
const auto* val = pnode.node()->outputs()[i];
|
||||
|
|
@ -151,19 +151,20 @@ std::vector<std::pair<size_t, at::Tensor*>> assignStorageToOutputTensors(
|
|||
} // namespace
|
||||
|
||||
MemoryPlanner::MemoryPlanner(
|
||||
StaticRuntime* runtime,
|
||||
const ValueGroup& value_group,
|
||||
const FastSet<const Value*>& managed_tensor_values,
|
||||
const FastSet<const Value*>& managed_output_tensor_values,
|
||||
const FastSet<const Value*>& leaked_values,
|
||||
const ManagedTensorRanges& ranges,
|
||||
BlockRunner* block_runner,
|
||||
const BlockInfo& block_info,
|
||||
bool enable_out_variant,
|
||||
bool manage_output_tensors,
|
||||
bool optimize_memory) {
|
||||
const auto& managed_tensor_values = block_info.managed_tensor_values();
|
||||
const auto& managed_output_tensor_values =
|
||||
block_info.managed_output_tensor_values();
|
||||
const auto& leaked_values = block_info.leaked_values();
|
||||
|
||||
// collect unmanaged output ivalues
|
||||
FastSet<IValue*> unmanaged_ivalues;
|
||||
FastSet<IValue*> unmanaged_borrowed_ivalues;
|
||||
for (ProcessedNode& pnode : runtime->nodes()) {
|
||||
for (ProcessedNode& pnode : block_runner->nodes()) {
|
||||
const auto borrows_outputs = borrowsOutputs(pnode.node()->kind());
|
||||
for (const auto i : c10::irange(pnode.outputs().size())) {
|
||||
const Value* out_v = pnode.node()->outputs()[i];
|
||||
|
|
@ -189,7 +190,7 @@ MemoryPlanner::MemoryPlanner(
|
|||
}
|
||||
}
|
||||
}
|
||||
for (IValue* output : runtime->outputs()) {
|
||||
for (IValue* output : block_runner->outputs()) {
|
||||
auto it = unmanaged_borrowed_ivalues.find(output);
|
||||
if (it != unmanaged_borrowed_ivalues.end()) {
|
||||
borrowed_ivalues_needing_incref_.push_back(output);
|
||||
|
|
@ -213,10 +214,12 @@ MemoryPlanner::MemoryPlanner(
|
|||
|
||||
if (enable_out_variant) {
|
||||
const auto tensor_value_to_tensor =
|
||||
tensorValueToTensor(runtime->nodes(), managed_tensor_values);
|
||||
tensorValueToTensor(block_runner->nodes(), managed_tensor_values);
|
||||
if (optimize_memory) {
|
||||
managed_tensors_ = assignStorageToManagedTensors(
|
||||
runtime->node_ptrs(), ranges, tensor_value_to_tensor);
|
||||
block_info.node_ptrs(),
|
||||
block_info.managed_tensor_ranges(),
|
||||
tensor_value_to_tensor);
|
||||
} else {
|
||||
for (auto& tensor : tensor_value_to_tensor) {
|
||||
managed_tensors_.emplace_back(tensor.second);
|
||||
|
|
@ -225,8 +228,8 @@ MemoryPlanner::MemoryPlanner(
|
|||
}
|
||||
|
||||
if (enable_out_variant && manage_output_tensors) {
|
||||
managed_output_tensors_ =
|
||||
assignStorageToOutputTensors(runtime, managed_output_tensor_values);
|
||||
managed_output_tensors_ = assignStorageToOutputTensors(
|
||||
block_runner, managed_output_tensor_values);
|
||||
}
|
||||
|
||||
num_managed_tensors_ = 0;
|
||||
|
|
|
|||
|
|
@ -93,12 +93,8 @@ TORCH_API std::vector<StorageGroup> assignStorageToManagedTensors(
|
|||
class MemoryPlanner {
|
||||
public:
|
||||
explicit MemoryPlanner(
|
||||
StaticRuntime* runtime,
|
||||
const ValueGroup& value_group,
|
||||
const FastSet<const Value*>& managed_tensor_values,
|
||||
const FastSet<const Value*>& managed_output_tensor_values,
|
||||
const FastSet<const Value*>& leaked_values,
|
||||
const ManagedTensorRanges& ranges,
|
||||
BlockRunner* block_runner,
|
||||
const BlockInfo& block_info,
|
||||
bool enable_out_variant,
|
||||
bool manage_output_tensors,
|
||||
bool optimize_memory);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user