Files
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

290 lines
12 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/KernelGraph/ControlGraph/LastRWTracer.hpp>
#include <rocRoller/KernelGraph/KernelGraph.hpp>
#include <rocRoller/KernelGraph/Transforms/OrderEpilogueBlocks.hpp>
#include <rocRoller/KernelGraph/Utils.hpp>
#include <rocRoller/KernelGraph/Visitors.hpp>
namespace rocRoller
{
namespace KernelGraph
{
/**
* @brief Order Epilogue Blocks Transformation
*
* This transformation changes:
*
* ForLoopY
* | | |
* Body Body ... Body
* | | |
* Epilogue Epilogue Epilogue
*
* To
*
* ForLoopY
* |
* Body
* |
* Scope -> Sequence -> Scope -> ... Sequence -> Scope
* | | |
* Body Body Body
* | | |
* Epilogue Epilogue Epilogue
*/
namespace OrderEpilogueBlocksNS
{
using GD = rocRoller::Graph::Direction;
using namespace ControlGraph;
/**
* @brief Delete Body edges from a parent node to all its children
*
*
*
* @param graph The kernel graph
* @param parent The parent node
* @param children The children nodes
*/
void deleteBodyEdges(KernelGraph& graph, int parent, std::vector<int> const& children)
{
for(auto const& child : children)
{
graph.control.deleteElement<Body>(std::vector<int>{parent},
std::vector<int>{child});
Log::debug(" Deleted Body edge from {} to {}", parent, child);
}
}
/**
* @brief Create a chain of Scopes connected by Sequence edges
*
*
* Creates: parent -> Body -> Scope -> Sequence -> Scope -> Sequence -> ... -> Scope
* | | |
* Body Body Body
* | | |
* children[0] children[1] children[n]
*
* @param graph The kernel graph
* @param parent The parent node to attach the first Scope to
* @param children The children nodes to place under each Scope
*/
void createScopeChain(KernelGraph& graph, int parent, std::vector<int> const& children)
{
if(children.empty())
return;
auto firstScope = graph.control.addElement(Scope());
graph.control.addElement(Body(), {parent}, {firstScope});
auto prevScope = firstScope;
Log::debug(
" Creating scope chain: parent {} -> {} scopes", parent, children.size());
for(auto i = children.begin(); i != children.end(); i++)
{
graph.control.addElement(Body(), {prevScope}, {*i});
if(std::next(i) != children.end())
{
auto nextScope = graph.control.addElement(Scope());
graph.control.addElement(Sequence(), {prevScope}, {nextScope});
prevScope = nextScope;
}
}
}
/**
* @brief Check if a ForLoopOp is or is contained within a RECEIVE loop
*/
bool isWithinReceiveLoop(KernelGraph const& graph, int forLoopTag)
{
auto containingForLoops = graph.control.nodesContaining(forLoopTag)
.filter(graph.control.isElemType<ForLoopOp>());
for(auto containingLoop : containingForLoops)
{
if(getForLoopName(const_cast<KernelGraph&>(graph), containingLoop)
== rocRoller::RECEIVE)
{
return true;
}
}
return false;
}
/**
* @brief Order fixup chains within unrolled RECEIVE loops
*
* This transformation finds fixup chains across different unrolled iterations
* and orders them sequentially using Scopes and Sequence edges, similar to
* how orderEpilogueBlocks orders epilogue blocks. This change reduces the register pressure
* by avoiding overlapping the fixup chains.
*
* Before:
* ForLoop (within RECEIVE)
* | | |
* Body Body Body
* | | |
* Fixup1 Fixup2 Fixup3
*
* After:
* ForLoop (within RECEIVE)
* |
* Body
* |
* Scope --Sequence--> Scope --Sequence--> Scope
* | | |
* Body Body Body
* | | |
* Fixup1 Fixup2 Fixup3
*/
void orderFixupChains(KernelGraph& graph, int tag)
{
Log::debug("OrderEpilogueBlocks::orderFixupChains({})", tag);
// Only order fixup chains for loops within RECEIVE
if(getForLoopName(graph, tag) != rocRoller::YLOOP
|| !isWithinReceiveLoop(graph, tag))
{
return;
}
// Get all Body children of this ForLoop (these are the unrolled iterations)
auto forChildren = graph.control.getOutputNodeIndices<Body>(tag).to<std::vector>();
if(forChildren.size() <= 1)
{
Log::debug(" Only {} body children, no need to order", forChildren.size());
return;
}
// Sort children by their execution order in the control graph
std::sort(forChildren.begin(), forChildren.end(), [&](int a, int b) {
auto order = graph.control.compareNodes(UpdateCache, a, b);
return order == NodeOrdering::LeftFirst;
});
for(auto child : forChildren)
{
Log::debug(" Sorted child: {}", child);
}
// Delete the sequence edges between consecutive SetCoordinate operations
for(size_t i = 0; i + 1 < forChildren.size(); i++)
{
auto currentNode = forChildren[i];
auto nextNode = forChildren[i + 1];
// Verify nodes are SetCoordinate operations
auto currentSetCoordinate = graph.control.get<SetCoordinate>(currentNode);
auto nextSetCoordinate = graph.control.get<SetCoordinate>(nextNode);
AssertFatal(currentSetCoordinate && nextSetCoordinate,
"SetCoordinate operation not found");
// Verify there is a sequence edge between nodes before deletion
auto neighbors = graph.control.getOutputNodeIndices<Sequence>(currentNode)
.to<std::vector>();
if(std::find(neighbors.begin(), neighbors.end(), nextNode) == neighbors.end())
Throw<FatalError>("Sequence edge not found");
graph.control.deleteElement<Sequence>(std::vector<int>{currentNode},
std::vector<int>{nextNode});
Log::debug(" Deleted Sequence edge from {} to {}", currentNode, nextNode);
}
// Delete the original Body edges from the ForLoop to the bodies
deleteBodyEdges(graph, tag, forChildren);
// Create a chain of Scopes connected by Sequence edges
createScopeChain(graph, tag, forChildren);
}
void orderEpilogueBlocks(KernelGraph& graph, int tag, UnrollColouring const& colouring)
{
rocRoller::Log::getLogger()->debug("KernelGraph::OrderEpilogueBlocks({})", tag);
//Right now we only do this for the YLOOP, this is Specific to GEMM
//TODO: Generalize beyond GEMM.
auto loopNode = graph.control.get<ForLoopOp>(tag);
if(loopNode->loopName != rocRoller::YLOOP)
{
return;
}
auto forChildren = graph.control.getOutputNodeIndices<Body>(tag).to<std::vector>();
auto stores = filter(graph.control.isElemType<StoreTiled>(),
graph.control.depthFirstVisit(forChildren, GD::Downstream))
.to<std::vector>();
//Indicates only one epilogue or not an epilogue at all.
//This is specific to GEMMs. Here we have one store per epilogue.
//TODO: Generalize beyond GEMM.
if(stores.size() <= 1 || forChildren.size() <= 1)
return;
for(auto t : forChildren)
{
Log::debug(" Ordering under {}", t);
}
auto reachable = graph.control.depthFirstVisit(forChildren, GD::Downstream)
.to<std::unordered_set>();
for(auto k : colouring.separators)
{
if(reachable.contains(k))
{
Log::debug(" Deleting separator {}", k);
graph.control.deleteElement(k);
}
}
//Delete the original Epilogue Body Edges from the loop.
deleteBodyEdges(graph, tag, forChildren);
//Connect Epilogue Blocks via Scopes and Sequences to
//order the Epilogues
createScopeChain(graph, tag, forChildren);
}
}
KernelGraph OrderEpilogueBlocks::apply(KernelGraph const& original)
{
auto graph = original;
// Exclude Unrolls that aren't associated with
// JammedWaveTileNumbers from the colouring.
auto exclude = std::unordered_set<int>();
for(auto unroll : graph.coordinates.getNodes<CoordinateGraph::Unroll>())
{
bool isJammed = false;
for(auto input :
graph.coordinates.getInputNodeIndices(unroll, [](auto) { return true; }))
{
auto maybeJammed
= graph.coordinates.get<CoordinateGraph::JammedWaveTileNumber>(input);
if(maybeJammed)
isJammed = true;
}
if(!isJammed)
exclude.insert(unroll);
}
auto colouring = colourByUnrollValue(original, -1, exclude);
for(const auto node : graph.control.getNodes<ControlGraph::ForLoopOp>())
{
OrderEpilogueBlocksNS::orderEpilogueBlocks(graph, node, colouring);
OrderEpilogueBlocksNS::orderFixupChains(graph, node);
}
return graph;
}
}
}