[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:
Vladislav Golubev 2024-03-15 10:39:14 +01:00 committed by GitHub
parent c04ed93342
commit 6f8b70f245
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 717 additions and 300 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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)));