[PT FE] Implement override_all_inputs, override_all_outputs (#19642)

* Implement override_all_inputs, override_all_outputs

* Apply suggestions from code review

Co-authored-by: Piotr Krzemiński <piotrkrzeminski1234@gmail.com>

* Update src/frontends/pytorch/src/input_model.cpp

* Update src/frontends/pytorch/src/input_model.cpp

* Resolve problem with self input

* Update place.cpp

* Update place.cpp

* Fix build

---------

Co-authored-by: Piotr Krzemiński <piotrkrzeminski1234@gmail.com>
This commit is contained in:
Maxim Vafin 2023-09-08 22:31:49 +02:00 committed by GitHub
parent 815ce1c595
commit e83697ded4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 243 additions and 123 deletions

View File

@ -35,6 +35,9 @@ public:
m_decoder_outputs(decoder->outputs()) {
FRONT_END_GENERAL_CHECK(m_tensor_map != nullptr && m_external_parameters != nullptr &&
m_mutated_tensors != nullptr && m_translate_session != nullptr);
for (size_t i = 0; i < m_decoder_inputs.size(); i++) {
m_inputs_is_none.push_back(decoder->input_is_none(i));
}
}
// Do not search for input in tensor map; try to access it as a constant of specified type T and return its value
@ -48,7 +51,7 @@ public:
// Search for input in tensor map and return an output port for already converted op
// TODO: int due to base class uses it, but naturally it should be size_t for PT
Output<Node> get_input(int index) const override {
FRONT_END_GENERAL_CHECK(!m_decoder->input_is_none(index), "Input is none with index: ", index);
FRONT_END_GENERAL_CHECK(!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.");
return m_tensor_map->at(input);
@ -71,7 +74,7 @@ public:
}
bool input_is_none(size_t index) const {
return m_decoder->input_is_none(index);
return index >= m_inputs_is_none.size() || m_inputs_is_none.at(index);
}
Any get_output_type(size_t index) const {
@ -147,6 +150,7 @@ private:
TranslateSession* m_translate_session = nullptr;
const std::vector<size_t> m_decoder_inputs;
const std::vector<size_t> m_decoder_outputs;
std::vector<bool> m_inputs_is_none;
};
using CreatorFunction = std::function<ov::OutputVector(const ov::frontend::pytorch::NodeContext&)>;

View File

@ -19,12 +19,7 @@ InputModel::InputModel(const std::shared_ptr<TorchDecoder>& model_decoder) : m_m
for (const auto& name : in_place->get_names()) {
m_name_to_place.emplace(name, std::dynamic_pointer_cast<frontend::Place>(in_place));
}
auto type_any = simplified_type_interpret(m_model_decoder->get_input_type(i));
auto dtype = element::dynamic;
if (type_any.is<element::Type>()) {
dtype = type_any.as<element::Type>();
}
m_descriptors.emplace(inputs[i], PlaceDesc(dtype, m_model_decoder->get_input_shape(i)));
m_inputs.push_back(in_place);
}
const auto& outputs = m_model_decoder->outputs();
for (size_t i = 0; i < outputs.size(); ++i) {
@ -33,39 +28,24 @@ InputModel::InputModel(const std::shared_ptr<TorchDecoder>& model_decoder) : m_m
for (const auto& name : out_place->get_names()) {
m_name_to_place.emplace(name, std::dynamic_pointer_cast<frontend::Place>(out_place));
}
auto type_any = simplified_type_interpret(m_model_decoder->get_output_type(i));
auto dtype = element::dynamic;
if (type_any.is<element::Type>()) {
dtype = type_any.as<element::Type>();
}
m_descriptors.emplace(outputs[i], PlaceDesc(dtype, m_model_decoder->get_output_shape(i)));
m_outputs.push_back(out_place);
}
}
std::vector<ov::frontend::Place::Ptr> InputModel::get_inputs() const {
std::vector<ov::frontend::Place::Ptr> res;
for (const auto& input_idx : m_model_decoder->inputs()) {
auto place_it = m_name_to_place.find(std::to_string(input_idx));
FRONT_END_GENERAL_CHECK(place_it != m_name_to_place.end(), "Couldn't find Place for input.");
const auto& names = place_it->second->get_names();
if (input_idx == 0 && std::any_of(names.cbegin(), names.cend(), [](const std::string& n) {
if (m_inputs.size() > 0 && m_inputs[0]) {
// We need to remove "self" input to not confuse external users
const auto& names = m_inputs[0]->get_names();
if (std::any_of(names.cbegin(), names.cend(), [](const std::string& n) {
return n.find("self") != std::string::npos;
})) {
continue;
}
res.push_back(place_it->second);
}))
return std::vector<ov::frontend::Place::Ptr>(m_inputs.begin() + 1, m_inputs.end());
}
return res;
return m_inputs;
}
std::vector<ov::frontend::Place::Ptr> InputModel::get_outputs() const {
std::vector<ov::frontend::Place::Ptr> res;
for (const auto& output_idx : m_model_decoder->outputs()) {
auto place_it = m_name_to_place.find(std::to_string(output_idx));
FRONT_END_GENERAL_CHECK(place_it != m_name_to_place.end(), "Couldn't find Place for output.");
res.push_back(place_it->second);
}
return res;
return m_outputs;
}
Place::Ptr InputModel::get_place_by_tensor_name(const std::string& tensor_name) const {
@ -81,10 +61,7 @@ void InputModel::set_partial_shape(const Place::Ptr& place, const ov::PartialSha
"Provided place is invalid, only inputs are supported for setting shape.");
auto pytorch_place = std::dynamic_pointer_cast<pytorch::Place>(place);
FRONT_END_GENERAL_CHECK(pytorch_place, "Only place produced by PyTorch Frontend is supported");
auto it = m_descriptors.find(pytorch_place->get_tensor_index());
if (it != m_descriptors.end()) {
it->second.m_pshape = shape;
}
pytorch_place->m_pshape = shape;
}
ov::PartialShape InputModel::get_partial_shape(const Place::Ptr& place) const {
@ -92,11 +69,7 @@ ov::PartialShape InputModel::get_partial_shape(const Place::Ptr& place) const {
FRONT_END_GENERAL_CHECK(
pytorch_place,
"Provided place is invalid. Only place of input or output is supported by PyTorch Frontend.");
auto it = m_descriptors.find(pytorch_place->get_tensor_index());
if (it != m_descriptors.end()) {
return it->second.m_pshape;
}
return PartialShape::dynamic();
return pytorch_place->get_partial_shape();
}
void InputModel::set_element_type(const Place::Ptr& place, const ov::element::Type& type) {
@ -104,10 +77,7 @@ void InputModel::set_element_type(const Place::Ptr& place, const ov::element::Ty
"Provided place is invalid, only inputs are supported for setting element type.");
auto pytorch_place = std::dynamic_pointer_cast<pytorch::Place>(place);
FRONT_END_GENERAL_CHECK(pytorch_place, "Only place produced by PyTorch Frontend is supported");
auto it = m_descriptors.find(pytorch_place->get_tensor_index());
if (it != m_descriptors.end()) {
it->second.m_type = type;
}
pytorch_place->m_type = type;
}
ov::element::Type InputModel::get_element_type(const Place::Ptr& place) const {
@ -115,11 +85,7 @@ ov::element::Type InputModel::get_element_type(const Place::Ptr& place) const {
FRONT_END_GENERAL_CHECK(
pytorch_place,
"Provided place is invalid. Only place of input or output is supported by PyTorch Frontend.");
auto it = m_descriptors.find(pytorch_place->get_tensor_index());
if (it != m_descriptors.end()) {
return it->second.m_type;
}
return element::dynamic;
return pytorch_place->get_element_type();
}
void InputModel::set_tensor_value(const Place::Ptr& place, const void* value) {
@ -127,22 +93,84 @@ void InputModel::set_tensor_value(const Place::Ptr& place, const void* value) {
"Provided place is invalid, only inputs are supported for setting tensor value.");
auto pytorch_place = std::dynamic_pointer_cast<pytorch::Place>(place);
FRONT_END_GENERAL_CHECK(pytorch_place, "Only place produced by PyTorch Frontend is supported");
auto it = m_descriptors.find(pytorch_place->get_tensor_index());
const auto el_type = pytorch_place->m_type;
const auto p_shape = pytorch_place->m_pshape;
FRONT_END_GENERAL_CHECK(el_type.is_static() && p_shape.is_static(),
"Shape and type must be statically defined before calling set_tensor_value");
auto const_node = ov::op::v0::Constant::create(el_type, p_shape.to_shape(), value);
const auto tensor_id = pytorch_place->get_tensor_index();
auto it = m_descriptors.find(tensor_id);
if (it != m_descriptors.end()) {
auto el_type = it->second.m_type;
auto p_shape = it->second.m_pshape;
FRONT_END_GENERAL_CHECK(el_type.is_static() && p_shape.is_static(),
"Shape and type must be statically defined before calling set_tensor_value");
it->second.m_value = ov::op::v0::Constant::create(el_type, p_shape.to_shape(), value);
it->second.m_value = const_node;
} else {
FRONT_END_GENERAL_CHECK(false, "Place is not known.");
m_descriptors.emplace(tensor_id, PlaceDesc(const_node));
}
// remove place from inputs
m_inputs.erase(std::remove_if(m_inputs.begin(),
m_inputs.end(),
[&](const Place::Ptr& p) {
return p->is_equal(place);
}),
m_inputs.end());
}
void InputModel::override_all_outputs(const std::vector<Place::Ptr>& outputs) {
// Topology modification is not allowed, but order can be changed
auto all_outputs = std::all_of(outputs.cbegin(), outputs.cend(), [](const Place::Ptr& p) {
return p->is_output();
});
FRONT_END_GENERAL_CHECK(all_outputs, "Only initial outputs are supported by override_all_inputs.");
// Number of outputs can be lower then in original model
m_outputs = outputs;
}
void InputModel::override_all_inputs(const std::vector<Place::Ptr>& inputs) {
// Topology modification is not allowed, but order can be changed
auto all_inputs = std::all_of(inputs.cbegin(), inputs.cend(), [](const Place::Ptr& p) {
return p->is_input();
});
FRONT_END_GENERAL_CHECK(all_inputs, "Only initial inputs are supported by override_all_inputs.");
// We need to add back "self" input if it was in initial inputs
if (m_inputs.size() > 0 && m_inputs[0]) {
// We need to remove "self" input to not confuse external users
const auto& names = m_inputs[0]->get_names();
if (std::any_of(names.cbegin(), names.cend(), [](const std::string& n) {
return n.find("self") != std::string::npos;
})) {
FRONT_END_GENERAL_CHECK(inputs.size() == m_inputs.size() - 1,
"Number of inputs provided is incorrect. Graph modification is not supported for "
"this model. Expected number of inputs: ",
m_inputs.size() - 1,
" recieved ",
inputs.size());
auto self_place = m_inputs[0];
// Verify that no same place already in vector
auto no_self = std::all_of(inputs.cbegin(), inputs.cend(), [&](const Place::Ptr& p) {
return !p->is_equal(self_place);
});
FRONT_END_GENERAL_CHECK(no_self, "Unexpected input of 'self' was provided to override_all_inputs.");
m_inputs = std::vector<ov::frontend::Place::Ptr>{self_place};
m_inputs.insert(m_inputs.cend(), inputs.cbegin(), inputs.cend());
return;
}
}
FRONT_END_GENERAL_CHECK(inputs.size() == m_inputs.size(),
"Number of inputs provided is incorrect. Graph modification is not supported for "
"this model. Expected number of inputs: ",
m_inputs.size(),
" recieved ",
inputs.size());
m_inputs = inputs;
}
const std::string& InputModel::decoder_type_name() const {
return m_model_decoder->decoder_type_name();
}
std::shared_ptr<TorchDecoder> InputModel::get_decoder() const {
return m_model_decoder;
}
} // namespace pytorch
} // namespace frontend
} // namespace ov

