[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:
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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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, ¶meters, 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
|
||||
@@ -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, ¶meters, 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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user