[PT FE] Better handle tensor names (#15718)

* Add debugNames, smartly handle tensor names, add TranslateSession

* Add friendly name specification in mark_node

* Fix code style

* Fix supported_impl logic

* Fix supported logic 2

* Optimize includes
This commit is contained in:
Maxim Vafin authored and GitHub committed 2023-02-16 20:26:08 +01:00
1 parent 0efa17f95c
commit 878e24d6f6
16 files changed
+417 -273

No files matched your search

@@ -115,6 +115,9 @@ class TorchScriptPythonDecoder (Decoder):
def get_input(self, index: int):
return self.inputs()[index]
def get_input_debug_name(self, index: int) -> str:
return self._raw_input(index).debugName()
def get_input_shape(self, index: int):
raw_input = self._raw_input(index)
return self.get_shape_for_value(raw_input)
@@ -123,6 +126,9 @@ class TorchScriptPythonDecoder (Decoder):
raw_input = self._raw_input(index)
return self.get_type_for_value(raw_input)
def get_output_debug_name(self, index: int) -> str:
return self._raw_output(index).debugName()
def get_output_shape(self, index: int):
output = self._raw_output(index)
return self.get_shape_for_value(output)
@@ -22,6 +22,10 @@ class PyDecoder : public ov::frontend::pytorch::TorchDecoder {
PYBIND11_OVERRIDE_PURE(const std::vector<size_t>&, TorchDecoder, inputs);
}
const std::string& get_input_debug_name(size_t index) const override {
PYBIND11_OVERRIDE_PURE(const std::string&, TorchDecoder, get_input_debug_name, index);
}
ov::PartialShape get_input_shape(size_t index) const override {
PYBIND11_OVERRIDE_PURE(ov::PartialShape, TorchDecoder, get_input_shape, index);
}
@@ -34,8 +38,8 @@ class PyDecoder : public ov::frontend::pytorch::TorchDecoder {
PYBIND11_OVERRIDE_PURE(const std::vector<size_t>&, TorchDecoder, get_input_transpose_order, index);
}
const std::vector<size_t>& get_output_transpose_order(size_t index) const override {
PYBIND11_OVERRIDE_PURE(const std::vector<size_t>&, TorchDecoder, get_output_transpose_order, index);
const std::string& get_output_debug_name(size_t index) const override {
PYBIND11_OVERRIDE_PURE(const std::string&, TorchDecoder, get_output_debug_name, index);
}
ov::PartialShape get_output_shape(size_t index) const override {
@@ -46,6 +50,10 @@ class PyDecoder : public ov::frontend::pytorch::TorchDecoder {
PYBIND11_OVERRIDE_PURE(ov::Any, TorchDecoder, get_output_type, index);
}
const std::vector<size_t>& get_output_transpose_order(size_t index) const override {
PYBIND11_OVERRIDE_PURE(const std::vector<size_t>&, TorchDecoder, get_output_transpose_order, index);
}
bool input_is_none(size_t index) const override {
PYBIND11_OVERRIDE_PURE(bool, TorchDecoder, input_is_none, index);
}
@@ -4,12 +4,7 @@
#pragma once
#include <memory>
#include "openvino/core/any.hpp"
#include "openvino/core/node.hpp"
#include "openvino/core/node_output.hpp"
#include "openvino/core/partial_shape.hpp"
#include "openvino/frontend/decoder.hpp"
namespace ov {
@@ -36,6 +31,9 @@ public:
// Do we need a separate Decoder for Tensor to request properties of it instead of having an impression
// that inputs/outputs have types and shapes?
// Return debug name of the input tensor
virtual const std::string& get_input_debug_name(size_t index) const = 0;
// Return shape if inputs has torch::Tensor type in the original model, otherwise returns the shape [] of a scalar
virtual PartialShape get_input_shape(size_t index) const = 0;
@@ -46,8 +44,8 @@ public:
// TODO: Consider deleting this method, probably it doesn't make sence outside Torch JIT execution
virtual const std::vector<size_t>& get_input_transpose_order(size_t index) const = 0;
// TODO: Consider deleting this method, probably it doesn't make sence outside Torch JIT execution
virtual const std::vector<size_t>& get_output_transpose_order(size_t index) const = 0;
// Return debug name of the input tensor
virtual const std::string& get_output_debug_name(size_t index) const = 0;
// Return shape if inputs has torch::Tensor type in the original model, otherwise returns the shape [] of a scalar
virtual PartialShape get_output_shape(size_t index) const = 0;
@@ -55,6 +53,9 @@ public:
// Return element::Type when it the original type can be represented, otherwise returns PT-sepcific data type object
// (see custom_type.hpp)
virtual Any get_output_type(size_t index) const = 0;
// TODO: Consider deleting this method, probably it doesn't make sence outside Torch JIT execution
virtual const std::vector<size_t>& get_output_transpose_order(size_t index) const = 0;
// ------------------------------
// TODO: required? can be implemented in the context of a single node?
@@ -92,19 +93,6 @@ public:
// returns the same node as syntactically convenient way to make nested sentences in code
virtual std::shared_ptr<Node> mark_node(std::shared_ptr<Node> ov_node) const = 0;
// Call mark_node for each node from the vector
void mark_nodes(std::vector<std::shared_ptr<Node>> ov_nodes) const {
for (auto& ov_node : ov_nodes) {
mark_node(ov_node);
}
}
// Syntactic sugar around mark_node -- just calls it for corresponding node for the passed output port
Output<Node> mark_output(Output<Node> ov_output) const {
mark_node(ov_output.get_node_shared_ptr());
return ov_output;
}
/// \brief Returns the number of sub-graphs that can be enumerated with get_subgraph
virtual size_t get_subgraph_size() const = 0;
@@ -5,6 +5,7 @@
#pragma once
#include "openvino/frontend/frontend.hpp"
#include "openvino/frontend/pytorch/node_context.hpp"
#include "openvino/frontend/pytorch/visibility.hpp"
namespace ov {
@@ -14,6 +15,7 @@ namespace pytorch {
class PYTORCH_API FrontEnd : public ov::frontend::FrontEnd {
public:
using Ptr = std::shared_ptr<FrontEnd>;
FrontEnd();
/// \brief Completely convert and normalize entire Model, throws if it is not possible
/// \param model Input model
@@ -56,8 +58,9 @@ public:
protected:
bool supported_impl(const std::vector<ov::Any>& variants) const override;
ov::frontend::InputModel::Ptr load_impl(const std::vector<ov::Any>& variants) const override;
std::map<std::string, PytorchCreatorFunction> m_op_translators;
};
} // namespace pytorch
@@ -7,12 +7,13 @@
#include "openvino/frontend/exception.hpp"
#include "openvino/frontend/node_context.hpp"
#include "openvino/frontend/pytorch/decoder.hpp"
#include "openvino/util/log.hpp"
namespace ov {
namespace frontend {
namespace pytorch {
class TranslateSession;
typedef std::unordered_map<size_t, Output<Node>> TensorMap;
class NodeContext : public frontend::NodeContext {
@@ -20,15 +21,19 @@ public:
NodeContext(std::shared_ptr<TorchDecoder> decoder,
TensorMap* tensor_map,
ParameterVector* external_parameters,
const TensorMap& ext_tensor_map)
: // TODO: why the following ctor is explicit?
frontend::NodeContext(decoder->get_op_type()),
const TensorMap& ext_tensor_map,
TranslateSession* translate_session)
: frontend::NodeContext(decoder->get_op_type()),
m_decoder(decoder),
m_tensor_map(tensor_map),
m_ext_tensor_map(ext_tensor_map),
m_external_parameters(external_parameters),
m_translate_session(translate_session),
m_decoder_inputs(decoder->inputs()),
m_decoder_outputs(decoder->outputs()) {}
m_decoder_outputs(decoder->outputs()) {
FRONT_END_GENERAL_CHECK(tensor_map != nullptr && external_parameters != nullptr &&
translate_session != nullptr);
}
// Do not search for input in tensor map; try to access it as a constant of specified type T and return its value
template <typename T>
@@ -81,16 +86,19 @@ public:
return m_decoder->get_schema();
}
std::shared_ptr<Node> mark_node(std::shared_ptr<Node> ov_node) const {
return m_decoder->mark_node(ov_node);
}
std::shared_ptr<Node> mark_node(std::shared_ptr<Node> ov_node) const;
// Call mark_node for each node from the vector
void mark_nodes(std::vector<std::shared_ptr<Node>> ov_nodes) const {
return m_decoder->mark_nodes(ov_nodes);
for (auto& ov_node : ov_nodes) {
mark_node(ov_node);
}
}
// Syntactic sugar around mark_node -- just calls it for corresponding node for the passed output port
Output<Node> mark_output(Output<Node> ov_output) const {
return m_decoder->mark_node(ov_output.get_node_shared_ptr());
mark_node(ov_output.get_node_shared_ptr());
return ov_output;
}
Any get_attribute_as_any(const std::string&) const override {
@@ -98,16 +106,7 @@ public:
"There is no any named attributes in PyTorch node, query by attribute name is not implemented");
}
void mutate_input(size_t index, Output<Node> ov_output) {
FRONT_END_GENERAL_CHECK(!m_decoder->input_is_none(index), "Input is none with index: ", index);
auto input = m_decoder_inputs.at(index);
FRONT_END_GENERAL_CHECK(m_tensor_map->count(input), "No tensor corresponding input: ", input, " exist.");
m_tensor_map->at(input).get_tensor().set_names({std::to_string(input) + "_"});
// TODO: find out why this doesn't work
ov_output.get_tensor().add_names({std::to_string(input)});
(*m_tensor_map)[input] = ov_output;
m_mutated_tensors.insert(input);
}
void mutate_input(size_t index, Output<Node> ov_output);
std::set<size_t> get_mutated_tensors() const {
return m_mutated_tensors;
@@ -117,14 +116,12 @@ public:
return m_decoder;
}
void add_tensor_to_context(size_t index, Output<Node> ov_output) {
if (m_tensor_map->count(index)) {
OPENVINO_DEBUG << "[ WARNING ] Current context has tensor. Rewriting.\n";
}
ov_output.get_tensor().add_names({std::to_string(index)});
(*m_tensor_map)[index] = ov_output;
TranslateSession* get_session() const {
return m_translate_session;
}
void add_tensor_to_context(size_t index, Output<Node> ov_output);
Output<Node> get_tensor_from_model(size_t index) const {
if (m_tensor_map->find(index) != m_tensor_map->end()) {
return m_tensor_map->at(index);
@@ -143,10 +140,13 @@ private:
TensorMap* m_tensor_map;
const TensorMap& m_ext_tensor_map;
ParameterVector* m_external_parameters;
TranslateSession* m_translate_session;
const std::vector<size_t> m_decoder_inputs;
const std::vector<size_t> m_decoder_outputs;
};
using PytorchCreatorFunction = std::function<OutputVector(NodeContext&)>;
} // namespace pytorch
} // namespace frontend
} // namespace ov
+12 -5
View File
@@ -5,6 +5,7 @@
#include "openvino/frontend/pytorch/frontend.hpp"
#include "input_model.hpp"
#include "op_table.hpp"
#include "openvino/op/util/multi_subgraph_base.hpp"
#include "openvino/pass/constant_folding.hpp"
#include "openvino/util/log.hpp"
@@ -22,6 +23,7 @@
#include "transforms/prim_list_construct_pad.hpp"
#include "transforms/prim_list_tuple_construct_replacer.hpp"
#include "transforms/prim_list_unpack_replacer.hpp"
#include "translate_session.hpp"
namespace ov {
namespace frontend {
@@ -46,6 +48,8 @@ std::set<std::string> get_unconverted_types_from_model(const std::shared_ptr<Mod
}
} // namespace
FrontEnd::FrontEnd() : m_op_translators(get_supported_ops()) {}
std::shared_ptr<Model> FrontEnd::convert(const InputModel::Ptr& model) const {
auto converted_model = convert_partially(model);
normalize(converted_model);
@@ -64,11 +68,10 @@ void FrontEnd::convert(const std::shared_ptr<Model>& partiallyConverted) const {
}
std::shared_ptr<Model> FrontEnd::convert_partially(const ov::frontend::InputModel::Ptr& model) const {
FRONT_END_GENERAL_CHECK(std::dynamic_pointer_cast<pytorch::InputModel>(model), "Invalid input model");
try {
auto pytorch_model = std::dynamic_pointer_cast<pytorch::InputModel>(model);
auto model = convert_pytorch_model(pytorch_model->m_model);
return model;
TranslateSession translate_session(model, m_op_translators);
return translate_session.get_converted_model();
} catch (const std::runtime_error& e) {
std::cerr << "[ ERROR ] Unexpected error while converting pytorch model: " << e.what() << '\n';
std::cerr << "Rethrowing. Misleading error message from pybind11 may come next. TODO.";
@@ -125,7 +128,11 @@ void FrontEnd::add_extension(const std::shared_ptr<ov::Extension>& extension) {
}
bool FrontEnd::supported_impl(const std::vector<ov::Any>& variants) const {
return false;
// Currently PyTorch FrontEnd only support TorchDecoder as input
if (variants.size() != 1 || !variants[0].is<std::shared_ptr<IDecoder>>())
return false;
auto decoder = variants[0].as<std::shared_ptr<IDecoder>>();
return decoder && std::dynamic_pointer_cast<TorchDecoder>(decoder);
}
ov::frontend::InputModel::Ptr FrontEnd::load_impl(const std::vector<ov::Any>& variants) const {
+2 -2
View File
@@ -5,14 +5,14 @@
#pragma once
#include "openvino/frontend/pytorch/decoder.hpp"
#include "openvino/frontend/pytorch/frontend.hpp"
#include "translate_session.hpp"
namespace ov {
namespace frontend {
namespace pytorch {
class InputModel : public ov::frontend::InputModel {
friend class ::ov::frontend::pytorch::FrontEnd;
friend class ::ov::frontend::pytorch::TranslateSession;
std::shared_ptr<TorchDecoder> m_model;
public:
+27 -5
View File
@@ -9,6 +9,7 @@
#include "openvino/opsets/opset10.hpp"
#include "openvino/util/log.hpp"
#include "pt_framework_node.hpp"
#include "translate_session.hpp"
namespace ov {
namespace frontend {
@@ -36,13 +37,35 @@ OutputVector NodeContext::as_constant() const {
}
}
std::shared_ptr<Node> NodeContext::mark_node(std::shared_ptr<Node> ov_node) const {
ov_node->set_friendly_name(get_op_type() + '_' + std::to_string(m_translate_session->m_friendly_name_counter++));
return m_decoder->mark_node(ov_node);
}
void NodeContext::mutate_input(size_t index, Output<Node> ov_output) {
FRONT_END_GENERAL_CHECK(!m_decoder->input_is_none(index), "Input is none with index: ", index);
auto input_id = m_decoder_inputs.at(index);
FRONT_END_GENERAL_CHECK(m_tensor_map->count(input_id), "No tensor corresponding input: ", input_id, " exist.");
m_translate_session->encode_tensor_name(ov_output, input_id, m_decoder->get_input_debug_name(index));
(*m_tensor_map)[input_id] = ov_output;
m_mutated_tensors.insert(input_id);
}
void NodeContext::add_tensor_to_context(size_t index, Output<Node> ov_output) {
if (m_tensor_map->count(index)) {
OPENVINO_DEBUG << "[ WARNING ] Current context has tensor. Rewriting.\n";
}
m_translate_session->encode_tensor_name(ov_output, index);
(*m_tensor_map)[index] = ov_output;
}
Output<Node> NodeContext::get_tensor_from_model_or_create_input(size_t index) {
if (m_tensor_map->find(index) != m_tensor_map->end()) {
return m_tensor_map->at(index);
} else {
// nested subgraphs case
auto parameter = std::make_shared<opset10::Parameter>(element::dynamic, PartialShape::dynamic());
parameter->get_output_tensor(0).add_names({std::to_string(index)});
m_translate_session->encode_tensor_name(parameter->output(0), index);
(*m_tensor_map)[index] = parameter;
m_external_parameters->push_back(parameter);
OPENVINO_DEBUG << "Nested case, created: " << parameter << '\n';
@@ -56,8 +79,7 @@ Output<Node> NodeContext::get_input_from_visible_context(size_t index) const {
auto input_node = input_tensor.get_node_shared_ptr();
if (std::dynamic_pointer_cast<opset10::Parameter>(input_node)) {
// We need to look into external context for inputs that would be feed into this parameter
auto name = input_node->get_output_tensor(0).get_any_name();
size_t tensor_idx = (size_t)std::stoll(name);
size_t tensor_idx = m_translate_session->decode_tensor_name(input_node->output(0));
if (m_ext_tensor_map.count(tensor_idx)) {
input_tensor = m_ext_tensor_map.at(tensor_idx);
}
@@ -72,10 +94,10 @@ std::shared_ptr<ov::Model> NodeContext::convert_subgraph(size_t index) {
// link internal context with external
TensorMap ext_map(m_ext_tensor_map);
// map::insert does not update elements if their key is already in map; so if we have real tensors in outter scope
// we will not add Parameters we creeated in inner scope.
// we will not add Parameters we created in inner scope.
ext_map.insert(m_tensor_map->begin(), m_tensor_map->end());
auto model = convert_pytorch_model(subgraph_decoder, ext_map);
auto model = m_translate_session->convert_pytorch_model(subgraph_decoder, ext_map);
// Remove unused parameters, they could be created as inputs to the parts of graph that weren't
// used for generating output.
for (auto i = subgraph_decoder->inputs().size(); i < model->get_parameters().size(); i++) {
+8 -10
View File
@@ -5,6 +5,7 @@
#include "openvino/frontend/pytorch/node_context.hpp"
#include "openvino/opsets/opset10.hpp"
#include "openvino/util/log.hpp"
#include "translate_session.hpp"
#include "utils.hpp"
namespace ov {
@@ -34,9 +35,9 @@ OutputVector translate_if(NodeContext& context) {
std::map<size_t, ParameterVector> inputs_map;
std::map<size_t, ResultVector> outputs_map;
auto session = context.get_session();
for (const auto& param : then_body->get_parameters()) {
auto name = param->get_output_tensor(0).get_any_name();
size_t input_idx = (size_t)std::stoll(name);
auto input_idx = session->decode_tensor_name(param->output(0));
FRONT_END_OP_CONVERSION_CHECK(inputs_map.count(input_idx) == 0,
"More than one then_body input with same tensor name: ",
input_idx,
@@ -47,8 +48,7 @@ OutputVector translate_if(NodeContext& context) {
inputs_map[input_idx] = {param, nullptr};
}
for (const auto& param : else_body->get_parameters()) {
auto name = param->get_output_tensor(0).get_any_name();
size_t input_idx = (size_t)std::stoll(name);
auto input_idx = session->decode_tensor_name(param->output(0));
if (inputs_map.count(input_idx)) {
inputs_map[input_idx][1] = param;
} else {
@@ -70,8 +70,7 @@ OutputVector translate_if(NodeContext& context) {
std::set<size_t> extra_output_idxs;
for (size_t i = num_outs; i < then_results.size(); i++) {
const auto result = then_results[i];
const auto name = result->input(0).get_tensor().get_any_name();
size_t output_idx = (size_t)std::stoll(name);
auto output_idx = session->decode_tensor_name(result->input(0).get_source_output());
FRONT_END_OP_CONVERSION_CHECK(extra_then_body_results.count(output_idx) == 0,
"More than one then_body output with same tensor name: ",
output_idx,
@@ -84,8 +83,7 @@ OutputVector translate_if(NodeContext& context) {
}
for (size_t i = num_outs; i < else_results.size(); i++) {
const auto result = else_results[i];
const auto name = result->input(0).get_tensor().get_any_name();
size_t output_idx = (size_t)std::stoll(name);
auto output_idx = session->decode_tensor_name(result->input(0).get_source_output());
FRONT_END_OP_CONVERSION_CHECK(extra_else_body_results.count(output_idx) == 0,
"More than one else_body output with same tensor name: ",
output_idx,
@@ -102,7 +100,7 @@ OutputVector translate_if(NodeContext& context) {
if (!extra_then_body_results.count(output_idx)) {
// Need to add Parameter->Result construction in then body
auto new_parameter = std::make_shared<opset10::Parameter>(element::dynamic, PartialShape::dynamic());
new_parameter->get_output_tensor(0).add_names({std::to_string(output_idx)});
session->encode_tensor_name(new_parameter->output(0), output_idx);
auto new_result = std::make_shared<opset10::Result>(new_parameter);
then_body->add_parameters({new_parameter});
then_body->add_results({new_result});
@@ -114,7 +112,7 @@ OutputVector translate_if(NodeContext& context) {
} else if (!extra_else_body_results.count(output_idx)) {
// Need to add Parameter->Result construction in else body
auto new_parameter = std::make_shared<opset10::Parameter>(element::dynamic, PartialShape::dynamic());
new_parameter->get_output_tensor(0).add_names({std::to_string(output_idx)});
session->encode_tensor_name(new_parameter->output(0), output_idx);
auto new_result = std::make_shared<opset10::Result>(new_parameter);
else_body->add_parameters({new_parameter});
else_body->add_results({new_result});
+4 -4
View File
@@ -5,6 +5,7 @@
#include "openvino/op/loop.hpp"
#include "openvino/frontend/pytorch/node_context.hpp"
#include "translate_session.hpp"
#include "utils.hpp"
namespace ov {
@@ -31,10 +32,10 @@ OutputVector translate_loop(NodeContext& context) {
loop->set_invariant_inputs(inputs[i], {body_parameters[i - 1]});
}
// Connect inputs from external context
auto session = context.get_session();
for (auto i = inputs.size() - 1; i < body_parameters.size(); i++) {
auto param = body_parameters[i];
auto name = param->get_output_tensor(0).get_any_name();
size_t input_idx = (size_t)std::stoll(name);
auto input_idx = session->decode_tensor_name(param->output(0));
auto external_output = context.get_tensor_from_model_or_create_input(input_idx);
loop->set_invariant_inputs(external_output, {param});
}
@@ -45,8 +46,7 @@ OutputVector translate_loop(NodeContext& context) {
// 0 output is condition, do not need to connect it
for (size_t i = 1; i < body_results.size(); i++) {
auto result = body_results[i];
auto name = result->input(0).get_tensor().get_any_name();
size_t out_idx = (size_t)std::stoll(name);
auto out_idx = session->decode_tensor_name(result->input(0).get_source_output());
FRONT_END_OP_CONVERSION_CHECK(output_idxs.count(out_idx) == 0,
"More then one body output with same tensor name.");
output_idxs.insert(out_idx);
+1 -1
View File
@@ -118,7 +118,7 @@ OP_CONVERTER(translate_zeros_like);
} // namespace op
const std::map<std::string, CreatorFunction> get_supported_ops() {
const std::map<std::string, PytorchCreatorFunction> get_supported_ops() {
return {
{"aten::__and__", op::translate_1to1_match_2_inputs<opset10::LogicalAnd>}, // TODO: cover numerical cases
{"aten::__getitem__", op::translate_getitem},
+1 -2
View File
@@ -9,9 +9,8 @@
namespace ov {
namespace frontend {
namespace pytorch {
using CreatorFunction = std::function<OutputVector(NodeContext&)>;
const std::map<std::string, CreatorFunction> get_supported_ops();
const std::map<std::string, PytorchCreatorFunction> get_supported_ops();
} // namespace pytorch
} // namespace frontend
@@ -0,0 +1,232 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "translate_session.hpp"
#include "input_model.hpp"
#include "openvino/op/constant.hpp"
#include "openvino/op/parameter.hpp"
#include "openvino/op/reshape.hpp"
#include "openvino/op/result.hpp"
#include "openvino/op/transpose.hpp"
#include "openvino/util/log.hpp"
#include "utils.hpp"
namespace ov {
namespace frontend {
namespace pytorch {
using namespace ov::op;
TranslateSession::TranslateSession(const ov::frontend::InputModel::Ptr& input_model,
const std::map<std::string, PytorchCreatorFunction>& translator_map)
: m_input_model(input_model),
m_translator_map(translator_map),
m_ov_model(nullptr) {}
std::shared_ptr<ov::Model> TranslateSession::get_converted_model() {
if (m_ov_model) {
return m_ov_model;
}
m_ov_model = translate_graph(m_input_model);
return m_ov_model;
}
std::shared_ptr<ov::Model> TranslateSession::translate_graph(const ov::frontend::InputModel::Ptr& input_model) {
auto pytorch_model = std::dynamic_pointer_cast<pytorch::InputModel>(input_model);
FRONT_END_GENERAL_CHECK(pytorch_model != nullptr, "Invalid input model");
return convert_pytorch_model(pytorch_model->m_model);
}
std::shared_ptr<Model> TranslateSession::convert_pytorch_model(std::shared_ptr<TorchDecoder> pytorch_model,
const TensorMap& external_tensor_map) {
std::shared_ptr<Model> resulting_model; // define here to make a conversion in a nested scope
{
ParameterVector parameters;
TensorMap tensor_map; // tensor map of the current context
std::set<size_t> mutated_tensors;
// Go over all pytorch_model inputs and register them in the tensor map:
auto inputs = pytorch_model->inputs();
for (size_t i = 0; i < inputs.size(); ++i) {
PartialShape ps = pytorch_model->get_input_shape(i);
auto type = simplified_type_interpret(pytorch_model->get_input_type(i));
// TODO: Use special API to set custom type detalization
auto parameter = std::make_shared<v0::Parameter>(element::dynamic, ps);
encode_tensor_name(parameter->output(0), inputs.at(i), pytorch_model->get_input_debug_name(i));
parameters.push_back(parameter);
auto order = pytorch_model->get_input_transpose_order(i);
if (order.size() > 0 && !std::is_sorted(order.begin(), order.end())) {
FRONT_END_GENERAL_CHECK(ps.is_static(), "Shape must be static."); // TODO: make dynamic
auto sh = ps.get_shape();
Shape new_shape(sh.size());
for (size_t i = 0; i < sh.size(); i++) {
new_shape[order[i]] = sh[i];
}
auto shape_const = v0::Constant::create(element::i64, {new_shape.size()}, new_shape);
auto reshape = std::make_shared<v1::Reshape>(parameter, shape_const, false);
auto order_const = v0::Constant::create(element::i32, {order.size()}, order);
auto transpose = std::make_shared<v1::Transpose>(reshape, order_const);
tensor_map[inputs.at(i)] = transpose;
} else {
tensor_map[inputs.at(i)] = parameter;
}
}
auto node_visitor = [&](std::shared_ptr<TorchDecoder> node) {
// Explore all inputs of node. Node may refer to input value that hasn't been created in the current scope.
// But this value can be found in the outer scope, for this purpose we create new input for the model to
// link with external scope on a higher level.
auto raw_inputs = node->inputs();
for (size_t i = 0; i < raw_inputs.size(); ++i) {
auto input = raw_inputs.at(i);
if (tensor_map.find(input) == tensor_map.end()) {
// Input refers value in the outer scope, need to create a new Parameter in the current scope
// Linkage to external scope will be performed on the level of the parent operation (if or loop)
// TODO: Eliminate duplication with the main code for Parameters creation
PartialShape ps = node->get_input_shape(i);
auto type = simplified_type_interpret(node->get_input_type(i));
// TODO: Use special API to set custom type detalization
auto parameter = std::make_shared<v0::Parameter>(element::dynamic, ps);
// TODO: Missing get_input_transpose_order handling for not trivial layouts
tensor_map[input] = parameter;
// set name of parameter to the index of node in the model
encode_tensor_name(parameter->output(0), input);
parameters.push_back(parameter);
}
}
auto context = NodeContext(node, &tensor_map, &parameters, external_tensor_map, this);
auto converted_outputs = convert_node(context);
auto mutated_t = context.get_mutated_tensors();
mutated_tensors.insert(mutated_t.begin(), mutated_t.end());
auto fw_outputs = node->outputs();
// Ops with subgraphs or with mutated inputs may have more outputs after conversion compared to pytorch ones
FRONT_END_OP_CONVERSION_CHECK(fw_outputs.size() <= converted_outputs.size(),
"Number of ",
node->get_op_type(),
" outputs greater then number of converted outputs.");
// TODO: Make sure that mapping of fw_outputs to converted_outputs does always work
// FIXME: Now it is not true for at least prim::Constant
for (size_t i = 0; i < fw_outputs.size(); ++i) {
size_t fw_tensor_id = node->output(i);
FRONT_END_GENERAL_CHECK(tensor_map.find(fw_tensor_id) == tensor_map.end(),
"Duplicated producer for PT value with unique ID: ",
fw_tensor_id);
tensor_map[fw_tensor_id] = converted_outputs[i];
encode_tensor_name(converted_outputs[i], fw_tensor_id, node->get_output_debug_name(i));
}
};
FRONT_END_GENERAL_CHECK(pytorch_model->get_subgraph_size() == 1, "Model should have exactly 1 subgraph.");
pytorch_model->visit_subgraph(node_visitor);
ResultVector results;
for (size_t i = 0; i < pytorch_model->num_of_outputs(); ++i) {
size_t id = pytorch_model->output(i);
if (tensor_map.find(id) == tensor_map.end()) {
// Not found in this scope, adding Parameter to connect to external scope
auto parameter = std::make_shared<v0::Parameter>(element::dynamic, PartialShape::dynamic());
encode_tensor_name(parameter->output(0), id);
parameters.push_back(parameter);
tensor_map[id] = parameter;
}
auto ov_output = tensor_map[id];
auto order = pytorch_model->get_output_transpose_order(i);
FRONT_END_GENERAL_CHECK(order.size() == 0 || std::is_sorted(order.begin(), order.end()),
"Output strides have wrong order.");
FRONT_END_GENERAL_CHECK(ov_output.get_names().size() > 0,
"Tensor doesn't have name, while it should have name: ",
id);
auto result = std::make_shared<v0::Result>(ov_output);
results.push_back(result);
}
// Since parameters can be added we need to list all current parameters
std::set<size_t> param_names;
for (const auto& param : parameters) {
auto input_idx = decode_tensor_name(param->output(0));
param_names.insert(input_idx);
}
for (const auto& tensor_id : mutated_tensors) {
if (param_names.count(tensor_id)) {
FRONT_END_GENERAL_CHECK(tensor_map.count(tensor_id),
"Tensor with id: ",
tensor_id,
" doesn't exist in tensor map.");
// model input was mutated we need to make a result for it
auto mutated_tensor = tensor_map.at(tensor_id);
// empty external_tensor_map means this is main body of the model and we don't want to create
// additional outputs in that case.
if (mutated_tensor.get_target_inputs().empty() && !external_tensor_map.empty())
results.push_back(std::make_shared<v0::Result>(tensor_map.at(tensor_id)));
}
}
resulting_model = std::make_shared<Model>(results, parameters);
// Did a conversion in a nested scope to automatically remove any holders of nodes except those in the graph
}
return resulting_model;
}
OutputVector TranslateSession::convert_node(NodeContext& context) {
try {
auto it = m_translator_map.find(context.get_op_type());
if (it != m_translator_map.end()) {
return it->second(context);
}
} catch (std::runtime_error& e) {
OPENVINO_DEBUG << "Exception happened during conversion of op: " << context.get_op_type()
<< " with schema: " << context.get_schema() << ": " << e.what() << '\n';
} catch (...) {
OPENVINO_DEBUG << "Some exception happened during conversion of node of type: " << context.get_op_type()
<< '\n';
}
// Create PtFrameworkNode for everything that wasn't able to be converted normally
return make_framework_node(context);
}
void TranslateSession::encode_tensor_name(Output<Node> output, size_t tensor_idx, std::string debug_name) {
if (!output.get_names().empty()) {
OPENVINO_DEBUG << "Tensor names already exist: " << output.get_any_name() << ". Rewriting with " << tensor_idx;
}
auto has_dname = !debug_name.empty();
auto name = std::to_string(tensor_idx);
if (has_dname && name == debug_name)
has_dname = false;
if (m_counter_map.count(tensor_idx)) {
auto&& pair = m_counter_map[tensor_idx];
auto new_name = name + '_' + std::to_string(++pair.first);
pair.second.set_names({new_name});
pair.second = output;
if (has_dname) {
output.set_names({name, debug_name});
} else {
output.set_names({name});
}
} else {
m_counter_map[tensor_idx] = {0, output};
if (has_dname) {
output.set_names({name, debug_name});
} else {
output.set_names({name});
}
}
}
size_t TranslateSession::decode_tensor_name(const Output<Node>& output) {
// any_name should always return numerical value even if there is a word value exist in names
const auto& name = output.get_any_name();
// numbers after "_" will be ignored by stoll function
return static_cast<size_t>(std::stoll(name));
}
} // namespace pytorch
} // namespace frontend
} // namespace ov
@@ -0,0 +1,51 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/frontend/input_model.hpp"
#include "openvino/frontend/pytorch/frontend.hpp"
#include "openvino/frontend/pytorch/node_context.hpp"
namespace ov {
namespace frontend {
namespace pytorch {
/// For one call of convert and decode method of Frontend, it creates one TranslateSession object to save data for the
/// translation session: telemetry statistics, operation translators (including extensions) registered for this
/// translation session.
class TranslateSession {
public:
TranslateSession(const frontend::InputModel::Ptr& input_model,
const std::map<std::string, PytorchCreatorFunction>& translator_map);
std::shared_ptr<Model> get_converted_model();
std::shared_ptr<Model> translate_graph(const frontend::InputModel::Ptr& input_model);
/// \brief Completely convert pytorch_model, creates PtFrameworkNode if not possible to convert node
/// \param pytorch_model Input model
/// \param external_tensor_map Is used for recursive calls of convert_pytorch_model and represent the external
/// context which is visible from nested model. Empty external_tensor_map is used as an indication that this is a
/// main body conversion.
/// \return fully converted OV Model
std::shared_ptr<Model> convert_pytorch_model(std::shared_ptr<TorchDecoder> pytorch_model,
const TensorMap& external_tensor_map = {});
void encode_tensor_name(Output<Node> tensor_desc, size_t tensor_idx, std::string debug_name = "");
size_t decode_tensor_name(const Output<Node>& tensor_desc);
size_t m_friendly_name_counter = 0;
private:
OutputVector convert_node(NodeContext& context);
const frontend::InputModel::Ptr m_input_model;
const std::map<std::string, PytorchCreatorFunction>& m_translator_map;
std::shared_ptr<Model> m_ov_model;
std::map<size_t, std::pair<size_t, Output<Node>>> m_counter_map;
};
} // namespace pytorch
} // namespace frontend
} // namespace ov
+24 -191
View File
@@ -9,6 +9,7 @@
#include "openvino/opsets/opset10.hpp"
#include "openvino/util/log.hpp"
#include "pt_framework_node.hpp"
#include "translate_session.hpp"
namespace ov {
namespace frontend {
@@ -161,24 +162,23 @@ std::shared_ptr<Node> concat_list_construct(std::shared_ptr<Node> input) {
return input;
}
OutputVector make_framework_node(NodeContext* context) {
auto schema = context->get_schema();
OutputVector make_framework_node(NodeContext& context) {
auto schema = context.get_schema();
// TODO: properly process schema to get the actual position of mutable input
// Hack. Can indicate mutable inputs, but can it be reliable?
if (schema.find('!') != std::string::npos) {
// We create additional output for such nodes. It contains new tensor that represents input that was changed.
auto fw_node = std::make_shared<PtFrameworkNode>(context->get_decoder(),
context->inputs(),
context->get_output_size() + 1);
fw_node->set_friendly_name(context->get_op_type());
auto fw_node =
std::make_shared<PtFrameworkNode>(context.get_decoder(), context.inputs(), context.get_output_size() + 1);
fw_node->set_friendly_name(context.get_op_type());
auto outputs = fw_node->outputs();
// Usually mutated input index is 0, because it is usually "self" input, so we need to replace this tensor with
// output we created.
context->mutate_input(0, outputs.back());
context.mutate_input(0, outputs.back());
OPENVINO_DEBUG << "Created node with mutated 0 input. Schema: " << schema << '\n';
context->mark_node(fw_node);
context.mark_node(fw_node);
// For simplification we do not expect such operations to have extra bodies
FRONT_END_OP_CONVERSION_CHECK(context->get_decoder()->get_subgraph_size() == 0,
FRONT_END_OP_CONVERSION_CHECK(context.get_decoder()->get_subgraph_size() == 0,
"Mutable operation has subgraphs.");
return outputs;
}
@@ -191,15 +191,15 @@ OutputVector make_framework_node(NodeContext* context) {
// We need to remember initial inputs to be able to find extra inputs to body that were created to propagate
// external context
size_t num_body_outs = 0;
for (size_t i = 0; i < context->get_decoder()->get_subgraph_size(); ++i) {
auto subgraph_decoder = context->get_decoder()->get_subgraph_decoder(i);
auto session = context.get_session();
for (size_t i = 0; i < context.get_decoder()->get_subgraph_size(); ++i) {
auto subgraph_decoder = context.get_decoder()->get_subgraph_decoder(i);
auto inputs = subgraph_decoder->inputs();
input_idxs.insert(inputs.begin(), inputs.end());
auto body = context->convert_subgraph(i);
auto body = context.convert_subgraph(i);
bodies.push_back(body);
for (const auto& param : body->get_parameters()) {
auto name = param->get_output_tensor(0).get_any_name();
size_t input_idx = (size_t)std::stoll(name);
auto input_idx = session->decode_tensor_name(param->output(0));
inputs_map[input_idx].push_back(param);
}
auto body_outputs = subgraph_decoder->outputs();
@@ -213,8 +213,7 @@ OutputVector make_framework_node(NodeContext* context) {
// Some bodies may have mutated inputs which we need to propagate to external context
auto body_results = body->get_results();
for (size_t i = num_body_outs; i < body_results.size(); i++) {
auto name = body_results[i]->input(0).get_tensor().get_any_name();
size_t out_idx = (size_t)std::stoll(name);
auto out_idx = session->decode_tensor_name(body_results[i]->input(0).get_source_output());
FRONT_END_OP_CONVERSION_CHECK(extra_outputs_map.count(out_idx) == 0,
"More then one body output with same tensor name.");
extra_outputs_map[out_idx].push_back(body_results[i]);
@@ -223,14 +222,13 @@ OutputVector make_framework_node(NodeContext* context) {
// Number of body outputs can be higher then number of pt node outputs, e.g. in case of loop first body output is
// condition, we have to skip such outputs.
auto num_skip_body_outputs =
num_body_outs > context->get_output_size() ? num_body_outs - context->get_output_size() : 0;
num_body_outs > context.get_output_size() ? num_body_outs - context.get_output_size() : 0;
// We need to reduce number of outputs, because some outputs are outputs from body
auto fw_node =
std::make_shared<PtFrameworkNode>(context->get_decoder(),
context->inputs(),
context->get_output_size() - num_body_outs + num_skip_body_outputs);
fw_node->set_friendly_name(context->get_op_type());
auto fw_node = std::make_shared<PtFrameworkNode>(context.get_decoder(),
context.inputs(),
context.get_output_size() - num_body_outs + num_skip_body_outputs);
fw_node->set_friendly_name(context.get_op_type());
for (size_t i = 0; i < bodies.size(); ++i) {
fw_node->set_function(static_cast<int>(i), bodies[i]);
}
@@ -238,16 +236,16 @@ OutputVector make_framework_node(NodeContext* context) {
// Connect inputs with external context
for (const auto& input : inputs_map) {
if (!input_idxs.count(input.first)) {
auto external_output = context->get_tensor_from_model_or_create_input(input.first);
auto external_output = context.get_tensor_from_model_or_create_input(input.first);
fw_node->set_invariant_inputs(external_output, input.second);
} else {
auto external_output = context->get_tensor_from_model(input.first);
auto external_output = context.get_tensor_from_model(input.first);
if (external_output.get_node()) {
fw_node->set_invariant_inputs(external_output, input.second);
}
}
}
OutputVector res(context->mark_node(fw_node)->outputs());
OutputVector res(context.mark_node(fw_node)->outputs());
if (fw_node->get_internal_subgraphs_size() > 0) {
auto first_body_results = fw_node->get_function(0)->get_results();
std::vector<ResultVector> outputs;
@@ -266,176 +264,11 @@ OutputVector make_framework_node(NodeContext* context) {
}
// Propagate extra outputs to external context
for (const auto& output : extra_outputs_map) {
context->add_tensor_to_context(output.first, fw_node->set_body_outputs(output.second));
context.add_tensor_to_context(output.first, fw_node->set_body_outputs(output.second));
}
return res;
}
OutputVector convert_node(NodeContext* context) {
try {
auto CONVERTERS_MAP = get_supported_ops();
auto it = CONVERTERS_MAP.find(context->get_op_type());
if (it != CONVERTERS_MAP.end()) {
return it->second(*context);
}
} catch (std::runtime_error& e) {
OPENVINO_DEBUG << "Exception happened during conversion of op: " << context->get_op_type()
<< " with schema: " << context->get_schema() << ": " << e.what() << '\n';
} catch (...) {
OPENVINO_DEBUG << "Some exception happened during conversion of node of type: " << context->get_op_type()
<< '\n';
}
// Create PtFrameworkNode for everything that wasn't able to be converted normally
return make_framework_node(context);
}
/// \brief Completely convert pytorch_model, creates PtFrameworkNode if not possible to convert node
/// \param pytorch_model Input model
/// \param external_tensor_map Is used for recursive calls of convert_pytorch_model and represent the external context
/// which is visible from nested model. Empty external_tensor_map is used as an indication that this is a main body
/// conversion.
/// \return fully converted OV Model
std::shared_ptr<Model> convert_pytorch_model(std::shared_ptr<TorchDecoder> pytorch_model,
const TensorMap& external_tensor_map) {
std::shared_ptr<Model> resulting_model; // define here to make a conversion in a nested scope
{
ParameterVector parameters;
TensorMap tensor_map; // tensor map of the current context
std::set<size_t> mutated_tensors;
// Go over all pytorch_model inputs and register them in the tensor map:
auto inputs = pytorch_model->inputs();
for (size_t i = 0; i < inputs.size(); ++i) {
PartialShape ps = pytorch_model->get_input_shape(i);
auto type = simplified_type_interpret(pytorch_model->get_input_type(i));
// TODO: Use special API to set custom type detalization
auto parameter = std::make_shared<opset10::Parameter>(element::dynamic, ps);
parameter->get_output_tensor(0).add_names({std::to_string(inputs.at(i))});
parameters.push_back(parameter);
auto order = pytorch_model->get_input_transpose_order(i);
if (order.size() > 0 && !std::is_sorted(order.begin(), order.end())) {
FRONT_END_GENERAL_CHECK(ps.is_static(), "Shape must be static."); // TODO: make dynamic
auto sh = ps.get_shape();
Shape new_shape(sh.size());
for (size_t i = 0; i < sh.size(); i++) {
new_shape[order[i]] = sh[i];
}
auto shape_const = opset10::Constant::create(element::i64, {new_shape.size()}, new_shape);
auto reshape = std::make_shared<opset10::Reshape>(parameter, shape_const, false);
auto order_const = opset10::Constant::create(element::i32, {order.size()}, order);
auto transpose = std::make_shared<opset10::Transpose>(reshape, order_const);
tensor_map[inputs.at(i)] = transpose;
} else {
tensor_map[inputs.at(i)] = parameter;
}
}
auto node_visitor = [&](std::shared_ptr<TorchDecoder> node) {
// Explore all inputs of node. Node may refer to input value that hasn't been created in the current scope.
// But this value can be found in the outer scope, for this purpose we create new input for the model to
// link with external scope on a higher level.
auto raw_inputs = node->inputs();
for (size_t i = 0; i < raw_inputs.size(); ++i) {
auto input = raw_inputs.at(i);
if (tensor_map.find(input) == tensor_map.end()) {
// Input refers value in the outer scope, need to create a new Parameter in the current scope
// Linkage to external scope will be performed on the level of the parent operation (if or loop)
// TODO: Eliminate duplication with the main code for Parameters creation
PartialShape ps = node->get_input_shape(i);
auto type = simplified_type_interpret(node->get_input_type(i));
// TODO: Use special API to set custom type detalization
auto parameter = std::make_shared<opset10::Parameter>(element::dynamic, ps);
// TODO: Missing get_input_transpose_order handling for not trivial layouts
tensor_map[input] = parameter;
// set name of parameter to the index of node in the model
parameter->get_output_tensor(0).add_names({std::to_string(input)});
parameters.push_back(parameter);
}
}
auto context = NodeContext(node, &tensor_map, &parameters, external_tensor_map);
auto converted_outputs = convert_node(&context);
auto mutated_t = context.get_mutated_tensors();
mutated_tensors.insert(mutated_t.begin(), mutated_t.end());
auto fw_outputs = node->outputs();
// Ops with subgraphs or with mutated inputs may have more outputs after conversion compared to pytorch ones
FRONT_END_OP_CONVERSION_CHECK(fw_outputs.size() <= converted_outputs.size(),
"Number of ",
node->get_op_type(),
" outputs greater then number of converted outputs.");
// TODO: Make sure that mapping of fw_outputs to converted_outputs does always work
// FIXME: Now it is not true for at least prim::Constant
for (size_t i = 0; i < fw_outputs.size(); ++i) {
size_t fw_tensor_id = node->output(i);
if (tensor_map.find(fw_tensor_id) != tensor_map.end()) {
throw std::runtime_error("Duplicated producer for PT value with unique ID: " +
std::to_string(fw_tensor_id));
}
// Output shape of converted node should match the original output shape
// OV_FRONTEND_REQUIRE(get_ov_shape(fw_outputs[i]) == converted_outputs[i].get_partial_shape());
tensor_map[fw_tensor_id] = converted_outputs[i];
converted_outputs[i].get_tensor().add_names({std::to_string(fw_tensor_id)});
}
};
FRONT_END_GENERAL_CHECK(pytorch_model->get_subgraph_size() == 1, "Model should have exactly 1 subgraph.");
pytorch_model->visit_subgraph(node_visitor);
ResultVector results;
for (size_t i = 0; i < pytorch_model->num_of_outputs(); ++i) {
size_t id = pytorch_model->output(i);
if (tensor_map.find(id) == tensor_map.end()) {
// Not found in this scope, adding Parameter to connect to external scope
auto parameter = std::make_shared<opset10::Parameter>(element::dynamic, PartialShape::dynamic());
parameter->get_output_tensor(0).add_names({std::to_string(id)});
parameters.push_back(parameter);
tensor_map[id] = parameter;
}
auto ov_output = tensor_map[id];
auto order = pytorch_model->get_output_transpose_order(i);
FRONT_END_GENERAL_CHECK(order.size() == 0 || std::is_sorted(order.begin(), order.end()),
"Output strides have wrong order.");
FRONT_END_GENERAL_CHECK(ov_output.get_names().size() > 0,
"Tensor doesn't have name, while it should have name: ",
id);
auto result = std::make_shared<opset10::Result>(ov_output);
results.push_back(result);
}
// Since parameters can be added we need to list all current parameters
std::set<size_t> param_names;
for (const auto& param : parameters) {
auto name = param->get_output_tensor(0).get_any_name();
size_t input_idx = (size_t)std::stoll(name);
param_names.insert(input_idx);
}
for (const auto& tensor_id : mutated_tensors) {
if (param_names.count(tensor_id)) {
FRONT_END_GENERAL_CHECK(tensor_map.count(tensor_id),
"Tensor with id: ",
tensor_id,
" doesn't exist in tensor map.");
// model input was mutated we need to make a result for it
auto mutated_tensor = tensor_map.at(tensor_id);
// empty external_tensor_map means this is main body of the model and we don't want to create
// additional outputs in that case.
if (mutated_tensor.get_target_inputs().empty() && !external_tensor_map.empty())
results.push_back(std::make_shared<opset10::Result>(tensor_map.at(tensor_id)));
}
}
resulting_model = std::make_shared<Model>(results, parameters);
// Did a conversion in a nested scope to automatically remove any holders of nodes except those in the graph
}
return resulting_model;
}
std::shared_ptr<ov::op::util::FrameworkNode> cast_fw_node(std::shared_ptr<Node> node, const std::string& type) {
auto fw_node = std::dynamic_pointer_cast<ov::op::util::FrameworkNode>(node);
if (!fw_node) {
+1 -4
View File
@@ -45,10 +45,7 @@ op::PadType convert_pad(const std::string& pt_pad);
std::shared_ptr<Node> concat_list_construct(std::shared_ptr<Node> input);
std::shared_ptr<Model> convert_pytorch_model(std::shared_ptr<TorchDecoder> pytorch_model,
const TensorMap& external_tensor_map = {});
OutputVector convert_node(NodeContext* context);
OutputVector make_framework_node(NodeContext& context);
std::shared_ptr<op::util::FrameworkNode> cast_fw_node(std::shared_ptr<Node> node, const std::string& type);