View File

@ -17,18 +17,12 @@ class Place;
class TorchDecoder;
struct PlaceDesc {
PlaceDesc(const element::Type& type, const PartialShape& pshape)
: m_type(type),
m_pshape(pshape),
m_value(nullptr) {}
element::Type m_type;
PartialShape m_pshape;
PlaceDesc(std::shared_ptr<Node> value) : m_value(value) {}
std::shared_ptr<Node> m_value;
};
class InputModel : public ov::frontend::InputModel {
friend class ::ov::frontend::pytorch::TranslateSession;
friend class ::ov::frontend::pytorch::Place;
public:
explicit InputModel(const std::shared_ptr<TorchDecoder>& model_decoder);
@ -41,11 +35,16 @@ public:
void set_element_type(const frontend::Place::Ptr& place, const ov::element::Type& type) override;
ov::element::Type get_element_type(const frontend::Place::Ptr& place) const override;
void set_tensor_value(const frontend::Place::Ptr& place, const void* value) override;
void override_all_outputs(const std::vector<frontend::Place::Ptr>& outputs) override;
void override_all_inputs(const std::vector<frontend::Place::Ptr>& inputs) override;
const std::string& decoder_type_name() const;
std::shared_ptr<TorchDecoder> get_decoder() const;
private:
std::shared_ptr<TorchDecoder> m_model_decoder;
std::unordered_map<std::string, std::shared_ptr<frontend::Place>> m_name_to_place;
std::vector<std::shared_ptr<frontend::Place>> m_inputs;
std::vector<std::shared_ptr<frontend::Place>> m_outputs;
std::unordered_map<size_t, PlaceDesc> m_descriptors;
};

