978 lines
44 KiB
C++
978 lines
44 KiB
C++
// Copyright (C) 2018-2022 Intel Corporation
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
// clang-format off
|
|
#include "ie_metric_helpers.hpp"
|
|
#include "executable_network.hpp"
|
|
#include "async_infer_request.hpp"
|
|
#include "itt.hpp"
|
|
#include "ie_precision.hpp"
|
|
#include "openvino/core/dimension.hpp"
|
|
#include "openvino/core/except.hpp"
|
|
#include "openvino/core/type.hpp"
|
|
#include "openvino/core/type/element_type.hpp"
|
|
#include "openvino/op/result.hpp"
|
|
#include "transformations/utils/utils.hpp"
|
|
#include "openvino/op/parameter.hpp"
|
|
#include "xml_parse_utils.h"
|
|
#include <caseless.hpp>
|
|
|
|
#include <vector>
|
|
#include <deque>
|
|
#include <map>
|
|
#include <utility>
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <memory>
|
|
#include <unordered_set>
|
|
#include <array>
|
|
#include <cstdint>
|
|
|
|
#include "openvino/pass/serialize.hpp"
|
|
#include "openvino/runtime/properties.hpp"
|
|
#include "ie_ngraph_utils.hpp"
|
|
#include "ie_plugin_config.hpp"
|
|
#include "ie_algorithm.hpp"
|
|
#include "cpp_interfaces/interface/ie_internal_plugin_config.hpp"
|
|
#include "plugin.hpp"
|
|
#include <ie_algorithm.hpp>
|
|
|
|
#include <ngraph/function.hpp>
|
|
#include <ngraph/variant.hpp>
|
|
#include <ngraph/graph_util.hpp>
|
|
#include <ngraph/op/result.hpp>
|
|
#include <ngraph/op/parameter.hpp>
|
|
#include <ngraph/op/util/op_types.hpp>
|
|
#include <ngraph/rt_info.hpp>
|
|
#include <ngraph/pass/visualize_tree.hpp>
|
|
// clang-format on
|
|
|
|
using namespace InferenceEngine;
|
|
using namespace details;
|
|
using namespace HeteroPlugin;
|
|
using namespace InferenceEngine::PluginConfigParams;
|
|
using namespace InferenceEngine::HeteroConfigParams;
|
|
|
|
template <typename T>
|
|
using NodeMap = std::unordered_map<ngraph::Node*, T>;
|
|
|
|
HeteroExecutableNetwork::HeteroExecutableNetwork(const InferenceEngine::CNNNetwork& network,
|
|
const Engine::Configs& config,
|
|
Engine* plugin)
|
|
: InferenceEngine::ExecutableNetworkThreadSafeDefault(nullptr,
|
|
std::make_shared<InferenceEngine::ImmediateExecutor>()),
|
|
_heteroPlugin{plugin},
|
|
_name{network.getName()},
|
|
_config{config} {
|
|
auto function = network.getFunction();
|
|
IE_ASSERT(function != nullptr);
|
|
auto clonedFunction = ngraph::clone_function(*function);
|
|
bool dumpDotFile = false;
|
|
if (std::getenv("OPENVINO_HETERO_VISUALIZE")) {
|
|
dumpDotFile = true;
|
|
} else {
|
|
auto itDumpDotFile = _config.find(HETERO_CONFIG_KEY(DUMP_GRAPH_DOT));
|
|
dumpDotFile = itDumpDotFile != _config.end() ? (itDumpDotFile->second == YES) : false;
|
|
}
|
|
|
|
QueryNetworkResult queryNetworkResult;
|
|
auto orderedOps = clonedFunction->get_ordered_ops();
|
|
bool allEmpty = true;
|
|
// Get user defined affinity
|
|
for (auto&& node : orderedOps) {
|
|
auto& nodeInfo = node->get_rt_info();
|
|
auto itInfo = nodeInfo.find("affinity");
|
|
if (itInfo != nodeInfo.end()) {
|
|
IE_ASSERT(itInfo->second.is<std::string>());
|
|
queryNetworkResult.supportedLayersMap.emplace(node->get_friendly_name(), itInfo->second.as<std::string>());
|
|
allEmpty = false;
|
|
}
|
|
}
|
|
|
|
if (queryNetworkResult.supportedLayersMap.empty()) {
|
|
auto it = _config.find("TARGET_FALLBACK");
|
|
if (it == _config.end()) {
|
|
it = _config.find(ov::device::priorities.name());
|
|
}
|
|
if (it != _config.end()) {
|
|
queryNetworkResult = _heteroPlugin->QueryNetwork(network, _config);
|
|
} else {
|
|
IE_THROW() << "The '" << ov::device::priorities.name()
|
|
<< "' option was not defined for heterogeneous plugin";
|
|
}
|
|
}
|
|
|
|
using Input = ngraph::Input<ngraph::Node>;
|
|
using NodeSet = std::unordered_set<ngraph::Node*>;
|
|
using InputSet = std::set<Input>;
|
|
|
|
auto InputNode = [](const ngraph::Input<ngraph::Node>& input) {
|
|
return input.get_source_output().get_node();
|
|
};
|
|
|
|
// Set results, constants and parameters affinity
|
|
for (auto&& node : clonedFunction->get_ops()) {
|
|
if (ngraph::op::is_constant(node) || ngraph::op::is_output(node) || ngraph::op::is_parameter(node)) {
|
|
if (!contains(queryNetworkResult.supportedLayersMap, node->get_friendly_name())) {
|
|
auto& nodeWithAffinityName =
|
|
ngraph::op::is_output(node)
|
|
? node->input_value(0).get_node()->get_friendly_name()
|
|
: node->output(0).get_target_inputs().begin()->get_node()->get_friendly_name();
|
|
auto itAffinity = queryNetworkResult.supportedLayersMap.find(nodeWithAffinityName);
|
|
if (itAffinity == queryNetworkResult.supportedLayersMap.end()) {
|
|
IE_THROW() << "Node " << nodeWithAffinityName << " was not assigned on any pointed device.";
|
|
}
|
|
queryNetworkResult.supportedLayersMap.emplace(node->get_friendly_name(), itAffinity->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unordered_set<std::string> devices;
|
|
NodeMap<std::string> affinities;
|
|
// Check that all nodes has user or plugin defined affinities
|
|
for (auto&& node : orderedOps) {
|
|
auto itAffinity = queryNetworkResult.supportedLayersMap.find(node->get_friendly_name());
|
|
if (itAffinity != queryNetworkResult.supportedLayersMap.end()) {
|
|
affinities[node.get()] = itAffinity->second;
|
|
devices.emplace(itAffinity->second);
|
|
} else if (allEmpty) {
|
|
IE_THROW() << "Hetero device used default fallback policy, but some layers eg: \n(Name:"
|
|
<< node->get_friendly_name() << ", Type: " << node->get_type_name()
|
|
<< ") were not able to be assigned on any pointed device.\n"
|
|
<< "It happened because these layers are not supported in plugins by default.\n"
|
|
<< "You need to implement custom layers to support them.";
|
|
} else {
|
|
IE_THROW() << "Network passed to LoadNetwork has affinity assigned, but some layers eg: \n(Name:"
|
|
<< node->get_friendly_name() << ", Type: " << node->get_type_name()
|
|
<< ") were not assigned to any device.\n"
|
|
<< "It might happen if you assigned layers manually and missed some layers or\n"
|
|
<< "if you used some automatic assigning mode which decided that these layers are not\n"
|
|
<< "supported by any plugin";
|
|
}
|
|
}
|
|
|
|
static const std::array<const char*, 14> colors = {
|
|
"aliceblue",
|
|
"antiquewhite4",
|
|
"aquamarine4",
|
|
"azure4",
|
|
"bisque3",
|
|
"blue1",
|
|
"brown",
|
|
"burlywood",
|
|
"cadetblue",
|
|
"chartreuse",
|
|
"chocolate",
|
|
"coral",
|
|
"cornflowerblue",
|
|
"cornsilk4",
|
|
};
|
|
|
|
if (dumpDotFile) {
|
|
ngraph::pass::VisualizeTree{
|
|
"hetero_affinity_" + _name + ".dot",
|
|
[&](const ngraph::Node& node, std::vector<std::string>& attributes) {
|
|
auto nodeDevice = queryNetworkResult.supportedLayersMap.at(node.get_friendly_name());
|
|
int colorIndex = 0;
|
|
for (auto&& device : devices) {
|
|
if (device == nodeDevice) {
|
|
attributes.push_back(std::string{"fillcolor="} + colors[colorIndex % colors.size()] +
|
|
" style=filled");
|
|
auto itLabel =
|
|
std::find_if(std::begin(attributes), std::end(attributes), [](const std::string& str) {
|
|
return str.find("label") != std::string::npos;
|
|
});
|
|
auto label =
|
|
"\\ndevice=" + queryNetworkResult.supportedLayersMap.at(node.get_friendly_name()) + '\"';
|
|
IE_ASSERT(itLabel != attributes.end());
|
|
itLabel->pop_back();
|
|
(*itLabel) += label;
|
|
break;
|
|
}
|
|
colorIndex++;
|
|
}
|
|
}}
|
|
.run_on_model(ngraph::clone_function(*function));
|
|
}
|
|
|
|
NodeMap<InputSet> nodeInputDependencies;
|
|
NodeSet graphInputNodes;
|
|
InputSet subgraphInputs;
|
|
// Get all subgraph inputs using just node affinities. Also collect transitive closure
|
|
for (auto&& node : orderedOps) {
|
|
if (ngraph::op::is_parameter(node) || ngraph::op::is_constant(node)) {
|
|
graphInputNodes.insert(node.get());
|
|
subgraphInputs.insert(Input{node.get(), 0});
|
|
nodeInputDependencies[node.get()].insert(Input{node.get(), 0});
|
|
} else {
|
|
auto inputs = node->inputs();
|
|
auto& nodeInputDependency = nodeInputDependencies[node.get()];
|
|
for (auto&& input : inputs) {
|
|
nodeInputDependency.insert(input);
|
|
auto& inputDependency = nodeInputDependencies[InputNode(input)];
|
|
nodeInputDependency.insert(inputDependency.begin(), inputDependency.end());
|
|
if (affinities[node.get()] != affinities[InputNode(input)]) {
|
|
subgraphInputs.insert(input);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assign each node subgraph ID
|
|
auto CollectSubgraphs = [&] {
|
|
std::deque<int> subgraphIds;
|
|
NodeMap<int*> subgraphIdPtrs;
|
|
for (auto&& node : orderedOps) {
|
|
auto allNodeInputs = node->inputs();
|
|
std::vector<Input> inputs;
|
|
for (auto&& input : allNodeInputs) {
|
|
if (!contains(subgraphInputs, input)) {
|
|
inputs.emplace_back(std::move(input));
|
|
}
|
|
}
|
|
if (inputs.empty()) {
|
|
subgraphIds.push_back(subgraphIds.size());
|
|
subgraphIdPtrs.emplace(node.get(), &(subgraphIds.back()));
|
|
} else {
|
|
auto firstInputSubgraphIdPtr = subgraphIdPtrs[InputNode(inputs.front())];
|
|
for (auto&& input : inputs) {
|
|
auto inputId = *subgraphIdPtrs[InputNode(input)];
|
|
for (auto& subgraphId : subgraphIds) {
|
|
if (subgraphId == inputId) {
|
|
subgraphId = *firstInputSubgraphIdPtr;
|
|
}
|
|
}
|
|
}
|
|
subgraphIdPtrs.emplace(node.get(), firstInputSubgraphIdPtr);
|
|
}
|
|
}
|
|
NodeMap<int> result;
|
|
for (auto&& subgraphIdPtr : subgraphIdPtrs) {
|
|
result.emplace(subgraphIdPtr.first, *(subgraphIdPtr.second));
|
|
}
|
|
return result;
|
|
};
|
|
|
|
// Split cyclic dependencies.
|
|
for (std::size_t prevSubgraphs = 0, cyclicSplitStep = 0; prevSubgraphs != subgraphInputs.size();
|
|
++cyclicSplitStep) {
|
|
IE_ASSERT(cyclicSplitStep < orderedOps.size());
|
|
prevSubgraphs = subgraphInputs.size();
|
|
auto subgraphIds = CollectSubgraphs();
|
|
// All inputs that belong to the same subgraph as node
|
|
std::unordered_map<ngraph::Node*, InputSet> nodeSubgraphInputDependencies;
|
|
// All inputs that depends on the same subgraph as node
|
|
std::unordered_map<ngraph::Node*, InputSet> nodeSubgraphCyclicInputDependencies;
|
|
for (auto&& node : orderedOps) {
|
|
auto& nodeSubgraphInputDependency = nodeSubgraphInputDependencies[node.get()];
|
|
auto allNodeSubgraphInputs = Intersection(nodeInputDependencies[node.get()], subgraphInputs);
|
|
for (auto&& subgraphInput : allNodeSubgraphInputs) {
|
|
if (subgraphIds[node.get()] == subgraphIds[subgraphInput.get_node()]) {
|
|
nodeSubgraphInputDependency.emplace(subgraphInput);
|
|
}
|
|
}
|
|
auto& nodeSubgraphCyclicInputDependency = nodeSubgraphCyclicInputDependencies[node.get()];
|
|
for (auto&& subgraphInput : allNodeSubgraphInputs) {
|
|
if (!ngraph::op::is_parameter(subgraphInput.get_node()) &&
|
|
!ngraph::op::is_constant(subgraphInput.get_node()) &&
|
|
subgraphIds[node.get()] == subgraphIds[InputNode(subgraphInput)]) {
|
|
nodeSubgraphCyclicInputDependency.emplace(subgraphInput);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto&& node : orderedOps) {
|
|
auto& nodeSubgraphCyclicInputDependency = nodeSubgraphCyclicInputDependencies[node.get()];
|
|
if (!nodeSubgraphCyclicInputDependency.empty()) {
|
|
// Collect all subgraph inputs that cyclic subgraph output depends on
|
|
InputSet cyclicInputsDependencies;
|
|
for (auto&& cyclicInput : nodeSubgraphCyclicInputDependency) {
|
|
for (auto&& input : nodeSubgraphInputDependencies[InputNode(cyclicInput)]) {
|
|
cyclicInputsDependencies.emplace(input);
|
|
}
|
|
}
|
|
for (auto&& input : node->inputs()) {
|
|
auto& inputNodeSubgraphCyclicInputDependency =
|
|
nodeSubgraphCyclicInputDependencies[InputNode(input)];
|
|
auto& inputNodeSubgraphInputDependency = nodeSubgraphInputDependencies[InputNode(input)];
|
|
if (!Intersects(nodeSubgraphCyclicInputDependency, inputNodeSubgraphCyclicInputDependency) &&
|
|
Intersects(cyclicInputsDependencies, inputNodeSubgraphInputDependency)) {
|
|
subgraphInputs.insert(input);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto subgraphIds = CollectSubgraphs();
|
|
|
|
if (dumpDotFile) {
|
|
std::map<std::string, int> map_id;
|
|
for (auto&& v : subgraphIds) {
|
|
map_id.emplace(v.first->get_friendly_name(), v.second);
|
|
}
|
|
ngraph::pass::VisualizeTree{
|
|
"hetero_subgraphs_" + _name + ".dot",
|
|
[&](const ngraph::Node& node, std::vector<std::string>& attributes) {
|
|
attributes.push_back(std::string{"fillcolor="} +
|
|
colors[map_id.at(node.get_friendly_name()) % colors.size()] + " style=filled");
|
|
auto itLabel = std::find_if(std::begin(attributes), std::end(attributes), [](const std::string& str) {
|
|
return str.find("label") != std::string::npos;
|
|
});
|
|
auto label = "\\nsubgraph=" + std::to_string(map_id.at(node.get_friendly_name())) + "\\n" +
|
|
"device=" + queryNetworkResult.supportedLayersMap.at(node.get_friendly_name()) + '\"';
|
|
IE_ASSERT(itLabel != attributes.end());
|
|
itLabel->pop_back();
|
|
(*itLabel) += label;
|
|
}}
|
|
.run_on_model(std::const_pointer_cast<ov::Model>(function));
|
|
}
|
|
|
|
// Break graph using insertion of result parameter split
|
|
NodeMap<ngraph::Node*> subgraphParameterToPrevResult;
|
|
std::vector<std::shared_ptr<ngraph::op::Result>> results;
|
|
{
|
|
std::set<ngraph::Output<ngraph::Node>> subgraphOutputs;
|
|
for (auto&& input : subgraphInputs) {
|
|
if (!ngraph::op::is_parameter(input.get_node()) && !ngraph::op::is_constant(input.get_node())) {
|
|
subgraphOutputs.insert(input.get_source_output());
|
|
}
|
|
}
|
|
for (auto&& output : subgraphOutputs) {
|
|
auto output_subgraph_id = subgraphIds.at(output.get_node());
|
|
auto inputs = output.get_target_inputs();
|
|
// Collect input subsets from other subgraphs. Each subset of inputs belongs to the same subgraph
|
|
std::map<int, std::set<ngraph::Input<ngraph::Node>>> input_subsets;
|
|
for (auto&& input : inputs) {
|
|
auto input_subgraph_id = subgraphIds.at(input.get_node());
|
|
if (output_subgraph_id != input_subgraph_id) {
|
|
input_subsets[input_subgraph_id].emplace(input);
|
|
}
|
|
}
|
|
// for each subset of inputs create separate Result operation if subset belongs to other
|
|
for (auto&& input_subset : input_subsets) {
|
|
auto result = std::make_shared<ngraph::op::Result>(output);
|
|
result->set_friendly_name(output.get_node()->get_friendly_name() + "_" +
|
|
std::to_string(output.get_index()) + "_" +
|
|
std::to_string(input_subset.first) + "_result");
|
|
ngraph::copy_runtime_info(output.get_node_shared_ptr(), result);
|
|
subgraphIds.emplace(result.get(), output_subgraph_id);
|
|
results.push_back(result);
|
|
for (auto&& input : input_subset.second) {
|
|
output.remove_target_input(input);
|
|
auto parameter =
|
|
std::make_shared<ngraph::op::Parameter>(output.get_element_type(), output.get_partial_shape());
|
|
parameter->set_friendly_name(input.get_node()->get_friendly_name() + "_" +
|
|
std::to_string(input.get_index()) + "_parameter");
|
|
ngraph::copy_runtime_info(input.get_node()->shared_from_this(), parameter);
|
|
input.replace_source_output(parameter->output(0));
|
|
subgraphIds.emplace(parameter.get(), input_subset.first);
|
|
subgraphParameterToPrevResult.emplace(parameter.get(), result.get());
|
|
_blobNameMap.emplace(
|
|
parameter->get_friendly_name(),
|
|
output.get_node()->get_friendly_name() + ((output.get_node()->get_output_size() != 1)
|
|
? ("." + std::to_string(output.get_index()))
|
|
: std::string{}));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Subgraph {
|
|
ngraph::ResultVector _results;
|
|
ngraph::ParameterVector _parameters;
|
|
ngraph::SinkVector _sinks;
|
|
std::string _affinity;
|
|
};
|
|
std::unordered_map<int, Subgraph> subgraphs;
|
|
// Extracts subgraph parameters, results and affinities
|
|
for (auto&& subgraphIdPtrValue : subgraphIds) {
|
|
auto node = subgraphIdPtrValue.first;
|
|
auto& subgraph = subgraphs[subgraphIdPtrValue.second];
|
|
if (ngraph::op::is_output(node)) {
|
|
subgraph._results.emplace_back(std::dynamic_pointer_cast<ngraph::op::v0::Result>(node->shared_from_this()));
|
|
} else if (ngraph::op::is_parameter(node)) {
|
|
subgraph._parameters.emplace_back(
|
|
std::dynamic_pointer_cast<ngraph::op::v0::Parameter>(node->shared_from_this()));
|
|
} else if (ngraph::op::is_sink(node)) {
|
|
subgraph._sinks.emplace_back(std::dynamic_pointer_cast<ngraph::op::Sink>(node->shared_from_this()));
|
|
}
|
|
auto itAffinity = affinities.find(node);
|
|
if (itAffinity != affinities.end()) {
|
|
subgraph._affinity = itAffinity->second;
|
|
}
|
|
}
|
|
results = {};
|
|
|
|
// Subgraph topological sort
|
|
std::vector<Subgraph> allSubgraphs;
|
|
for (auto&& subgraph : subgraphs) {
|
|
allSubgraphs.emplace_back(std::move(subgraph.second));
|
|
}
|
|
|
|
std::vector<Subgraph> orderedSubgraphs;
|
|
NodeSet prevResults;
|
|
size_t subgraphTopoSortsStep = 0;
|
|
do {
|
|
IE_ASSERT(subgraphTopoSortsStep < subgraphs.size());
|
|
++subgraphTopoSortsStep;
|
|
std::vector<Subgraph> newOrderedSubgraphs;
|
|
auto IsOrderedSubGraph = [&](const Subgraph& subgraph) {
|
|
auto& parameters = subgraph._parameters;
|
|
return std::all_of(parameters.begin(),
|
|
parameters.end(),
|
|
[&](const ngraph::ParameterVector::value_type& parameter) {
|
|
return contains(graphInputNodes, parameter.get()) ||
|
|
contains(prevResults, subgraphParameterToPrevResult[parameter.get()]);
|
|
});
|
|
};
|
|
std::remove_copy_if(std::begin(allSubgraphs),
|
|
std::end(allSubgraphs),
|
|
std::back_inserter(newOrderedSubgraphs),
|
|
[&](const Subgraph& subgraph) {
|
|
return !IsOrderedSubGraph(subgraph);
|
|
});
|
|
allSubgraphs.erase(std::remove_if(std::begin(allSubgraphs), std::end(allSubgraphs), IsOrderedSubGraph),
|
|
std::end(allSubgraphs));
|
|
for (auto&& subgraph : newOrderedSubgraphs) {
|
|
for (auto&& result : subgraph._results) {
|
|
prevResults.insert(result.get());
|
|
}
|
|
}
|
|
std::move(std::begin(newOrderedSubgraphs), std::end(newOrderedSubgraphs), std::back_inserter(orderedSubgraphs));
|
|
} while (!allSubgraphs.empty());
|
|
|
|
InputsDataMap externalInputsData = network.getInputsInfo();
|
|
OutputsDataMap externalOutputsData = network.getOutputsInfo();
|
|
_networks.resize(orderedSubgraphs.size());
|
|
std::vector<std::shared_ptr<ngraph::Function>> subFunctions(orderedSubgraphs.size());
|
|
int id = 0;
|
|
for (auto&& subgraph : orderedSubgraphs) {
|
|
_networks[id]._device = subgraph._affinity;
|
|
subFunctions[id] = std::make_shared<ngraph::Function>(subgraph._results,
|
|
subgraph._sinks,
|
|
subgraph._parameters,
|
|
_name + '_' + std::to_string(id));
|
|
_networks[id]._clonedNetwork = CNNNetwork{subFunctions[id]};
|
|
// update of pre-processing info
|
|
auto clonedInputs = _networks[id]._clonedNetwork.getInputsInfo();
|
|
for (auto&& externalInput : externalInputsData) {
|
|
auto itClonedInput = clonedInputs.find(externalInput.first);
|
|
if (itClonedInput != clonedInputs.end() && nullptr != itClonedInput->second) {
|
|
itClonedInput->second->getPreProcess() = externalInput.second->getPreProcess();
|
|
itClonedInput->second->setPrecision(externalInput.second->getPrecision());
|
|
itClonedInput->second->setLayout(externalInput.second->getLayout());
|
|
}
|
|
}
|
|
// update output info
|
|
auto clonedOutputs = _networks[id]._clonedNetwork.getOutputsInfo();
|
|
for (auto&& externalOutput : externalOutputsData) {
|
|
auto itClonedOutput = clonedOutputs.find(externalOutput.first);
|
|
if (itClonedOutput != clonedOutputs.end() && nullptr != itClonedOutput->second) {
|
|
itClonedOutput->second->setPrecision(externalOutput.second->getPrecision());
|
|
itClonedOutput->second->setLayout(externalOutput.second->getLayout());
|
|
}
|
|
}
|
|
|
|
auto toLegacyType = [](const ngraph::element::Type& ngraph_type) {
|
|
return (ngraph_type == ngraph::element::f16 || ngraph_type == ngraph::element::bf16) ? ngraph::element::f32
|
|
: ngraph_type;
|
|
};
|
|
|
|
// CNNNetwork converts input and output types to preserve legacy behaviour
|
|
// Here io types are reverted to ngraph types with some common plugin behaviour assumption
|
|
// defined in `toLegacyType()`
|
|
for (auto&& input : clonedInputs) {
|
|
if (!InferenceEngine::details::contains(externalInputsData, input.first)) {
|
|
for (auto&& parameter : subgraph._parameters) {
|
|
auto name = parameter->get_friendly_name();
|
|
if (parameter->get_friendly_name() == input.first) {
|
|
input.second->setPrecision(
|
|
InferenceEngine::details::convertPrecision(toLegacyType(parameter->get_element_type())));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (auto&& output : clonedOutputs) {
|
|
if (!InferenceEngine::details::contains(externalOutputsData, output.first)) {
|
|
for (auto&& result : subgraph._results) {
|
|
auto source_output = result->input_value(0);
|
|
auto output_name = ngraph::op::util::create_ie_output_name(source_output);
|
|
if (output_name == output.first) {
|
|
output.second->setPrecision(
|
|
InferenceEngine::details::convertPrecision(toLegacyType(source_output.get_element_type())));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
++id;
|
|
}
|
|
for (auto&& network : _networks) {
|
|
auto metaDevices = _heteroPlugin->GetDevicePlugins(network._device, _config);
|
|
metaDevices[network._device].emplace(CONFIG_KEY_INTERNAL(FORCE_DISABLE_CACHE), "");
|
|
network._network = _heteroPlugin->GetCore()->LoadNetwork(network._clonedNetwork,
|
|
network._device,
|
|
metaDevices[network._device]);
|
|
}
|
|
}
|
|
|
|
HeteroExecutableNetwork::HeteroExecutableNetwork(std::istream& heteroModel,
|
|
const std::map<std::string, std::string>& configs,
|
|
Engine* heteroPlugin)
|
|
: _heteroPlugin(heteroPlugin) {
|
|
std::string heteroXmlStr;
|
|
std::getline(heteroModel, heteroXmlStr);
|
|
|
|
pugi::xml_document heteroXmlDoc;
|
|
pugi::xml_parse_result res = heteroXmlDoc.load_string(heteroXmlStr.c_str());
|
|
|
|
if (res.status != pugi::status_ok) {
|
|
IE_THROW(NetworkNotRead) << "Error reading HETERO device xml header";
|
|
}
|
|
|
|
using namespace XMLParseUtils;
|
|
|
|
pugi::xml_node heteroNode = heteroXmlDoc.document_element();
|
|
_name = GetStrAttr(heteroNode, "name");
|
|
|
|
std::unordered_set<std::string> networkInputs;
|
|
pugi::xml_node inputsNode = heteroNode.child("inputs");
|
|
FOREACH_CHILD (inputNode, inputsNode, "input") { networkInputs.insert(GetStrAttr(inputNode, "name")); }
|
|
|
|
std::unordered_set<std::string> networkOutputs;
|
|
pugi::xml_node outputsNode = heteroNode.child("outputs");
|
|
FOREACH_CHILD (outputNode, outputsNode, "output") { networkOutputs.insert(GetStrAttr(outputNode, "name")); }
|
|
|
|
Engine::Configs importedConfigs;
|
|
auto configsNode = heteroNode.child("configs");
|
|
FOREACH_CHILD (configNode, configsNode, "config") {
|
|
importedConfigs.emplace(GetStrAttr(configNode, "key"), GetStrAttr(configNode, "value"));
|
|
}
|
|
|
|
auto blobNamesNode = heteroNode.child("blob_names_map");
|
|
FOREACH_CHILD (blobNameNode, blobNamesNode, "blob_name_map") {
|
|
_blobNameMap.emplace(GetStrAttr(blobNameNode, "key"), GetStrAttr(blobNameNode, "value"));
|
|
}
|
|
|
|
for (auto&& config : configs) {
|
|
importedConfigs[config.first] = config.second;
|
|
}
|
|
|
|
std::vector<NetworkDesc> descs;
|
|
pugi::xml_node subnetworksNode = heteroNode.child("subnetworks");
|
|
FOREACH_CHILD (subnetworkNode, subnetworksNode, "subnetwork") {
|
|
auto deviceName = GetStrAttr(subnetworkNode, "device");
|
|
|
|
auto metaDevices = _heteroPlugin->GetDevicePlugins(deviceName, importedConfigs);
|
|
assert(metaDevices.size() == 1);
|
|
auto& loadConfig = metaDevices[deviceName];
|
|
|
|
InferenceEngine::SoExecutableNetworkInternal executableNetwork;
|
|
CNNNetwork cnnnetwork;
|
|
bool loaded = false;
|
|
if (_heteroPlugin->GetCore()->DeviceSupportsImportExport(deviceName)) {
|
|
executableNetwork = _heteroPlugin->GetCore()->ImportNetwork(heteroModel, deviceName, loadConfig);
|
|
} else {
|
|
// read XML content
|
|
std::string xmlString;
|
|
std::uint64_t dataSize = 0;
|
|
heteroModel.read(reinterpret_cast<char*>(&dataSize), sizeof(dataSize));
|
|
xmlString.resize(dataSize);
|
|
heteroModel.read(const_cast<char*>(xmlString.c_str()), dataSize);
|
|
|
|
// read blob content
|
|
InferenceEngine::Blob::Ptr dataBlob;
|
|
heteroModel.read(reinterpret_cast<char*>(&dataSize), sizeof(dataSize));
|
|
if (0 != dataSize) {
|
|
dataBlob = InferenceEngine::make_shared_blob<std::uint8_t>(
|
|
InferenceEngine::TensorDesc(InferenceEngine::Precision::U8,
|
|
{static_cast<std::size_t>(dataSize)},
|
|
InferenceEngine::Layout::C));
|
|
dataBlob->allocate();
|
|
heteroModel.read(dataBlob->buffer(), dataSize);
|
|
}
|
|
|
|
cnnnetwork = _heteroPlugin->GetCore()->ReadNetwork(xmlString, std::move(dataBlob));
|
|
auto inputs = cnnnetwork.getInputsInfo();
|
|
auto inputsNode = subnetworkNode.child("inputs");
|
|
FOREACH_CHILD (inputNode, inputsNode, "input") {
|
|
auto inputName = GetStrAttr(inputNode, "name");
|
|
inputs[inputName]->setPrecision(Precision::FromStr(GetStrAttr(inputNode, "precision")));
|
|
}
|
|
|
|
auto outputs = cnnnetwork.getOutputsInfo();
|
|
auto outputsNode = subnetworkNode.child("outputs");
|
|
FOREACH_CHILD (outputNode, outputsNode, "output") {
|
|
auto outputName = GetStrAttr(outputNode, "name");
|
|
outputs[outputName]->setPrecision(Precision::FromStr(GetStrAttr(outputNode, "precision")));
|
|
}
|
|
|
|
executableNetwork = _heteroPlugin->GetCore()->LoadNetwork(cnnnetwork, deviceName, loadConfig);
|
|
loaded = true;
|
|
}
|
|
|
|
// restore network inputs and outputs
|
|
for (auto&& input : executableNetwork->GetInputsInfo()) {
|
|
if (networkInputs.end() != networkInputs.find(input.first)) {
|
|
_networkInputs.emplace(input.first, std::make_shared<InputInfo>(*input.second));
|
|
}
|
|
}
|
|
|
|
for (auto&& output : executableNetwork->GetOutputsInfo()) {
|
|
if (networkOutputs.end() != networkOutputs.find(output.first)) {
|
|
_networkOutputs.emplace(output.first, std::make_shared<Data>(*output.second));
|
|
}
|
|
}
|
|
|
|
descs.emplace_back(NetworkDesc{
|
|
deviceName,
|
|
loaded ? cnnnetwork : CNNNetwork{},
|
|
executableNetwork,
|
|
});
|
|
}
|
|
|
|
const auto parseNode = [](const pugi::xml_node& xml_node, bool is_param) -> std::shared_ptr<const ov::Node> {
|
|
const std::string operation_name = GetStrAttr(xml_node, "operation_name");
|
|
const auto elementType = ov::EnumNames<ov::element::Type_t>::as_enum(GetStrAttr(xml_node, "element_type"));
|
|
|
|
std::vector<ov::Dimension> partialShape;
|
|
pugi::xml_node partialShapeNode = xml_node.child("partial_shape");
|
|
FOREACH_CHILD (dimNode, partialShapeNode, "dim") {
|
|
partialShape.emplace_back(ov::Dimension(GetInt64Attr(dimNode, "value")));
|
|
}
|
|
|
|
pugi::xml_node tensorNamesNode = xml_node.child("tensor_names");
|
|
std::unordered_set<std::string> tensorNames;
|
|
FOREACH_CHILD (tensorNameNode, tensorNamesNode, "tensor_name") {
|
|
tensorNames.insert(GetStrAttr(tensorNameNode, "value"));
|
|
}
|
|
|
|
std::shared_ptr<ov::Node> node = std::make_shared<ov::op::v0::Parameter>(elementType, partialShape);
|
|
if (!is_param)
|
|
node = std::make_shared<ov::op::v0::Result>(node);
|
|
node->set_friendly_name(operation_name);
|
|
node->output(0).get_tensor().add_names(tensorNames);
|
|
|
|
return node;
|
|
};
|
|
(void)parseNode;
|
|
|
|
pugi::xml_node parametersNode = heteroNode.child("parameters");
|
|
FOREACH_CHILD (parameterNode, parametersNode, "parameter") {
|
|
_parameters.emplace_back(parseNode(parameterNode, true));
|
|
}
|
|
|
|
pugi::xml_node resultsNode = heteroNode.child("results");
|
|
FOREACH_CHILD (resultNode, resultsNode, "result") { _results.emplace_back(parseNode(resultNode, false)); }
|
|
|
|
// save state
|
|
this->_config = importedConfigs;
|
|
this->_networks = std::move(descs);
|
|
this->SetPointerToPlugin(_heteroPlugin->shared_from_this());
|
|
}
|
|
|
|
void HeteroExecutableNetwork::Export(std::ostream& heteroModel) {
|
|
pugi::xml_document doc;
|
|
auto heteroNode = doc.append_child("hetero");
|
|
heteroNode.append_attribute("name").set_value(_name.c_str());
|
|
|
|
// CNNNetwork inputs and outputs information
|
|
|
|
auto inputsNode = heteroNode.append_child("inputs");
|
|
for (auto&& networkInput : _networkInputs) {
|
|
inputsNode.append_child("input").append_attribute("name").set_value(networkInput.first.c_str());
|
|
}
|
|
|
|
auto outputsNode = heteroNode.append_child("outputs");
|
|
for (auto&& networkInput : _networkOutputs) {
|
|
outputsNode.append_child("output").append_attribute("name").set_value(networkInput.first.c_str());
|
|
}
|
|
|
|
const auto serializeNode = [&](const std::shared_ptr<const ov::Node>& node, pugi::xml_node& xml_node) {
|
|
const bool is_result = ov::is_type<ov::op::v0::Result>(node);
|
|
const std::string name =
|
|
is_result ? ngraph::op::util::create_ie_output_name(node->input_value(0)) : node->get_friendly_name();
|
|
xml_node.append_attribute("operation_name").set_value(name.c_str());
|
|
xml_node.append_attribute("element_type").set_value(node->get_output_element_type(0).get_type_name().c_str());
|
|
|
|
const auto& pShape = node->get_output_partial_shape(0);
|
|
OPENVINO_ASSERT(pShape.rank().is_static(), "Serialization of shapes with dynamic rank is not supported");
|
|
auto partialShapeNode = xml_node.append_child("partial_shape");
|
|
for (auto&& dim : node->get_output_partial_shape(0)) {
|
|
if (dim.is_dynamic())
|
|
partialShapeNode.append_child("dim").append_attribute("value").set_value("-1");
|
|
else
|
|
partialShapeNode.append_child("dim").append_attribute("value").set_value(
|
|
std::to_string(dim.get_length()).c_str());
|
|
}
|
|
|
|
auto tensorNamesNode = xml_node.append_child("tensor_names");
|
|
for (auto& tensorName : node->get_output_tensor(0).get_names()) {
|
|
tensorNamesNode.append_child("tensor_name").append_attribute("value").set_value(tensorName.c_str());
|
|
}
|
|
};
|
|
|
|
// ngraph parameters info
|
|
auto subnetworkParamsNode = heteroNode.append_child("parameters");
|
|
for (auto&& parameter : getInputs()) {
|
|
auto parameterNode = subnetworkParamsNode.append_child("parameter");
|
|
serializeNode(parameter, parameterNode);
|
|
}
|
|
|
|
// ngraph results info
|
|
auto subnetworkResultsNode = heteroNode.append_child("results");
|
|
for (auto&& result : getOutputs()) {
|
|
auto parameterNode = subnetworkResultsNode.append_child("result");
|
|
serializeNode(result, parameterNode);
|
|
}
|
|
|
|
auto subnetworksNode = heteroNode.append_child("subnetworks");
|
|
for (auto&& subnetwork : _networks) {
|
|
auto subnet = subnetwork._clonedNetwork;
|
|
IE_ASSERT(subnet.getFunction() != nullptr);
|
|
|
|
auto subnetworkNode = subnetworksNode.append_child("subnetwork");
|
|
subnetworkNode.append_attribute("device").set_value(subnetwork._device.c_str());
|
|
|
|
// inputs info
|
|
auto subnetworkInputsNode = subnetworkNode.append_child("inputs");
|
|
auto inputInfo = subnet.getInputsInfo();
|
|
for (auto&& input : inputInfo) {
|
|
auto inputNode = subnetworkInputsNode.append_child("input");
|
|
inputNode.append_attribute("name").set_value(input.first.c_str());
|
|
inputNode.append_attribute("precision").set_value(input.second->getPrecision().name());
|
|
}
|
|
|
|
// outputs info
|
|
auto subnetworkOutputsNode = subnetworkNode.append_child("outputs");
|
|
auto outputInfo = subnet.getOutputsInfo();
|
|
for (auto&& output : outputInfo) {
|
|
auto outputNode = subnetworkOutputsNode.append_child("output");
|
|
outputNode.append_attribute("name").set_value(output.first.c_str());
|
|
outputNode.append_attribute("precision").set_value(output.second->getPrecision().name());
|
|
}
|
|
}
|
|
|
|
auto configsNode = heteroNode.append_child("configs");
|
|
for (auto&& config : _config) {
|
|
auto configNode = configsNode.append_child("config");
|
|
configNode.append_attribute("key").set_value(config.first.c_str());
|
|
configNode.append_attribute("value").set_value(config.second.c_str());
|
|
}
|
|
|
|
auto blobNamesNode = heteroNode.append_child("blob_names_map");
|
|
for (auto&& kvp : _blobNameMap) {
|
|
auto blobNameNode = blobNamesNode.append_child("blob_name_map");
|
|
blobNameNode.append_attribute("key").set_value(kvp.first.c_str());
|
|
blobNameNode.append_attribute("value").set_value(kvp.second.c_str());
|
|
}
|
|
|
|
doc.save(heteroModel, nullptr, pugi::format_raw);
|
|
doc.reset();
|
|
heteroModel << std::endl;
|
|
|
|
for (auto&& subnetwork : _networks) {
|
|
if (_heteroPlugin->GetCore()->DeviceSupportsImportExport(subnetwork._device)) {
|
|
subnetwork._network->Export(heteroModel);
|
|
} else {
|
|
auto subnet = subnetwork._clonedNetwork;
|
|
if (!subnet.getFunction()) {
|
|
IE_THROW() << "Hetero device supports only ngraph function representation";
|
|
}
|
|
|
|
// Note: custom ngraph extensions are not supported
|
|
std::stringstream xmlFile, binFile;
|
|
ov::pass::Serialize serializer(xmlFile, binFile, ov::pass::Serialize::Version::IR_V10);
|
|
serializer.run_on_model(subnet.getFunction());
|
|
|
|
auto m_constants = binFile.str();
|
|
auto m_model = xmlFile.str();
|
|
|
|
auto dataSize = static_cast<std::uint64_t>(m_model.size());
|
|
heteroModel.write(reinterpret_cast<char*>(&dataSize), sizeof(dataSize));
|
|
heteroModel.write(m_model.c_str(), dataSize);
|
|
|
|
dataSize = static_cast<std::uint64_t>(m_constants.size());
|
|
heteroModel.write(reinterpret_cast<char*>(&dataSize), sizeof(dataSize));
|
|
heteroModel.write(reinterpret_cast<char*>(&m_constants[0]), dataSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
IInferRequestInternal::Ptr HeteroExecutableNetwork::CreateInferRequestImpl(
|
|
const std::vector<std::shared_ptr<const ov::Node>>& inputs,
|
|
const std::vector<std::shared_ptr<const ov::Node>>& outputs) {
|
|
if (!this->_plugin)
|
|
return nullptr;
|
|
const auto& core = _plugin->GetCore();
|
|
if (!core || !core->isNewAPI())
|
|
return nullptr;
|
|
HeteroInferRequest::SubRequestsList inferRequests;
|
|
int index = 0;
|
|
for (auto&& subnetwork : _networks) {
|
|
HeteroInferRequest::SubRequestDesc desc;
|
|
desc._network = subnetwork._network;
|
|
desc._profilingTask = openvino::itt::handle("Infer" + std::to_string(index++));
|
|
inferRequests.push_back(desc);
|
|
}
|
|
return std::make_shared<HeteroInferRequest>(inputs, outputs, inferRequests, _blobNameMap);
|
|
}
|
|
|
|
IInferRequestInternal::Ptr HeteroExecutableNetwork::CreateInferRequestImpl(InputsDataMap networkInputs,
|
|
OutputsDataMap networkOutputs) {
|
|
HeteroInferRequest::SubRequestsList inferRequests;
|
|
int index = 0;
|
|
for (auto&& subnetwork : _networks) {
|
|
HeteroInferRequest::SubRequestDesc desc;
|
|
desc._network = subnetwork._network;
|
|
desc._profilingTask = openvino::itt::handle("Infer" + std::to_string(index++));
|
|
inferRequests.push_back(desc);
|
|
}
|
|
return std::make_shared<HeteroInferRequest>(networkInputs, networkOutputs, inferRequests, _blobNameMap);
|
|
}
|
|
|
|
IInferRequestInternal::Ptr HeteroExecutableNetwork::CreateInferRequest() {
|
|
return CreateAsyncInferRequestFromSync<HeteroAsyncInferRequest>();
|
|
}
|
|
|
|
InferenceEngine::Parameter HeteroExecutableNetwork::GetConfig(const std::string& name) const {
|
|
InferenceEngine::Parameter result;
|
|
if (name == "TARGET_FALLBACK" || name == ov::device::priorities.name()) {
|
|
auto it = _config.find("TARGET_FALLBACK");
|
|
if (it == _config.end()) {
|
|
it = _config.find(ov::device::priorities.name());
|
|
}
|
|
if (it != _config.end()) {
|
|
result = it->second;
|
|
} else {
|
|
result = std::string{};
|
|
}
|
|
} else if (name == HETERO_CONFIG_KEY(DUMP_GRAPH_DOT) || name == CONFIG_KEY(EXCLUSIVE_ASYNC_REQUESTS)) {
|
|
auto it = _config.find(name);
|
|
IE_ASSERT(it != _config.end());
|
|
result = it->second == YES ? true : false;
|
|
} else {
|
|
// find config key among plugin config keys
|
|
for (auto&& desc : _networks) {
|
|
auto execNetwork = desc._network;
|
|
auto param = execNetwork->GetMetric(METRIC_KEY(SUPPORTED_CONFIG_KEYS));
|
|
for (auto&& configKey : param.as<std::vector<std::string>>()) {
|
|
if (configKey == name) {
|
|
return execNetwork->GetConfig(configKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
IE_THROW() << "Unsupported ExecutableNetwork config key: " << name;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
using Metrics = std::map<std::string, Parameter>;
|
|
|
|
namespace {
|
|
|
|
void collectPluginMetrics(std::vector<std::string>& baseMetrics, const std::vector<::Metrics> pluginMetrics) {
|
|
// check whether the metric has unique name and value among all the plugins
|
|
auto isMetricValueUnique = [&](const std::string& key, const Parameter& value) -> bool {
|
|
if (std::find(baseMetrics.begin(), baseMetrics.end(), key) != baseMetrics.end())
|
|
return false;
|
|
|
|
for (auto&& metrics : pluginMetrics) {
|
|
for (auto&& metric : metrics)
|
|
if (key == metric.first && value != metric.second)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// collect only unique metrics
|
|
std::vector<std::string> uniqueMetrics;
|
|
for (auto&& metrics : pluginMetrics) {
|
|
for (auto&& metric : metrics) {
|
|
if (isMetricValueUnique(metric.first, metric.second)) {
|
|
uniqueMetrics.push_back(metric.first);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add plugin specific metrics which don't conflict with base ones
|
|
std::copy(uniqueMetrics.begin(), uniqueMetrics.end(), std::back_inserter(baseMetrics));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
InferenceEngine::Parameter HeteroExecutableNetwork::GetMetric(const std::string& name) const {
|
|
if (EXEC_NETWORK_METRIC_KEY(SUPPORTED_METRICS) == name) {
|
|
std::vector<std::string> heteroMetrics = {ov::model_name.name(),
|
|
METRIC_KEY(SUPPORTED_METRICS),
|
|
METRIC_KEY(SUPPORTED_CONFIG_KEYS),
|
|
ov::optimal_number_of_infer_requests.name()};
|
|
|
|
{
|
|
std::vector<::Metrics> pluginMetrics;
|
|
for (auto&& desc : _networks) {
|
|
auto execNetwork = desc._network;
|
|
auto param = execNetwork->GetMetric(METRIC_KEY(SUPPORTED_METRICS));
|
|
::Metrics metrics;
|
|
for (auto&& metricName : param.as<std::vector<std::string>>()) {
|
|
metrics[metricName] = execNetwork->GetMetric(metricName);
|
|
}
|
|
pluginMetrics.push_back(std::move(metrics));
|
|
}
|
|
|
|
collectPluginMetrics(heteroMetrics, pluginMetrics);
|
|
}
|
|
|
|
IE_SET_METRIC_RETURN(SUPPORTED_METRICS, heteroMetrics);
|
|
} else if (EXEC_NETWORK_METRIC_KEY(SUPPORTED_CONFIG_KEYS) == name) {
|
|
std::vector<std::string> heteroConfigKeys = {"TARGET_FALLBACK",
|
|
ov::device::priorities.name(),
|
|
HETERO_CONFIG_KEY(DUMP_GRAPH_DOT),
|
|
CONFIG_KEY(EXCLUSIVE_ASYNC_REQUESTS)};
|
|
|
|
{
|
|
std::vector<::Metrics> pluginConfigKeys;
|
|
for (auto&& desc : _networks) {
|
|
auto execNetwork = desc._network;
|
|
auto param = execNetwork->GetMetric(METRIC_KEY(SUPPORTED_CONFIG_KEYS));
|
|
::Metrics configKeys;
|
|
for (auto&& metricName : param.as<std::vector<std::string>>()) {
|
|
configKeys[metricName] = execNetwork->GetConfig(metricName);
|
|
}
|
|
pluginConfigKeys.push_back(std::move(configKeys));
|
|
}
|
|
|
|
collectPluginMetrics(heteroConfigKeys, pluginConfigKeys);
|
|
}
|
|
|
|
IE_SET_METRIC_RETURN(SUPPORTED_CONFIG_KEYS, heteroConfigKeys);
|
|
} else if (ov::model_name == name) {
|
|
return decltype(ov::model_name)::value_type{_name};
|
|
} else if (ov::optimal_number_of_infer_requests == name) {
|
|
unsigned int value = 0u;
|
|
for (auto&& desc : _networks) {
|
|
value = std::max(value,
|
|
desc._network->GetMetric(METRIC_KEY(OPTIMAL_NUMBER_OF_INFER_REQUESTS)).as<unsigned int>());
|
|
}
|
|
return decltype(ov::optimal_number_of_infer_requests)::value_type{value};
|
|
} else {
|
|
// find metric key among plugin metrics
|
|
for (auto&& desc : _networks) {
|
|
auto execNetwork = desc._network;
|
|
auto param = execNetwork->GetMetric(METRIC_KEY(SUPPORTED_METRICS));
|
|
for (auto&& metricKey : param.as<std::vector<std::string>>()) {
|
|
if (metricKey == name) {
|
|
return execNetwork->GetMetric(metricKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
IE_THROW() << "Unsupported ExecutableNetwork metric key: " << name;
|
|
}
|
|
}
|