Files
TheRock/rocm-libraries/shared/rocroller/lib/source/KernelGraph/Transformations/PrefetchScale.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

905 lines
40 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/KernelGraph/KernelGraph.hpp>
#include <rocRoller/KernelGraph/Transforms/PrefetchScale.hpp>
#include <rocRoller/KernelGraph/Utils.hpp>
#include <rocRoller/KernelOptions_detail.hpp>
// TODO: Extract most of this to a detail header file and add tests
namespace rocRoller
{
namespace KernelGraph
{
using GD = rocRoller::Graph::Direction;
using namespace ControlGraph;
using namespace CoordinateGraph;
namespace Expression = rocRoller::Expression;
using namespace Expression;
/**
* Represents the transition point between loads with different K unroll values.
*/
struct LoadBoundary
{
int prevLoad; /**< Last load before K iteration changes */
int nextLoad; /**< First load after K iteration changes */
};
/**
* Finds where the unroll K value changes between consecutive loads.
*
* @param graph Kernel graph containing the loads
* @param colouring Maps operations to unroll values
* @param loads Load operations to scan for the unroll K value change
* @return LoadBoundary with the loads before and after the unroll K value change
*/
LoadBoundary FindLoadBoundary(KernelGraph const& graph,
UnrollColouring const& colouring,
std::vector<int> const& loads)
{
std::optional<int> unrollKVal;
std::optional<int> prevLoad;
std::optional<int> nextLoad;
for(auto const load : loads)
{
auto unrollMap = colouring.operationColour.at(load);
auto unrollKDim = graph.mapper.get<Unroll>(load, rocRoller::KLOOP_UNROLL);
if(unrollKVal && *unrollKVal != unrollMap.at(unrollKDim))
{
nextLoad = load;
break;
}
unrollKVal = unrollMap.at(unrollKDim);
prevLoad = load;
}
AssertFatal(prevLoad && nextLoad, "couldn't find a position to in-loop scale prefetch");
return LoadBoundary{*prevLoad, *nextLoad};
}
/**
* Separates loads by memory type: WAVE_SWIZZLE vs standard layouts.
*/
struct SwizzlePartition
{
std::vector<int> swizzleLoads;
std::vector<int> nonSwizzleLoads;
};
/**
* Groups loads by memory type to apply appropriate prefetch strategies.
*
* @param graph Kernel graph containing the loads
* @param loads Load operation tags to group
* @return SwizzlePartition with loads separated by memory type
*/
SwizzlePartition GroupLoadsBySwizzle(KernelGraph const& graph,
std::vector<int> const& loads)
{
SwizzlePartition result;
for(auto const loadTag : loads)
{
auto macTileTag = graph.mapper.get<MacroTile>(loadTag);
auto macTile = graph.coordinates.getNode<MacroTile>(macTileTag);
if(macTile.memoryType == MemoryType::WAVE_SWIZZLE)
result.swizzleLoads.push_back(loadTag);
else
result.nonSwizzleLoads.push_back(loadTag);
}
return result;
}
/**
* Inserts prefetched loads before the loop to enable data pre-loading.
* Chains loads with sequence edges and positions them at the K iteration boundary.
*
* @param graph Kernel graph to modify
* @param colouring Maps operations to unroll values
* @param preLoopLoads Load operations to insert before the loop
* @param loads All loads in scope, used to find the insertion boundary
*/
void InsertPreLoopLoads(KernelGraph& graph,
UnrollColouring const& colouring,
std::vector<int> const& preLoopLoads,
std::vector<int> loads)
{
auto preNOP = graph.control.addElement(NOP());
auto prev = preNOP;
for(auto const next : preLoopLoads)
{
graph.control.addElement(Sequence(), {prev}, {next});
prev = next;
}
auto boundary = FindLoadBoundary(graph, colouring, loads);
auto prevTopOp = getTopSetCoordinate(graph, boundary.prevLoad);
auto nextTopOp = getTopSetCoordinate(graph, boundary.nextLoad);
graph.control.addElement(Sequence(), {prevTopOp}, {preNOP});
graph.control.addElement(Sequence(), {prev}, {nextTopOp});
}
/**
* Inserts duplicated load chains inside the loop with incremented K coordinates.
*
* @param graph Kernel graph to modify
* @param colouring Maps operations to unroll values
* @param inLoopLoads (load chain, unroll dimension) pairs to insert
* @param forLoop For-loop where loads will be inserted
*/
void InsertInLoopLoads(KernelGraph& graph,
UnrollColouring const& colouring,
std::vector<std::pair<int, int>> const& inLoopLoads,
int forLoop)
{
auto loopLoads = filter(graph.control.isElemType<LoadTiled>(),
graph.control.depthFirstVisit(forLoop))
.to<std::vector>();
AssertFatal(!loopLoads.empty());
std::sort(loopLoads.begin(), loopLoads.end(), TopologicalCompare(graph));
auto boundary = FindLoadBoundary(graph, colouring, loopLoads);
auto postNOP = graph.control.addElement(NOP());
auto prev = postNOP;
for(auto const [loadChain, _ignore] : inLoopLoads)
{
graph.control.addElement(Sequence(), {prev}, {loadChain});
prev = loadChain;
}
auto prevTopOp = getTopSetCoordinate(graph, boundary.prevLoad);
auto nextTopOp = getTopSetCoordinate(graph, boundary.nextLoad);
graph.control.addElement(Sequence(), {prevTopOp}, {postNOP});
graph.control.addElement(Sequence(), {prev}, {nextTopOp});
for(auto const [loadChain, unrollCoord] : inLoopLoads)
{
std::optional<int> maybeOperation = loadChain;
while(maybeOperation)
{
auto operationTag = maybeOperation.value();
if(isOperation<LoadTiled>(graph.control.getElement(operationTag)))
break;
auto maybeSetCoordinate = graph.control.get<SetCoordinate>(operationTag);
AssertFatal(maybeSetCoordinate);
auto unroll = graph.mapper.get<Unroll>(operationTag);
AssertFatal(unroll > 0,
"SetCoordinate is not connected to the Unroll dimension",
ShowValue(operationTag),
ShowValue(unroll));
if(unroll == unrollCoord)
{
int unrollKSize = getUnrollSize(graph, unroll);
auto valueExpr = maybeSetCoordinate.value().value;
AssertFatal(
evaluationTimes(valueExpr)[Expression::EvaluationTime::Translate],
"SetCoordinate::value should be a literal.");
auto value = getUnsignedInt(evaluate(valueExpr));
auto newOp = SetCoordinate(Expression::literal(value + unrollKSize));
graph.control.setElement(operationTag, newOp);
break;
}
maybeOperation = only(graph.control.getOutputNodeIndices<Body>(operationTag));
}
}
}
/**
* Inserts copy operations before their associated exchanges inside the loop.
* Sorts exchanges topologically and sequences copies before them to ensure data availability.
*
* @param graph Kernel graph to modify
* @param copies Map from copy tags to their associated exchange tags
*/
void InsertInLoopCopies(KernelGraph& graph, auto const& copies)
{
for(auto const copy : copies)
{
auto exchangeTags = copy.second;
std::sort(exchangeTags.begin(), exchangeTags.end(), TopologicalCompare(graph));
insertBefore(graph, exchangeTags[0], copy.first, copy.first);
for(auto const exchangeTag : exchangeTags)
graph.control.addElement(Sequence(), {copy.first}, {exchangeTag});
}
}
/**
* Metadata for creating copy operations: data type and register requirements.
* Cached and reused across multiple loads to avoid redundant computation.
*/
struct CopyInfo
{
DataType dataType; /**< Type of values being copied */
size_t numVGPRs; /**< VGPRs required to hold the copy */
};
/**
* Computes data type and VGPR count needed to copy a load's data.
* Derives requirements from wave tile size, variable type, and packing factor.
*
* @param graph Kernel graph containing the load
* @param context
* @param loadTag Load operation to analyze
* @return CopyInfo with data type and VGPR count
*/
CopyInfo GetCopyInfo(KernelGraph const& graph, ContextPtr context, int loadTag)
{
auto waveTileTag = graph.mapper.get<WaveTile>(loadTag);
auto waveTile = graph.coordinates.get<WaveTile>(waveTileTag);
auto elements = waveTile.value().elements();
auto varType = getVariableType(graph, loadTag);
// TODO: This assumes that eventually (after
// LoadPacked) the incoming data will be packed
auto maybePacked = DataTypeInfo::Get(varType).packedVariableType();
if(maybePacked)
varType = *maybePacked;
auto packFactor = DataTypeInfo::Get(varType).packing;
uint wfs = context->kernel()->wavefront_size();
return CopyInfo{varType.dataType, elements / wfs / packFactor};
}
/**
* Creates an Assign operation that copies loaded data into new VGPRs.
* Creates a DataFlow edge from source to destination macro tile.
*
* @param graph Kernel graph to modify
* @param context
* @param loadTag Load operation (used to compute copyInfo if not cached)
* @param macTileTag Source macro tile with original data
* @param copyInfo Optional cached copy information; computed if not provided
* @return (copy operation tag, destination macro tile tag)
*/
std::pair<int, int> CreateCopy(KernelGraph& graph,
ContextPtr context,
int loadTag,
int macTileTag,
std::optional<CopyInfo>& copyInfo)
{
if(!copyInfo)
copyInfo = GetCopyInfo(graph, context, loadTag);
auto copyExpr = std::make_shared<Expression::Expression>(
Expression::DataFlowTag{macTileTag, Register::Type::Vector, copyInfo->dataType});
auto copyTag = graph.control.addElement(
Assign{Register::Type::Vector, copyExpr, copyInfo->numVGPRs});
auto destMacTileTag = graph.coordinates.addElement(MacroTile());
// macTile is being copied into destMacTile through this assign node
graph.coordinates.addElement(DataFlow(), {macTileTag}, {destMacTileTag});
graph.mapper.connect(copyTag, destMacTileTag, NaryArgument::DEST);
return std::make_pair(copyTag, destMacTileTag);
}
/**
* Redirects exchanges to use the copied destination tile instead of the original source.
* Rewires Index edges from source to destination and records exchanges for proper sequencing.
*
* @param graph Kernel graph to modify
* @param loadTag Unused, kept for consistency
* @param macTileTag Source macro tile originally used by exchanges
* @param destMacTileTag Destination macro tile for exchanges to use
* @param copyTag Copy operation used as key in inLoopCopies
* @param inLoopCopies Output map of exchange tags associated with each copy
*/
void UpdateExchangeMacroTiles(KernelGraph& graph,
int loadTag,
int macTileTag,
int destMacTileTag,
int copyTag,
std::map<int, std::vector<int>>& inLoopCopies)
{
auto location = graph.coordinates.getLocation(macTileTag);
for(auto const& input : location.incoming)
{
auto edge = graph.coordinates.getElement(input);
auto maybeIndex = graph.coordinates.get<Index>(input);
if(!maybeIndex)
continue;
auto exchangeTileTag
= only(graph.coordinates.getNeighbours<Graph::Direction::Upstream>(input));
AssertFatal(exchangeTileTag,
"Expected a single upstream Exchange neighbour for: ",
ShowValue(input));
graph.coordinates.deleteElement(input);
graph.coordinates.addElement(edge, {*exchangeTileTag}, {destMacTileTag});
std::optional<int> exchangeTag;
for(auto const c : graph.mapper.getCoordinateConnections(*exchangeTileTag))
{
auto maybeExchange = graph.control.get<Exchange>(c.control);
if(maybeExchange)
{
exchangeTag = c.control;
break;
}
}
AssertFatal(exchangeTag,
"Expected an Exchange connection for: ",
ShowValue(*exchangeTileTag));
auto exchangeTopOp = getTopSetCoordinate(graph, *exchangeTag);
inLoopCopies[copyTag].push_back(exchangeTopOp);
}
}
/**
* Implements prefetch-scale for WAVE_SWIZZLE loads using pre-loop copies.
* Moves first iteration outside loop, creates copies, and duplicates load chains inside loop.
* Used when macKUnrollSize * unrollKSize is divisible by factorK.
*
* @param graph Kernel graph to transform
* @param context
* @param loads All loads in scope
* @param loopLoads Loads inside loop to transform
* @param colouring Maps operations to unroll values
*/
void PrefetchScaleLoadsImpl(KernelGraph& graph,
ContextPtr context,
std::vector<int> const& loads,
std::vector<int> const& loopLoads,
UnrollColouring const& colouring)
{
std::vector<int> preLoopLoads;
std::map<int, std::vector<int>> inLoopCopies;
std::vector<std::pair<int, int>> inLoopLoads;
auto forLoop = -1;
std::optional<CopyInfo> copyInfo;
auto partition = GroupLoadsBySwizzle(graph, loopLoads);
auto& swizzleLoads = partition.swizzleLoads;
for(auto const loadTag : swizzleLoads)
{
auto macTileTag = graph.mapper.get<MacroTile>(loadTag);
// the swizzle scale loads must be inside the loop K
auto maybeForLoop = findContainingOperation<ForLoopOp>(loadTag, graph);
AssertFatal(maybeForLoop);
AssertFatal(forLoop == -1 || forLoop == *maybeForLoop,
ShowValue(forLoop),
ShowValue(*maybeForLoop));
forLoop = *maybeForLoop;
auto [copyTag, destMacTileTag]
= CreateCopy(graph, context, loadTag, macTileTag, copyInfo);
auto topOp = getTopSetCoordinate(graph, loadTag);
replaceWith(graph, topOp, graph.control.addElement(NOP()), false);
preLoopLoads.push_back(topOp);
auto inLoopLoad = duplicateChain(graph, {topOp});
graph.control.addElement(Sequence(), {copyTag}, {inLoopLoad});
auto unrollDim = graph.mapper.get<Unroll>(loadTag, rocRoller::KLOOP_UNROLL);
inLoopLoads.push_back(std::make_pair(inLoopLoad, unrollDim));
UpdateExchangeMacroTiles(
graph, loadTag, macTileTag, destMacTileTag, copyTag, inLoopCopies);
}
if(preLoopLoads.empty())
return;
AssertFatal(!loads.empty(),
"WAVE_SWIZZLE loads require non-empty loads for prefetch positioning");
InsertPreLoopLoads(graph, colouring, preLoopLoads, loads);
AssertFatal(forLoop != -1);
InsertInLoopLoads(graph, colouring, inLoopLoads, forLoop);
InsertInLoopCopies(graph, inLoopCopies);
}
/**
* Finds all exchange operations connected to a load's macro tile.
*
* @param loadTag Load operation whose exchanges to find
* @param graph Kernel graph containing the load and exchanges
* @return Exchange operation tags associated with the load
*/
std::vector<int> GetExchangesForLoad(int loadTag, KernelGraph const& graph)
{
auto isExchangePredicate = [&](int operation) -> bool {
auto maybeExchange = graph.control.get<Exchange>(operation);
return maybeExchange.has_value();
};
auto findConnections = [&](int coordinate) -> std::vector<int> {
std::vector<int> exchanges;
for(auto c : graph.mapper.getCoordinateConnections(coordinate))
if(isExchangePredicate(c.control))
exchanges.push_back(c.control);
return exchanges;
};
auto tileTag = graph.mapper.get<MacroTile>(loadTag);
if(auto exchanges = findConnections(tileTag); !exchanges.empty())
return exchanges;
std::vector<int> exchanges;
for(auto edge : graph.coordinates.getNeighbours<GD::Upstream>(tileTag))
{
auto maybeIndex = graph.coordinates.get<Index>(edge);
if(!maybeIndex)
continue;
auto indexTileTag = *only(graph.coordinates.getNeighbours<GD::Upstream>(edge));
if(auto temp = findConnections(indexTileTag); !temp.empty())
exchanges.insert(exchanges.end(), temp.begin(), temp.end());
}
return exchanges;
}
/**
* Insertion positions for prefetch and exchange operations.
* Indexed by sub-iteration, relative to top SetCoordinate of non-swizzle loads.
*/
struct PrefetchPositions
{
std::vector<int> prefetchPosition; /**< Where to insert prefetch loads */
std::vector<int> exchangePosition; /**< Where to insert exchanges and copies */
};
/**
* Computes insertion positions for prefetch and exchange operations.
* Analyzes non-swizzle loads to find unroll K value boundaries and tracks loop entry.
*
* @param graph Kernel graph containing the loads
* @param colouring Maps operations to unroll values
* @param nonSwizzleLoads Non-WAVE_SWIZZLE loads used as position anchors
* @param kLoopLoadSet Loads that are inside the K loop (used to distinguish
* pre-loop loads from K loop body loads)
* @return PrefetchPositions indexed by sub-iteration
*/
PrefetchPositions DeterminePrefetchPositions(KernelGraph const& graph,
UnrollColouring const& colouring,
std::vector<int> const& nonSwizzleLoads,
std::unordered_set<int> const& kLoopLoadSet)
{
std::vector<int> prefetchPosition;
std::vector<int> exchangePosition;
std::optional<int> prevPosition;
std::optional<int> unrollKVal;
bool isInsideLoop = false;
for(auto const loadTag : nonSwizzleLoads)
{
auto unrollMap = colouring.operationColour.at(loadTag);
auto unrollKDim = graph.mapper.get<Unroll>(loadTag, rocRoller::KLOOP_UNROLL);
auto subiter = unrollMap.at(unrollKDim);
// Detect if we've entered the K loop (not just any ForLoopOp)
if(!isInsideLoop)
{
if(kLoopLoadSet.contains(loadTag))
isInsideLoop = true;
}
auto topOp = getTopSetCoordinate(graph, loadTag);
if(!unrollKVal || *unrollKVal != subiter)
{
unrollKVal = subiter;
prefetchPosition.push_back(topOp);
// Only add to exchangePosition once we're inside the loop
if(isInsideLoop && prevPosition)
exchangePosition.push_back(*prevPosition);
prevPosition = topOp;
}
}
if(prevPosition)
exchangePosition.push_back(*prevPosition);
AssertFatal(!nonSwizzleLoads.empty(),
"WAVE_SWIZZLE loads require non-swizzle loads for prefetch positioning");
AssertFatal(!prefetchPosition.empty(),
"Failed to determine prefetch positions from non-swizzle loads");
return PrefetchPositions{std::move(prefetchPosition), std::move(exchangePosition)};
}
/**
* Inserts prefetched loads at computed positions to enable early execution.
* Chains prefetch nodes with sequence edges and inserts before computed positions.
*
* @param graph Kernel graph to modify
* @param nextIterPrefetch Map from sub-iteration to prefetch operation tags
* @param prefetchPosition Control flow positions indexed by sub-iteration
*/
void InsertPrefetchNodes(KernelGraph& graph,
std::map<int, std::vector<int>> const& nextIterPrefetch,
std::vector<int> const& prefetchPosition)
{
for(auto const [subiter, prefetchNodes] : nextIterPrefetch)
{
auto preNOP = graph.control.addElement(NOP());
auto prev = preNOP;
for(auto const next : prefetchNodes)
{
graph.control.addElement(Sequence(), {prev}, {next});
prev = next;
}
auto pos = prefetchPosition[subiter];
Log::debug(" Inserting prefetch for subiter={} at position {}", subiter, pos);
insertBefore(graph, pos, preNOP, prev);
}
}
/**
* Inserts exchanges and copies at computed positions.
*
* @param graph Kernel graph to modify
* @param exchangePrefetch Map from sub-iteration to exchange operation tags
* @param copy Map from sub-iteration to copy operation tags (may be empty)
* @param exchangePosition Control flow positions indexed by sub-iteration
*/
void InsertExchangeNodes(KernelGraph& graph,
std::map<int, std::vector<int>> const& exchangePrefetch,
std::map<int, std::vector<int>> const& copy,
std::vector<int> const& exchangePosition)
{
for(auto const [subiter, exchangeNodes] : exchangePrefetch)
{
auto preNOP = graph.control.addElement(NOP());
auto prev = preNOP;
if(!copy.empty())
{
for(auto const c : copy.at(subiter))
{
graph.control.addElement(Sequence(), {prev}, {c});
prev = c;
}
}
for(auto const next : exchangeNodes)
{
graph.control.addElement(Sequence(), {prev}, {next});
prev = next;
}
auto pos = exchangePosition[subiter];
Log::debug(" Inserting exchange for subiter={} at position {}", subiter, pos);
insertBefore(graph, pos, preNOP, prev);
}
}
/**
* Implements prefetch-scale for WAVE_SWIZZLE loads using per-unroll prefetching.
* Replaces loads with NOPs and creates prefetched versions at computed insertion points.
*
* @param graph Kernel graph to transform
* @param context
* @param loads All loads in scope
* @param loopLoads Loads inside loop
* @param colouring Maps operations to unroll values
*/
void PrefetchScaleLoadsPerUnrollImpl(KernelGraph& graph,
ContextPtr context,
std::vector<int> const& loads,
std::vector<int> const& loopLoads,
UnrollColouring const& colouring)
{
std::optional<int> numInFlight;
std::map<int, std::vector<int>> nextIterPrefetch;
std::map<int, std::vector<int>> exchangePrefetch;
std::map<int, std::vector<int>> copy;
std::optional<CopyInfo> copyInfo;
auto loopPartition = GroupLoadsBySwizzle(graph, loopLoads);
auto allPartition = GroupLoadsBySwizzle(graph, loads);
auto& swizzleLoads = loopPartition.swizzleLoads;
auto& nonSwizzleLoads = allPartition.nonSwizzleLoads;
if(swizzleLoads.empty())
return;
std::unordered_set<int> kLoopLoadSet(loopLoads.begin(), loopLoads.end());
auto [prefetchPosition, exchangePosition]
= DeterminePrefetchPositions(graph, colouring, nonSwizzleLoads, kLoopLoadSet);
for(auto const loadTag : swizzleLoads)
{
auto macTileTag = graph.mapper.get<MacroTile>(loadTag);
if(!numInFlight)
numInFlight = static_cast<int>(prefetchPosition.size());
auto unrollMap = colouring.operationColour.at(loadTag);
auto unrollKDim = graph.mapper.get<Unroll>(loadTag, rocRoller::KLOOP_UNROLL);
auto subiter = unrollMap.at(unrollKDim);
auto topOp = getTopSetCoordinate(graph, loadTag);
replaceWith(graph, topOp, graph.control.addElement(NOP()), false);
nextIterPrefetch[subiter].push_back(topOp);
if(context->kernelOptions()->scaleSkipPermlane
!= rocRoller::ScaleSkipPermlaneMode::None)
{
auto [copyTag, destMacTileTag]
= CreateCopy(graph, context, loadTag, macTileTag, copyInfo);
auto unrollKSize = getUnrollSize(graph, unrollKDim);
auto location = graph.coordinates.getLocation(macTileTag);
for(auto const& input : location.incoming)
{
auto edge = graph.coordinates.getElement(input);
auto maybeIndex = graph.coordinates.get<Index>(input);
if(!maybeIndex)
continue;
auto exchangeTileTag = only(
graph.coordinates.getNeighbours<Graph::Direction::Upstream>(input));
AssertFatal(exchangeTileTag);
graph.coordinates.deleteElement(input);
graph.coordinates.addElement(edge, {*exchangeTileTag}, {destMacTileTag});
for(auto const c : graph.mapper.getCoordinateConnections(*exchangeTileTag))
{
auto maybeExchange = graph.control.get<Exchange>(c.control);
if(maybeExchange)
{
auto exchange = c.control;
replaceWith(
graph, exchange, graph.control.addElement(NOP()), false);
exchangePrefetch[subiter].push_back(exchange);
copy[subiter].push_back(copyTag);
if(subiter == 0)
{
auto exchangeDup = duplicateControlNode(graph, exchange);
exchangePrefetch[unrollKSize].push_back(exchangeDup);
auto copyDup = duplicateControlNode(graph, copyTag);
copy[unrollKSize].push_back(copyDup);
}
break;
}
}
}
}
else
{
auto exchanges = GetExchangesForLoad(loadTag, graph);
auto unrollKSize = getUnrollSize(graph, unrollKDim);
for(auto const exchange : exchanges)
{
replaceWith(graph, exchange, graph.control.addElement(NOP()), false);
exchangePrefetch[subiter].push_back(exchange);
if(subiter == 0)
{
auto exchangeDup = duplicateControlNode(graph, exchange);
exchangePrefetch[unrollKSize].push_back(exchangeDup);
}
}
}
if(subiter < *numInFlight)
{
auto prefetchTopOp = duplicateChain(graph, {topOp});
std::optional<int> maybeOperation = prefetchTopOp;
while(maybeOperation)
{
auto operationTag = *maybeOperation;
if(isOperation<LoadTiled>(graph.control.getElement(operationTag)))
break;
auto maybeSetCoordinate = graph.control.get<SetCoordinate>(operationTag);
AssertFatal(maybeSetCoordinate);
auto unroll = graph.mapper.get<Unroll>(operationTag);
AssertFatal(unroll > 0,
"SetCoordinate is not connected to the Unroll dimension",
ShowValue(operationTag),
ShowValue(unroll));
if(unroll == unrollKDim)
{
int unrollKSize = getUnrollSize(graph, unroll);
auto valueExpr = maybeSetCoordinate.value().value;
AssertFatal(
evaluationTimes(valueExpr)[Expression::EvaluationTime::Translate],
"SetCoordinate::value should be a literal.");
auto value = getUnsignedInt(evaluate(valueExpr));
auto newOp = SetCoordinate(Expression::literal(value + unrollKSize));
graph.control.setElement(operationTag, newOp);
nextIterPrefetch[value + unrollKSize].push_back(prefetchTopOp);
break;
}
maybeOperation
= only(graph.control.getOutputNodeIndices<Body>(operationTag));
}
}
}
InsertPrefetchNodes(graph, nextIterPrefetch, prefetchPosition);
InsertExchangeNodes(graph, exchangePrefetch, copy, exchangePosition);
}
/**
* Orchestrates prefetch-scale transformation for WAVE_SWIZZLE loads in the K loop.
* Collects loads from enclosing scope and selects the appropriate prefetch strategy.
*
* @param graph Kernel graph to transform
* @param context
* @param forLoop K loop operation to transform
*/
void PrefetchScaleLoads(KernelGraph& graph, ContextPtr context, int forLoop)
{
auto stack = controlStack(forLoop, graph);
std::optional<int> scope;
for(auto iter = stack.rbegin(); iter != stack.rend(); ++iter)
{
if(graph.control.get<Scope>(*iter))
{
scope = *iter;
break;
}
}
AssertFatal(
scope.has_value(), "Could not find enclosing Scope for forLoop {}", forLoop);
auto bodyChildren = graph.control.getOutputNodeIndices<Body>(*scope).to<std::vector>();
std::unordered_set<int> loadSet;
auto dontWalkPastForLoop = [&](int edgeTag) -> bool {
auto dsts = graph.control.getNeighbours<Graph::Direction::Downstream>(edgeTag);
for(auto dst : dsts)
{
if(graph.control.get<ForLoopOp>(dst))
return false;
}
return true;
};
for(auto node :
filter(graph.control.isElemType<LoadTiled>(),
graph.control.depthFirstVisit(
bodyChildren, dontWalkPastForLoop, Graph::Direction::Downstream))
.to<std::vector>())
{
loadSet.insert(node);
}
// Only include loads that are inside the loop
std::vector<int> loopLoads;
for(auto bodyNode : graph.control.getOutputNodeIndices<Body>(forLoop))
{
auto bodyLoads = filter(graph.control.isElemType<LoadTiled>(),
graph.control.depthFirstVisit(bodyNode))
.to<std::vector>();
for(auto load : bodyLoads)
{
auto [it, inserted] = loadSet.insert(load);
if(inserted)
loopLoads.push_back(load);
}
}
std::vector<int> loads(loadSet.begin(), loadSet.end());
std::sort(loads.begin(), loads.end(), TopologicalCompare(graph));
auto swizzleLoadIt = std::find_if(loopLoads.begin(), loopLoads.end(), [&](int loadTag) {
auto macTileTag = graph.mapper.get<MacroTile>(loadTag);
auto macTile = graph.coordinates.getNode<MacroTile>(macTileTag);
return macTile.memoryType == MemoryType::WAVE_SWIZZLE;
});
if(swizzleLoadIt == loopLoads.end())
return; // No WAVE_SWIZZLE loads found in this loop
auto loadTag = *swizzleLoadIt;
auto macTileTag = graph.mapper.get<MacroTile>(loadTag);
auto macTile = graph.coordinates.getNode<MacroTile>(macTileTag);
AssertFatal(macTile.layoutType == LayoutType::MATRIX_A
|| macTile.layoutType == LayoutType::MATRIX_B,
ShowValue(macTile.layoutType));
auto unroll0 = graph.mapper.get<Unroll>(loadTag, rocRoller::XLOOP_UNROLL);
auto unroll1 = graph.mapper.get<Unroll>(loadTag, rocRoller::YLOOP_UNROLL);
auto unroll2 = graph.mapper.get<Unroll>(loadTag, rocRoller::KLOOP_UNROLL);
unsigned int xyUnrollSize = 0, macKUnrollSize = 0;
if(macTile.layoutType == LayoutType::MATRIX_A)
{
// A : M x K
xyUnrollSize = getUnrollSize(graph, unroll0);
macKUnrollSize = getUnrollSize(graph, unroll1);
}
else
{
// B : K x N
xyUnrollSize = getUnrollSize(graph, unroll1);
macKUnrollSize = getUnrollSize(graph, unroll0);
}
// if unroll2 is -1, this returns 1.
auto unrollKSize = getUnrollSize(graph, unroll2);
AssertFatal(xyUnrollSize != 0 && macKUnrollSize != 0 && unrollKSize != 0,
ShowValue(xyUnrollSize),
ShowValue(macKUnrollSize),
ShowValue(unrollKSize));
auto waveM = macTile.subTileSizes.at(0);
auto waveN = macTile.subTileSizes.at(1);
auto waveK = macTile.subTileSizes.at(2);
AssertFatal(
waveM == waveN, "waveM is not equal to waveN", ShowValue(waveM), ShowValue(waveN));
auto miM = macTile.miTileSizes.at(0);
auto miN = macTile.miTileSizes.at(1);
auto miK = macTile.miTileSizes.at(2);
AssertFatal(miM == miN, "miM is not equal to miN", ShowValue(miM), ShowValue(miN));
auto factorMN = waveM / miM;
auto factorK = waveK / miK;
auto colouring = colourByUnrollValue(graph);
if(xyUnrollSize % factorMN == 0 && macKUnrollSize % factorK == 0)
{
Log::debug(" Using PrefetchScaleLoadsPerUnrollImpl (xy={}, macK={}, factors: "
"MN={}, K={})",
xyUnrollSize,
macKUnrollSize,
factorMN,
factorK);
PrefetchScaleLoadsPerUnrollImpl(graph, context, loads, loopLoads, colouring);
}
else if(xyUnrollSize % factorMN == 0 && (macKUnrollSize * unrollKSize) % factorK == 0)
{
Log::debug(" Using PrefetchScaleLoadsImpl (xy={}, macK={}, unrollK={}, factors: "
"MN={}, K={})",
xyUnrollSize,
macKUnrollSize,
unrollKSize,
factorMN,
factorK);
PrefetchScaleLoadsImpl(graph, context, loads, loopLoads, colouring);
}
else
{
Throw<FatalError>("Prefetch Scale not supported for the given swizzled tile.");
}
}
KernelGraph PrefetchScale::apply(KernelGraph const& original)
{
auto newGraph = original;
auto root = *newGraph.control.roots().only();
std::optional<int> maybeKLoop;
for(auto const loop : filter(newGraph.control.isElemType<ForLoopOp>(),
newGraph.control.depthFirstVisit(root)))
{
auto forloop = *newGraph.control.get<ForLoopOp>(loop);
if(forloop.loopName == rocRoller::KLOOP)
{
maybeKLoop = loop;
break;
}
}
AssertFatal(maybeKLoop, "PrefetchScale requires a KLoop");
auto kLoop = *maybeKLoop;
PrefetchScaleLoads(newGraph, m_context, kLoop);
return newGraph;
}
}
}