View File

@ -54,7 +54,7 @@ std::shared_ptr<Node> NodeContext::mark_node(std::shared_ptr<Node> ov_node) cons
}
void NodeContext::mutate_input(size_t index, Output<Node> ov_output) const {
FRONT_END_GENERAL_CHECK(!m_decoder->input_is_none(index), "Input is none with index: ", index);
FRONT_END_GENERAL_CHECK(!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)});

View File

@ -8,6 +8,7 @@
#include "openvino/frontend/exception.hpp"
#include "openvino/frontend/pytorch/decoder.hpp"
#include "openvino/util/log.hpp"
#include "utils.hpp"
namespace ov {
namespace frontend {
@ -15,25 +16,37 @@ namespace pytorch {
Place::Place(const ov::frontend::InputModel& input_model, size_t tensor_index)
: m_input_model(input_model),
m_tensor_index(tensor_index),
m_is_input(false),
m_is_output(false) {
m_tensor_index(tensor_index) {
const auto im = dynamic_cast<const ov::frontend::pytorch::InputModel*>(&m_input_model);
FRONT_END_GENERAL_CHECK(im, "PyTorch Place requires PyTorch InputModel class.");
const auto& inputs = im->m_model_decoder->inputs();
const auto& outputs = im->m_model_decoder->outputs();
auto decoder = im->get_decoder();
const auto& inputs = decoder->inputs();
const auto& outputs = decoder->outputs();
auto in_it = std::find(inputs.begin(), inputs.end(), tensor_index);
if (in_it != inputs.end()) {
m_is_input = true;
const auto& signature_name =
im->m_model_decoder->get_input_signature_name(std::distance(inputs.begin(), in_it));
auto idx = std::distance(inputs.begin(), in_it);
const auto& signature_name = decoder->get_input_signature_name(idx);
m_names.push_back(signature_name);
auto type_any = simplified_type_interpret(decoder->get_input_type(idx));
if (type_any.is<element::Type>()) {
m_type = type_any.as<element::Type>();
}
m_pshape = decoder->get_input_shape(idx);
}
auto out_it = std::find(outputs.begin(), outputs.end(), tensor_index);
if (out_it != outputs.end()) {
m_is_output = true;
const auto& debug_name = im->m_model_decoder->get_output_debug_name(std::distance(outputs.begin(), out_it));
auto idx = std::distance(outputs.begin(), out_it);
const auto& debug_name = decoder->get_output_debug_name(idx);
m_names.push_back(debug_name);
auto type_any = simplified_type_interpret(decoder->get_output_type(idx));
if (type_any.is<element::Type>()) {
m_type = type_any.as<element::Type>();
}
m_pshape = decoder->get_output_shape(idx);
}
if (m_is_input && m_is_output) {
OPENVINO_DEBUG << "[WARNING] Place " << tensor_index << " is input and output at a same time.";

View File

@ -11,7 +11,12 @@ namespace ov {
namespace frontend {
namespace pytorch {
class InputModel;
// Place represents a tensor in the model
class Place : public ov::frontend::Place {
friend class ::ov::frontend::pytorch::InputModel;
public:
Place(const ov::frontend::InputModel& input_model, size_t tensor_index);
@ -32,7 +37,12 @@ public:
size_t get_tensor_index() const {
return m_tensor_index;
}
element::Type get_element_type() const {
return m_type;
}
PartialShape get_partial_shape() const {
return m_pshape;
}
bool is_equal_data(const Ptr& another) const override {
const auto another_pt = dynamic_cast<ov::frontend::pytorch::Place*>(another.get());
if (!another_pt) {
@ -45,8 +55,10 @@ private:
const ov::frontend::InputModel& m_input_model;
const size_t m_tensor_index;
std::vector<std::string> m_names;
bool m_is_input;
bool m_is_output;
element::Type m_type = element::dynamic;
PartialShape m_pshape;
bool m_is_input = false;
bool m_is_output = false;
};
} // namespace pytorch

View File

@ -17,6 +17,7 @@
#include "openvino/op/slice.hpp"
#include "openvino/op/transpose.hpp"
#include "openvino/util/log.hpp"
#include "place.hpp"
#include "pt_framework_node.hpp"
#include "utils.hpp"
@ -54,7 +55,7 @@ std::shared_ptr<ov::Model> TranslateSession::get_converted_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");
auto model = convert_pytorch_model(pytorch_model->m_model_decoder, {}, pytorch_model->m_descriptors);
auto model = convert_pytorch_model(pytorch_model->m_model_decoder, {}, pytorch_model);
// First delete tensor indexes from outputs then resolve input names, otherwise Parameter->Result will fail
for (auto& result : model->get_results()) {
auto tensor_desc = result->input_value(0);
@ -79,7 +80,7 @@ std::shared_ptr<ov::Model> TranslateSession::translate_graph(const ov::frontend:
std::shared_ptr<Model> TranslateSession::convert_pytorch_model(
std::shared_ptr<TorchDecoder> pytorch_model,
const TensorMap& external_tensor_map,
const std::unordered_map<size_t, PlaceDesc>& external_descriptors) {
const std::shared_ptr<pytorch::InputModel>& input_model) {
// FIXME: don't use global variable to count inlined inputs, no we should use global counter because ID should be
// unique for all new inlined inputs
static size_t inlined_nodes_counter = 100000000; // Suppose there are not graph with more than 10M nodes
@ -90,36 +91,43 @@ std::shared_ptr<Model> TranslateSession::convert_pytorch_model(
auto tensor_map = std::make_shared<TensorMap>(); // tensor map of the current context
auto mutated_tensors = std::make_shared<std::set<size_t>>();
// 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) {
std::shared_ptr<Node> input_node;
element::Type type = element::dynamic;
PartialShape pshape;
auto desc = external_descriptors.find(inputs[i]);
if (desc != external_descriptors.end()) {
if (desc->second.m_value) {
input_node = desc->second.m_value;
} else {
pshape = desc->second.m_pshape;
type = desc->second.m_type;
}
} else {
pshape = pytorch_model->get_input_shape(i);
if (input_model) {
// When we have input model we should use its inputs order to create Parameters
// We use m_inputs instead of get_inputs() because latter doesn't have "self" input
for (auto input_p : input_model->m_inputs) {
auto pytorch_place = std::dynamic_pointer_cast<pytorch::Place>(input_p);
FRONT_END_GENERAL_CHECK(pytorch_place, "Only place produced by PyTorch Frontend is supported.");
auto tensor_id = pytorch_place->get_tensor_index();
element::Type type = pytorch_place->get_element_type();
PartialShape pshape = pytorch_place->get_partial_shape();
auto parameter = std::make_shared<v0::Parameter>(type, pshape);
if (pytorch_place->get_names().size() > 0)
parameter->set_friendly_name(pytorch_place->get_names().at(0));
encode_tensor_name(parameter->output(0), tensor_id);
parameters->push_back(parameter);
(*tensor_map)[tensor_id] = parameter;
}
// Add all tensors that were frozen
for (auto desc : input_model->m_descriptors) {
(*tensor_map)[desc.first] = desc.second.m_value;
}
} else {
// 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) {
element::Type type = element::dynamic;
PartialShape pshape = pytorch_model->get_input_shape(i);
auto type_any = simplified_type_interpret(pytorch_model->get_input_type(i));
// TODO: Use special API to set custom type specification
if (type_any.is<element::Type>()) {
type = type_any.as<element::Type>();
}
}
if (!input_node) {
auto parameter = std::make_shared<v0::Parameter>(type, pshape);
parameter->set_friendly_name(pytorch_model->get_input_signature_name(i));
encode_tensor_name(parameter->output(0), inputs.at(i), {pytorch_model->get_input_debug_name(i)});
parameters->push_back(parameter);
input_node = parameter;
(*tensor_map)[inputs.at(i)] = parameter;
}
(*tensor_map)[inputs.at(i)] = input_node;
}
auto node_visitor = [&](std::shared_ptr<TorchDecoder> node) {
@ -215,21 +223,36 @@ std::shared_ptr<Model> TranslateSession::convert_pytorch_model(
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;
if (input_model) {
// For the case when we have InputModel we need to have same order as its outputs
for (auto output_p : input_model->get_outputs()) {
auto pytorch_place = std::dynamic_pointer_cast<pytorch::Place>(output_p);
FRONT_END_GENERAL_CHECK(pytorch_place, "Only place produced by PyTorch Frontend is supported.");
auto tensor_id = pytorch_place->get_tensor_index();
auto ov_output = tensor_map->at(tensor_id);
FRONT_END_GENERAL_CHECK(ov_output.get_names().size() > 0,
"Tensor doesn't have name, while it should have name: ",
tensor_id);
auto result = std::make_shared<v0::Result>(ov_output);
results.push_back(result);
}
} else {
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->at(id);
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);
}
auto ov_output = tensor_map->at(id);
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

View File

@ -30,10 +30,9 @@ public:
/// 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 = {},
const std::unordered_map<size_t, PlaceDesc>& external_descriptors = {});
std::shared_ptr<Model> convert_pytorch_model(std::shared_ptr<TorchDecoder> pytorch_model,
const TensorMap& external_tensor_map = {},
const std::shared_ptr<pytorch::InputModel>& input_model = nullptr);
/// \brief Returns backprop operations for direct operation
Output<Node> get_backprop_op(const std::shared_ptr<TorchDecoder>& node,

View File

@ -20,6 +20,11 @@ class aten_relu(torch.nn.Module):
return x, torch.nn.functional.relu(x)
class aten_multi_input_output(torch.nn.Module):
def forward(self, x, y, z):
return torch.nn.functional.relu(x), x * y, z / x
def get_scripted_model(model):
with torch.no_grad():
model = torch.jit.script(model)
@ -74,6 +79,40 @@ def test_pytorch_fe_set_input_value():
assert len(om.get_parameters()) == 0
def test_pytorch_fe_override_all_inputs():
from openvino.frontend.pytorch.ts_decoder import TorchScriptPythonDecoder
decoder = TorchScriptPythonDecoder(aten_multi_input_output())
fe_manager = FrontEndManager()
fe = fe_manager.load_by_framework("pytorch")
im = fe.load(decoder)
inputs = im.get_inputs()
assert len(inputs) == 3, "Model should have 3 inputs."
im.override_all_inputs([inputs[1], inputs[2], inputs[0]])
om = fe.convert(im)
assert om.get_parameters()[0].friendly_name == "y"
assert om.get_parameters()[1].friendly_name == "z"
assert om.get_parameters()[2].friendly_name == "x"
def test_pytorch_fe_removes_input_set_value():
from openvino.frontend.pytorch.ts_decoder import TorchScriptPythonDecoder
decoder = TorchScriptPythonDecoder(aten_multi_input_output())
fe_manager = FrontEndManager()
fe = fe_manager.load_by_framework("pytorch")
im = fe.load(decoder)
assert len(im.get_inputs()) == 3, "Model should have 3 inputs."
place = im.get_place_by_tensor_name("y")
im.set_partial_shape(place, PartialShape([1]))
im.set_element_type(place, Type.f32)
im.set_tensor_value(place, np.random.randn(1).astype(np.float32))
assert len(im.get_inputs()) == 2, "Model should have 2 inputs."
om = fe.convert(im)
assert om.get_parameters()[0].friendly_name == "x"
assert om.get_parameters()[1].friendly_name == "z"
def test_conversion_extension():
from openvino.frontend.pytorch.ts_decoder import TorchScriptPythonDecoder
@ -108,7 +147,8 @@ def test_conversion_extension():
inp = node.get_input(0)
approximate = node.get_values_from_const_input(1)
if approximate == "none":
f = ops.erf(ops.divide(inp, ops.constant(np.array([math.sqrt(2.0)], dtype=np.float32))))
f = ops.erf(ops.divide(inp, ops.constant(
np.array([math.sqrt(2.0)], dtype=np.float32))))
elif approximate == "tanh":
f = ops.tanh(ops.multiply(ops.constant(np.array([math.sqrt(2.0 / math.pi)], dtype=np.float32)),
ops.add(inp, ops.multiply(ops.constant(np.array([0.044715], dtype=np.float32)),
@ -128,7 +168,6 @@ def test_conversion_extension():
div = ops.divide(exp, reduce_sum)
return div.outputs()
def convert_vector_norm(node: NodeContext):
inp = node.get_input(0)
ord = node.get_values_from_const_input(1)
@ -142,14 +181,14 @@ def test_conversion_extension():
rm = ops.reduce_max(ops.abs(inp), reduce_axes, False)
return rm.outputs()
fem = FrontEndManager()
fe = fem.load_by_framework(framework="pytorch")
assert fe
fe.add_extension(ConversionExtension("aten::elu", convert_elu))
fe.add_extension(ConversionExtension("aten::gelu", convert_gelu))
fe.add_extension(ConversionExtension("aten::softmax", convert_softmax))
fe.add_extension(ConversionExtension("aten::linalg_vector_norm", convert_vector_norm))
fe.add_extension(ConversionExtension(
"aten::linalg_vector_norm", convert_vector_norm))
input_model = fe.load(decoder)
assert input_model
converted_model = fe.convert(input_model)
@ -177,7 +216,8 @@ def get_builtin_extensions_path():
base_paths.append(repo_dir)
for base_path in base_paths:
paths = glob.glob(os.path.join(base_path, "**", "*test_builtin_extensions*"), recursive=True)
paths = glob.glob(os.path.join(
base_path, "**", "*test_builtin_extensions*"), recursive=True)
for path in paths:
if re.search(r"(lib)?test_builtin_extensions.?\.(dll|so)", path):
return path
@ -206,12 +246,14 @@ def test_op_extension():
assert input_model
converted_model = fe.convert(input_model)
assert converted_model
assert [n.get_type_name() for n in converted_model.get_ordered_ops()] == ["Parameter", "Elu", "Result"]
assert [n.get_type_name() for n in converted_model.get_ordered_ops()] == [
"Parameter", "Elu", "Result"]
fe.add_extension(get_builtin_extensions_path())
converted_model = fe.convert(input_model)
assert converted_model
assert [n.get_type_name() for n in converted_model.get_ordered_ops()] == ["Parameter", "CustomElu", "Result"]
assert [n.get_type_name() for n in converted_model.get_ordered_ops()] == [
"Parameter", "CustomElu", "Result"]
def test_pytorch_telemetry():