[CPU] MergeTransposeReorder extending (#18999)
### Details: - *MergeTransposeReorder: reverse operations order support* - *MergeTransposeReorder: reshape between Transpose and Reorder is supported with the following limitations:* - Direct order (Transpose -> Reshape -> Reorder): reshape which separates one of the dimension on 2 consecutive ones - reverse order: (Reorder -> Reshape -> Transpose): reshape which fuses 2 consecutive dimensions into one - Common code, which performs the fusion, is moved to a separate function. ### Tickets: - *CVS-113363*
This commit is contained in:
parent
c04ed93342
commit
6f8b70f245
|
|
@ -259,12 +259,14 @@ TRANSFORMATIONS_API std::vector<Input<Node>> get_node_target_inputs(const std::s
|
|||
TRANSFORMATIONS_API std::shared_ptr<Node> node_to_get_shape_value_of_indices_from_shape_node(
|
||||
const std::shared_ptr<Node>& shape_node,
|
||||
const std::vector<size_t>& indices,
|
||||
const std::vector<std::shared_ptr<Node>>& copy_rt_info_from = {});
|
||||
const std::vector<std::shared_ptr<Node>>& copy_rt_info_from = {},
|
||||
const ov::element::Type& shape_path_precision = ov::element::i64);
|
||||
|
||||
TRANSFORMATIONS_API std::shared_ptr<Node> node_to_get_shape_value_of_indices_from_shape_source(
|
||||
const Output<Node>& shape_source,
|
||||
const std::vector<size_t>& indices,
|
||||
const std::vector<std::shared_ptr<Node>>& copy_rt_info_from = {});
|
||||
const std::vector<std::shared_ptr<Node>>& copy_rt_info_from = {},
|
||||
const ov::element::Type& shape_path_precision = ov::element::i64);
|
||||
|
||||
TRANSFORMATIONS_API bool is_dequantization_subgraph(const Output<Node>& node);
|
||||
|
||||
|
|
|
|||
|
|
@ -241,9 +241,10 @@ std::vector<Input<Node>> get_node_target_inputs(const std::shared_ptr<Node>& nod
|
|||
std::shared_ptr<ov::Node> node_to_get_shape_value_of_indices_from_shape_node(
|
||||
const std::shared_ptr<ov::Node>& shape_node,
|
||||
const std::vector<size_t>& indices,
|
||||
const std::vector<std::shared_ptr<ov::Node>>& copy_rt_info_from) {
|
||||
const auto& indices_op = v0::Constant::create(ov::element::i64, {indices.size()}, indices);
|
||||
const auto& axis_op = v0::Constant::create(ov::element::i64, {}, {0});
|
||||
const std::vector<std::shared_ptr<ov::Node>>& copy_rt_info_from,
|
||||
const ov::element::Type& shape_path_precision) {
|
||||
const auto& indices_op = v0::Constant::create(shape_path_precision, {indices.size()}, indices);
|
||||
const auto& axis_op = v0::Constant::create(shape_path_precision, {}, {0});
|
||||
auto op = make_try_fold<v7::Gather>(shape_node, indices_op, axis_op);
|
||||
if (!copy_rt_info_from.empty())
|
||||
ov::copy_runtime_info(copy_rt_info_from, {op, indices_op, axis_op});
|
||||
|
|
@ -253,11 +254,15 @@ std::shared_ptr<ov::Node> node_to_get_shape_value_of_indices_from_shape_node(
|
|||
std::shared_ptr<ov::Node> node_to_get_shape_value_of_indices_from_shape_source(
|
||||
const ov::Output<ov::Node>& shape_source,
|
||||
const std::vector<size_t>& indices,
|
||||
const std::vector<std::shared_ptr<ov::Node>>& copy_rt_info_from) {
|
||||
const auto& shape_node = make_try_fold<v3::ShapeOf>(shape_source);
|
||||
const std::vector<std::shared_ptr<ov::Node>>& copy_rt_info_from,
|
||||
const ov::element::Type& shape_path_precision) {
|
||||
const auto& shape_node = make_try_fold<v3::ShapeOf>(shape_source, shape_path_precision);
|
||||
if (!copy_rt_info_from.empty())
|
||||
ov::copy_runtime_info(copy_rt_info_from, shape_node);
|
||||
return node_to_get_shape_value_of_indices_from_shape_node(shape_node, indices, copy_rt_info_from);
|
||||
return node_to_get_shape_value_of_indices_from_shape_node(shape_node,
|
||||
indices,
|
||||
copy_rt_info_from,
|
||||
shape_path_precision);
|
||||
}
|
||||
|
||||
bool shapes_equal_except_dynamic_expected_batch(const ov::PartialShape& expected, const ov::PartialShape& actual) {
|
||||
|
|
|
|||
|
|
@ -201,6 +201,9 @@ void GraphOptimizer::ApplyImplSpecificGraphOptimizations(Graph &graph) {
|
|||
MergeTransposeAndReorder(graph);
|
||||
graph.RemoveDroppedNodes();
|
||||
|
||||
MergeReorderAndTranspose(graph);
|
||||
graph.RemoveDroppedNodes();
|
||||
|
||||
graph.RemoveDroppedEdges();
|
||||
}
|
||||
|
||||
|
|
@ -2562,10 +2565,189 @@ void GraphOptimizer::FusePerformedAsScaleShiftAndFakeQuantize(Graph &graph) {
|
|||
}
|
||||
}
|
||||
|
||||
void GraphOptimizer::MergeTransposeAndReorder(Graph &graph) {
|
||||
bool GraphOptimizer::checkAscendingFinalOrder(const VectorDims& transposeOrder,
|
||||
const VectorDims& layoutOrder,
|
||||
const VectorDims& reorderInOrder,
|
||||
const VectorDims& reorderOutOrder) {
|
||||
if (transposeOrder.size() != layoutOrder.size() || layoutOrder.size() != reorderInOrder.size() ||
|
||||
reorderInOrder.size() != reorderOutOrder.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// revLayoutOrder - reverse permutation for layoutOrder
|
||||
auto revLayoutOrder = VectorDims(layoutOrder.size());
|
||||
for (size_t i = 0; i < revLayoutOrder.size(); i++) {
|
||||
revLayoutOrder[layoutOrder[i]] = i;
|
||||
}
|
||||
|
||||
// newTransposeOrder - Transpose layout-aware permutation
|
||||
auto newTransposeOrder = VectorDims(transposeOrder.size());
|
||||
for (size_t i = 0; i < newTransposeOrder.size(); i++) {
|
||||
newTransposeOrder[i] = layoutOrder[transposeOrder[revLayoutOrder[i]]];
|
||||
}
|
||||
|
||||
// reorderOrder - Reorder layout-aware permutation
|
||||
auto reorderOrder = VectorDims(reorderOutOrder.size());
|
||||
for (size_t i = 0; i < reorderOrder.size(); i++) {
|
||||
for (size_t j = 0; j < reorderOrder.size(); j++) {
|
||||
if (reorderOutOrder[i] == reorderInOrder[j]) {
|
||||
reorderOrder[i] = j;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// summaryOrder - resulting Transpose+Reorder permutation
|
||||
auto summaryOrder = VectorDims(transposeOrder.size());
|
||||
for (size_t i = 0; i < summaryOrder.size(); i++) {
|
||||
summaryOrder[i] = reorderOrder[newTransposeOrder[i]];
|
||||
}
|
||||
|
||||
// check that Transpose+Reorder is the identical permutation
|
||||
for (size_t i = 0; i < summaryOrder.size(); i++) {
|
||||
if (summaryOrder[i] != i) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GraphOptimizer::mergeTransposeReshapeReorder(Graph& graph,
|
||||
const NodePtr& transposeNode,
|
||||
const NodePtr& reshapeNode,
|
||||
const NodePtr& reorderNode,
|
||||
const bool reverseOrder) {
|
||||
const auto& parentNode = reverseOrder ? reorderNode : transposeNode;
|
||||
const auto& childNode = reverseOrder ? transposeNode : reorderNode;
|
||||
auto nodeBeforeSequence = parentNode->getParentEdgeAt(0)->getParent();
|
||||
auto nodeBeforeSequencePort = parentNode->getParentEdgeAt(0)->getInputNum();
|
||||
auto nodeAfterSequence = childNode->getChildEdgeAt(0)->getChild();
|
||||
|
||||
graph.RemoveEdge(transposeNode->getParentEdgeAt(1));
|
||||
if (reshapeNode)
|
||||
graph.RemoveEdge(reshapeNode->getParentEdgeAt(1));
|
||||
|
||||
// to prevent inPlace conflict we must check that the memory reference is unidirectional or
|
||||
// inPlace memory is not used
|
||||
const auto parentInPlace = parentNode->getParentEdgeAt(0)->inPlace(Edge::LOOK_UP);
|
||||
const auto& childEdges = childNode->getChildEdgesAtPort(0);
|
||||
|
||||
const auto childInPlace = std::any_of(childEdges.begin(), childEdges.end(), [](const EdgePtr& edge) {
|
||||
return edge->inPlace(Edge::LOOK_DOWN);
|
||||
});
|
||||
|
||||
// Note: this value must be computed before detaching nodes
|
||||
bool isOptimized = !(parentInPlace && childInPlace);
|
||||
|
||||
// hold references to all children before dropping reorder_node
|
||||
std::vector<std::pair<NodePtr, int>> reorderChildren;
|
||||
for (auto ccEdge : childEdges)
|
||||
reorderChildren.emplace_back(ccEdge->getChild(), ccEdge->getOutputNum());
|
||||
|
||||
// detach nodes from graph by remove all of their edges
|
||||
// they will be removed in future graph.RemoveDroppedNodes() call
|
||||
auto detachNode = [&](const std::shared_ptr<Node>& node) {
|
||||
std::vector<EdgeWeakPtr> edges;
|
||||
edges = node->getParentEdges();
|
||||
for (auto& edge : edges)
|
||||
graph.RemoveEdge(edge.lock());
|
||||
edges = node->getChildEdges();
|
||||
for (auto& edge : edges)
|
||||
graph.RemoveEdge(edge.lock());
|
||||
};
|
||||
detachNode(transposeNode);
|
||||
detachNode(reorderNode);
|
||||
if (reshapeNode)
|
||||
detachNode(reshapeNode);
|
||||
|
||||
auto reorderInDesc = parentNode->getSelectedPrimitiveDescriptor()->getConfig().inConfs[0].getMemDesc();
|
||||
auto finalDesc = childNode->getSelectedPrimitiveDescriptor()->getConfig().outConfs[0].getMemDesc();
|
||||
auto reorderOutDesc = finalDesc->cloneWithNewPrecision(reorderInDesc->getPrecision());
|
||||
|
||||
std::vector<int> srcPerm;
|
||||
auto* castedTranspose = dynamic_cast<Transpose*>(transposeNode.get());
|
||||
OPENVINO_ASSERT(castedTranspose,
|
||||
"[CPU] parent node of type:",
|
||||
transposeNode->getTypeStr(),
|
||||
" with name: ",
|
||||
transposeNode->getName(),
|
||||
" is not a transpose node");
|
||||
|
||||
const auto& inOrder = transposeNode->getSelectedPrimitiveDescriptor()->getConfig().inConfs[0].getMemDesc()->as<BlockedMemoryDesc>()->getOrder();
|
||||
const auto& outOrder = reorderOutDesc->as<BlockedMemoryDesc>()->getOrder();
|
||||
// Permutation must be set and reorder mustn't be optimized in 2 cases:
|
||||
// 1. Transpose has blocked input & non-blocked output
|
||||
// 2. Transpose and Reorder do opposite permutation to each other as expected,
|
||||
// but isOptimized is already set to false due to some preliminarily checks.
|
||||
if (!isOptimized || inOrder.size() > outOrder.size()) {
|
||||
isOptimized = false;
|
||||
// inDesc should be permuted before calling reorder
|
||||
auto& ord = castedTranspose->getOrder();
|
||||
srcPerm = std::vector<int>(ord.size());
|
||||
for (size_t i = 0; i < ord.size(); i++) {
|
||||
srcPerm[ord[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
std::string reorderName = nodeBeforeSequence->getName() + "_" + Reorder::getReorderArgs(*reorderInDesc, *reorderOutDesc);
|
||||
if (isOptimized)
|
||||
reorderName += "_fake";
|
||||
DEBUG_LOG("mergeTransposeAndReorder ", parentNode->getName(), " and ", childNode->getName(), " -> ", reorderName);
|
||||
auto reorder_layout = std::make_shared<node::Reorder>(*reorderInDesc, *reorderOutDesc, reorderName, graph.getGraphContext());
|
||||
reorder_layout->setOptimized(isOptimized);
|
||||
reorder_layout->setSrcPermutation(srcPerm);
|
||||
|
||||
graph.CreateEdge(nodeBeforeSequence, reorder_layout, nodeBeforeSequencePort, 0);
|
||||
|
||||
// If precisions don't match, another reorder must be inserted to perform conversion
|
||||
auto reorder_last = reorder_layout;
|
||||
if (reorderOutDesc->getPrecision() != finalDesc->getPrecision()) {
|
||||
std::string reorderLayerName2 = reorder_layout->getName() + "_" +
|
||||
Reorder::getReorderArgs(*reorderOutDesc, *finalDesc) + "_" +
|
||||
nodeAfterSequence->getName();
|
||||
|
||||
reorder_last = std::make_shared<node::Reorder>(*reorderOutDesc,
|
||||
*finalDesc,
|
||||
reorderLayerName2,
|
||||
graph.getGraphContext());
|
||||
reorder_last->setOptimized(false);
|
||||
reorder_last->setSrcPermutation(srcPerm);
|
||||
graph.CreateEdge(reorder_layout, reorder_last, 0, 0);
|
||||
}
|
||||
|
||||
for (auto& cc : reorderChildren)
|
||||
graph.CreateEdge(reorder_last, cc.first, 0, cc.second);
|
||||
|
||||
// initialize and add nodes into graph
|
||||
std::vector<NodePtr> new_nodes;
|
||||
new_nodes.push_back(reorder_layout);
|
||||
if (reorder_last != reorder_layout) {
|
||||
new_nodes.push_back(reorder_last);
|
||||
}
|
||||
for (auto& node : new_nodes)
|
||||
graph.AddNode(node);
|
||||
|
||||
// multiple nodes must be initialized in specific order
|
||||
for (auto& node : new_nodes)
|
||||
node->init();
|
||||
for (auto& node : new_nodes) {
|
||||
node->getSupportedDescriptors();
|
||||
node->initSupportedPrimitiveDescriptors();
|
||||
node->filterSupportedPrimitiveDescriptors();
|
||||
}
|
||||
for (auto& node : new_nodes)
|
||||
node->selectOptimalPrimitiveDescriptor();
|
||||
for (auto& node : new_nodes)
|
||||
node->resolveInPlaceDirection();
|
||||
for (auto& node : new_nodes)
|
||||
node->initOptimalPrimitiveDescriptor();
|
||||
}
|
||||
|
||||
void GraphOptimizer::MergeTransposeAndReorder(Graph& graph) {
|
||||
auto& graphNodes = graph.GetNodes();
|
||||
|
||||
auto isSuitableParentNode = [](NodePtr node) {
|
||||
auto isSuitableTranspose = [](NodePtr node) {
|
||||
// WA: to avoid broken memory pointer for conv + sum
|
||||
auto prevNodeIsConvSum = [](NodePtr node) -> bool {
|
||||
const auto parent = node->getParentEdgeAt(0)->getParent();
|
||||
|
|
@ -2588,22 +2770,105 @@ void GraphOptimizer::MergeTransposeAndReorder(Graph &graph) {
|
|||
&& !prevNodeIsConvSum(node);
|
||||
};
|
||||
|
||||
auto isSuitableChildNode = [](NodePtr node) {
|
||||
auto isSuitableReshape = [](NodePtr node) {
|
||||
if (node->getChildEdges().size() != 1 || node->getOutputShapeAtPort(0).isDynamic() ||
|
||||
node->getInputShapeAtPort(0).isDynamic())
|
||||
return false;
|
||||
// Reshape supported only in one case: if one of the input dims is split into 2 consecutive dims
|
||||
const auto& inDims = node->getInputShapeAtPort(0).getDims();
|
||||
const auto& outDims = node->getOutputShapeAtPort(0).getDims();
|
||||
if (outDims.size() - inDims.size() != 1)
|
||||
return false;
|
||||
|
||||
size_t mismatchCount = 0;
|
||||
for (size_t i = 0; i < inDims.size(); ++i) {
|
||||
if (i + mismatchCount >= outDims.size())
|
||||
return false;
|
||||
if (inDims[i] != outDims[i + mismatchCount]) {
|
||||
mismatchCount++;
|
||||
}
|
||||
}
|
||||
return mismatchCount == 1;
|
||||
};
|
||||
|
||||
auto isSuitableReorder = [](NodePtr node) {
|
||||
return node->getType() == Type::Reorder
|
||||
&& !node->isDynamicNode(); // TODO [DS]: enable for dynamic shapes when inPlace in the dynamic case is available (CVS-74863)
|
||||
};
|
||||
|
||||
// Method checkAscendingSummaryOrder() checks that after the sequential execution of Transpose and Reorder nodes,
|
||||
// the order of the elements in the memory will not change. In other words, that Transpose+Reorder is identical permutation.
|
||||
auto checkAscendingSummaryOrder = [](std::shared_ptr<Node> &parentNode, std::shared_ptr<Node> &childNode) -> bool {
|
||||
auto* transposeNode = dynamic_cast<Transpose*>(parentNode.get());
|
||||
auto* reorderNode = dynamic_cast<Reorder*>(childNode.get());
|
||||
if (!transposeNode || !reorderNode) {
|
||||
return false;
|
||||
auto updateOrder = [](const VectorDims& originalOrder, NodePtr reshape) {
|
||||
if (!reshape)
|
||||
return originalOrder;
|
||||
|
||||
// Further logic works with transpose order without Reshape.
|
||||
// If there is a Reshape node, which splits one of the dimensions into 2 consecutive ones,
|
||||
// the order must be updated as if Transpose is done after Reshape
|
||||
// Example. For this sequence:
|
||||
// [1,12,5] -> Transpose(0,2,1) -> Reshape(1,5,3,4) -> [1,5,3,4]
|
||||
// updated order must be (0,3,1,2):
|
||||
// - dim with idx=1 is split into 2 parts: 1 and 2
|
||||
// - dim idxes which was greater then 1, increments by 1
|
||||
const auto& reshapeInShape = reshape->getInputShapeAtPort(0).getDims();
|
||||
const auto& reshapeOutShape = reshape->getOutputShapeAtPort(0).getDims();
|
||||
const size_t splitDimIdx = [&]() {
|
||||
for (size_t i = 0; i < reshapeInShape.size(); ++i) {
|
||||
if (reshapeInShape[i] != reshapeOutShape[i]) {
|
||||
for (size_t j = 0; j < originalOrder.size(); ++j) {
|
||||
if (originalOrder[j] == i)
|
||||
return j;
|
||||
}
|
||||
}
|
||||
}
|
||||
OPENVINO_THROW("splitDimIdx can not be found");
|
||||
}();
|
||||
|
||||
auto transformedOrder = originalOrder;
|
||||
auto insertIt = transformedOrder.end();
|
||||
for (auto it = transformedOrder.begin(); it != transformedOrder.end(); ++it) {
|
||||
auto& elem = *it;
|
||||
if (elem > splitDimIdx) {
|
||||
elem++;
|
||||
} else if (elem == splitDimIdx) {
|
||||
insertIt = it + 1;
|
||||
}
|
||||
}
|
||||
transformedOrder.insert(insertIt, splitDimIdx + 1);
|
||||
return transformedOrder;
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < graphNodes.size(); i++) {
|
||||
auto parentNode = graphNodes[i];
|
||||
if (!isSuitableTranspose(parentNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& transposeOrder = transposeNode->getOrder();
|
||||
auto layoutOrder = transposeNode->getSelectedPrimitiveDescriptor()->getConfig().outConfs[0].getMemDesc()->as<BlockedMemoryDesc>()->getOrder();
|
||||
CPU_GRAPH_OPTIMIZER_SCOPE(MergeTransposeAndReorder_ParentNode);
|
||||
|
||||
auto childNode = parentNode->getChildEdgesAtPort(0).front()->getChild();
|
||||
NodePtr intermNode;
|
||||
if (childNode->getType() == Type::Reshape) {
|
||||
intermNode = childNode;
|
||||
if (!isSuitableReshape(intermNode)) {
|
||||
continue;
|
||||
}
|
||||
childNode = intermNode->getChildEdgesAtPort(0).front()->getChild();
|
||||
}
|
||||
if (!isSuitableReorder(childNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CPU_GRAPH_OPTIMIZER_SCOPE(MergeTransposeAndReorder_ChildNode);
|
||||
|
||||
const auto transposeNode = std::dynamic_pointer_cast<Transpose>(parentNode);
|
||||
const auto reorderNode = std::dynamic_pointer_cast<Reorder>(childNode);
|
||||
std::shared_ptr<Reshape> reshapeNode = intermNode != nullptr ? std::dynamic_pointer_cast<Reshape>(intermNode) : nullptr;
|
||||
if (!transposeNode || !reorderNode || (intermNode && !reshapeNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto transposeOrder = updateOrder(transposeNode->getOrder(), reshapeNode);
|
||||
auto descBeforeReorder = reorderNode->getParentEdgeAt(0)->getParent()->getSelectedPrimitiveDescriptor()->getConfig().outConfs[0].getMemDesc();
|
||||
auto layoutOrder = descBeforeReorder->as<BlockedMemoryDesc>()->getOrder();
|
||||
|
||||
auto inBlockedDesc = reorderNode->getSelectedPrimitiveDescriptor()->getConfig().inConfs[0].getMemDesc()->as<BlockedMemoryDesc>();
|
||||
auto outBlockedDesc = reorderNode->getSelectedPrimitiveDescriptor()->getConfig().outConfs[0].getMemDesc()->as<BlockedMemoryDesc>();
|
||||
|
|
@ -2611,201 +2876,125 @@ void GraphOptimizer::MergeTransposeAndReorder(Graph &graph) {
|
|||
auto& inOrder = inBlockedDesc->getOrder();
|
||||
auto& outOrder = outBlockedDesc->getOrder();
|
||||
|
||||
if (transposeOrder.size() != layoutOrder.size() || layoutOrder.size() != inOrder.size() || inOrder.size() != outOrder.size()) {
|
||||
return false;
|
||||
if (checkAscendingFinalOrder(transposeOrder, layoutOrder, inOrder, outOrder)) {
|
||||
mergeTransposeReshapeReorder(graph, transposeNode, reshapeNode, reorderNode, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// revLayoutOrder - reverse permutation for layoutOrder
|
||||
auto revLayoutOrder = VectorDims(layoutOrder.size());
|
||||
for (size_t i = 0; i < revLayoutOrder.size(); i++) {
|
||||
revLayoutOrder[layoutOrder[i]] = i;
|
||||
}
|
||||
void GraphOptimizer::MergeReorderAndTranspose(Graph &graph) {
|
||||
auto& graphNodes = graph.GetNodes();
|
||||
|
||||
// newTransposeOrder - Transpose layout-aware permutation
|
||||
auto newTransposeOrder = VectorDims(transposeOrder.size());
|
||||
for (size_t i = 0; i < newTransposeOrder.size(); i++) {
|
||||
newTransposeOrder[i] = layoutOrder[transposeOrder[revLayoutOrder[i]]];
|
||||
}
|
||||
|
||||
// reorderOrder - Reorder layout-aware permutation
|
||||
auto reorderOrder = VectorDims(outOrder.size());
|
||||
for (size_t i = 0; i < reorderOrder.size(); i++) {
|
||||
for (size_t j = 0; j < reorderOrder.size(); j++) {
|
||||
if (outOrder[i] == inOrder[j]) {
|
||||
reorderOrder[i] = j;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// summaryOrder - resulting Transpose+Reorder permutation
|
||||
auto summaryOrder = VectorDims(transposeOrder.size());
|
||||
for (size_t i = 0; i < summaryOrder.size(); i++) {
|
||||
summaryOrder[i] = reorderOrder[newTransposeOrder[i]];
|
||||
}
|
||||
|
||||
// check that Transpose+Reorder is the identical permutation
|
||||
for (size_t i = 0; i < summaryOrder.size(); i++) {
|
||||
if (summaryOrder[i] != i) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
auto isSuitableTranspose = [](NodePtr node) {
|
||||
return node->getType() == Type::Transpose
|
||||
&& node->getChildEdges().size() == 1
|
||||
&& !node->isDynamicNode();
|
||||
};
|
||||
|
||||
// Transpose and Reorder do opposite permutation to each other.
|
||||
// Example:
|
||||
// chain [physical layout: NCHW, logical layout: NCHW] -> Transpose(order=0312) -> [physical layout: NWCH, logical layout: NCHW] ->
|
||||
// Reorder(nchw->nhwc) -> [physical layout: NCHW, logical layout: NHWC] can be replaced with Reorder(nchw->nhwc; isOptimized=true)
|
||||
// which will just reinterprets layout without physical change of the memory.
|
||||
// Two cases are possible:
|
||||
// 1) inPrec = outPrec
|
||||
// In this case, we replace Transpose+Reorder pattern with a new Reorder that does nothing.
|
||||
// 2) inPrec != outPrec
|
||||
// As in the first case, we also replace Transpose+Reorder pattern with a new Reorder.
|
||||
// Additionally, we insert another Reorder that performs the conversion from the input precision (inPrec)
|
||||
// to the output precision (outPrec)
|
||||
auto mergeTransposeAndReorder = [&](std::shared_ptr<Node>& trans_node, std::shared_ptr<Node>& reorder_node) {
|
||||
// parentParentNode ===> trans_node ===> reorder_node ===> cc0, cc1, ...
|
||||
// is transfomed into
|
||||
// parentParentNode ===> reorder_nop ===> [reorder_convert] ==> cc0, cc1, ...
|
||||
auto parentParentNode = trans_node->getParentEdgeAt(0)->getParent();
|
||||
auto parentParenPort = trans_node->getParentEdgeAt(0)->getInputNum();
|
||||
auto parentParentConstNode = trans_node->getParentEdgeAt(1)->getParent();
|
||||
auto isSuitableReshape = [](NodePtr node) {
|
||||
if (node->getChildEdges().size() != 1 || node->getOutputShapeAtPort(0).isDynamic() ||
|
||||
node->getInputShapeAtPort(0).isDynamic())
|
||||
return false;
|
||||
// Reshape supported only in one case: if two consecutive input dims are merged into 1
|
||||
const auto& inShape = node->getInputShapeAtPort(0).getDims();
|
||||
const auto& outShape = node->getOutputShapeAtPort(0).getDims();
|
||||
if (inShape.size() - outShape.size() != 1)
|
||||
return false;
|
||||
|
||||
auto remEdge = trans_node->getParentEdgeAt(1);
|
||||
graph.RemoveEdge(remEdge);
|
||||
|
||||
// to prevent inPlace conflict we must check that the memory reference is unidirectional or
|
||||
// inPlace memory is not used
|
||||
const auto parentInPlace = trans_node->getParentEdgeAt(0)->inPlace(Edge::LOOK_UP);
|
||||
const auto& childEdges = reorder_node->getChildEdgesAtPort(0);
|
||||
|
||||
const auto childInPlace = std::any_of(childEdges.begin(), childEdges.end(),
|
||||
[](const EdgePtr& edge){ return edge->inPlace(Edge::LOOK_DOWN); });
|
||||
|
||||
bool isOptimized = !(parentInPlace && childInPlace);
|
||||
|
||||
// hold references to all children before dropping reorder_node
|
||||
std::vector<std::pair<NodePtr, int>> reorderChildren;
|
||||
for (auto ccEdge : childEdges)
|
||||
reorderChildren.emplace_back(ccEdge->getChild(), ccEdge->getOutputNum());
|
||||
|
||||
// detach trans_node and reorder_node from graph by remove all of their edges
|
||||
// they will be removed in future graph.RemoveDroppedNodes() call
|
||||
auto detachNode = [&](std::shared_ptr<Node>& node) {
|
||||
std::vector<EdgeWeakPtr> edges;
|
||||
edges = node->getParentEdges();
|
||||
for (auto& edge : edges)
|
||||
graph.RemoveEdge(edge.lock());
|
||||
edges = node->getChildEdges();
|
||||
for (auto& edge : edges)
|
||||
graph.RemoveEdge(edge.lock());
|
||||
};
|
||||
detachNode(trans_node);
|
||||
detachNode(reorder_node);
|
||||
|
||||
auto reorderInDesc = trans_node->getSelectedPrimitiveDescriptor()->getConfig().inConfs[0].getMemDesc();
|
||||
auto finalDesc = reorder_node->getSelectedPrimitiveDescriptor()->getConfig().outConfs[0].getMemDesc();
|
||||
auto reorderOutDesc = finalDesc->cloneWithNewPrecision(reorderInDesc->getPrecision());
|
||||
|
||||
std::string reorderlayerName = parentParentNode->getName() + "_" +
|
||||
Reorder::getReorderArgs(*reorderInDesc, *reorderOutDesc) + "_" + "fake";
|
||||
|
||||
DEBUG_LOG("mergeTransposeAndReorder ", trans_node->getName(), " and ", reorder_node->getName(), " -> ", reorderlayerName);
|
||||
|
||||
std::vector<int> srcPerm;
|
||||
// case 1. transposeNode support blocked input & non-blocked output, in the case, the reorder
|
||||
// cannot be optimized
|
||||
// case 2. Transpose and Reorder do opposite permutation to each other as expected, but isOptimized is already set false
|
||||
// due to some preliminarily checks. We need to reinterpret layout Transpose input without physical change of the memory.
|
||||
auto* transposeNode = dynamic_cast<Transpose*>(trans_node.get());
|
||||
if (transposeNode == nullptr) {
|
||||
OPENVINO_THROW("[CPU] parent node of type:",
|
||||
trans_node->getTypeStr(),
|
||||
" with name: ",
|
||||
trans_node->getName(),
|
||||
" is not a transpose node");
|
||||
}
|
||||
const auto& inOrder = transposeNode->getSelectedPrimitiveDescriptor()->getConfig().inConfs[0].getMemDesc()->as<BlockedMemoryDesc>()->getOrder();
|
||||
const auto& outOrder = reorderOutDesc->as<BlockedMemoryDesc>()->getOrder();
|
||||
if (!isOptimized || inOrder.size() > outOrder.size()) {
|
||||
isOptimized = false;
|
||||
// inDesc should be permuted before calling reorder
|
||||
auto & ord = transposeNode->getOrder();
|
||||
srcPerm = std::vector<int>(ord.size());
|
||||
for (size_t i = 0; i < ord.size(); i++) {
|
||||
srcPerm[ord[i]] = i;
|
||||
size_t mismatchCount = 0;
|
||||
for (size_t i = 0; i < outShape.size(); ++i) {
|
||||
if (i + mismatchCount >= inShape.size())
|
||||
return false;
|
||||
if (outShape[i] != inShape[i + mismatchCount]) {
|
||||
mismatchCount++;
|
||||
}
|
||||
}
|
||||
auto reorder_layout =
|
||||
std::make_shared<node::Reorder>(*reorderInDesc, *reorderOutDesc, reorderlayerName, graph.getGraphContext());
|
||||
reorder_layout->setOptimized(isOptimized);
|
||||
reorder_layout->setSrcPermutation(srcPerm);
|
||||
return mismatchCount == 1;
|
||||
};
|
||||
|
||||
graph.CreateEdge(parentParentNode, reorder_layout, parentParenPort, 0);
|
||||
auto isSuitableReorder = [](NodePtr node) {
|
||||
return node->getType() == Type::Reorder && node->getChildEdges().size() == 1 && !node->isDynamicNode();
|
||||
};
|
||||
|
||||
// case 2
|
||||
auto reorder_last = reorder_layout;
|
||||
if (reorderOutDesc->getPrecision() != finalDesc->getPrecision()) {
|
||||
std::string reorderLayerName2 = reorder_layout->getName() + "_" +
|
||||
Reorder::getReorderArgs(*reorderOutDesc, *finalDesc) + "_x_" +
|
||||
reorderChildren[0].first->getName();
|
||||
reorder_last = std::make_shared<node::Reorder>(*reorderOutDesc,
|
||||
*finalDesc,
|
||||
reorderLayerName2,
|
||||
graph.getGraphContext());
|
||||
reorder_last->setOptimized(false);
|
||||
reorder_last->setSrcPermutation(srcPerm);
|
||||
graph.CreateEdge(reorder_layout, reorder_last, 0, 0);
|
||||
auto updateOrder = [](const VectorDims& originalOrder, NodePtr reshape) {
|
||||
if (!reshape)
|
||||
return originalOrder;
|
||||
|
||||
// Further logic works with order without Reshape.
|
||||
// If there is Reshape node which merges 2 consecutive dims into one,
|
||||
// the order must be updated as like Transpose is done before Reshape
|
||||
// Example. For this sequence:
|
||||
// [1,3,4,5] -> Reshape(1,12,5) -> Transpose(0,2,1) -> [1,5,12]
|
||||
// updated order must be (0,3,1,2):
|
||||
// - dim with idx=2 is split into 2 parts: 2 and 3
|
||||
const auto& reshapeInShape = reshape->getInputShapeAtPort(0).getDims();
|
||||
const auto& reshapeOutShape = reshape->getOutputShapeAtPort(0).getDims();
|
||||
const size_t mergedDimIdx = [&]() {
|
||||
for (size_t i = 0; i < reshapeInShape.size(); ++i) {
|
||||
if (reshapeInShape[i] != reshapeOutShape[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
OPENVINO_THROW("mergedDimIdx can not be found");
|
||||
}();
|
||||
|
||||
auto transformedOrder = originalOrder;
|
||||
auto insertIt = transformedOrder.end();
|
||||
for (auto it = transformedOrder.begin(); it != transformedOrder.end(); ++it) {
|
||||
auto& elem = *it;
|
||||
if (elem > mergedDimIdx) {
|
||||
elem++;
|
||||
} else if (elem == mergedDimIdx) {
|
||||
insertIt = it + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& cc : reorderChildren)
|
||||
graph.CreateEdge(reorder_last, cc.first, 0, cc.second);
|
||||
|
||||
// initialize and add nodes into graph
|
||||
std::vector<NodePtr> new_nodes;
|
||||
new_nodes.push_back(reorder_layout);
|
||||
if (reorder_last != reorder_layout) {
|
||||
new_nodes.push_back(reorder_last);
|
||||
}
|
||||
for (auto& node : new_nodes)
|
||||
graph.AddNode(node);
|
||||
|
||||
// multiple nodes must be initialized in specific order
|
||||
for (auto& node : new_nodes)
|
||||
node->init();
|
||||
for (auto& node : new_nodes) {
|
||||
node->getSupportedDescriptors();
|
||||
node->initSupportedPrimitiveDescriptors();
|
||||
node->filterSupportedPrimitiveDescriptors();
|
||||
}
|
||||
for (auto& node : new_nodes)
|
||||
node->selectOptimalPrimitiveDescriptor();
|
||||
for (auto& node : new_nodes)
|
||||
node->resolveInPlaceDirection();
|
||||
for (auto& node : new_nodes)
|
||||
node->initOptimalPrimitiveDescriptor();
|
||||
transformedOrder.insert(insertIt, mergedDimIdx + 1);
|
||||
return transformedOrder;
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < graphNodes.size(); i++) {
|
||||
auto parentNode = graphNodes[i];
|
||||
if (!isSuitableParentNode(parentNode)) {
|
||||
if (!isSuitableReorder(parentNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CPU_GRAPH_OPTIMIZER_SCOPE(MergeTransposeAndReorder_ParentNode);
|
||||
|
||||
auto childNode = parentNode->getChildEdgeAt(0)->getChild();
|
||||
if (!isSuitableChildNode(childNode)) {
|
||||
auto childNode = parentNode->getChildEdgesAtPort(0).front()->getChild();
|
||||
NodePtr intermNode;
|
||||
if (childNode->getType() == Type::Reshape) {
|
||||
intermNode = childNode;
|
||||
if (!isSuitableReshape(intermNode)) {
|
||||
continue;
|
||||
}
|
||||
childNode = intermNode->getChildEdgeAt(0)->getChild();
|
||||
}
|
||||
if (!isSuitableTranspose(childNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CPU_GRAPH_OPTIMIZER_SCOPE(MergeTransposeAndReorder_ChildNode);
|
||||
|
||||
if (checkAscendingSummaryOrder(parentNode, childNode)) {
|
||||
mergeTransposeAndReorder(parentNode, childNode);
|
||||
auto transposeNode = std::dynamic_pointer_cast<Transpose>(childNode);
|
||||
auto reorderNode = std::dynamic_pointer_cast<Reorder>(parentNode);
|
||||
std::shared_ptr<Reshape> reshapeNode = intermNode != nullptr ? std::dynamic_pointer_cast<Reshape>(intermNode) : nullptr;
|
||||
if (!transposeNode || !reorderNode || (intermNode && !reshapeNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto transposeOrder = updateOrder(transposeNode->getOrder(), reshapeNode);
|
||||
auto descAfterTranspose = transposeNode->getSelectedPrimitiveDescriptor()->getConfig().outConfs[0].getMemDesc();
|
||||
auto layoutOrder = updateOrder(descAfterTranspose->as<BlockedMemoryDesc>()->getOrder(), reshapeNode);
|
||||
|
||||
auto inBlockedDesc = reorderNode->getSelectedPrimitiveDescriptor()->getConfig().inConfs[0].getMemDesc()->as<BlockedMemoryDesc>();
|
||||
auto outBlockedDesc = reorderNode->getSelectedPrimitiveDescriptor()->getConfig().outConfs[0].getMemDesc()->as<BlockedMemoryDesc>();
|
||||
|
||||
auto& inOrder = inBlockedDesc->getOrder();
|
||||
auto& outOrder = outBlockedDesc->getOrder();
|
||||
|
||||
if (checkAscendingFinalOrder(transposeOrder, layoutOrder, inOrder, outOrder)) {
|
||||
mergeTransposeReshapeReorder(graph, transposeNode, reshapeNode, reorderNode, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,11 +47,45 @@ private:
|
|||
void FusePerformedAsScaleShiftAndFakeQuantize(Graph &graph);
|
||||
void FuseClampAndFakeQuantize(Graph &graph);
|
||||
void MergeTransposeAndReorder(Graph &graph);
|
||||
void MergeReorderAndTranspose(Graph &graph);
|
||||
void reshapeRnnSeq(Graph &graph);
|
||||
void RemoveSameConvert(Graph &graph);
|
||||
void RemoveMemoryInputConvert(Graph &graph);
|
||||
void RemoveConvertMemoryOutput(Graph &graph);
|
||||
void MatchSdpaKvCache(Graph &graph);
|
||||
|
||||
// Method checks that after the sequential execution of Transpose and Reorder nodes,
|
||||
// the order of the elements in the memory (physical layout) will not change.
|
||||
bool checkAscendingFinalOrder(const VectorDims& transposeOrder,
|
||||
const VectorDims& layoutOrder,
|
||||
const VectorDims& reorderInOrder,
|
||||
const VectorDims& reorderOutOrder);
|
||||
// Method merges Transpose -> Reshape(optional) -> Reorder sequences which do opposite permutation to each other.
|
||||
// Reverse order Reorder -> Reshape(optional) -> Transpose is supported too.
|
||||
// Reshape support has the following limitations:
|
||||
// - direct order: Only reshape which split one of the dimension into 2 consecutive ones is supported
|
||||
// - reverse order: Only reshape which fuses 2 consecutive dimensions into one is supported
|
||||
// Examples:
|
||||
// 1. Direct order, no Reshape node.
|
||||
// Before: [N,C,H,W]abcd==>Transpose(0312)==>[N,W,C,H]abcd==>Reorder(abcd->acdb)==>[N,W,C,H]acdb
|
||||
// [N,C,H,W]abcd is equivalent to the [N,W,C,H]acdb, so the Transpose and Reorder can be fused into single optimized Reorder:
|
||||
// After: [N,C,H,W]abcd==>Reorder(abcd->acdb, isOptimized=true)==>[N,W,C,H]acdb
|
||||
// 2. Reverse order, no Reshape node.
|
||||
// Before: [N,W,C,H]acdb==>Reorder(acdb->abcd)==>[N,W,C,H]abcd==>Transpose(0231)==>[N,C,H,W]abcd
|
||||
// [N,W,C,H]acdb is equivalent to the [N,C,H,W]abcd, so the Transpose and Reorder can be fused into single optimized Reorder:
|
||||
// After: [N,W,C,H]acdb==>Reorder(acdb->abcd, isOptimized=true)==>[N,C,H,W]abcd
|
||||
// 3. Direct order with Reshape node (L = H x w).
|
||||
// Before: [N,L,C]abc==>Transpose(021)==>[N,C,L]abc==>Reshape==>[N,C,H,W]abcd==>Reoder(abcd->acdb)==>[N,C,H,W]acdb
|
||||
// After: [N,L,C]abc==>Reorder(abc->acdb, isOptimized=true)==>[N,C,H,W]acdb
|
||||
// 4. Reverse order with Reshape node (L = H x W).
|
||||
// Before: [N,C,H,W]acdb==>Reorder(acdb->abcd)==>[N,C,H,W]abcd==>Reshape==>[N,C,L]abc==>Transpose(021)==>[N,L,C]abc
|
||||
// After: [N,C,H,W]acdb==>Reorder(acdb->abc, isOptimized=true)==>[N,L,C]abc
|
||||
// Note: in some cases (inplace conflicts or transpose with blocked input and non-blocked output) the merged Reorder can not be optimized.
|
||||
void mergeTransposeReshapeReorder(Graph& graph,
|
||||
const NodePtr& transposeNode,
|
||||
const NodePtr& reshapeNode,
|
||||
const NodePtr& reorderNode,
|
||||
const bool reverseOrder);
|
||||
};
|
||||
|
||||
} // namespace intel_cpu
|
||||
|
|
|
|||
|
|
@ -177,15 +177,15 @@ INSTANTIATE_TEST_SUITE_P(smoke_Basic, FuseTransposeAndReorderTest1, fuseTranspos
|
|||
|Input | |Input |
|
||||
--------- ---------
|
||||
| |
|
||||
| -------------
|
||||
--------- | ----------- |
|
||||
|Reorder| | |Transpose| |
|
||||
--------- | ----------- |
|
||||
| | | |
|
||||
--------- | ----------- |
|
||||
|Transpose| | |Reorder| |
|
||||
--------- | ----------- |
|
||||
| |-------------|
|
||||
|------------ | |-------------|
|
||||
| ----------- | | ----------- |
|
||||
| |Reorder| | | |Transpose| |
|
||||
| ----------- | | ----------- |
|
||||
| | | | | |
|
||||
| ----------- | | ----------- |
|
||||
| |Transpose| | | |Reorder| |
|
||||
| ----------- | | ----------- |
|
||||
|------------ | |-------------|
|
||||
| |
|
||||
-------- --------
|
||||
| |
|
||||
|
|
@ -224,7 +224,7 @@ void FuseTransposeAndReorderTest2::create_model() {
|
|||
|
||||
TEST_P(FuseTransposeAndReorderTest2, CompareWithRefs) {
|
||||
run();
|
||||
check_transpose_count(1);
|
||||
check_transpose_count(0);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(smoke_Basic, FuseTransposeAndReorderTest2, fuseTransposeAndReorderCommonParams, FuseTransposeAndReorderTest::getTestCaseName);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
// Copyright (C) 2018-2023 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "common_test_utils/common_utils.hpp"
|
||||
#include "common_test_utils/node_builders/constant.hpp"
|
||||
#include "openvino/opsets/opset10.hpp"
|
||||
#include "shared_test_classes/base/ov_subgraph.hpp"
|
||||
#include "transformations/utils/utils.hpp"
|
||||
#include "utils/cpu_test_utils.hpp"
|
||||
|
||||
using namespace CPUTestUtils;
|
||||
using namespace ov::test;
|
||||
|
||||
namespace CPUSubgraphTestsDefinitions {
|
||||
template <typename NodeType, typename... Args>
|
||||
static std::shared_ptr<ov::Node> make_layer_with_bias(Args&&... args) {
|
||||
const auto node = std::make_shared<NodeType>(std::forward<Args>(args)...);
|
||||
const auto& precision = node->get_output_element_type(0);
|
||||
const auto bias_const = ov::test::utils::deprecated::make_constant<float>(precision, ov::Shape{}, {}, true);
|
||||
const auto bias = std::make_shared<ov::opset10::Add>(node, bias_const);
|
||||
return bias;
|
||||
}
|
||||
|
||||
/*
|
||||
Parameter(4D)
|
||||
|
|
||||
Reshape(3D)
|
||||
|
|
||||
Transpose(0, 2, 1)
|
||||
|
|
||||
MatMul
|
||||
|
|
||||
Transpose(0, 2, 1)
|
||||
|
|
||||
Reshape(4D)
|
||||
|
|
||||
GroupConvolution
|
||||
|
|
||||
Reshape(3D)
|
||||
|
|
||||
Transpose(0, 2, 1)
|
||||
|
|
||||
MatMul
|
||||
*/
|
||||
|
||||
struct ExpectedResult {
|
||||
size_t expected_reshape_count;
|
||||
size_t expected_transpose_count;
|
||||
size_t expected_reorder_count;
|
||||
};
|
||||
|
||||
using MergeTransposeReorderTestParams = std::tuple<InputShape, ExpectedResult>;
|
||||
class MergeTransposeReorderCPUTest : public testing::WithParamInterface<MergeTransposeReorderTestParams>, virtual public SubgraphBaseTest, public CPUTestsBase {
|
||||
public:
|
||||
static std::string getTestCaseName(const testing::TestParamInfo<MergeTransposeReorderTestParams> &obj) {
|
||||
InputShape input_shape;
|
||||
ExpectedResult expected_result;
|
||||
std::tie(input_shape, expected_result) = obj.param;
|
||||
|
||||
std::ostringstream results;
|
||||
results << "IS=(" << ov::test::utils::partialShape2str({input_shape.first}) << "_";
|
||||
results << ")_TS=(";
|
||||
for (const auto& static_shape : input_shape.second) {
|
||||
results << ov::test::utils::vec2str(static_shape) << "_";
|
||||
}
|
||||
results << ")_reshape_count=" << expected_result.expected_reshape_count;
|
||||
results << "_transpose_count=" << expected_result.expected_transpose_count;
|
||||
results << "_reorder_count=" << expected_result.expected_reorder_count;
|
||||
return results.str();
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetUp() override {
|
||||
targetDevice = ov::test::utils::DEVICE_CPU;
|
||||
InputShape input_shape;
|
||||
std::tie(input_shape, m_expected_result) = this->GetParam();
|
||||
init_input_shapes({input_shape});
|
||||
|
||||
const auto precision = ov::element::f32;
|
||||
const auto shapeof_subgraph_prc = ov::element::i32;
|
||||
OPENVINO_ASSERT(inputDynamicShapes[0].rank().is_static() && inputDynamicShapes[0].size() == 4, "initSubgraph: only 4D shapes are supported");
|
||||
OPENVINO_ASSERT(inputDynamicShapes[0][1].is_static(), "initSubgraph: only static channels dim is supported");
|
||||
|
||||
const auto param = std::make_shared<ov::opset10::Parameter>(precision, inputDynamicShapes[0]);
|
||||
const auto reshape_const_1 = ov::opset10::Constant::create(shapeof_subgraph_prc, {3}, {0, 0, -1});
|
||||
const auto reshape_1 = std::make_shared<ov::opset10::Reshape>(param, reshape_const_1, true);
|
||||
|
||||
const auto transpose_const_1 = ov::opset10::Constant::create(shapeof_subgraph_prc, {3}, {0, 2, 1});
|
||||
const auto transpose_1 = std::make_shared<ov::opset10::Transpose>(reshape_1, transpose_const_1);
|
||||
|
||||
const size_t channels = inputDynamicShapes[0][1].get_length();
|
||||
const size_t fc_out_channels = 512;
|
||||
const auto fc_weights_1 = ov::test::utils::deprecated::make_constant<float>(precision, ov::Shape{fc_out_channels, channels}, {}, true);
|
||||
const auto fc_1 = make_layer_with_bias<ov::opset10::MatMul>(transpose_1, fc_weights_1, false, true);
|
||||
|
||||
const auto transpose_const_2 = ov::opset10::Constant::create(shapeof_subgraph_prc, {3}, {0, 2, 1});
|
||||
const auto transpose_2 = std::make_shared<ov::opset10::Transpose>(fc_1, transpose_const_2);
|
||||
const auto spatial_dims = ov::op::util::node_to_get_shape_value_of_indices_from_shape_source(param, {2, 3}, {}, shapeof_subgraph_prc);
|
||||
const auto unchangable_dims = ov::opset10::Constant::create(shapeof_subgraph_prc, {2}, {0, 0});
|
||||
const auto reshape_const_2 = ov::op::util::make_try_fold<ov::opset10::Concat>(ov::OutputVector{unchangable_dims, spatial_dims}, 0);
|
||||
const auto reshape_2 = std::make_shared<ov::opset10::Reshape>(transpose_2, reshape_const_2, true);
|
||||
|
||||
const auto conv_weights = ov::test::utils::deprecated::make_constant<float>(precision, ov::Shape{fc_out_channels, 1, 1, 3, 3}, {}, true);
|
||||
const auto conv_with_bias = make_layer_with_bias<ov::opset10::GroupConvolution>(reshape_2,
|
||||
conv_weights,
|
||||
ov::Strides{1, 1},
|
||||
ov::CoordinateDiff{1, 1},
|
||||
ov::CoordinateDiff{1, 1},
|
||||
ov::Strides{1, 1});
|
||||
// It's necessary to force acdb layout to be sure that the reorder, which changes dims order, will be inserted
|
||||
// (by default acdb layout is chosen only on >= AVX512 platforms)
|
||||
const auto conv = conv_with_bias->get_input_node_shared_ptr(0);
|
||||
const auto acdb_format = CPUTestUtils::cpu_memory_format_t::acdb;
|
||||
conv->get_rt_info() = makeCPUInfo({acdb_format}, {acdb_format}, {});
|
||||
|
||||
const auto dim_h = ov::op::util::node_to_get_shape_value_of_indices_from_shape_source(param, {2}, {}, shapeof_subgraph_prc);
|
||||
const auto dim_w = ov::op::util::node_to_get_shape_value_of_indices_from_shape_source(param, {3}, {}, shapeof_subgraph_prc);
|
||||
const auto fused_spatial_dims = ov::op::util::make_try_fold<ov::opset10::Multiply>(dim_h, dim_w);
|
||||
const auto reshape_const_3 = ov::op::util::make_try_fold<ov::opset10::Concat>(ov::OutputVector{unchangable_dims, fused_spatial_dims}, 0);
|
||||
const auto reshape_3 = std::make_shared<ov::opset10::Reshape>(conv_with_bias, reshape_const_3, true);
|
||||
const auto transpose_const_3 = ov::opset10::Constant::create(shapeof_subgraph_prc, {3}, {0, 2, 1});
|
||||
const auto transpose_3 = std::make_shared<ov::opset10::Transpose>(reshape_3, transpose_const_3);
|
||||
|
||||
const auto fc_weights_2 = ov::test::utils::deprecated::make_constant<float>(precision, ov::Shape{channels, fc_out_channels}, {}, true);
|
||||
const auto fc_2 = make_layer_with_bias<ov::opset10::MatMul>(transpose_3, fc_weights_2, false, true);
|
||||
function = std::make_shared<ov::Model>(fc_2, ov::ParameterVector{param}, "MergeTransposeReorderModel");
|
||||
}
|
||||
|
||||
void validate_exec_graph() {
|
||||
CheckNumberOfNodesWithType(compiledModel, "Reshape", m_expected_result.expected_reshape_count);
|
||||
CheckNumberOfNodesWithType(compiledModel, "Transpose", m_expected_result.expected_transpose_count);
|
||||
CheckNumberOfNodesWithType(compiledModel, "Reorder", m_expected_result.expected_reorder_count);
|
||||
}
|
||||
|
||||
private:
|
||||
ExpectedResult m_expected_result;
|
||||
};
|
||||
|
||||
TEST_P(MergeTransposeReorderCPUTest, CompareWithRefs) {
|
||||
run();
|
||||
validate_exec_graph();
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::vector<InputShape> static_shapes = {
|
||||
InputShape{{}, {{1, 32, 16, 16}}},
|
||||
};
|
||||
|
||||
const ExpectedResult successfull_fuse_result{1, 1, 2};
|
||||
const ExpectedResult unsuccessfull_fuse_result{3, 3, 2};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(smoke_MergeTransposeReorder_static, MergeTransposeReorderCPUTest,
|
||||
::testing::Combine(::testing::ValuesIn(static_shapes),
|
||||
::testing::Values(successfull_fuse_result)),
|
||||
MergeTransposeReorderCPUTest::getTestCaseName);
|
||||
|
||||
std::vector<InputShape> dynamic_shapes = {
|
||||
InputShape{{-1, 32, -1, -1}, {{1, 32, 16, 16}}},
|
||||
InputShape{{-1, 32, 16, 16}, {{1, 32, 16, 16}}},
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(smoke_MergeTransposeReorder_dynamic, MergeTransposeReorderCPUTest,
|
||||
::testing::Combine(::testing::ValuesIn(dynamic_shapes),
|
||||
::testing::Values(unsuccessfull_fuse_result)),
|
||||
MergeTransposeReorderCPUTest::getTestCaseName);
|
||||
} // namespace
|
||||
} // namespace CPUSubgraphTestsDefinitions
|
||||
|
|
@ -3,10 +3,12 @@
|
|||
//
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <common_test_utils/test_common.hpp>
|
||||
|
||||
#include "dummy_node.hpp"
|
||||
#include "graph.h"
|
||||
#include "nodes/reorder.h"
|
||||
#include "nodes/input.h"
|
||||
#include "nodes/reorder.h"
|
||||
#include "nodes/transpose.h"
|
||||
|
||||
#include "openvino/op/transpose.hpp"
|
||||
|
|
@ -16,52 +18,88 @@
|
|||
#include "common_test_utils/node_builders/constant.hpp"
|
||||
|
||||
using namespace ov::intel_cpu;
|
||||
using LOOK = Edge::LOOK;
|
||||
|
||||
struct Result {
|
||||
size_t transpose_count;
|
||||
size_t optimized_reorder_count;
|
||||
size_t not_optimized_reorder_count;
|
||||
};
|
||||
|
||||
struct MergeTransposeReorderTestParam {
|
||||
LayoutType firstNodeLayout;
|
||||
LOOK firstNodeInplaceDirection;
|
||||
LayoutType lastNodeLayout;
|
||||
LOOK lastNodeInplaceDirection;
|
||||
size_t num_consumers;
|
||||
Result test_result;
|
||||
};
|
||||
|
||||
using MergeTransposeReorderTestParams = std::tuple<ov::Shape, MergeTransposeReorderTestParam>;
|
||||
|
||||
class MergeTransposeReorderCPUTest : public testing::WithParamInterface<MergeTransposeReorderTestParams>,
|
||||
public ov::test::TestsCommon {
|
||||
public:
|
||||
void Validate() const {
|
||||
const auto& result = std::get<1>(GetParam()).test_result;
|
||||
CheckTransposeCount(result.transpose_count);
|
||||
CheckReorderCount(result.optimized_reorder_count, result.not_optimized_reorder_count);
|
||||
}
|
||||
|
||||
class MergeTransposeReordersCPUTest : public ::testing::Test {
|
||||
protected:
|
||||
/* graph typology
|
||||
---------
|
||||
|Input |
|
||||
---------
|
||||
|
|
||||
----------
|
||||
| Dummy | <*NOTE: fake node with laytout NCSP, and inplace from upstream*>
|
||||
----------
|
||||
|
|
||||
|---------------|
|
||||
| ---------- |
|
||||
| |Transpose| |
|
||||
| --------- |
|
||||
| | |
|
||||
| --------- |
|
||||
| |Reorder | | <*NOTE: Reorder is inheristically inserted since Multiply is asking NSPC input.*>
|
||||
| --------- |
|
||||
|---------------|
|
||||
|
|
||||
-----------
|
||||
| Dummy | <*NOTE: fake node with laytout NSPC, and inplace from downstream*>
|
||||
-----------
|
||||
|
|
||||
---------
|
||||
|Output |
|
||||
---------
|
||||
void SetUp() override {
|
||||
const auto& shape = std::get<0>(GetParam());
|
||||
const auto& params = std::get<1>(GetParam());
|
||||
CreateGraph(shape,
|
||||
params.firstNodeLayout,
|
||||
params.firstNodeInplaceDirection,
|
||||
params.lastNodeLayout,
|
||||
params.lastNodeInplaceDirection,
|
||||
params.num_consumers);
|
||||
}
|
||||
|
||||
/* graph topology
|
||||
┌───────┐
|
||||
│ Input │
|
||||
└───┬───┘
|
||||
│
|
||||
┌───┴───┐
|
||||
│ Dummy │ <*NOTE: fake node with firstNodeLayout, and firstNodeInplaceDirection*>
|
||||
└───┬───┘
|
||||
│
|
||||
┌────┴────┐
|
||||
│Transpose│ <*NOTE: Reorder is inserted before/after Transpose depending on first/second node layouts.*>
|
||||
└────┬────┘
|
||||
│
|
||||
┌───┴───┐
|
||||
│ Dummy │ <*NOTE: fake node with lastNodeLayout, and lastNodeInplaceDirection*>
|
||||
└───┬───┘
|
||||
│
|
||||
┌────┴───┐
|
||||
│ Output │
|
||||
└────────┘
|
||||
*/
|
||||
void CreateGraph(int num_consumers, int consumer_in_place_direction) {
|
||||
//
|
||||
void CreateGraph(const ov::Shape& testShape,
|
||||
LayoutType firstNodeLayout,
|
||||
LOOK firstNodeInplaceDirection,
|
||||
LayoutType lastNodeLayout,
|
||||
LOOK lastNodeInplaceDirection,
|
||||
size_t num_consumers) {
|
||||
Config conf;
|
||||
conf.rtCacheCapacity = 100;
|
||||
auto context = std::make_shared<GraphContext>(conf, nullptr, false);
|
||||
const dnnl::engine cpuEngine = context->getEngine();
|
||||
|
||||
m_graph = std::unique_ptr<Graph>(new Graph());
|
||||
|
||||
const auto precision = ov::element::f32;
|
||||
OPENVINO_ASSERT(testShape.size() == 4 || testShape.size() == 3, "Only 4D and 3D shapes are supported");
|
||||
// ov::Model with only a transpose node
|
||||
ov::ParameterVector params{std::make_shared<ov::op::v0::Parameter>(testPrec, ov::Shape(testShape))};
|
||||
auto order = std::vector<int32_t>{0, 3, 1, 2};
|
||||
ov::ParameterVector params{std::make_shared<ov::op::v0::Parameter>(precision, testShape)};
|
||||
auto order = testShape.size() == 4 ? std::vector<int32_t>{0, 3, 1, 2} : std::vector<int32_t>{0, 2, 1};
|
||||
auto constOrder = ov::test::utils::deprecated::make_constant(ov::element::i32, {order.size()}, order);
|
||||
auto transpose = std::make_shared<ov::op::v1::Transpose>(params[0], constOrder);
|
||||
ov::ResultVector results;
|
||||
for (int i = 0; i < num_consumers; i++)
|
||||
for (size_t i = 0; i < num_consumers; i++)
|
||||
results.push_back(std::make_shared<ov::op::v0::Result>(transpose));
|
||||
|
||||
// Replicate
|
||||
|
|
@ -78,32 +116,26 @@ protected:
|
|||
|
||||
auto inputNode = std::make_shared<node::Input>(params[0], context);
|
||||
|
||||
// dummy ncsp + inPlace LOOK_UP
|
||||
auto dummyNode1 = std::make_shared<cpu_unit_test::DummyNode>(
|
||||
testShape, testPrec, "reshape", "DummyNode", context, LayoutType::ncsp, Edge::LOOK::LOOK_UP);
|
||||
testShape, precision, "reshape", "DummyNode", context, firstNodeLayout, firstNodeInplaceDirection);
|
||||
|
||||
auto orderNode = std::make_shared<node::Input>(constOrder, context); // const order
|
||||
auto orderNode = std::make_shared<node::Input>(constOrder, context);
|
||||
auto transposeNode = std::make_shared<node::Transpose>(transpose, context);
|
||||
transposeNode->filterSupportedPrimitiveDescriptors();
|
||||
|
||||
|
||||
addEdge(inputNode, dummyNode1, 0, 0);
|
||||
addEdge(dummyNode1, transposeNode, 0, 0);
|
||||
addEdge(orderNode, transposeNode, 0, 1);
|
||||
|
||||
// dummy nspc + inPlace LOOK_DOWN
|
||||
const ov::Shape shape_tranpose{testShape[0],
|
||||
testShape[3],
|
||||
testShape[1],
|
||||
testShape[2]}; // shape after transpose
|
||||
for (int i = 0; i < num_consumers; i++) {
|
||||
auto dummyConsumer = std::make_shared<cpu_unit_test::DummyNode>(shape_tranpose,
|
||||
testPrec,
|
||||
const auto& transpose_shape = transpose->get_output_shape(0);
|
||||
for (size_t i = 0; i < num_consumers; i++) {
|
||||
auto dummyConsumer = std::make_shared<cpu_unit_test::DummyNode>(transpose_shape,
|
||||
precision,
|
||||
"multiply",
|
||||
"DummyNode",
|
||||
context,
|
||||
LayoutType::nspc,
|
||||
consumer_in_place_direction);
|
||||
lastNodeLayout,
|
||||
lastNodeInplaceDirection);
|
||||
auto outputNode = std::make_shared<node::Input>(results[i], context);
|
||||
addEdge(transposeNode, dummyConsumer, 0, 0);
|
||||
addEdge(dummyConsumer, outputNode, 0, 0);
|
||||
|
|
@ -119,71 +151,53 @@ protected:
|
|||
m_graph->CreateGraph(graphNodes, graphEdges, context, "fused_graph");
|
||||
}
|
||||
|
||||
// helper to check if Transpose node is fused.
|
||||
void CheckTransposeCount(const size_t expectedTransposeCount) const {
|
||||
const std::vector<NodePtr>& graph_nodes = m_graph->GetNodes();
|
||||
size_t actualTransposeCount = 0;
|
||||
for (auto &node : graph_nodes) {
|
||||
void CheckTransposeCount(size_t ref_transpose_count) const {
|
||||
size_t transpose_count = 0;
|
||||
for (auto &node : m_graph->GetNodes()) {
|
||||
if (node->getType() == Type::Transpose) {
|
||||
actualTransposeCount++;
|
||||
transpose_count++;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(expectedTransposeCount, actualTransposeCount);
|
||||
ASSERT_EQ(ref_transpose_count, transpose_count);
|
||||
}
|
||||
|
||||
// helper to check isOptimized of Reorder node with a part of its name
|
||||
void CheckReorderOptimized(const std::string &patial_name, const bool expectedOptimized) const {
|
||||
const std::vector<NodePtr>& graph_nodes = m_graph->GetNodes();
|
||||
size_t actualCount = 0;
|
||||
for (auto &node : graph_nodes) {
|
||||
auto reorder_node = std::dynamic_pointer_cast<node::Reorder>(node);
|
||||
if (reorder_node && node->getName().find(patial_name) != std::string::npos) {
|
||||
ASSERT_EQ(expectedOptimized, reorder_node->getOptimized());
|
||||
actualCount++;
|
||||
void CheckReorderCount(size_t ref_optimized_reorder_count, size_t ref_not_optimized_reorder_count) const {
|
||||
size_t optimized_count = 0;
|
||||
size_t not_optimized_count = 0;
|
||||
for (auto &node : m_graph->GetNodes()) {
|
||||
if (auto reorder_node = std::dynamic_pointer_cast<node::Reorder>(node)) {
|
||||
if (reorder_node->getOptimized())
|
||||
optimized_count++;
|
||||
else
|
||||
not_optimized_count++;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(1, actualCount);
|
||||
ASSERT_EQ(ref_optimized_reorder_count, optimized_count);
|
||||
ASSERT_EQ(ref_not_optimized_reorder_count, not_optimized_count);
|
||||
}
|
||||
|
||||
protected:
|
||||
const ov::element::Type_t testPrec = ov::element::Type_t::f32;
|
||||
const ov::Shape testShape{1, 3, 8, 16};
|
||||
|
||||
private:
|
||||
std::unique_ptr<Graph> m_graph;
|
||||
}; // class MergeTransposeReordersCPUTest
|
||||
}; // class MergeTransposeReorderCPUTest
|
||||
|
||||
// upstream node or downstream node is inPlaced thereby the inserted Reorder cannot be optimized.
|
||||
TEST_F(MergeTransposeReordersCPUTest, smoke_Run_MergeTransposeReorders_isOptimized) {
|
||||
CreateGraph(1, Edge::LOOK::LOOK_DOWN);
|
||||
CheckTransposeCount(0);
|
||||
CheckReorderOptimized(std::string("_fake"), false); // the fused node is of name "reshape_abcd_acdb_fake"
|
||||
TEST_P(MergeTransposeReorderCPUTest, smoke_Run_MergeTransposeReorder) {
|
||||
Validate();
|
||||
}
|
||||
|
||||
// 3 non-inplace consumers share a single optimized reorder fused with Transpose
|
||||
TEST_F(MergeTransposeReordersCPUTest, smoke_Run_MergeTransposeReorders_shared) {
|
||||
CreateGraph(3, 0);
|
||||
CheckTransposeCount(0);
|
||||
CheckReorderOptimized(std::string("_fake"), true);
|
||||
}
|
||||
const std::vector<ov::Shape> input_shapes{{1, 3, 8, 16}, {3, 8, 16}};
|
||||
|
||||
// 3 inplace consumers cannot share reorders thus transpose is not fused with reorders
|
||||
// there will be also 3 reorders between 3 dummyNode-consumers and 3 Result nodes
|
||||
TEST_F(MergeTransposeReordersCPUTest, smoke_Run_MergeTransposeReorders_notFused) {
|
||||
CreateGraph(3, Edge::LOOK::LOOK_DOWN);
|
||||
CheckTransposeCount(1);
|
||||
size_t reorderCount = 0;
|
||||
for (auto& node : m_graph->GetNodes()) {
|
||||
auto reorder_node = std::dynamic_pointer_cast<node::Reorder>(node);
|
||||
if (reorder_node) {
|
||||
// there should be no "_fake" reorders generated by merging transpose + reorder
|
||||
ASSERT_EQ(node->getName().find("_fake"), std::string::npos);
|
||||
reorderCount++;
|
||||
}
|
||||
}
|
||||
const std::vector<MergeTransposeReorderTestParam> test_params = {
|
||||
// upstream node or downstream node is inPlaced thereby the inserted Reorder cannot be optimized.
|
||||
{LayoutType::ncsp, LOOK::LOOK_UP, LayoutType::nspc, LOOK::LOOK_DOWN, 1, Result{0, 0, 2}},
|
||||
// no inplace conflict: a single optimized reorder fused with Transpose
|
||||
{LayoutType::ncsp, LOOK::LOOK_DOWN, LayoutType::nspc, LOOK::LOOK_UP, 1, Result{0, 1, 1}},
|
||||
// 3 non-inplace consumers share a single optimized reorder fused with Transpose
|
||||
{LayoutType::ncsp, LOOK::LOOK_UP, LayoutType::nspc, LOOK::LOOK_UP, 3, Result{0, 1, 3}},
|
||||
// 3 inplace consumers cannot share reorders thus transpose is not fused with reorders
|
||||
// there will be also 3 reorders between 3 dummyNode-consumers and 3 Result nodes
|
||||
{LayoutType::ncsp, LOOK::LOOK_UP, LayoutType::nspc, LOOK::LOOK_DOWN, 3, Result{1, 0, 6}},
|
||||
};
|
||||
|
||||
// 3 for layout conflist between [transpose => dummyConsumer]
|
||||
// 3 for layout conflist between [dummyConsumer => result]
|
||||
ASSERT_EQ(6, reorderCount);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(smoke_Run_MergeTransposeReorder,
|
||||
MergeTransposeReorderCPUTest,
|
||||
::testing::Combine(::testing::ValuesIn(input_shapes), ::testing::ValuesIn(test_params)));
|
||||
|
|
|
|||
Loading…
Reference in New Issue