Files
TheRock/rocm-libraries/shared/rocroller/lib/source/KernelGraph/ControlGraph/ControlGraph.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

537 lines
19 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/KernelGraph/ControlGraph/ControlGraph.hpp>
#include <rocRoller/Utilities/Settings.hpp>
#include <rocRoller/Utilities/Timer.hpp>
#include <bitset>
#include <cmath>
#include <iomanip>
namespace rocRoller::KernelGraph::ControlGraph
{
std::unordered_map<int, std::unordered_map<int, NodeOrdering>>
ControlGraph::nodeOrderTable() const
{
populateOrderCache();
std::unordered_map<int, std::unordered_map<int, NodeOrdering>> table;
for(auto const& [node, orders] : m_orderCache)
{
for(int other : orders.after)
{
if(node < other)
table[node][other] = NodeOrdering::LeftFirst;
}
for(int other : orders.inBody)
{
if(node < other)
table[node][other] = NodeOrdering::RightInBodyOfLeft;
else
table[other][node] = NodeOrdering::LeftInBodyOfRight;
}
}
return table;
}
std::string ControlGraph::nodeOrderTableString(std::set<int> const& nodes) const
{
populateOrderCache();
if(nodes.empty())
{
return "Empty order cache.\n";
}
std::ostringstream msg;
int width = std::ceil(std::log10(static_cast<float>(*nodes.rbegin())));
width = std::max(width, 3);
msg << std::setw(width) << " "
<< "\\";
for(int n : nodes)
msg << " " << std::setw(width) << n;
for(int i : nodes)
{
msg << std::endl << std::setw(width) << i << "|";
for(int j : nodes)
{
if(i == j)
{
msg << " ";
auto oldFill = msg.fill('-');
msg << std::setw(width) << "-";
msg.fill(oldFill);
}
else
{
msg << " " << std::setw(width) << abbrev(lookupOrder(CacheOnly, i, j));
}
}
msg << " | " << std::setw(width) << i;
}
msg << std::endl
<< std::setw(width) << " "
<< "|";
for(int n : nodes)
msg << " " << std::setw(width) << n;
msg << std::endl;
return msg.str();
}
std::string ControlGraph::nodeOrderTableString() const
{
populateOrderCache();
TIMER(t, "nodeOrderTable");
std::set<int> nodes;
for(auto const& [node, orders] : m_orderCache)
{
if(!orders.after.empty() or !orders.before.empty() or !orders.inBody.empty()
or !orders.containing.empty())
{
nodes.insert(node);
}
for(int n : orders.after)
nodes.insert(n);
for(int n : orders.before)
nodes.insert(n);
for(int n : orders.inBody)
nodes.insert(n);
for(int n : orders.containing)
nodes.insert(n);
}
return nodeOrderTableString(nodes);
}
void ControlGraph::clearCache(Graph::GraphModification modification)
{
Hypergraph<Operation, ControlEdge, false>::clearCache(modification);
if(modification == Graph::GraphModification::AddElement
&& m_cacheStatus != CacheStatus::Invalid)
{
// If adding a new element and order is non-empty (partial or valid)
m_cacheStatus = CacheStatus::Partial;
}
else
{
m_orderCache.clear();
m_cacheStatus = CacheStatus::Invalid;
}
m_descendentCache.clear();
}
bool ControlGraph::isModificationAllowed(int index) const
{
if(not Settings::getInstance()->get(Settings::EnforceGraphConstraints))
return true;
if(not m_changesRestricted)
return true;
auto const& el = getElement(index);
if(std::holds_alternative<Operation>(el))
{
return std::visit(
[](auto&& arg) {
using OpType = std::decay_t<decltype(arg)>;
return !(
std::is_same_v<OpType, ForLoopOp> or std::is_same_v<OpType, SetCoordinate>);
},
std::get<Operation>(el));
}
else
{
//
// Theoretically, add/delete Body edge should be disallowed. But sometimes
// delete Body edges is OK (e.g., Simplify), and currently there is no way
// to know if this is called in a valid or invalid use case.
//
return true;
}
}
void ControlGraph::sortOrderCache() const
{
int const maxId = std::ranges::max(m_orderCache | std::views::keys);
std::vector<std::bitset<64>> bits((maxId + 64) / 64);
auto check = [&](const auto&... vec) {
(
[&]() {
for(auto v : vec)
{
int const id = v / 64;
int const remain = v & 63;
AssertFatal(!bits[id][remain],
"A node has two orders",
ShowValue(id),
ShowValue(remain),
ShowValue(v));
bits[id].set(remain);
}
}(),
...);
std::ranges::fill(bits, std::bitset<64>{});
};
for(auto& [node, orders] : m_orderCache)
{
std::ranges::sort(orders.after);
std::ranges::sort(orders.before);
std::ranges::sort(orders.inBody);
std::ranges::sort(orders.containing);
check(orders.after, orders.before, orders.inBody, orders.containing);
}
}
void ControlGraph::populateOrderCache() const
{
TIMER(t, "populateOrderCache");
if(m_cacheStatus == CacheStatus::Valid)
return;
m_orderCache.clear();
auto rootNodes = roots().to<std::vector>();
populateOrderCache(rootNodes);
sortOrderCache();
m_cacheStatus = CacheStatus::Valid;
//
// m_descendentCache is only used to help build m_orderCache,
// and it must be cleared after finish building m_orderCache
// to ensure no stale data being used when building m_orderCache
// next time.
//
m_descendentCache.clear();
}
template <CForwardRangeOf<int> Range>
std::vector<int> ControlGraph::populateOrderCache(Range const& startingNodes) const
{
std::vector<int> rv;
for(auto it = startingNodes.begin(); it != startingNodes.end(); ++it)
{
auto nodes = populateOrderCache(*it);
rv.insert(rv.end(), nodes.begin(), nodes.end());
}
return rv;
}
std::vector<int> ControlGraph::populateOrderCache(int startingNode) const
{
auto ccEntry = m_descendentCache.find(startingNode);
if(ccEntry != m_descendentCache.end())
return ccEntry->second;
static_assert(std::variant_size_v<ControlEdge> == 5,
"Currently the available edge types are Sequence(0), Initialize(1), "
"ForLoopIncrement(2), Body(3) and Else(4)."
"If more edge types are added, this function has to be updated.");
using GD = Graph::Direction;
// Edge variant indices: Sequence(0), Initialize(1), ForLoopIncrement(2), Body(3), Else(4)
std::array<std::vector<int>, std::variant_size_v<ControlEdge>> directChildren;
for(auto edge : getNeighbours<GD::Downstream>(startingNode))
{
auto edgeTypeIndex = getEdge(edge).index();
for(auto child : getNeighbours<GD::Downstream>(edge))
directChildren[edgeTypeIndex].push_back(child);
}
auto addDescendents = [this](std::vector<int> const& children) -> std::vector<int> {
auto descendents = populateOrderCache(children);
std::vector<int> result;
result.reserve(children.size() + descendents.size());
result.insert(result.end(), children.begin(), children.end());
result.insert(result.end(), descendents.begin(), descendents.end());
std::ranges::sort(result);
result.erase(std::unique(result.begin(), result.end()), result.end());
return result;
};
// Index: Initialize(1), Body(3), Else(4), ForLoopIncrement(2), Sequence(0)
auto initNodes = addDescendents(directChildren[1]);
auto bodyNodes = addDescendents(directChildren[3]);
auto elseNodes = addDescendents(directChildren[4]);
auto incNodes = addDescendents(directChildren[2]);
auto seqNodes = addDescendents(directChildren[0]);
// {init, body, else, inc} nodes are in the body of the current node
writeOrderCache({startingNode}, initNodes, NodeOrdering::RightInBodyOfLeft);
writeOrderCache({startingNode}, bodyNodes, NodeOrdering::RightInBodyOfLeft);
writeOrderCache({startingNode}, elseNodes, NodeOrdering::RightInBodyOfLeft);
writeOrderCache({startingNode}, incNodes, NodeOrdering::RightInBodyOfLeft);
// Sequence connected nodes are after the current node
writeOrderCache({startingNode}, seqNodes, NodeOrdering::LeftFirst);
// {body, else, inc, sequence} are after init nodes
writeOrderCache(initNodes, bodyNodes, NodeOrdering::LeftFirst);
writeOrderCache(initNodes, elseNodes, NodeOrdering::LeftFirst);
writeOrderCache(initNodes, incNodes, NodeOrdering::LeftFirst);
writeOrderCache(initNodes, seqNodes, NodeOrdering::LeftFirst);
// {else, inc, sequence} are after body nodes
writeOrderCache(bodyNodes, elseNodes, NodeOrdering::LeftFirst);
writeOrderCache(bodyNodes, incNodes, NodeOrdering::LeftFirst);
writeOrderCache(bodyNodes, seqNodes, NodeOrdering::LeftFirst);
// {inc, sequence} are after else nodes
writeOrderCache(elseNodes, incNodes, NodeOrdering::LeftFirst);
writeOrderCache(elseNodes, seqNodes, NodeOrdering::LeftFirst);
// sequence are after inc nodes
writeOrderCache(incNodes, seqNodes, NodeOrdering::LeftFirst);
std::vector<int> allNodes;
allNodes.reserve(initNodes.size() + bodyNodes.size() + elseNodes.size() + incNodes.size()
+ seqNodes.size());
allNodes.insert(allNodes.end(), initNodes.begin(), initNodes.end());
allNodes.insert(allNodes.end(), bodyNodes.begin(), bodyNodes.end());
allNodes.insert(allNodes.end(), elseNodes.begin(), elseNodes.end());
allNodes.insert(allNodes.end(), incNodes.begin(), incNodes.end());
allNodes.insert(allNodes.end(), seqNodes.begin(), seqNodes.end());
std::ranges::sort(allNodes);
allNodes.erase(std::unique(allNodes.begin(), allNodes.end()), allNodes.end());
m_descendentCache[startingNode] = allNodes;
return allNodes;
}
template <CForwardRangeOf<int> ARange, CForwardRangeOf<int> BRange>
void ControlGraph::writeOrderCache(ARange const& nodesA,
BRange const& nodesB,
NodeOrdering order) const
{
if(nodesA.size() == 0 or nodesB.size() == 0)
return;
auto selectVec = [](NodeOrders& orders, NodeOrdering order) -> std::vector<int>& {
switch(order)
{
case NodeOrdering::LeftFirst:
return orders.after;
case NodeOrdering::RightFirst:
return orders.before;
case NodeOrdering::RightInBodyOfLeft:
return orders.inBody;
case NodeOrdering::LeftInBodyOfRight:
return orders.containing;
default:
break;
}
AssertFatal(false, "Invalid order: ", ShowValue(order));
return orders.after; // this statement should never be reached
};
for(int a : nodesA)
{
auto& vec = selectVec(m_orderCache[a], order);
vec.insert(vec.end(), nodesB.begin(), nodesB.end());
}
auto oppositeOrder = opposite(order);
for(int b : nodesB)
{
auto& vec = selectVec(m_orderCache[b], oppositeOrder);
vec.insert(vec.end(), nodesA.begin(), nodesA.end());
}
}
void ControlGraph::writeOrderCache(int nodeA, int nodeB, NodeOrdering order) const
{
switch(order)
{
case NodeOrdering::LeftFirst:
m_orderCache[nodeA].after.push_back(nodeB);
m_orderCache[nodeB].before.push_back(nodeA);
break;
case NodeOrdering::RightFirst:
m_orderCache[nodeA].before.push_back(nodeB);
m_orderCache[nodeB].after.push_back(nodeA);
break;
case NodeOrdering::RightInBodyOfLeft:
m_orderCache[nodeA].inBody.push_back(nodeB);
m_orderCache[nodeB].containing.push_back(nodeA);
break;
case NodeOrdering::LeftInBodyOfRight:
m_orderCache[nodeA].containing.push_back(nodeB);
m_orderCache[nodeB].inBody.push_back(nodeA);
break;
default:
break;
}
}
NodeOrdering ControlGraph::lookupOrder(IgnoreCachePolicy const, int nodeA, int nodeB) const
{
TIMER(t, "ControlGraph::lookupOrder");
using GD = Graph::Direction;
std::unordered_set<int> visited_nodes;
// Decide order of A and B when A is parent of B
auto const getOrderIfParent = [&](int edge) {
return std::holds_alternative<Sequence>(getEdge(edge))
? NodeOrdering::LeftFirst
: NodeOrdering::RightInBodyOfLeft;
};
// Decide order of A and B when A and B are descendants of a node
// And order of edge type :
// Initialize -> Body -> Else -> ForLoopIncrement -> Sequence.
auto const getOrderOfDescendants = [&](int edgeA, int edgeB) {
auto edgeAElem = getEdge(edgeA);
auto edgeBElem = getEdge(edgeB);
AssertFatal(edgeAElem.index() != edgeBElem.index(),
"edgeA and edgeB types should not be the same");
return std::visit(
rocRoller::overloaded{
[&](auto const&) {
AssertFatal(false, "Unhandled edge type in getOrderOfDescendants");
return NodeOrdering::Undefined;
},
[&](Body const&) {
return std::holds_alternative<Initialize>(edgeBElem)
? opposite(NodeOrdering::LeftFirst)
: NodeOrdering::LeftFirst;
},
[&](Sequence const&) { return opposite(NodeOrdering::LeftFirst); },
[&](Initialize const&) { return NodeOrdering::LeftFirst; },
[&](ForLoopIncrement const&) {
return std::holds_alternative<Sequence>(edgeBElem)
? NodeOrdering::LeftFirst
: opposite(NodeOrdering::LeftFirst);
},
[&](Else const&) {
return std::holds_alternative<Initialize>(edgeBElem)
|| std::holds_alternative<Body>(edgeBElem)
? opposite(NodeOrdering::LeftFirst)
: NodeOrdering::LeftFirst;
}},
edgeAElem);
};
// {key, value} = {node index, edge index}
std::unordered_map<int, int> A_ancestors;
// stack
std::vector<int> stk{nodeA};
// Traverse upstream from A to collect all ancestors of A
while(!stk.empty())
{
auto node = stk.back();
stk.pop_back();
for(auto edge : getNeighbours<GD::Upstream>(node))
{
for(auto parent : getNeighbours<GD::Upstream>(edge))
{
if(parent == nodeB)
return opposite(getOrderIfParent(edge));
A_ancestors.insert({parent, edge});
if(!visited_nodes.contains(parent))
{
visited_nodes.insert(parent);
stk.push_back(parent);
}
}
}
}
AssertFatal(stk.empty());
stk.push_back(nodeB);
visited_nodes.clear();
// Traverse upstream from B to find common ancestors to decide order
while(!stk.empty())
{
auto node = stk.back();
stk.pop_back();
for(auto edge : getNeighbours<GD::Upstream>(node))
{
for(auto parent : getNeighbours<GD::Upstream>(edge))
{
if(parent == nodeA)
return getOrderIfParent(edge);
if(A_ancestors.contains(parent))
{
// If this is a common ancestor, compare the types of both edges to
// know the order
if(getEdge(A_ancestors.at(parent)).index() != getEdge(edge).index())
{
return getOrderOfDescendants(A_ancestors.at(parent), edge);
}
}
if(!visited_nodes.contains(parent))
{
visited_nodes.insert(parent);
stk.push_back(parent);
}
}
}
}
return NodeOrdering::Undefined;
}
static void validateNodes(ControlGraph const& control, int nodeA, int nodeB)
{
AssertFatal(nodeA != nodeB, ShowValue(nodeA));
AssertFatal(control.getElementType(nodeA) == Graph::ElementType::Node
&& control.getElementType(nodeB) == Graph::ElementType::Node,
ShowValue(control.getElementType(nodeA)),
ShowValue(control.getElementType(nodeB)));
}
NodeOrdering ControlGraph::compareNodes(CacheOnlyPolicy const, int nodeA, int nodeB) const
{
AssertFatal(m_cacheStatus == CacheStatus::Valid);
validateNodes(*this, nodeA, nodeB);
return lookupOrder(CacheOnly, nodeA, nodeB);
}
NodeOrdering ControlGraph::compareNodes(UpdateCachePolicy const, int nodeA, int nodeB) const
{
if(m_cacheStatus != CacheStatus::Valid)
populateOrderCache();
return compareNodes(CacheOnly, nodeA, nodeB);
}
NodeOrdering
ControlGraph::compareNodes(UseCacheIfAvailablePolicy const, int nodeA, int nodeB) const
{
validateNodes(*this, nodeA, nodeB);
return m_cacheStatus == CacheStatus::Valid ? compareNodes(CacheOnly, nodeA, nodeB)
: compareNodes(IgnoreCache, nodeA, nodeB);
}
NodeOrdering ControlGraph::compareNodes(IgnoreCachePolicy const, int nodeA, int nodeB) const
{
validateNodes(*this, nodeA, nodeB);
return lookupOrder(IgnoreCache, nodeA, nodeB);
}
}