Moved graph validation utils to ov (#7529)
This commit is contained in:
1 parent
9cfdad9afc
commit
850c526e3b
7 files changed
+516
-461
No files matched your search
@@ -23,12 +23,10 @@ acos0->set_argument(0, add0);
|
||||
add1->set_argument(0, acos0);
|
||||
add1->set_argument(1, abs0);
|
||||
|
||||
// Run shape inference on the nodes
|
||||
NodeVector ops{arg0, arg1, add0, abs0, acos0, add1};
|
||||
validate_nodes_and_infer_types(ops);
|
||||
|
||||
// Create a graph with one output (add1) and four inputs (arg0, arg1)
|
||||
auto ng_function = make_shared<Function>(OutputVector{add1}, ParameterVector{arg0, arg1});
|
||||
// Run shape inference on the nodes
|
||||
ng_function->validate_nodes_and_infer_types();
|
||||
|
||||
//! [part0]
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "ngraph/check.hpp"
|
||||
#include "ngraph/function.hpp"
|
||||
#include "ngraph/node.hpp"
|
||||
#include "openvino/core/graph_util.hpp"
|
||||
|
||||
namespace ov {
|
||||
namespace op {
|
||||
@@ -33,249 +34,19 @@ using ov::op::v0::Parameter;
|
||||
}
|
||||
} // namespace op
|
||||
|
||||
NGRAPH_API
|
||||
void traverse_nodes(const std::shared_ptr<const Function> p, std::function<void(std::shared_ptr<Node>)> f);
|
||||
|
||||
NGRAPH_API
|
||||
void traverse_nodes(const Function* p, std::function<void(std::shared_ptr<Node>)> f);
|
||||
|
||||
/// \brief Visit each node in a sub-graph of the entire graph
|
||||
/// \param subgraph_results The output nodes of the sub-graph
|
||||
/// \param f Function to execute at each node in the traversal
|
||||
/// \param subgraph_params Input nodes of the sub-graph (optional)
|
||||
///
|
||||
/// Traverses a sub-graph starting from subgraph_results moving up
|
||||
/// towards parameter nodes. Traversal stops if it hits a node in
|
||||
/// subgraph_params.
|
||||
///
|
||||
/// Most useful for finding parameters of a graph directly from the
|
||||
/// result nodes and not from function parameters or extracting a
|
||||
/// subgraph relevant to the computation of certain outputs
|
||||
NGRAPH_API
|
||||
void traverse_nodes(const NodeVector& subgraph_results,
|
||||
std::function<void(std::shared_ptr<Node>)> f,
|
||||
const NodeVector& subgraph_params = {});
|
||||
|
||||
/// \brief Replace the node `target` with the node `replacement`, i.e.,
|
||||
/// redirect all users and control dependencies of `target` to
|
||||
/// `replacement`.
|
||||
///
|
||||
/// \param target Node to be replaced.
|
||||
/// \param replacement Node to replace `target` with.
|
||||
/// \param output_order Vector determines order of replacement node's outputs.
|
||||
///
|
||||
/// This is primarily used in graph-rewriting passes. For example, we
|
||||
/// might "fuse" two Concat operations as follows:
|
||||
///
|
||||
/// (Step 0: Original graph)
|
||||
///
|
||||
/// A B
|
||||
/// | |
|
||||
/// v v
|
||||
/// N0[Concat, concatenation_axis=3] C
|
||||
/// | |
|
||||
/// v v
|
||||
/// N1[Concat, concatenation_axis=3]
|
||||
/// | |
|
||||
/// v v
|
||||
/// some_user another_user
|
||||
///
|
||||
/// (Step 1: Construct replacement)
|
||||
///
|
||||
/// shared_ptr<Node> new_N1 = make_shared<op::Concat>({A,B,C},3);
|
||||
///
|
||||
/// A----------------------------------------.
|
||||
/// | |
|
||||
/// | B----------------)--.
|
||||
/// | | | |
|
||||
/// v v | |
|
||||
/// N0[Concat, concatenation_axis=3] C-----)--)--.
|
||||
/// | | | | |
|
||||
/// v v v v v
|
||||
/// N1[Concat, concatenation_axis=3] new_N1[Concat, concatenation_axis=3]
|
||||
/// | |
|
||||
/// v v
|
||||
/// some_user another_user
|
||||
///
|
||||
/// (Step 2: Replace N1 with new_N1)
|
||||
///
|
||||
/// replace_node(N1, new_N1);
|
||||
///
|
||||
/// A----------------------------------------.
|
||||
/// | |
|
||||
/// | B----------------)--.
|
||||
/// | | | |
|
||||
/// v v | |
|
||||
/// N0[Concat, concatenation_axis=3] C-----)--)--.
|
||||
/// | | | | |
|
||||
/// v v v v v
|
||||
/// N1[Concat, concatenation_axis=3] new_N1[Concat, concatenation_axis=3]
|
||||
/// | |
|
||||
/// v v
|
||||
/// some_user another_user
|
||||
///
|
||||
/// (Step 3: N0 and N1 are now dead, nodes will be freed)
|
||||
///
|
||||
/// [happens automatically, once all shared_ptrs to N1 are released]
|
||||
///
|
||||
/// A----------------------------------------.
|
||||
/// |
|
||||
/// B----------------)--.
|
||||
/// | |
|
||||
/// | |
|
||||
/// C-----)--)--.
|
||||
/// | | |
|
||||
/// v v v
|
||||
/// new_N1[Concat, concatenation_axis=3]
|
||||
/// | |
|
||||
/// v v
|
||||
/// some_user another_user
|
||||
///
|
||||
/// NOTE 1: replace_node is not type-safe (the graph is not revalidated).
|
||||
/// For example, the following is allowed, even if node `some_user`
|
||||
/// requires an input of shape 2x2:
|
||||
///
|
||||
/// (Before)
|
||||
/// A(shape=2x2) B(shape=3x3)
|
||||
/// |
|
||||
/// v
|
||||
/// some_user(requires 2x2 input)
|
||||
///
|
||||
/// (After -- graph is now invalid)
|
||||
///
|
||||
/// replace_node(A, B);
|
||||
///
|
||||
/// A(shape=2x2) B(shape=3x3)
|
||||
/// |
|
||||
/// v
|
||||
/// some_user(requires 2x2 input)
|
||||
///
|
||||
/// NOTE 2: it is possible to insert a cycle into the graph with
|
||||
/// replace_node, resulting in an invalid graph. Care must be taken to
|
||||
/// avoid this. One common example is when you are attempting to insert a
|
||||
/// new node `M` "after"` a node `N`. For example, you might expect this
|
||||
/// to work:
|
||||
///
|
||||
/// shared_ptr<Node> M = make_shared<SomeUnaryOp>(N);
|
||||
/// replace_node(M, N);
|
||||
///
|
||||
/// The problem is that at replacement time, N itself is a user of M. So
|
||||
/// we end up introducing a cycle as follows:
|
||||
///
|
||||
/// N
|
||||
/// |
|
||||
/// v
|
||||
/// other users...
|
||||
///
|
||||
/// |||
|
||||
/// vvv
|
||||
///
|
||||
/// N------------>M
|
||||
/// |
|
||||
/// v
|
||||
/// other users...
|
||||
///
|
||||
/// |||
|
||||
/// vvv
|
||||
///
|
||||
/// .----.
|
||||
/// | |
|
||||
/// | |
|
||||
/// N `----->M
|
||||
/// |
|
||||
/// v
|
||||
/// other users...
|
||||
///
|
||||
/// To avoid the cycle, a valid way to perform the above desired insertion would be,
|
||||
///
|
||||
/// auto new_N = N->clone_with_new_inputs(N->input_values());
|
||||
/// shared_ptr<Node> M = make_shared<SomeUnaryOp>(new_N);
|
||||
/// replace_node(N, M);
|
||||
NGRAPH_API
|
||||
void replace_node(std::shared_ptr<Node> target,
|
||||
std::shared_ptr<Node> replacement,
|
||||
const std::vector<int64_t>& output_order);
|
||||
|
||||
/// Replace target.outputs[i] with replacement_values[i] and transfer control dependents and
|
||||
/// provenance from target to the node(s) in replacement_values.
|
||||
NGRAPH_API
|
||||
void replace_node(const std::shared_ptr<Node>& target, const OutputVector& replacement_values);
|
||||
|
||||
NGRAPH_API
|
||||
void replace_node(std::shared_ptr<Node> target, std::shared_ptr<Node> replacement);
|
||||
|
||||
/// \brief Replace multiple nodes in a function.
|
||||
/// \param f Function where replacement is taking place.
|
||||
/// \param parameter_replacement_map A mapping from parameter shared pointers to parameter
|
||||
/// shared pointers. For each pair (k,v) in the map, parameter
|
||||
/// k is replaced by parameter v, except if k==v or k is not a
|
||||
/// parameter bound by f, in which case the pair (k,v) is
|
||||
/// ignored.
|
||||
/// \param body_replacement_map A mapping from node shared pointers to node shared pointers.
|
||||
/// For each pair (k,v) in the map, node k is replaced by node v,
|
||||
/// except if k==v, the pair (k,v) is ignored.
|
||||
/// Note that if k is a parameter, its users will be redirected to
|
||||
/// v, but k will _not_ be replaced in the function's parameter
|
||||
/// list.
|
||||
///
|
||||
/// Limitations:
|
||||
///
|
||||
/// - No check is made that the replaced nodes in `parameter_replacement_map` are actually
|
||||
/// among the bound parameters of `f`. (If a parameter appears in the map that is not
|
||||
/// bound by `f`, it will be silently ignored.)
|
||||
/// - If a parameter node appears as a key in both `parameter_replacement_map` _and_ in
|
||||
/// `body_replacement_map`, behavior is unspecified.
|
||||
NGRAPH_API
|
||||
void replace_nodes(const std::shared_ptr<Function>& f,
|
||||
const std::unordered_map<std::shared_ptr<op::v0::Parameter>, std::shared_ptr<op::v0::Parameter>>&
|
||||
parameter_replacement_map,
|
||||
const std::unordered_map<std::shared_ptr<Node>, std::shared_ptr<Node>>& body_replacement_map);
|
||||
using ov::clone_function;
|
||||
using ov::compare_constants;
|
||||
using ov::replace_node;
|
||||
using ov::replace_node_update_name;
|
||||
using ov::replace_nodes;
|
||||
using ov::replace_output_update_name;
|
||||
using ov::topological_sort;
|
||||
using ov::traverse_nodes;
|
||||
|
||||
NGRAPH_DEPRECATED("This method is deprecated and will be removed soon")
|
||||
NGRAPH_API
|
||||
NodeVector find_common_args(std::shared_ptr<Node> target, std::shared_ptr<Node> replacement);
|
||||
|
||||
/// Topological sort of nodes needed to compute root_nodes
|
||||
template <typename T>
|
||||
std::vector<std::shared_ptr<Node>> topological_sort(T root_nodes) {
|
||||
std::stack<Node*, std::vector<Node*>> nodes_to_do;
|
||||
std::unordered_set<Node*> nodes_done;
|
||||
std::vector<std::shared_ptr<Node>> result;
|
||||
|
||||
for (auto& node : root_nodes) {
|
||||
nodes_to_do.push(node.get());
|
||||
}
|
||||
while (nodes_to_do.size() > 0) {
|
||||
Node* node = nodes_to_do.top();
|
||||
if (nodes_done.count(node) == 0) {
|
||||
bool can_add = true;
|
||||
size_t arg_count = node->get_input_size();
|
||||
for (size_t i = 0; i < arg_count; ++i) {
|
||||
Node* dep = node->get_input_node_ptr(arg_count - i - 1);
|
||||
if (nodes_done.count(dep) == 0) {
|
||||
can_add = false;
|
||||
nodes_to_do.push(dep);
|
||||
}
|
||||
}
|
||||
for (auto& depptr : node->get_control_dependencies()) {
|
||||
Node* dep = depptr.get();
|
||||
if (nodes_done.count(dep) == 0) {
|
||||
can_add = false;
|
||||
nodes_to_do.push(dep);
|
||||
}
|
||||
}
|
||||
if (can_add) {
|
||||
result.push_back(node->shared_from_this());
|
||||
nodes_to_do.pop();
|
||||
nodes_done.insert(node);
|
||||
}
|
||||
} else {
|
||||
nodes_to_do.pop();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Topological sort of just nodes
|
||||
template <typename T>
|
||||
std::vector<std::shared_ptr<Node>> subgraph_topological_sort(T nodes) {
|
||||
@@ -356,16 +127,6 @@ NGRAPH_API
|
||||
std::list<std::shared_ptr<ngraph::Node>> clone_nodes(const std::vector<std::shared_ptr<ngraph::Node>>& nodes,
|
||||
RawNodeOutputMap& node_map);
|
||||
|
||||
// input function is cloned and returned
|
||||
// NodeMap input may contain default node mapping i.e. pre-cloned nodes
|
||||
// NodeMap output (by reference) fully maps input and cloned function ops
|
||||
NGRAPH_API
|
||||
std::shared_ptr<ngraph::Function> clone_function(const ngraph::Function& func, NodeMap& node_map);
|
||||
|
||||
// input function is cloned and returned
|
||||
NGRAPH_API
|
||||
std::shared_ptr<ngraph::Function> clone_function(const ngraph::Function& func);
|
||||
|
||||
NGRAPH_DEPRECATED("This method is deprecated and will be removed soon")
|
||||
NGRAPH_API
|
||||
std::pair<std::shared_ptr<op::Result>, std::shared_ptr<op::v0::Parameter>> insert_result_parameter_split(
|
||||
@@ -406,9 +167,6 @@ NGRAPH_DEPRECATED("This method is deprecated and will be removed soon")
|
||||
NGRAPH_API
|
||||
bool is_one(const Output<Node>& reduce_constant);
|
||||
|
||||
NGRAPH_API
|
||||
bool compare_constants(const std::shared_ptr<Node>& n1, const std::shared_ptr<Node>& n2);
|
||||
|
||||
// Returns true if `node` is live in the graph i.e. a result op
|
||||
// transitively uses this `node`
|
||||
NGRAPH_API
|
||||
@@ -456,12 +214,6 @@ std::vector<Output<Node>> get_outputs_to(Node& src, Node& dst);
|
||||
NGRAPH_DEPRECATED("This method is deprecated and will be removed soon")
|
||||
NGRAPH_API
|
||||
bool check_for_cycles(const ngraph::Function* func, ngraph::NodeVector& cycle_nodes, bool& is_bkwd_cycle);
|
||||
|
||||
NGRAPH_API
|
||||
bool replace_output_update_name(Output<Node> node, const Output<Node>& node_input);
|
||||
|
||||
NGRAPH_API
|
||||
bool replace_node_update_name(std::shared_ptr<Node> target, std::shared_ptr<Node> replacement);
|
||||
} // namespace ngraph
|
||||
|
||||
using ngraph::replace_node;
|
||||
|
||||
@@ -11,8 +11,16 @@
|
||||
#include "ngraph/op/op.hpp"
|
||||
#include "ngraph/op/util/attr_types.hpp"
|
||||
#include "ngraph/op/util/variable_context.hpp"
|
||||
#include "openvino/core/validation_util.hpp"
|
||||
|
||||
namespace ngraph {
|
||||
using ov::evaluate_as_partial_shape;
|
||||
using ov::get_constant_from_source;
|
||||
using ov::infer_auto_padding;
|
||||
using ov::infer_convolution_forward;
|
||||
using ov::normalize_axes;
|
||||
using ov::normalize_axis;
|
||||
|
||||
NGRAPH_API
|
||||
Strides conv_default_strides(const Node* node, const PartialShape& data_batch_shape, const PartialShape& filters_shape);
|
||||
|
||||
@@ -63,16 +71,6 @@ PartialShape validate_and_infer_convolution_forward_output_shape(const Node* nod
|
||||
CoordinateDiff& pads_begin,
|
||||
CoordinateDiff& pads_end);
|
||||
|
||||
NGRAPH_API
|
||||
PartialShape infer_convolution_forward(const Node* node,
|
||||
const PartialShape& data_batch_shape,
|
||||
const Strides& data_dilation,
|
||||
const CoordinateDiff& data_padding_below,
|
||||
const CoordinateDiff& data_padding_above,
|
||||
const PartialShape& filters_shape,
|
||||
const Strides& filter_strides,
|
||||
const Strides& filter_dilation);
|
||||
|
||||
NGRAPH_API
|
||||
PartialShape infer_batched_pooling_forward(const Node* node,
|
||||
const PartialShape& data_batch_shape,
|
||||
@@ -129,15 +127,6 @@ bool try_apply_auto_padding(const PartialShape& image_shape,
|
||||
CoordinateDiff& padding_above,
|
||||
CoordinateDiff& padding_below);
|
||||
|
||||
NGRAPH_API
|
||||
void infer_auto_padding(const Shape& image_shape,
|
||||
const Shape& filter_shape,
|
||||
const Strides& filter_strides,
|
||||
const Strides& filter_dilations,
|
||||
const op::PadType pad_type,
|
||||
CoordinateDiff& padding_above,
|
||||
CoordinateDiff& padding_below);
|
||||
|
||||
NGRAPH_API
|
||||
PartialShape infer_slice_shape(const Node* node,
|
||||
const PartialShape& input_shape,
|
||||
@@ -150,80 +139,6 @@ PartialShape infer_slice_shape(const Node* node,
|
||||
const AxisSet& shrink_axis_mask,
|
||||
const AxisSet& ellipsis_mask);
|
||||
|
||||
/// \brief Handle out of range axis.
|
||||
///
|
||||
/// \param[in] node The node with requested axis.
|
||||
/// \param[in] axis The requested axis value.
|
||||
/// \param[in] tensor_rank The corresponding tensor rank.
|
||||
///
|
||||
/// \return Checking if axis is in range [-tensor_rank, tensor_rank-1], otherwise
|
||||
/// returns error. If negative axis, it counts from the last to the first axis,
|
||||
/// by adding tensor_rank to axis.
|
||||
NGRAPH_API
|
||||
int64_t normalize_axis(const Node* node, std::int64_t axis, const Rank& tensor_rank);
|
||||
|
||||
/// \brief Handle out of range axes in vector.
|
||||
///
|
||||
/// \param[in] node_description The name of node with requested axes.
|
||||
/// \param[in] axes The requested vector of axes.
|
||||
/// \param[in] tensor_rank The corresponding tensor rank.
|
||||
///
|
||||
/// \return If any negative axis in vector, it counts from the last to the first
|
||||
/// axis, by adding tensor_rank to axis.
|
||||
///
|
||||
NGRAPH_API
|
||||
std::vector<size_t> normalize_axes(const std::string& node_description,
|
||||
const std::vector<int64_t>& axes,
|
||||
const Rank& tensor_rank);
|
||||
|
||||
/// \brief Handle out of range axis.
|
||||
///
|
||||
/// \param[in] node_description The node with requested axis.
|
||||
/// \param[in] axis The requested axis value.
|
||||
/// \param[in] tensor_rank The corresponding tensor rank.
|
||||
///
|
||||
/// \return Checking if axis is in range [-tensor_rank, tensor_rank-1], otherwise
|
||||
/// returns error. If negative axis, it counts from the last to the first axis,
|
||||
/// by adding tensor_rank to axis.
|
||||
NGRAPH_API
|
||||
int64_t normalize_axis(const std::string& node_description, std::int64_t axis, const Rank& tensor_rank);
|
||||
|
||||
/// \brief Handle out of range axis.
|
||||
///
|
||||
/// \param[in] node The node with requested axis.
|
||||
/// \param[in] axis The requested axis value.
|
||||
/// \param[in] tensor_rank The corresponding tensor rank.
|
||||
/// \param[in] axis_range_min The min value of accepted range for axis.
|
||||
/// \param[in] axis_range_max The max value of accepted range for axis.
|
||||
///
|
||||
/// \return Checking if axis is in range [axis_range_min, axis_range_max], otherwise
|
||||
/// returns error. If negative axis, it counts from the last to the first axis,
|
||||
/// by adding tensor_rank to axis.
|
||||
NGRAPH_API
|
||||
int64_t normalize_axis(const Node* node,
|
||||
std::int64_t axis,
|
||||
std::uint64_t tensor_rank,
|
||||
std::int64_t axis_range_min,
|
||||
std::int64_t axis_range_max);
|
||||
|
||||
/// \brief Handle out of range axis.
|
||||
///
|
||||
/// \param[in] node_description The name of node with requested axis.
|
||||
/// \param[in] axis The requested axis value.
|
||||
/// \param[in] tensor_rank The corresponding tensor rank.
|
||||
/// \param[in] axis_range_min The min value of accepted range for axis.
|
||||
/// \param[in] axis_range_max The max value of accepted range for axis.
|
||||
///
|
||||
/// \return Checking if axis is in range [axis_range_min, axis_range_max], otherwise
|
||||
/// returns error. If negative axis, it counts from the last to the first axis,
|
||||
/// by adding tensor_rank to axis.
|
||||
NGRAPH_API
|
||||
int64_t normalize_axis(const std::string& node_description,
|
||||
std::int64_t axis,
|
||||
std::uint64_t tensor_rank,
|
||||
std::int64_t axis_range_min,
|
||||
std::int64_t axis_range_max);
|
||||
|
||||
/// \brief Try to compute the maximum value of value
|
||||
/// \return (true, max_value) if can be determined, or (false, numeric_limits<uint64_t>::max())
|
||||
/// if not.
|
||||
@@ -263,13 +178,6 @@ NGRAPH_API HostTensorPtr evaluate_upper_bound(const Output<Node>& output);
|
||||
/// could be HostTensorPtr to estimated value if particular bound can be determined, or nullptr.
|
||||
NGRAPH_API std::pair<HostTensorPtr, HostTensorPtr> evaluate_both_bounds(const Output<Node>& output);
|
||||
|
||||
/// \brief Evaluates lower and upper value estimations for the output tensor. Estimation would
|
||||
/// be represented as partial shape object using Dimension(min, max) for each element.
|
||||
/// \param output Node output pointing to the tensor for estimation.
|
||||
/// \param pshape Resulting estimation would be stored in this PartialShape.
|
||||
/// \return boolean status if value evaluation was successful.
|
||||
NGRAPH_API bool evaluate_as_partial_shape(const Output<Node>& output, PartialShape& pshape);
|
||||
|
||||
/// \brief Estimates upper bound for node output tensors using only upper bounds of the nodes
|
||||
/// inputs.
|
||||
/// \param node Operation to be performed
|
||||
@@ -302,10 +210,6 @@ NGRAPH_API bool host_tensor_is_positive(const HostTensorPtr& bound);
|
||||
/// only on pointers comparison.
|
||||
NGRAPH_API bool has_and_set_equal_bounds(const Output<Node>& source);
|
||||
|
||||
/// \brief Runs an estimation of source tensor. If it succeeded to calculate both bounds and
|
||||
/// they are the same returns Constant operation from the resulting bound, otherwise nullptr.
|
||||
NGRAPH_API std::shared_ptr<op::Constant> get_constant_from_source(const Output<Node>& source);
|
||||
|
||||
/// \brief Returns a Constant storing scalar value equal to std::numeric_limits<t>::max()
|
||||
NGRAPH_API std::shared_ptr<op::Constant> get_constant_max_of_type(element::Type_t t);
|
||||
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright (C) 2018-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "openvino/core/core_visibility.hpp"
|
||||
#include "openvino/core/function.hpp"
|
||||
#include "openvino/core/node.hpp"
|
||||
#include "openvino/op/parameter.hpp"
|
||||
|
||||
namespace ov {
|
||||
|
||||
OPENVINO_API
|
||||
void traverse_nodes(const std::shared_ptr<const Function>& p,
|
||||
const std::function<void(const std::shared_ptr<Node>&)>& f);
|
||||
|
||||
OPENVINO_API
|
||||
void traverse_nodes(const Function* p, const std::function<void(const std::shared_ptr<Node>&)>& f);
|
||||
|
||||
/// \brief Visit each node in a sub-graph of the entire graph
|
||||
/// \param subgraph_results The output nodes of the sub-graph
|
||||
/// \param f Function to execute at each node in the traversal
|
||||
/// \param subgraph_params Input nodes of the sub-graph (optional)
|
||||
///
|
||||
/// Traverses a sub-graph starting from subgraph_results moving up
|
||||
/// towards parameter nodes. Traversal stops if it hits a node in
|
||||
/// subgraph_params.
|
||||
///
|
||||
/// Most useful for finding parameters of a graph directly from the
|
||||
/// result nodes and not from function parameters or extracting a
|
||||
/// subgraph relevant to the computation of certain outputs
|
||||
OPENVINO_API
|
||||
void traverse_nodes(const NodeVector& subgraph_results,
|
||||
const std::function<void(const std::shared_ptr<Node>&)>& f,
|
||||
const NodeVector& subgraph_params = {});
|
||||
|
||||
/// \brief Replace the node `target` with the node `replacement`, i.e.,
|
||||
/// redirect all users and control dependencies of `target` to
|
||||
/// `replacement`.
|
||||
///
|
||||
/// \param target Node to be replaced.
|
||||
/// \param replacement Node to replace `target` with.
|
||||
/// \param output_order Vector determines order of replacement node's outputs.
|
||||
///
|
||||
/// This is primarily used in graph-rewriting passes. For example, we
|
||||
/// might "fuse" two Concat operations as follows:
|
||||
///
|
||||
/// (Step 0: Original graph)
|
||||
///
|
||||
/// A B
|
||||
/// | |
|
||||
/// v v
|
||||
/// N0[Concat, concatenation_axis=3] C
|
||||
/// | |
|
||||
/// v v
|
||||
/// N1[Concat, concatenation_axis=3]
|
||||
/// | |
|
||||
/// v v
|
||||
/// some_user another_user
|
||||
///
|
||||
/// (Step 1: Construct replacement)
|
||||
///
|
||||
/// shared_ptr<Node> new_N1 = make_shared<op::Concat>({A,B,C},3);
|
||||
///
|
||||
/// A----------------------------------------.
|
||||
/// | |
|
||||
/// | B----------------)--.
|
||||
/// | | | |
|
||||
/// v v | |
|
||||
/// N0[Concat, concatenation_axis=3] C-----)--)--.
|
||||
/// | | | | |
|
||||
/// v v v v v
|
||||
/// N1[Concat, concatenation_axis=3] new_N1[Concat, concatenation_axis=3]
|
||||
/// | |
|
||||
/// v v
|
||||
/// some_user another_user
|
||||
///
|
||||
/// (Step 2: Replace N1 with new_N1)
|
||||
///
|
||||
/// replace_node(N1, new_N1);
|
||||
///
|
||||
/// A----------------------------------------.
|
||||
/// | |
|
||||
/// | B----------------)--.
|
||||
/// | | | |
|
||||
/// v v | |
|
||||
/// N0[Concat, concatenation_axis=3] C-----)--)--.
|
||||
/// | | | | |
|
||||
/// v v v v v
|
||||
/// N1[Concat, concatenation_axis=3] new_N1[Concat, concatenation_axis=3]
|
||||
/// | |
|
||||
/// v v
|
||||
/// some_user another_user
|
||||
///
|
||||
/// (Step 3: N0 and N1 are now dead, nodes will be freed)
|
||||
///
|
||||
/// [happens automatically, once all shared_ptrs to N1 are released]
|
||||
///
|
||||
/// A----------------------------------------.
|
||||
/// |
|
||||
/// B----------------)--.
|
||||
/// | |
|
||||
/// | |
|
||||
/// C-----)--)--.
|
||||
/// | | |
|
||||
/// v v v
|
||||
/// new_N1[Concat, concatenation_axis=3]
|
||||
/// | |
|
||||
/// v v
|
||||
/// some_user another_user
|
||||
///
|
||||
/// NOTE 1: replace_node is not type-safe (the graph is not revalidated).
|
||||
/// For example, the following is allowed, even if node `some_user`
|
||||
/// requires an input of shape 2x2:
|
||||
///
|
||||
/// (Before)
|
||||
/// A(shape=2x2) B(shape=3x3)
|
||||
/// |
|
||||
/// v
|
||||
/// some_user(requires 2x2 input)
|
||||
///
|
||||
/// (After -- graph is now invalid)
|
||||
///
|
||||
/// replace_node(A, B);
|
||||
///
|
||||
/// A(shape=2x2) B(shape=3x3)
|
||||
/// |
|
||||
/// v
|
||||
/// some_user(requires 2x2 input)
|
||||
///
|
||||
/// NOTE 2: it is possible to insert a cycle into the graph with
|
||||
/// replace_node, resulting in an invalid graph. Care must be taken to
|
||||
/// avoid this. One common example is when you are attempting to insert a
|
||||
/// new node `M` "after"` a node `N`. For example, you might expect this
|
||||
/// to work:
|
||||
///
|
||||
/// shared_ptr<Node> M = make_shared<SomeUnaryOp>(N);
|
||||
/// replace_node(M, N);
|
||||
///
|
||||
/// The problem is that at replacement time, N itself is a user of M. So
|
||||
/// we end up introducing a cycle as follows:
|
||||
///
|
||||
/// N
|
||||
/// |
|
||||
/// v
|
||||
/// other users...
|
||||
///
|
||||
/// |||
|
||||
/// vvv
|
||||
///
|
||||
/// N------------>M
|
||||
/// |
|
||||
/// v
|
||||
/// other users...
|
||||
///
|
||||
/// |||
|
||||
/// vvv
|
||||
///
|
||||
/// .----.
|
||||
/// | |
|
||||
/// | |
|
||||
/// N `----->M
|
||||
/// |
|
||||
/// v
|
||||
/// other users...
|
||||
///
|
||||
/// To avoid the cycle, a valid way to perform the above desired insertion would be,
|
||||
///
|
||||
/// auto new_N = N->clone_with_new_inputs(N->input_values());
|
||||
/// shared_ptr<Node> M = make_shared<SomeUnaryOp>(new_N);
|
||||
/// replace_node(N, M);
|
||||
OPENVINO_API
|
||||
void replace_node(const std::shared_ptr<Node>& target,
|
||||
const std::shared_ptr<Node>& replacement,
|
||||
const std::vector<int64_t>& output_order);
|
||||
|
||||
/// Replace target.outputs[i] with replacement_values[i] and transfer control dependents and
|
||||
/// provenance from target to the node(s) in replacement_values.
|
||||
OPENVINO_API
|
||||
void replace_node(const std::shared_ptr<Node>& target, const OutputVector& replacement_values);
|
||||
|
||||
OPENVINO_API
|
||||
void replace_node(const std::shared_ptr<Node>& target, const std::shared_ptr<Node>& replacement);
|
||||
|
||||
/// \brief Replace multiple nodes in a function.
|
||||
/// \param f Function where replacement is taking place.
|
||||
/// \param parameter_replacement_map A mapping from parameter shared pointers to parameter
|
||||
/// shared pointers. For each pair (k,v) in the map, parameter
|
||||
/// k is replaced by parameter v, except if k==v or k is not a
|
||||
/// parameter bound by f, in which case the pair (k,v) is
|
||||
/// ignored.
|
||||
/// \param body_replacement_map A mapping from node shared pointers to node shared pointers.
|
||||
/// For each pair (k,v) in the map, node k is replaced by node v,
|
||||
/// except if k==v, the pair (k,v) is ignored.
|
||||
/// Note that if k is a parameter, its users will be redirected to
|
||||
/// v, but k will _not_ be replaced in the function's parameter
|
||||
/// list.
|
||||
///
|
||||
/// Limitations:
|
||||
///
|
||||
/// - No check is made that the replaced nodes in `parameter_replacement_map` are actually
|
||||
/// among the bound parameters of `f`. (If a parameter appears in the map that is not
|
||||
/// bound by `f`, it will be silently ignored.)
|
||||
/// - If a parameter node appears as a key in both `parameter_replacement_map` _and_ in
|
||||
/// `body_replacement_map`, behavior is unspecified.
|
||||
OPENVINO_API
|
||||
void replace_nodes(const std::shared_ptr<Function>& f,
|
||||
const std::unordered_map<std::shared_ptr<op::v0::Parameter>, std::shared_ptr<op::v0::Parameter>>&
|
||||
parameter_replacement_map,
|
||||
const std::unordered_map<std::shared_ptr<Node>, std::shared_ptr<Node>>& body_replacement_map);
|
||||
|
||||
/// Topological sort of nodes needed to compute root_nodes
|
||||
template <typename T>
|
||||
std::vector<std::shared_ptr<Node>> topological_sort(T root_nodes) {
|
||||
std::stack<Node*, std::vector<Node*>> nodes_to_do;
|
||||
std::unordered_set<Node*> nodes_done;
|
||||
std::vector<std::shared_ptr<Node>> result;
|
||||
|
||||
for (auto& node : root_nodes) {
|
||||
nodes_to_do.push(node.get());
|
||||
}
|
||||
while (nodes_to_do.size() > 0) {
|
||||
Node* node = nodes_to_do.top();
|
||||
if (nodes_done.count(node) == 0) {
|
||||
bool can_add = true;
|
||||
size_t arg_count = node->get_input_size();
|
||||
for (size_t i = 0; i < arg_count; ++i) {
|
||||
Node* dep = node->get_input_node_ptr(arg_count - i - 1);
|
||||
if (nodes_done.count(dep) == 0) {
|
||||
can_add = false;
|
||||
nodes_to_do.push(dep);
|
||||
}
|
||||
}
|
||||
for (auto& depptr : node->get_control_dependencies()) {
|
||||
Node* dep = depptr.get();
|
||||
if (nodes_done.count(dep) == 0) {
|
||||
can_add = false;
|
||||
nodes_to_do.push(dep);
|
||||
}
|
||||
}
|
||||
if (can_add) {
|
||||
result.push_back(node->shared_from_this());
|
||||
nodes_to_do.pop();
|
||||
nodes_done.insert(node);
|
||||
}
|
||||
} else {
|
||||
nodes_to_do.pop();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// input function is cloned and returned
|
||||
// NodeMap input may contain default node mapping i.e. pre-cloned nodes
|
||||
// NodeMap output (by reference) fully maps input and cloned function ops
|
||||
OPENVINO_API
|
||||
std::shared_ptr<ov::Function> clone_function(const ov::Function& func, NodeMap& node_map);
|
||||
|
||||
// input function is cloned and returned
|
||||
OPENVINO_API
|
||||
std::shared_ptr<ov::Function> clone_function(const ov::Function& func);
|
||||
|
||||
OPENVINO_API
|
||||
bool compare_constants(const std::shared_ptr<Node>& n1, const std::shared_ptr<Node>& n2);
|
||||
|
||||
OPENVINO_API
|
||||
bool replace_output_update_name(Output<Node> node, const Output<Node>& node_input);
|
||||
|
||||
OPENVINO_API
|
||||
bool replace_node_update_name(const std::shared_ptr<Node>& target, const std::shared_ptr<Node>& replacement);
|
||||
} // namespace ov
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (C) 2018-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "openvino/core/coordinate_diff.hpp"
|
||||
#include "openvino/core/node.hpp"
|
||||
#include "openvino/op/constant.hpp"
|
||||
#include "openvino/op/util/attr_types.hpp"
|
||||
|
||||
namespace ov {
|
||||
|
||||
OPENVINO_API
|
||||
PartialShape infer_convolution_forward(const Node* node,
|
||||
const PartialShape& data_batch_shape,
|
||||
const Strides& data_dilation,
|
||||
const CoordinateDiff& data_padding_below,
|
||||
const CoordinateDiff& data_padding_above,
|
||||
const PartialShape& filters_shape,
|
||||
const Strides& filter_strides,
|
||||
const Strides& filter_dilation);
|
||||
|
||||
OPENVINO_API
|
||||
void infer_auto_padding(const Shape& image_shape,
|
||||
const Shape& filter_shape,
|
||||
const Strides& filter_strides,
|
||||
const Strides& filter_dilations,
|
||||
const op::PadType pad_type,
|
||||
CoordinateDiff& padding_above,
|
||||
CoordinateDiff& padding_below);
|
||||
|
||||
/// \brief Handle out of range axis.
|
||||
///
|
||||
/// \param[in] node The node with requested axis.
|
||||
/// \param[in] axis The requested axis value.
|
||||
/// \param[in] tensor_rank The corresponding tensor rank.
|
||||
///
|
||||
/// \return Checking if axis is in range [-tensor_rank, tensor_rank-1], otherwise
|
||||
/// returns error. If negative axis, it counts from the last to the first axis,
|
||||
/// by adding tensor_rank to axis.
|
||||
OPENVINO_API
|
||||
int64_t normalize_axis(const Node* node, std::int64_t axis, const Rank& tensor_rank);
|
||||
|
||||
/// \brief Handle out of range axes in vector.
|
||||
///
|
||||
/// \param[in] node_description The name of node with requested axes.
|
||||
/// \param[in] axes The requested vector of axes.
|
||||
/// \param[in] tensor_rank The corresponding tensor rank.
|
||||
///
|
||||
/// \return If any negative axis in vector, it counts from the last to the first
|
||||
/// axis, by adding tensor_rank to axis.
|
||||
///
|
||||
OPENVINO_API
|
||||
std::vector<size_t> normalize_axes(const std::string& node_description,
|
||||
const std::vector<int64_t>& axes,
|
||||
const Rank& tensor_rank);
|
||||
|
||||
/// \brief Handle out of range axis.
|
||||
///
|
||||
/// \param[in] node_description The node with requested axis.
|
||||
/// \param[in] axis The requested axis value.
|
||||
/// \param[in] tensor_rank The corresponding tensor rank.
|
||||
///
|
||||
/// \return Checking if axis is in range [-tensor_rank, tensor_rank-1], otherwise
|
||||
/// returns error. If negative axis, it counts from the last to the first axis,
|
||||
/// by adding tensor_rank to axis.
|
||||
OPENVINO_API
|
||||
int64_t normalize_axis(const std::string& node_description, std::int64_t axis, const Rank& tensor_rank);
|
||||
|
||||
/// \brief Handle out of range axis.
|
||||
///
|
||||
/// \param[in] node The node with requested axis.
|
||||
/// \param[in] axis The requested axis value.
|
||||
/// \param[in] tensor_rank The corresponding tensor rank.
|
||||
/// \param[in] axis_range_min The min value of accepted range for axis.
|
||||
/// \param[in] axis_range_max The max value of accepted range for axis.
|
||||
///
|
||||
/// \return Checking if axis is in range [axis_range_min, axis_range_max], otherwise
|
||||
/// returns error. If negative axis, it counts from the last to the first axis,
|
||||
/// by adding tensor_rank to axis.
|
||||
OPENVINO_API
|
||||
int64_t normalize_axis(const Node* node,
|
||||
std::int64_t axis,
|
||||
std::uint64_t tensor_rank,
|
||||
std::int64_t axis_range_min,
|
||||
std::int64_t axis_range_max);
|
||||
|
||||
/// \brief Handle out of range axis.
|
||||
///
|
||||
/// \param[in] node_description The name of node with requested axis.
|
||||
/// \param[in] axis The requested axis value.
|
||||
/// \param[in] tensor_rank The corresponding tensor rank.
|
||||
/// \param[in] axis_range_min The min value of accepted range for axis.
|
||||
/// \param[in] axis_range_max The max value of accepted range for axis.
|
||||
///
|
||||
/// \return Checking if axis is in range [axis_range_min, axis_range_max], otherwise
|
||||
/// returns error. If negative axis, it counts from the last to the first axis,
|
||||
/// by adding tensor_rank to axis.
|
||||
OPENVINO_API
|
||||
int64_t normalize_axis(const std::string& node_description,
|
||||
std::int64_t axis,
|
||||
std::uint64_t tensor_rank,
|
||||
std::int64_t axis_range_min,
|
||||
std::int64_t axis_range_max);
|
||||
|
||||
/// \brief Evaluates lower and upper value estimations for the output tensor. Estimation would
|
||||
/// be represented as partial shape object using Dimension(min, max) for each element.
|
||||
/// \param output Node output pointing to the tensor for estimation.
|
||||
/// \param pshape Resulting estimation would be stored in this PartialShape.
|
||||
/// \return boolean status if value evaluation was successful.
|
||||
OPENVINO_API bool evaluate_as_partial_shape(const Output<Node>& output, PartialShape& pshape);
|
||||
|
||||
/// \brief Runs an estimation of source tensor. If it succeeded to calculate both bounds and
|
||||
/// they are the same returns Constant operation from the resulting bound, otherwise nullptr.
|
||||
OPENVINO_API std::shared_ptr<op::v0::Constant> get_constant_from_source(const Output<Node>& source);
|
||||
} // namespace ov
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "ngraph/descriptor/input.hpp"
|
||||
@@ -33,30 +34,31 @@ using namespace ngraph;
|
||||
|
||||
NGRAPH_SUPPRESS_DEPRECATED_START
|
||||
|
||||
void ngraph::traverse_nodes(const std::shared_ptr<const Function> p, std::function<void(std::shared_ptr<Node>)> f) {
|
||||
void ov::traverse_nodes(const std::shared_ptr<const Function>& p,
|
||||
const std::function<void(const std::shared_ptr<Node>&)>& f) {
|
||||
traverse_nodes(p.get(), f);
|
||||
}
|
||||
|
||||
void ngraph::traverse_nodes(const Function* p, std::function<void(std::shared_ptr<Node>)> f) {
|
||||
void ov::traverse_nodes(const Function* p, const std::function<void(const std::shared_ptr<Node>&)>& f) {
|
||||
NodeVector nodes;
|
||||
|
||||
for (auto r : p->get_results()) {
|
||||
for (const auto& r : p->get_results()) {
|
||||
nodes.push_back(r);
|
||||
}
|
||||
for (auto s : p->get_sinks()) {
|
||||
nodes.emplace_back(s);
|
||||
}
|
||||
|
||||
for (auto param : p->get_parameters()) {
|
||||
for (const auto& param : p->get_parameters()) {
|
||||
nodes.push_back(param);
|
||||
}
|
||||
|
||||
traverse_nodes(nodes, f);
|
||||
}
|
||||
|
||||
void ngraph::traverse_nodes(const NodeVector& subgraph_results,
|
||||
std::function<void(std::shared_ptr<Node>)> f,
|
||||
const NodeVector& subgraph_params) {
|
||||
void ov::traverse_nodes(const NodeVector& subgraph_results,
|
||||
const std::function<void(const std::shared_ptr<Node>&)>& f,
|
||||
const NodeVector& subgraph_params) {
|
||||
std::unordered_set<Node*> instances_seen;
|
||||
std::stack<Node*, std::vector<Node*>> stack;
|
||||
for (auto& node_ptr : subgraph_params) {
|
||||
@@ -85,19 +87,19 @@ void ngraph::traverse_nodes(const NodeVector& subgraph_results,
|
||||
NodeVector ngraph::find_common_args(std::shared_ptr<Node> node1, std::shared_ptr<Node> node2) {
|
||||
std::unordered_set<std::shared_ptr<Node>> node1_args;
|
||||
|
||||
auto compute_node1_args = [&node1_args](const std::shared_ptr<Node> node) {
|
||||
auto compute_node1_args = [&node1_args](const std::shared_ptr<Node>& node) {
|
||||
node1_args.insert(node);
|
||||
};
|
||||
|
||||
traverse_nodes({node1}, compute_node1_args, NodeVector{});
|
||||
traverse_nodes({std::move(node1)}, compute_node1_args, NodeVector{});
|
||||
|
||||
std::unordered_set<std::shared_ptr<Node>> node2_args;
|
||||
|
||||
auto compute_node2_args = [&node2_args](const std::shared_ptr<Node> node) {
|
||||
auto compute_node2_args = [&node2_args](const std::shared_ptr<Node>& node) {
|
||||
node2_args.insert(node);
|
||||
};
|
||||
|
||||
traverse_nodes({node2}, compute_node2_args, NodeVector{});
|
||||
traverse_nodes({std::move(node2)}, compute_node2_args, NodeVector{});
|
||||
|
||||
NodeVector common_args;
|
||||
for (const auto& e : node1_args) {
|
||||
@@ -109,9 +111,9 @@ NodeVector ngraph::find_common_args(std::shared_ptr<Node> node1, std::shared_ptr
|
||||
return common_args;
|
||||
}
|
||||
|
||||
void ngraph::replace_node(std::shared_ptr<Node> target,
|
||||
std::shared_ptr<Node> replacement,
|
||||
const std::vector<int64_t>& output_order) {
|
||||
void ov::replace_node(const std::shared_ptr<Node>& target,
|
||||
const std::shared_ptr<Node>& replacement,
|
||||
const std::vector<int64_t>& output_order) {
|
||||
if (ngraph::op::is_output(target)) {
|
||||
throw ngraph_error("Result nodes cannot be replaced.");
|
||||
}
|
||||
@@ -130,8 +132,8 @@ void ngraph::replace_node(std::shared_ptr<Node> target,
|
||||
|
||||
std::set<string> removed_subgraph_tags;
|
||||
|
||||
auto set_replacement_prov = [&removed_subgraph_tags](std::shared_ptr<Node> node) {
|
||||
for (auto tag : node->get_provenance_tags()) {
|
||||
auto set_replacement_prov = [&removed_subgraph_tags](const std::shared_ptr<Node>& node) {
|
||||
for (const auto& tag : node->get_provenance_tags()) {
|
||||
removed_subgraph_tags.insert(tag);
|
||||
}
|
||||
};
|
||||
@@ -139,7 +141,7 @@ void ngraph::replace_node(std::shared_ptr<Node> target,
|
||||
traverse_nodes({target}, set_replacement_prov, common_args);
|
||||
replacement->add_provenance_tags(removed_subgraph_tags);
|
||||
|
||||
auto set_prov_new_nodes = [&removed_subgraph_tags](std::shared_ptr<Node> node) {
|
||||
auto set_prov_new_nodes = [&removed_subgraph_tags](const std::shared_ptr<Node>& node) {
|
||||
node->add_provenance_tags(removed_subgraph_tags);
|
||||
};
|
||||
|
||||
@@ -158,7 +160,7 @@ void ngraph::replace_node(std::shared_ptr<Node> target,
|
||||
target->clear_control_dependents();
|
||||
}
|
||||
|
||||
void ngraph::replace_node(const std::shared_ptr<Node>& target, const OutputVector& replacement_values) {
|
||||
void ov::replace_node(const std::shared_ptr<Node>& target, const OutputVector& replacement_values) {
|
||||
if (ngraph::op::is_output(target)) {
|
||||
throw ngraph_error("Result nodes cannot be replaced.");
|
||||
}
|
||||
@@ -183,16 +185,16 @@ void ngraph::replace_node(const std::shared_ptr<Node>& target, const OutputVecto
|
||||
target->clear_control_dependents();
|
||||
}
|
||||
|
||||
void ngraph::replace_node(std::shared_ptr<Node> target, std::shared_ptr<Node> replacement) {
|
||||
void ov::replace_node(const std::shared_ptr<Node>& target, const std::shared_ptr<Node>& replacement) {
|
||||
auto default_output_order = vector<int64_t>(target->get_output_size());
|
||||
std::iota(default_output_order.begin(), default_output_order.end(), 0);
|
||||
replace_node(target, replacement, default_output_order);
|
||||
}
|
||||
|
||||
void ngraph::replace_nodes(
|
||||
const std::shared_ptr<Function>& f,
|
||||
const unordered_map<shared_ptr<op::Parameter>, shared_ptr<op::Parameter>>& parameter_replacement_map,
|
||||
const unordered_map<shared_ptr<Node>, shared_ptr<Node>>& body_replacement_map) {
|
||||
void ov::replace_nodes(const std::shared_ptr<Function>& f,
|
||||
const unordered_map<shared_ptr<ov::op::v0::Parameter>, shared_ptr<ov::op::v0::Parameter>>&
|
||||
parameter_replacement_map,
|
||||
const unordered_map<shared_ptr<Node>, shared_ptr<Node>>& body_replacement_map) {
|
||||
auto& params = f->get_parameters();
|
||||
|
||||
for (size_t i = 0; i < params.size(); i++) {
|
||||
@@ -239,7 +241,7 @@ std::vector<std::shared_ptr<ngraph::Node>> ngraph::clone_nodes(const std::vector
|
||||
NodeMap& node_map) {
|
||||
// for each node in topological order
|
||||
auto sorted_nodes = topological_sort(nodes);
|
||||
for (auto node : sorted_nodes) {
|
||||
for (const auto& node : sorted_nodes) {
|
||||
if (node_map.count(node.get()) == 0) {
|
||||
// get (already) cloned arguments and clone the node
|
||||
OutputVector cloned_args;
|
||||
@@ -267,7 +269,7 @@ std::vector<std::shared_ptr<ngraph::Node>> ngraph::clone_nodes(const std::vector
|
||||
new_output.get_rt_info() = output_rt_info;
|
||||
}
|
||||
|
||||
for (auto tag : node->get_provenance_tags()) {
|
||||
for (const auto& tag : node->get_provenance_tags()) {
|
||||
cloned_node->add_provenance_tag(tag);
|
||||
}
|
||||
cloned_node->set_op_annotations(node->get_op_annotations());
|
||||
@@ -279,7 +281,7 @@ std::vector<std::shared_ptr<ngraph::Node>> ngraph::clone_nodes(const std::vector
|
||||
// create and return vector of cloned nodes
|
||||
// order matches input vector (not necessarily topological)
|
||||
std::vector<std::shared_ptr<ngraph::Node>> cloned_nodes;
|
||||
for (auto node : nodes) {
|
||||
for (const auto& node : nodes) {
|
||||
cloned_nodes.push_back(node_map.at(node.get()));
|
||||
}
|
||||
return cloned_nodes;
|
||||
@@ -290,19 +292,19 @@ std::list<std::shared_ptr<ngraph::Node>> ngraph::clone_nodes(const std::vector<s
|
||||
// for each node in topological order
|
||||
auto sorted_nodes = topological_sort(nodes);
|
||||
std::list<shared_ptr<Node>> cloned_nodes;
|
||||
for (auto node : sorted_nodes) {
|
||||
for (const auto& node : sorted_nodes) {
|
||||
auto node_outputs = node->outputs();
|
||||
for (auto value : node_outputs) {
|
||||
for (const auto& value : node_outputs) {
|
||||
if (output_map.count(value) == 0) {
|
||||
// We need this node cloned
|
||||
// get (already) cloned arguments and clone the node
|
||||
OutputVector cloned_args;
|
||||
for (auto value : node->input_values()) {
|
||||
for (const auto& value : node->input_values()) {
|
||||
cloned_args.push_back(output_map.at(value));
|
||||
}
|
||||
NodeVector cloned_dependencies;
|
||||
for (auto& dependency : node->get_control_dependencies()) {
|
||||
for (auto dependency_value : dependency->outputs()) {
|
||||
for (const auto& dependency_value : dependency->outputs()) {
|
||||
shared_ptr<Node> dependent = output_map.at(dependency_value).get_node_shared_ptr();
|
||||
if (find(cloned_dependencies.begin(), cloned_dependencies.end(), dependent) ==
|
||||
cloned_dependencies.end()) {
|
||||
@@ -317,11 +319,11 @@ std::list<std::shared_ptr<ngraph::Node>> ngraph::clone_nodes(const std::vector<s
|
||||
auto rt_info = node->get_rt_info();
|
||||
cloned_node->get_rt_info() = rt_info;
|
||||
|
||||
for (auto tag : node->get_provenance_tags()) {
|
||||
for (const auto& tag : node->get_provenance_tags()) {
|
||||
cloned_node->add_provenance_tag(tag);
|
||||
}
|
||||
cloned_node->set_op_annotations(node->get_op_annotations());
|
||||
for (auto cloned_value : cloned_node->outputs()) {
|
||||
for (const auto& cloned_value : cloned_node->outputs()) {
|
||||
auto original_value = node_outputs.at(cloned_value.get_index());
|
||||
if (output_map.count(original_value) == 0) {
|
||||
output_map[original_value] = cloned_value;
|
||||
@@ -334,12 +336,12 @@ std::list<std::shared_ptr<ngraph::Node>> ngraph::clone_nodes(const std::vector<s
|
||||
return cloned_nodes;
|
||||
}
|
||||
|
||||
std::shared_ptr<ngraph::Function> ngraph::clone_function(const ngraph::Function& func) {
|
||||
std::shared_ptr<ngraph::Function> ov::clone_function(const ngraph::Function& func) {
|
||||
NodeMap nm;
|
||||
return clone_function(func, nm);
|
||||
}
|
||||
|
||||
std::shared_ptr<ngraph::Function> ngraph::clone_function(const ngraph::Function& func, NodeMap& node_map) {
|
||||
std::shared_ptr<ngraph::Function> ov::clone_function(const ngraph::Function& func, NodeMap& node_map) {
|
||||
// clone function operations
|
||||
clone_nodes(func.get_ops(), node_map);
|
||||
|
||||
@@ -365,7 +367,7 @@ std::shared_ptr<ngraph::Function> ngraph::clone_function(const ngraph::Function&
|
||||
// get cloned function results and sinks and parameters
|
||||
ResultVector cloned_results;
|
||||
for (shared_ptr<Node> node : func.get_results()) {
|
||||
auto result = ov::as_type_ptr<op::Result>(node_map.at(node.get()));
|
||||
auto result = ov::as_type_ptr<op::v0::Result>(node_map.at(node.get()));
|
||||
if (!result) {
|
||||
throw ngraph_error("Results should be of type op::Result");
|
||||
}
|
||||
@@ -376,9 +378,9 @@ std::shared_ptr<ngraph::Function> ngraph::clone_function(const ngraph::Function&
|
||||
cloned_sinks.push_back(static_pointer_cast<op::Sink>(node_map.at(node.get())));
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<op::Parameter>> cloned_params;
|
||||
std::vector<std::shared_ptr<op::v0::Parameter>> cloned_params;
|
||||
for (const auto& param : func.get_parameters()) {
|
||||
cloned_params.push_back(ov::as_type_ptr<op::Parameter>(node_map.at(param.get())));
|
||||
cloned_params.push_back(ov::as_type_ptr<op::v0::Parameter>(node_map.at(param.get())));
|
||||
}
|
||||
|
||||
// create and return cloned function
|
||||
@@ -543,7 +545,7 @@ NodeVector ngraph::get_subgraph_outputs(const NodeVector& nodes,
|
||||
|
||||
NodeVector outputs;
|
||||
|
||||
for (auto n : nodes) {
|
||||
for (const auto& n : nodes) {
|
||||
if (exclusions_set.count(n) != 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -564,7 +566,7 @@ NodeVector ngraph::extract_subgraph(const NodeVector& results, const NodeVector&
|
||||
NodeVector subgraph;
|
||||
traverse_nodes(
|
||||
results,
|
||||
[&](std::shared_ptr<Node> n) {
|
||||
[&](const std::shared_ptr<Node>& n) {
|
||||
subgraph.push_back(n);
|
||||
},
|
||||
args);
|
||||
@@ -636,13 +638,13 @@ bool ngraph::is_valid_rank(const std::shared_ptr<Node>& node, std::vector<size_t
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ngraph::compare_constants(const std::shared_ptr<Node>& n1, const std::shared_ptr<Node>& n2) {
|
||||
if (!(op::is_constant(n1) && op::is_constant(n2))) {
|
||||
bool ov::compare_constants(const std::shared_ptr<Node>& n1, const std::shared_ptr<Node>& n2) {
|
||||
if (!(op::util::is_constant(n1) && op::util::is_constant(n2))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static_pointer_cast<op::Constant>(n1)->get_value_strings() !=
|
||||
static_pointer_cast<op::Constant>(n2)->get_value_strings()) {
|
||||
if (static_pointer_cast<op::v0::Constant>(n1)->get_value_strings() !=
|
||||
static_pointer_cast<op::v0::Constant>(n2)->get_value_strings()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -654,7 +656,7 @@ void ngraph::plot_graph(std::shared_ptr<Function> f,
|
||||
std::function<void(const Node& node, std::vector<std::string>& attributes)> attributes) {
|
||||
ngraph::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<ngraph::pass::VisualizeTree>(filename, attributes);
|
||||
pass_manager.run_passes(f);
|
||||
pass_manager.run_passes(std::move(f));
|
||||
}
|
||||
|
||||
std::vector<Input<Node>> ngraph::get_inputs_from(Node& src, Node& dst) {
|
||||
@@ -690,7 +692,7 @@ std::vector<Output<Node>> ngraph::get_outputs_to(Node& src, Node& dst) {
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool check_for_cycles_bkwd(std::shared_ptr<ngraph::Node> node,
|
||||
static bool check_for_cycles_bkwd(const std::shared_ptr<ngraph::Node>& node,
|
||||
std::deque<std::shared_ptr<ngraph::Node>>& path,
|
||||
std::unordered_set<std::shared_ptr<ngraph::Node>>& path_set,
|
||||
ngraph::NodeVector& cycle_nodes) {
|
||||
@@ -699,7 +701,7 @@ static bool check_for_cycles_bkwd(std::shared_ptr<ngraph::Node> node,
|
||||
for (size_t i = 0; i < node->inputs().size(); i++) {
|
||||
auto arg = node->get_input_node_shared_ptr(i);
|
||||
if (path_set.find(arg) != path_set.end()) {
|
||||
for (auto it : path) {
|
||||
for (const auto& it : path) {
|
||||
cycle_nodes.push_back(it);
|
||||
}
|
||||
// last node
|
||||
@@ -715,7 +717,7 @@ static bool check_for_cycles_bkwd(std::shared_ptr<ngraph::Node> node,
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_for_cycles_fwd(std::shared_ptr<ngraph::Node> node,
|
||||
static bool check_for_cycles_fwd(const std::shared_ptr<ngraph::Node>& node,
|
||||
std::deque<std::shared_ptr<ngraph::Node>>& path,
|
||||
std::unordered_set<std::shared_ptr<ngraph::Node>>& path_set,
|
||||
ngraph::NodeVector& cycle_nodes) {
|
||||
@@ -723,7 +725,7 @@ static bool check_for_cycles_fwd(std::shared_ptr<ngraph::Node> node,
|
||||
path_set.insert(node);
|
||||
for (auto& arg : node->get_users()) {
|
||||
if (path_set.find(arg) != path_set.end()) {
|
||||
for (auto it : path) {
|
||||
for (const auto& it : path) {
|
||||
cycle_nodes.push_back(it);
|
||||
}
|
||||
// last node
|
||||
@@ -740,7 +742,7 @@ static bool check_for_cycles_fwd(std::shared_ptr<ngraph::Node> node,
|
||||
}
|
||||
|
||||
bool ngraph::check_for_cycles(const ngraph::Function* func, ngraph::NodeVector& cycle_nodes, bool& is_bkwd_cycle) {
|
||||
for (auto res : func->get_results()) {
|
||||
for (const auto& res : func->get_results()) {
|
||||
std::deque<std::shared_ptr<Node>> path;
|
||||
// mirror of path stack for faster cycle check
|
||||
std::unordered_set<std::shared_ptr<Node>> path_set;
|
||||
@@ -750,7 +752,7 @@ bool ngraph::check_for_cycles(const ngraph::Function* func, ngraph::NodeVector&
|
||||
}
|
||||
}
|
||||
|
||||
for (auto res : func->get_sinks()) {
|
||||
for (const auto& res : func->get_sinks()) {
|
||||
std::deque<std::shared_ptr<Node>> path;
|
||||
// mirror of path stack for faster cycle check
|
||||
std::unordered_set<std::shared_ptr<Node>> path_set;
|
||||
@@ -760,7 +762,7 @@ bool ngraph::check_for_cycles(const ngraph::Function* func, ngraph::NodeVector&
|
||||
}
|
||||
}
|
||||
|
||||
for (auto param : func->get_parameters()) {
|
||||
for (const auto& param : func->get_parameters()) {
|
||||
std::deque<std::shared_ptr<Node>> path;
|
||||
// mirror of path stack for faster cycle check
|
||||
std::unordered_set<std::shared_ptr<Node>> path_set;
|
||||
@@ -773,10 +775,10 @@ bool ngraph::check_for_cycles(const ngraph::Function* func, ngraph::NodeVector&
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ngraph::replace_output_update_name(Output<Node> output, const Output<Node>& replacement) {
|
||||
bool ov::replace_output_update_name(Output<Node> output, const Output<Node>& replacement) {
|
||||
bool has_result_output = false;
|
||||
for (auto& target_input : output.get_target_inputs()) {
|
||||
if (ov::is_type<op::Result>(target_input.get_node())) {
|
||||
if (ov::is_type<op::v0::Result>(target_input.get_node())) {
|
||||
// ignore trivial elimination
|
||||
has_result_output = true;
|
||||
if (ov::is_type<ngraph::op::Parameter>(replacement.get_node())) {
|
||||
@@ -807,10 +809,10 @@ bool ngraph::replace_output_update_name(Output<Node> output, const Output<Node>&
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ngraph::replace_node_update_name(std::shared_ptr<Node> target, std::shared_ptr<Node> replacement) {
|
||||
bool ov::replace_node_update_name(const std::shared_ptr<Node>& target, const std::shared_ptr<Node>& replacement) {
|
||||
for (auto& output : target->output(0).get_target_inputs()) {
|
||||
if (ov::as_type<ngraph::op::Parameter>(replacement->input_value(0).get_node()) &&
|
||||
ov::as_type<op::Result>(output.get_node())) {
|
||||
ov::as_type<op::v0::Result>(output.get_node())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,14 +297,14 @@ PartialShape ngraph::validate_and_infer_convolution_forward_output_shape(const N
|
||||
//
|
||||
// Infers the output batch shape and element type for convolution fprop.
|
||||
//
|
||||
PartialShape ngraph::infer_convolution_forward(const Node* node,
|
||||
const PartialShape& data_batch_shape,
|
||||
const Strides& data_dilation,
|
||||
const CoordinateDiff& data_padding_below,
|
||||
const CoordinateDiff& data_padding_above,
|
||||
const PartialShape& filters_shape,
|
||||
const Strides& filter_strides,
|
||||
const Strides& filter_dilation) {
|
||||
PartialShape ov::infer_convolution_forward(const Node* node,
|
||||
const PartialShape& data_batch_shape,
|
||||
const Strides& data_dilation,
|
||||
const CoordinateDiff& data_padding_below,
|
||||
const CoordinateDiff& data_padding_above,
|
||||
const PartialShape& filters_shape,
|
||||
const Strides& filter_strides,
|
||||
const Strides& filter_dilation) {
|
||||
Rank data_batch_filters_rank{Rank::dynamic()};
|
||||
|
||||
NODE_VALIDATION_CHECK(node,
|
||||
@@ -643,13 +643,13 @@ std::tuple<element::Type, PartialShape, PartialShape> ngraph::infer_batch_norm_f
|
||||
{{gamma_element_type, gamma_shape, "gamma"}, {beta_element_type, beta_shape, "beta"}});
|
||||
}
|
||||
|
||||
void ngraph::infer_auto_padding(const Shape& image_shape,
|
||||
const Shape& filter_shape,
|
||||
const Strides& filter_strides,
|
||||
const Strides& filter_dilations,
|
||||
const op::PadType pad_type,
|
||||
CoordinateDiff& padding_above,
|
||||
CoordinateDiff& padding_below) {
|
||||
void ov::infer_auto_padding(const Shape& image_shape,
|
||||
const Shape& filter_shape,
|
||||
const Strides& filter_strides,
|
||||
const Strides& filter_dilations,
|
||||
const op::PadType pad_type,
|
||||
CoordinateDiff& padding_above,
|
||||
CoordinateDiff& padding_below) {
|
||||
const auto image_dims = std::vector<Dimension>(std::begin(image_shape), std::end(image_shape));
|
||||
// because image_shape is fully known result of try_apply_infer_auto_padding is ignored
|
||||
try_apply_auto_padding(image_dims,
|
||||
@@ -676,9 +676,9 @@ bool ngraph::try_apply_auto_padding(const PartialShape& image_shape,
|
||||
const auto image_dims = static_cast<std::vector<Dimension>>(image_shape);
|
||||
for (size_t i = 0; i < static_cast<size_t>(filter_shape.size()); i++) {
|
||||
if (image_dims[i + 2].is_static()) {
|
||||
int64_t image_size = static_cast<int64_t>(image_dims[i + 2].get_length());
|
||||
auto image_size = static_cast<int64_t>(image_dims[i + 2].get_length());
|
||||
int64_t filter_size = (static_cast<int64_t>(filter_shape[i]) - 1) * filter_dilations[i] + 1;
|
||||
int64_t filter_stride = static_cast<int64_t>(filter_strides[i]);
|
||||
auto filter_stride = static_cast<int64_t>(filter_strides[i]);
|
||||
auto output_size = (image_size + filter_stride - 1) / filter_stride;
|
||||
|
||||
auto padding_needed = std::max(int64_t(0), (output_size - 1) * filter_stride + filter_size - image_size);
|
||||
@@ -847,9 +847,9 @@ PartialShape ngraph::infer_slice_shape(const Node* node,
|
||||
return dim;
|
||||
}
|
||||
|
||||
std::vector<size_t> ngraph::normalize_axes(const std::string& node_description,
|
||||
const std::vector<int64_t>& axes,
|
||||
const Rank& tensor_rank) {
|
||||
std::vector<size_t> ov::normalize_axes(const std::string& node_description,
|
||||
const std::vector<int64_t>& axes,
|
||||
const Rank& tensor_rank) {
|
||||
std::vector<size_t> new_axes;
|
||||
|
||||
for (const auto& axis : axes) {
|
||||
@@ -859,11 +859,11 @@ std::vector<size_t> ngraph::normalize_axes(const std::string& node_description,
|
||||
return new_axes;
|
||||
}
|
||||
|
||||
int64_t ngraph::normalize_axis(const Node* node, std::int64_t axis, const Rank& tensor_rank) {
|
||||
int64_t ov::normalize_axis(const Node* node, std::int64_t axis, const Rank& tensor_rank) {
|
||||
return normalize_axis(node->description(), axis, tensor_rank);
|
||||
}
|
||||
|
||||
int64_t ngraph::normalize_axis(const std::string& node_description, std::int64_t axis, const Rank& tensor_rank) {
|
||||
int64_t ov::normalize_axis(const std::string& node_description, std::int64_t axis, const Rank& tensor_rank) {
|
||||
if (axis < 0) {
|
||||
// Handling negative axis requires static tensor rank
|
||||
NGRAPH_CHECK(tensor_rank.is_static(),
|
||||
@@ -883,19 +883,19 @@ int64_t ngraph::normalize_axis(const std::string& node_description, std::int64_t
|
||||
tensor_rank_value ? (tensor_rank_value - 1) : 0);
|
||||
}
|
||||
|
||||
int64_t ngraph::normalize_axis(const Node* node,
|
||||
std::int64_t axis,
|
||||
std::uint64_t tensor_rank,
|
||||
std::int64_t axis_range_min,
|
||||
std::int64_t axis_range_max) {
|
||||
int64_t ov::normalize_axis(const Node* node,
|
||||
std::int64_t axis,
|
||||
std::uint64_t tensor_rank,
|
||||
std::int64_t axis_range_min,
|
||||
std::int64_t axis_range_max) {
|
||||
return ngraph::normalize_axis(node->description(), axis, tensor_rank, axis_range_min, axis_range_max);
|
||||
}
|
||||
|
||||
int64_t ngraph::normalize_axis(const std::string& node_description,
|
||||
std::int64_t axis,
|
||||
std::uint64_t tensor_rank,
|
||||
std::int64_t axis_range_min,
|
||||
std::int64_t axis_range_max) {
|
||||
int64_t ov::normalize_axis(const std::string& node_description,
|
||||
std::int64_t axis,
|
||||
std::uint64_t tensor_rank,
|
||||
std::int64_t axis_range_min,
|
||||
std::int64_t axis_range_max) {
|
||||
// Accepted range of value for axis is [axis_range_min, axis_range_max].
|
||||
NGRAPH_CHECK(((axis >= axis_range_min) && (axis <= axis_range_max)),
|
||||
node_description,
|
||||
@@ -954,7 +954,7 @@ namespace {
|
||||
/// For tensor values, we use the maximum value in the tensor
|
||||
struct MaxValue {
|
||||
/// \brief No information known about the output
|
||||
MaxValue() {}
|
||||
MaxValue() = default;
|
||||
/// \brief uint64_t assoiated with the output
|
||||
MaxValue(uint64_t value) : m_value(value) {}
|
||||
MaxValue(const vector<uint64_t>& slices, int64_t slice_axis) : m_slices(slices), m_slice_axis(slice_axis) {
|
||||
@@ -1027,7 +1027,7 @@ vector<MaxValue> exec_minimum(Node* node, vector<MaxValue>& inputs) {
|
||||
vector<MaxValue> exec_concat(Node* node, vector<MaxValue>& inputs) {
|
||||
auto op = ov::as_type<op::v0::Concat>(node);
|
||||
vector<uint64_t> slice_maxen;
|
||||
for (auto input : inputs) {
|
||||
for (const auto& input : inputs) {
|
||||
slice_maxen.push_back(input.m_value);
|
||||
}
|
||||
auto axis = op->get_concatenation_axis();
|
||||
@@ -1039,7 +1039,7 @@ vector<MaxValue> exec_reduce_min(Node* node, vector<MaxValue>& inputs) {
|
||||
if (data.m_slice_axis >= 0 && data.m_slices.size() > 1) {
|
||||
if (auto indices_const = ov::as_type<op::v0::Constant>(node->get_input_node_ptr(1))) {
|
||||
if (indices_const->get_output_element_type(0).is_integral()) {
|
||||
auto indices_shape = indices_const->get_output_shape(0);
|
||||
const auto& indices_shape = indices_const->get_output_shape(0);
|
||||
if (indices_shape == Shape{1}) {
|
||||
auto indices = indices_const->cast_vector<int64_t>();
|
||||
auto axis = indices.at(0);
|
||||
@@ -1254,13 +1254,13 @@ pair<HostTensorPtr, HostTensorPtr> ngraph::evaluate_both_bounds(const Output<Nod
|
||||
return {evaluate_lower_bound(output), evaluate_upper_bound(output)};
|
||||
}
|
||||
|
||||
bool ngraph::evaluate_as_partial_shape(const Output<Node>& output, PartialShape& pshape) {
|
||||
bool ov::evaluate_as_partial_shape(const Output<Node>& output, PartialShape& pshape) {
|
||||
HostTensorPtr lb, ub;
|
||||
std::tie(lb, ub) = evaluate_both_bounds(output);
|
||||
bool shape_defined = false;
|
||||
if (lb && ub) {
|
||||
const auto lower_bound = std::make_shared<op::Constant>(lb)->cast_vector<int64_t>();
|
||||
const auto upper_bound = std::make_shared<op::Constant>(ub)->cast_vector<int64_t>();
|
||||
const auto lower_bound = std::make_shared<op::v0::Constant>(lb)->cast_vector<int64_t>();
|
||||
const auto upper_bound = std::make_shared<op::v0::Constant>(ub)->cast_vector<int64_t>();
|
||||
NGRAPH_CHECK(lower_bound.size() == upper_bound.size());
|
||||
vector<Dimension> resulting_pshape(lower_bound.size());
|
||||
for (size_t i = 0; i < lower_bound.size(); ++i) {
|
||||
@@ -1497,12 +1497,12 @@ bool ngraph::has_and_set_equal_bounds(const Output<Node>& source) {
|
||||
return lb && lb == ub;
|
||||
}
|
||||
|
||||
shared_ptr<op::Constant> ngraph::get_constant_from_source(const Output<Node>& source) {
|
||||
shared_ptr<op::Constant> ov::get_constant_from_source(const Output<Node>& source) {
|
||||
if (!has_and_set_equal_bounds(source))
|
||||
return nullptr;
|
||||
if (const auto& c = ov::as_type_ptr<op::Constant>(source.get_node_shared_ptr()))
|
||||
if (const auto& c = ov::as_type_ptr<op::v0::Constant>(source.get_node_shared_ptr()))
|
||||
return c;
|
||||
return std::make_shared<op::Constant>(source.get_tensor().get_upper_value());
|
||||
return std::make_shared<op::v0::Constant>(source.get_tensor().get_upper_value());
|
||||
}
|
||||
|
||||
bool ngraph::validate_host_tensor_vector(const HostTensorVector& tensor_vector, const size_t& size) {
|
||||
|
||||
Reference in new issue
Block a user