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

1631 lines
71 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/KernelGraph/Transforms/AddPrefetch.hpp>
#include <rocRoller/KernelGraph/Transforms/AddPrefetch_detail.hpp>
#include <rocRoller/CommandSolution.hpp>
#include <rocRoller/Expression.hpp>
#include <rocRoller/KernelOptions_detail.hpp>
#include <rocRoller/KernelGraph/ControlGraph/ControlGraph.hpp>
#include <rocRoller/KernelGraph/ControlGraph/LastRWTracer.hpp>
#include <rocRoller/KernelGraph/ControlGraph/Operation.hpp>
#include <rocRoller/KernelGraph/ControlToCoordinateMapper.hpp>
#include <rocRoller/KernelGraph/CoordinateGraph/Dimension.hpp>
#include <rocRoller/KernelGraph/Transforms/Simplify.hpp>
#include <rocRoller/KernelGraph/Utils.hpp>
#include <rocRoller/KernelGraph/Visitors.hpp>
#include <rocRoller/Operations/Command.hpp>
#include <rocRoller/Operations/Operations.hpp>
/**
@class AddPrefetch
@brief Add prefetching for load operations.
# Overview
Prefetching is automatically applied to tiles loaded through LDS
within an unrolled ForLoop.
Prefetching is controlled by:
1. `bool prefetch` - Enable the prefetch transformation.
2. `int prefetchInFlight` - How many loads are put in-flight.
3. `bool prefetchMixMemOps` - Should global loads be inter-mixed with
math operations, or isolated?
4. `int prefetchLDSFactor` - Ratio of how many sub-tiles are
prefetched from LDS before the next unroll segment.
## Single prefetch
Focusing on how the two sets of buffers are operated on, we can view
the control flow of loads vertically as follows
Buf0 Buf1
============== for loop preamble
PG PG
CL
-------------- barrier
PL
============== unrolled for loop begin (counter += 2)
-=-=-=-=-=-=-= unroll u=0 segment
CV
OPR
PG CL
-------------- barrier
PL
-=-=-=-=-=-=-= unroll u=1 segment
CV
OPR
CL PG
-------------- barrier
PL
============== for loop end
where
- `PG` denotes _prefetch global_: issuing global to vgpr loads
- `CL` denotes _commit to lds_: vgpr to lds stores
- `PL` denotes _prefetch lds_: issuing lds to vgpr loads
- `CV` denotes _commit to vgpr_: waiting on lds to vgpr loads
- `OPR` denotes _operating on vgpr_: doing math (hopefully many cycles)
The order of these must be: `PG CL PL CV OPR`.
Note that:
1. Within the for-loop, there is always a barrier between a `CL` and a
`PL`.
2. Global prefetches are not stalled by barriers.
3. Within a unrolled segment; `CV` and `OPR` can be mixed. The
rocRoller scheduler will make sure, eg, appropriate wait-counts are
inserted to enforce dependencies.
4. The `PL` may be a "partial" prefetch. In this case, the remaining
loads from LDS into VGPRs happends in the immediately following
`CV`.
## Two prefetches, unrolled
With two-prefetches, we obtain
Buf0 Buf1 Buf2
======================== for loop preamble
PG PG PG
CL
------------------------ barrier
PL
======================== unrolled for loop begin (counter += 3)
-=-=-=-=-=-=-=-=-=-=-=-= unroll u=0 segment
CV
OPR
PG CL
------------------------ barrier
PL
-=-=-=-=-=-=-=-=-=-=-=-= unroll u=1 segment
CV
OPR
PG CL
------------------------ barrier
PL
-=-=-=-=-=-=-=-=-=-=-=-= unroll u=2 segment
CV
OPR
CL PG
------------------------ barrier
PL
======================== for loop end
In the single pre-fetch mode, we have $U=2$ unrolled segments. In the
two-prefetch mode, we have $U=3$ unrolled segments. In both cases,
the scheduling of operations in unrolled segment $u$ are:
-=-=-=-=-=-=-=-=-=-=-=-=-=- unroll u segment
CV [u]
OPR [u]
PG [u] CL [(u+1) % U]
--------------------------- barrier
PL [(u+1) % U]
*/
namespace rocRoller
{
namespace KernelGraph
{
namespace AddPrefetchDetail
{
namespace CF = rocRoller::KernelGraph::ControlGraph;
namespace CT = rocRoller::KernelGraph::CoordinateGraph;
using GD = rocRoller::Graph::Direction;
using namespace ControlGraph;
using namespace CoordinateGraph;
std::map<int, int> findPrefetch(KernelGraph const& kgraph)
{
std::map<int, int> rv;
auto candidates = kgraph.control.getNodes<LoadTiled>();
for(auto const& candidate : candidates)
{
auto [user, direction] = getOperationTarget(candidate, kgraph);
if(!kgraph.coordinates.get<User>(user).has_value())
continue;
auto [required, path] = findRequiredCoordinates(user, direction, kgraph);
auto forLoopCoordinates = filterCoordinates<ForLoop>(required, kgraph);
auto unrollCoordinates = filterCoordinates<Unroll>(required, kgraph);
auto maybeForLoop = findContainingOperation<ForLoopOp>(candidate, kgraph);
if(maybeForLoop.has_value())
{
if(rv.contains(*maybeForLoop))
continue;
// TODO: Only do the K-Loop for now
auto fl = kgraph.control.get<ForLoopOp>(*maybeForLoop);
if(fl->loopName != rocRoller::KLOOP)
continue;
// Prefetching requires LDS for double-buffering. Check if there are
// any StoreLDSTile operations in the loop body. Without LDS stores,
// prefetching doesn't make sense (e.g., BufferToVGPR load path).
auto isBodyPredicate = kgraph.control.isElemType<Body>();
auto isStoreLDSTile = kgraph.control.isElemType<StoreLDSTile>();
auto bodyEdges
= filter(isBodyPredicate,
kgraph.control.getNeighbours<GD::Downstream>(*maybeForLoop))
.to<std::vector>();
auto storeLDSTileNodes
= kgraph.control.findNodes(bodyEdges, isStoreLDSTile, GD::Downstream);
if(storeLDSTileNodes.empty())
{
Log::debug("KernelGraph::AddPrefetch(): ForLoop {} has no LDS stores, "
"skipping prefetch",
*maybeForLoop);
continue;
}
auto forLoopCoord = getForLoopCoords(*maybeForLoop, kgraph).first;
auto maybeUnrollCoord = findUnrollNeighbour(kgraph, forLoopCoord);
if(forLoopCoordinates.contains(forLoopCoord)
&& maybeUnrollCoord.has_value())
{
auto myUnroll
= getUnrollValueForOp(kgraph, *maybeUnrollCoord, candidate);
if(myUnroll > 0)
{
Dimension unroll
= kgraph.coordinates.get<Unroll>(*maybeUnrollCoord).value();
auto unrollSize = getUnsignedInt(evaluate(getSize(unroll)));
Log::debug("KernelGraph::AddPrefetch(): ForLoop {} is a prefetch "
"candidate: "
"{} {} ({})",
*maybeForLoop,
*maybeUnrollCoord,
unrollSize,
candidate);
rv[*maybeForLoop] = unrollSize;
}
}
}
}
return rv;
}
std::optional<int> getLoadForExchange(int exchangeTag, KernelGraph const& graph)
{
auto isLoadPredicate = [&](int operation) -> bool {
auto maybeLoad = graph.control.get<LoadTiled>(operation);
return maybeLoad.has_value();
};
auto findConnections = [&](int coordinate) -> std::optional<int> {
for(auto c : graph.mapper.getCoordinateConnections(coordinate))
if(isLoadPredicate(c.control))
return c.control;
return {};
};
auto exchangeTileTag = graph.mapper.get<MacroTile>(exchangeTag);
for(auto const edge :
graph.coordinates.getNeighbours<GD::Downstream>(exchangeTileTag))
{
auto maybeIndex = graph.coordinates.get<Index>(edge);
if(!maybeIndex)
continue;
auto indexTileTag
= only(graph.coordinates.getNeighbours<GD::Downstream>(edge)).value();
auto tag = findConnections(indexTileTag);
if(tag.has_value())
return tag;
}
return {};
}
bool isLoadForExchange(int loadTag, KernelGraph const& graph)
{
if(not(graph.control.get<LoadTiled>(loadTag)
or graph.control.get<LoadLDSTile>(loadTag)))
return false;
auto isExchangePredicate = [&](int operation) -> bool {
auto maybeExchange = graph.control.get<Exchange>(operation);
return maybeExchange.has_value();
};
auto checkConnections = [&](int coordinate) -> bool {
for(auto c : graph.mapper.getCoordinateConnections(coordinate))
if(isExchangePredicate(c.control))
return true;
return false;
};
auto tileTag = graph.mapper.get<MacroTile>(loadTag);
if(checkConnections(tileTag))
return true;
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)).value();
if(checkConnections(indexTileTag))
return true;
}
return false;
}
bool IsDirectLoadToVGPR(KernelGraph const& k, int loadTag)
{
const auto maybeLoad = k.control.get<LoadTiled>(loadTag);
if(!maybeLoad)
return false;
auto macroTileTag = k.mapper.get<MacroTile>(loadTag);
const auto maybeMacroTile = k.coordinates.get<MacroTile>(macroTileTag);
if(!maybeMacroTile)
return false;
const auto macroTile = maybeMacroTile.value();
const auto top = getTopSetCoordinate(k, loadTag);
auto isStoreLDSTilePredicate = k.control.isElemType<StoreLDSTile>();
auto storeLDSTileNodes
= k.control.findNodes(top, isStoreLDSTilePredicate, GD::Downstream);
const auto isLoadInLDSPathViaVGPR = std::any_of(
storeLDSTileNodes.begin(), storeLDSTileNodes.end(), [&](int storeTag) {
return macroTileTag == k.mapper.get<MacroTile>(storeTag);
});
return !isLoadInLDSPathViaVGPR;
}
std::vector<int> FindTopSetCoordOfLoadLDSTileOpsInCurrentSegment(
KernelGraph const& graph, int currentSegmentStart, int currentSegmentEnd)
{
auto isLoadLDSTile = graph.control.isElemType<LoadLDSTile>();
auto edgePredicate = [&](int edgeTag) {
for(auto neighbour : graph.control.getNeighbours(edgeTag, GD::Downstream))
{
if(currentSegmentEnd == neighbour)
return false;
}
return true;
};
auto topSetCoordOfLoadLDSTileOps
= graph.control
.depthFirstVisit(currentSegmentStart, edgePredicate, GD::Downstream)
.filter(isLoadLDSTile)
.map([&graph](int tag) { return getTopSetCoordinate(graph, tag); })
.to<std::vector>();
AssertFatal(
not topSetCoordOfLoadLDSTileOps.empty(),
" AddPrefetch: Expected at least one LoadLDSTile in the current segment");
return topSetCoordOfLoadLDSTileOps;
}
}
namespace CF = rocRoller::KernelGraph::ControlGraph;
namespace CT = rocRoller::KernelGraph::CoordinateGraph;
using GD = rocRoller::Graph::Direction;
using namespace ControlGraph;
using namespace CoordinateGraph;
using namespace Expression;
using namespace Register;
using namespace AddPrefetchDetail;
/**
* Add prefetch transformer.
*/
struct AddPrefetchVisitor
{
AddPrefetchVisitor(CommandParametersPtr params, ContextPtr context)
: m_params(params)
, m_context(context)
{
}
void commitForLoop(KernelGraph& graph,
NaryArgumentColouring const& colouring,
int forLoop,
int numUnroll);
void orderLoadsBeforeMultiplies(KernelGraph& graph, int forLoop, int numUnroll);
void stage(KernelGraph const& graph);
void commit(KernelGraph&, NaryArgumentColouring const&);
private:
// Keys are: ForLoop tag, Unroll value/segment, LDS tag
std::map<int, std::map<int, std::map<int, LDSOperationInfo>>> m_info;
std::map<int, int> m_scopes;
std::map<int, int> m_prefetchLoops;
std::map<int, std::map<int, std::unordered_set<int>>> m_prefetchUnrollBodyStarts;
std::map<int, std::map<int, std::unordered_set<int>>> m_prefetchUnrollBodyEnds;
std::map<int, std::map<int, std::set<std::tuple<int, int, int>>>>
m_prefetchFromLDSChains;
std::map<int, std::map<int, std::vector<int>>> m_loadFromLDSChains;
std::map<int, std::unordered_set<int>> m_prefetchDelete;
std::map<int, int> m_exchangeSegment;
std::map<int, std::map<int, std::vector<int>>> m_deferredToOrder;
struct LoadTiledInfoComparator
{
bool operator()(const LoadTiledInfo& a, const LoadTiledInfo& b) const
{
return a.top < b.top;
}
};
// Global Memory To VGPR loads not part of a to LDS path nor loads for exchange operations
// ForLoop -> Unroll value -> list of {topSetCoordinate, loadTag}
std::map<int, std::map<int, std::set<LoadTiledInfo, LoadTiledInfoComparator>>>
m_directLoads;
CommandParametersPtr m_params;
ContextPtr m_context;
};
KernelGraph AddPrefetch::apply(KernelGraph const& original)
{
auto graph = original;
removeRedundantBodyEdges(graph);
removeRedundantNOPs(graph);
auto colouring = colourByNaryArgument(graph);
if(m_params->prefetch)
{
auto visitor = AddPrefetchVisitor(m_params, m_context);
AssertFatal(m_params->unrollK > 1, "KLoop must be unrolled when prefetching.");
visitor.stage(graph);
visitor.commit(graph, colouring);
}
removeRedundantSequenceEdges(graph);
return graph;
}
void AddPrefetchVisitor::commit(KernelGraph& k, NaryArgumentColouring const& colouring)
{
Log::debug("KernelGraph::AddPrefetch()::commit()");
for(auto [forLoop, numUnroll] : m_prefetchLoops)
{
commitForLoop(k, colouring, forLoop, numUnroll);
}
removeRedundantSequenceEdges(k);
removeRedundantBodyEdges(k);
}
/**
* @brief Order loads before Multiplies
*/
void AddPrefetchVisitor::orderLoadsBeforeMultiplies(KernelGraph& graph, int forLoop, int u)
{
auto starts = m_prefetchUnrollBodyStarts[forLoop][u];
auto isLoadPredicate = [&graph](int x) {
return graph.control.get<LoadTiled>(x).has_value()
|| graph.control.get<LoadLDSTile>(x).has_value();
};
auto isMultiplyPredicate = graph.control.isElemType<Multiply>();
auto isLHSRHSPredicate = [](Connections::ConnectionSpec const& spec) -> bool {
auto nary = std::visit(
rocRoller::overloaded{[](Connections::JustNaryArgument arg) {
return std::optional<NaryArgument>{arg.argument};
},
[](Connections::TypeAndNaryArgument arg) {
return std::optional<NaryArgument>{arg.argument};
},
[](auto x) { return std::optional<NaryArgument>{}; }},
spec);
if(!nary.has_value())
return false;
return *nary == NaryArgument::LHS || *nary == NaryArgument::RHS
|| *nary == NaryArgument::LHS_SCALE || *nary == NaryArgument::RHS_SCALE;
};
std::map<int, int> loadMap{};
for(auto loadTag : graph.control.findNodes(starts, isLoadPredicate))
{
auto tileTag = graph.mapper.get<MacroTile>(loadTag);
loadMap[tileTag] = getTopSetCoordinate(graph, loadTag);
}
auto isExchangePredicate = graph.control.isElemType<Exchange>();
for(auto exchangeTag : graph.control.findNodes(starts, isExchangePredicate))
{
for(auto conn : graph.mapper.getConnections(exchangeTag))
{
auto coord = only(graph.coordinates.getOutputNodeIndices(conn.coordinate,
CT::isEdge<CT::Index>))
.value_or(conn.coordinate);
if(not loadMap.contains(coord))
continue;
Log::debug("Adding load-before-exchange Sequence edge from {} to {} for {}",
loadMap[coord],
exchangeTag,
toString(conn.connection));
graph.control.addElement(Sequence(), {loadMap[coord]}, {exchangeTag});
}
m_prefetchUnrollBodyStarts[forLoop][u].erase(exchangeTag);
}
for(auto exchangeTag : graph.control.findNodes(starts, isExchangePredicate))
{
auto destTileTag = graph.mapper.get(exchangeTag, NaryArgument::DEST);
auto pred = m_context->kernelOptions()->scaleSkipPermlane
!= rocRoller::ScaleSkipPermlaneMode::None
? CT::isEdge<Segment>
: CT::isEdge<Index>;
auto tileTags
= graph.coordinates.getInputNodeIndices(destTileTag, pred).to<std::vector>();
AssertFatal(!tileTags.empty(), "swizzle indexed tiles not found");
for(auto tileTag : tileTags)
loadMap[tileTag] = getTopSetCoordinate(graph, exchangeTag);
}
for(auto multiplyTag : graph.control.findNodes(starts, isMultiplyPredicate))
{
for(auto conn : graph.mapper.getConnections(multiplyTag))
{
// Multiply arguments may have been loaded by LDS
// prefetching, or may be single-scales from a
// LoadSGPR operations.
//
// These arguments won't be in the loadMap, and we
// don't need to order them here.
if(not loadMap.contains(conn.coordinate))
continue;
if(not isLHSRHSPredicate(conn.connection))
continue;
Log::debug("Adding load-before-multiply Sequence edge from {} to {} for {}",
loadMap[conn.coordinate],
multiplyTag,
toString(conn.connection));
graph.control.addElement(Sequence(), {loadMap[conn.coordinate]}, {multiplyTag});
}
m_prefetchUnrollBodyStarts[forLoop][u].erase(multiplyTag);
}
}
void AddPrefetchVisitor::commitForLoop(KernelGraph& graph,
NaryArgumentColouring const& colouring,
int forLoop,
int numUnroll)
{
auto logger = rocRoller::Log::getLogger();
logger->debug("KernelGraph::AddPrefetch()::commitForLoop({})", forLoop);
AssertFatal(isOperation<ForLoopOp>(graph.control.getElement(forLoop)));
auto forLoopCoord = getForLoopCoords(forLoop, graph).first;
auto unrollCoord = findUnrollNeighbour(graph, forLoopCoord).value();
std::vector<NaryArgument> argumentOrder = {NaryArgument::RHS,
NaryArgument::RHS_SCALE,
NaryArgument::LHS,
NaryArgument::LHS_SCALE};
auto sortBy = [&](auto& container, auto const& order, auto getter) {
std::stable_sort(
container.begin(), container.end(), [&](const auto& a, const auto& b) {
auto itA = std::find(
order.begin(), order.end(), colouring.coordinateColour.at(getter(a)));
AssertFatal(itA != order.end(), ShowValue(getter(a)));
auto itB = std::find(
order.begin(), order.end(), colouring.coordinateColour.at(getter(b)));
AssertFatal(itB != order.end(), ShowValue(getter(b)));
return itA < itB;
});
};
//
// Delete connecting edges
//
for(auto tag : m_prefetchDelete[forLoop])
{
if(graph.control.exists(tag))
graph.control.deleteElement(tag);
}
// At this point, each of the unrolled loop bodies are
// detached and isolated from the rest of the graph.
std::map<int, std::vector<LDSOperationInfo>> loadsByUnroll;
for(int u = 0; u < numUnroll; u++)
{
for(auto [target, info] : m_info[forLoop][u])
loadsByUnroll[u].push_back(info);
sortBy(loadsByUnroll[u], argumentOrder, [](const auto& info) { return info.user; });
}
AssertFatal(loadsByUnroll.size() == numUnroll,
ShowValue(loadsByUnroll.size()),
ShowValue(numUnroll));
std::map<int, std::vector<LoadTiledInfo>> directLoadsByUnroll;
for(int u = 0; u < numUnroll; u++)
{
for(auto load : m_directLoads[forLoop][u])
directLoadsByUnroll[u].push_back(load);
// TODO: Order by argument type?
}
const auto numDirectLoads = directLoadsByUnroll.size();
AssertFatal(numDirectLoads == 0 or numDirectLoads == numUnroll,
ShowValue(numDirectLoads),
ShowValue(numUnroll));
//
// Add Scope above the ForLoop
//
if(!m_scopes.contains(forLoop))
{
m_scopes[forLoop]
= replaceWith(graph, forLoop, graph.control.addElement(Scope()), false);
}
auto scope = m_scopes[forLoop];
//
// Prefetch before ForLoop
//
auto preNOP = graph.control.addElement(NOP());
graph.control.addElement(Sequence(), {preNOP}, {forLoop});
std::vector<int> preChain;
int numInFlight = m_params->prefetchInFlight;
// Loads first
for(int u = 0; u < numInFlight; ++u)
{
// Direct loads before global prefetch loads in pre-loop
if(directLoadsByUnroll.contains(u))
{
for(auto& load : directLoadsByUnroll[u])
{
logger->debug(" prefetch: pre-loop direct load: unroll {} load {} "
"of user {} with top {}.",
u,
load.tag,
load.user,
load.top);
auto loadChain = duplicateChain(graph, {load.top});
preChain.push_back(loadChain);
}
}
for(auto load : loadsByUnroll[u])
{
logger->debug(
" prefetch: pre-loop global load: unroll {} user {}", u, load.user);
auto loadChain = duplicateChain(graph, {load.globalChain});
preChain.push_back(loadChain);
}
}
// StoreLDS next
auto storeLDScounter = 0;
auto direct2LDSCounter = 0;
for(auto load : loadsByUnroll[0])
{
logger->debug(" prefetch: pre-loop commit lds: unroll {} user {}", 0, load.user);
auto storeChain = duplicateChain(graph, {load.ldsChain});
preChain.push_back(storeChain);
storeLDScounter++;
auto op = std::get<Operation>(graph.control.getElement(load.globalOperation));
if(std::holds_alternative<LoadTiled>(op))
{
auto macroTile = graph.coordinates.get<MacroTile>(
graph.mapper.get<MacroTile>(load.globalOperation));
if(macroTile && macroTile->memoryType == MemoryType::WAVE_Direct2LDS)
{
direct2LDSCounter++;
}
}
}
auto prefetchDirect2LDS
= ((direct2LDSCounter > 0) && (storeLDScounter == direct2LDSCounter));
graph.control.addElement(Body(), {scope}, {preChain[0]});
for(uint i = 1; i < preChain.size(); ++i)
{
graph.control.addElement(Sequence(), {preChain[i - 1]}, {preChain[i]});
}
auto addLDSPrefetchChains = [&](int u, int pre, int post, bool duplicate) -> int {
std::vector<int> prefetchChain;
std::map<std::tuple<NaryArgument, int>, std::vector<int>> ldsByArgAndSmallK;
std::map<NaryArgument, std::set<int>> ldsPrefetchValues;
for(auto [target, smallk, chain] : m_prefetchFromLDSChains[forLoop][u])
{
int dchain = duplicate ? duplicateChain(graph, {chain}) : chain;
AssertFatal(colouring.operationColour.contains(chain), ShowValue(chain));
auto argument = colouring.operationColour.at(chain);
ldsByArgAndSmallK[{argument, smallk}].push_back(dchain);
ldsPrefetchValues[argument].insert(smallk);
}
/*
*/
auto mixDataAndScale = [&](auto dataArgument, auto scaleArgument) {
// Count unique lds-prefetch unroll (small-k) values
int numDataPrefetchValues = ldsPrefetchValues[dataArgument].size();
int numScalePrefetchValues = ldsPrefetchValues[scaleArgument].size();
// Get number of data/scale chains (grows with number of jammed wavetiles)
size_t numDataChains = 0;
if(numDataPrefetchValues > 0)
{
int minDataK = *ldsPrefetchValues[dataArgument].begin();
numDataChains = ldsByArgAndSmallK.at({dataArgument, minDataK}).size();
}
size_t numScaleChains = 0;
if(numScalePrefetchValues > 0)
{
int minScaleK = *ldsPrefetchValues[scaleArgument].begin();
numScaleChains = ldsByArgAndSmallK.at({scaleArgument, minScaleK}).size();
}
if(numScaleChains > 0)
{
auto numDataPerScale = numDataChains / numScaleChains;
for(int idxScale = 0; idxScale < numScaleChains; ++idxScale)
{
for(int idxData = 0; idxData < numDataPerScale; ++idxData)
{
for(int k = 0; k < numDataPrefetchValues; ++k)
prefetchChain.push_back(
ldsByArgAndSmallK.at({dataArgument, k})
.at(idxScale * numDataPerScale + idxData));
}
for(int k = 0; k < numScalePrefetchValues; ++k)
prefetchChain.push_back(
ldsByArgAndSmallK.at({scaleArgument, k}).at(idxScale));
}
}
else
{
for(int idxData = 0; idxData < numDataChains; ++idxData)
{
for(int k = 0; k < numDataPrefetchValues; ++k)
prefetchChain.push_back(
ldsByArgAndSmallK.at({dataArgument, k}).at(idxData));
}
}
};
mixDataAndScale(NaryArgument::RHS, NaryArgument::RHS_SCALE);
mixDataAndScale(NaryArgument::LHS, NaryArgument::LHS_SCALE);
AssertFatal(!prefetchChain.empty());
logger->debug(" prefetch: lds prefetch: ordering {} to {} (top)",
pre,
prefetchChain.front());
graph.control.addElement(Sequence(), {pre}, {prefetchChain.front()});
for(uint i = 1; i < prefetchChain.size(); ++i)
{
logger->debug(" prefetch: lds prefetch: ordering {} to {} (chain)",
prefetchChain[i - 1],
prefetchChain[i]);
graph.control.addElement(
Sequence(), {prefetchChain[i - 1]}, {prefetchChain[i]});
}
logger->debug(" prefetch: lds prefetch: ordering {} to {} (bottom)",
prefetchChain.back(),
post);
graph.control.addElement(Sequence(), {prefetchChain.back()}, {post});
return prefetchChain.front();
};
if(!m_prefetchFromLDSChains[forLoop].empty())
addLDSPrefetchChains(0, preChain.back(), preNOP, true);
else
graph.control.addElement(Sequence(), {preChain.back()}, {preNOP});
//
// ForLoop body
//
// Update SetCoordinates for LoadTile operations
for(uint u = 0; u < numUnroll; ++u)
{
auto prefetchGlobalU = (u + numInFlight) % numUnroll;
auto prefetchCoordExpr = literal(u + numInFlight);
for(auto load : loadsByUnroll[prefetchGlobalU])
{
logger->debug(" prefetch: in-loop: set coordinate: load {} user {} expr {}",
prefetchGlobalU,
load.user,
toString(prefetchCoordExpr));
auto setPrefetchCoord = SetCoordinate(prefetchCoordExpr);
auto maybeSetCoordinate = graph.control.get<SetCoordinate>(load.globalChain);
auto loadUnrollCoord = graph.mapper.get<Unroll>(load.globalChain);
if(maybeSetCoordinate && loadUnrollCoord == unrollCoord)
{
graph.control.setElement(load.globalChain, setPrefetchCoord);
}
else
{
Throw<FatalError>("Mismatched SetCoordinate node above LoadTile.");
}
}
}
// Update SetCoordinates for direct loads operations
for(uint u = 0; u < numUnroll; ++u)
{
auto prefetchGlobalU = (u + numInFlight) % numUnroll;
for(auto load : m_directLoads[forLoop][prefetchGlobalU])
{
// for each SetCoordinate in chain
for(auto setCoordTag : graph.control.findNodes(
load.top, graph.control.isElemType<SetCoordinate>(), GD::Downstream))
{
auto setCoordOp = graph.control.getNode<SetCoordinate>(setCoordTag);
auto setCoordUnrollCoord = graph.mapper.get<Unroll>(setCoordTag);
if(setCoordUnrollCoord == unrollCoord)
{
auto prefetchCoordExpr = literal(u + numInFlight);
logger->debug(
" prefetch: in-loop: set coordinate: load {} user {} expr {}",
u,
load.user,
toString(prefetchCoordExpr));
graph.control.setElement(setCoordTag, SetCoordinate(prefetchCoordExpr));
}
}
}
}
// Build Unroll segment boundaries
std::vector<int> segmentBoundaries = {forLoop};
for(uint u = 0; u < numUnroll; ++u)
segmentBoundaries.push_back(graph.control.addElement(NOP()));
auto separateMemOps = !m_params->prefetchMixMemOps;
// Unrolled loop over prefetch segments
for(uint u = 0; u < numUnroll; ++u)
{
logger->debug(" prefetch: in-loop: segment {}", u);
// Connect the segment to the preceding segment boundary
//
// Note that the first boundary is the forLoop, and
// the remaining boundaries are NOPs. Therefore
// segmentBoundaries[u] is the "preceding" boundary.
for(auto tag : m_prefetchUnrollBodyStarts[forLoop][u])
{
if(u == 0)
graph.control.addElement(Body(), {segmentBoundaries[u]}, {tag});
else
{
auto descOfSegmentStart
= graph.control
.depthFirstVisit(
tag, graph.control.isElemType<Sequence>(), GD::Downstream)
.to<std::set>();
if(!descOfSegmentStart.contains(segmentBoundaries[u]))
{
graph.control.addElement(Sequence(), {segmentBoundaries[u]}, {tag});
}
}
}
auto toOrder = filter(graph.control.isElemType<LoadLDSTile>(),
graph.control.depthFirstVisit(segmentBoundaries[u],
Graph::Direction::Downstream))
.to<std::vector>();
orderMemoryNodes(graph, toOrder, false);
auto globalPrefetchU = (u + numInFlight) % numUnroll;
auto ldsPrefetchU = (u + 1) % numUnroll;
auto nop = separateMemOps ? graph.control.addElement(NOP()) : -1;
auto previousBoundaryStart = separateMemOps ? nop : segmentBoundaries[u];
// Order direct loads before global prefetch loads within this segment
// and chain them sequentially
if(directLoadsByUnroll.contains(globalPrefetchU))
{
auto directLoadPrefetchU = (u + numInFlight) % numUnroll;
const auto& directLoads = directLoadsByUnroll[directLoadPrefetchU];
const auto firstLoadTopSetCoord = directLoads[0].top;
if(separateMemOps)
{
graph.control.addElement(
Sequence(), {previousBoundaryStart}, {firstLoadTopSetCoord});
}
else
{
auto edge = (u == 0) ? ControlEdge{Body()} : ControlEdge{Sequence()};
graph.control.addElement(
edge, {previousBoundaryStart}, {firstLoadTopSetCoord});
}
for(size_t i = 1; i < directLoads.size(); ++i)
{
graph.control.addElement(
Sequence(), {directLoads[i - 1].top}, {directLoads[i].top});
}
previousBoundaryStart = directLoads[directLoads.size() - 1].top;
}
// Issue global loads
auto globalLoads = loadsByUnroll[globalPrefetchU];
logger->debug(" prefetch: in-loop: issue global loads {}",
globalLoads[0].globalChain);
if(separateMemOps)
{
graph.control.addElement(
Sequence(), {previousBoundaryStart}, {globalLoads[0].globalChain});
}
else
{
auto edge = (u == 0) ? ControlEdge{Body()} : ControlEdge{Sequence()};
graph.control.addElement(
edge, {previousBoundaryStart}, {globalLoads[0].globalChain});
}
logger->debug(" prefetch: in-loop: global load {} user {}",
globalPrefetchU,
globalLoads[0].user);
for(int i = 1; i < globalLoads.size(); i++)
{
graph.control.addElement(
Sequence(), {globalLoads[i - 1].globalChain}, {globalLoads[i].globalChain});
logger->debug(" prefetch: in-loop: global load {} user {}",
globalPrefetchU,
globalLoads[i].user);
}
if(separateMemOps)
{
graph.control.addElement(Sequence(),
{globalLoads[globalLoads.size() - 1].globalChain},
{segmentBoundaries[u + 1]});
}
// Commit in-flight to LDS
auto globalStores = loadsByUnroll[ldsPrefetchU];
logger->debug(" prefetch: in-loop: commit lds {} user {}",
ldsPrefetchU,
globalStores[0].user);
for(int i = 1; i < globalStores.size(); i++)
{
graph.control.addElement(
Sequence(), {globalStores[i - 1].ldsChain}, {globalStores[i].ldsChain});
logger->debug(" prefetch: in-loop: connecting: {} to {}",
globalStores[i - 1].ldsChain,
globalStores[i].ldsChain);
logger->debug(" prefetch: in-loop: commit lds {} user {}",
ldsPrefetchU,
globalStores[i].user);
}
logger->debug(" prefetch: in-loop: ordering {} to {}",
globalLoads[globalLoads.size() - 1].globalChain,
globalStores[0].ldsChain);
graph.control.addElement(Sequence(),
{globalLoads[globalLoads.size() - 1].globalChain},
{globalStores[0].ldsChain});
// Prefetch from LDS
int firstPrefetchFromLDS = -1;
if(m_prefetchFromLDSChains[forLoop].contains(ldsPrefetchU))
{
auto predecessorOfLoadLDSTileChain
= globalStores[globalStores.size() - 1].ldsChain;
// When prefetching Direct2LDS ops and mixing of memory operations is enabled,
// Direct2LDS and LoadLDSTile ops can be made parallel by connecting LoadLDSTile
// chains to the current segment boundary. Otherwise, LoadLDSTile operations
// are sequenced after Direct2LDS/StoreLDSTile.
if(prefetchDirect2LDS && m_params->prefetchMixMemOps)
{
auto predecessorBySequenceEdge
= only(graph.control.getInputNodeIndices<Sequence>(
globalLoads[0].globalChain));
auto maybeForLoop = only(
graph.control.getInputNodeIndices<Body>(globalLoads[0].globalChain));
AssertFatal(
maybeForLoop || predecessorBySequenceEdge,
"Expected prefetch global load to be connected to either the for "
"loop body or by a single sequence edge to a node, that is either the "
"segment boundary nop or a direct global load.");
predecessorOfLoadLDSTileChain = segmentBoundaries[u];
// In segment 0, make LoadLDSTile parallel with
// ForLoop --Body-> NOP --Sequence-> LDSPrefetchChain.
if(u == 0)
{
const auto nop = graph.control.addElement(NOP());
graph.control.addElement(Body(), {segmentBoundaries[u]}, {nop});
predecessorOfLoadLDSTileChain = nop;
}
graph.control.addElement(Sequence(),
{globalStores[globalStores.size() - 1].ldsChain},
{segmentBoundaries[u + 1]});
}
firstPrefetchFromLDS = addLDSPrefetchChains(ldsPrefetchU,
predecessorOfLoadLDSTileChain,
segmentBoundaries[u + 1],
false);
}
else
{
graph.control.addElement(Sequence(),
{globalStores[globalStores.size() - 1].ldsChain},
{segmentBoundaries[u + 1]});
}
// To ensure proper memory ordering, the last
// load-from-lds chain of the current segment must
// finish before the first prefetch-from-lds chain
// starts (which prefetchs for the next segment; but
// appears in the current segment)
if(firstPrefetchFromLDS != -1)
{
int lastLoadFromLDS = -1;
for(auto tagA : m_loadFromLDSChains[forLoop][u])
{
bool isLast = true;
for(auto tagB : m_loadFromLDSChains[forLoop][u])
{
if(tagA == tagB)
continue;
if(graph.control.compareNodes(rocRoller::UpdateCache, tagA, tagB)
== NodeOrdering::LeftFirst)
{
isLast = false;
break;
}
}
if(isLast)
{
lastLoadFromLDS = tagA;
break;
}
}
if(lastLoadFromLDS != -1)
{
Log::debug(" prefetch: in-loop: lds ordering {} to {}",
lastLoadFromLDS,
firstPrefetchFromLDS);
graph.control.addElement(
Sequence(), {lastLoadFromLDS}, {firstPrefetchFromLDS});
}
}
orderLoadsBeforeMultiplies(graph, forLoop, u);
auto loads
= filter(graph.control.isElemType<LoadTiled>(),
graph.control.depthFirstVisit(m_prefetchUnrollBodyStarts[forLoop][u],
Graph::Direction::Downstream))
.to<std::vector>();
toOrder.clear();
std::transform(std::begin(loads),
std::end(loads),
std::back_inserter(toOrder),
[&graph](int tag) { return getTopSetCoordinate(graph, tag); });
orderMemoryNodes(graph, loads, false);
// Connect the segment to the proceeding segment boundary
if(separateMemOps)
{
for(auto tag : m_prefetchUnrollBodyEnds[forLoop][u])
graph.control.addElement(Sequence(), {tag}, {nop});
}
else
{
for(auto tag : m_prefetchUnrollBodyEnds[forLoop][u])
graph.control.addElement(Sequence(), {tag}, {segmentBoundaries[u + 1]});
}
// When mixing memory operations and not prefetching LoadLDSTile ops,
// ensure that all LoadLDSTile operations in the current are scheduled
// before any StoreLDSTile part of prefetch load-store chains that
// are not Direct2LDS.
if(m_params->prefetchMixMemOps && m_params->prefetchLDSFactor == 0)
{
const auto currentSegmentStart = segmentBoundaries[u];
const auto currentSegmentEnd = segmentBoundaries[u + 1];
std::vector<int> nonDirect2LDSStoreLDSTileTops;
for(const auto storeLDSTileOp : globalStores)
{
auto macroTile = graph.coordinates.get<MacroTile>(
graph.mapper.get<MacroTile>(storeLDSTileOp.globalOperation));
AssertFatal(macroTile,
"Expected a MacroTile coordinate for StoreLDSTile operation.");
if(macroTile->memoryType != MemoryType::WAVE_Direct2LDS)
{
nonDirect2LDSStoreLDSTileTops.push_back(storeLDSTileOp.ldsChain);
}
}
for(const auto topSetCoordOfLoadLDSTile :
FindTopSetCoordOfLoadLDSTileOpsInCurrentSegment(
graph, currentSegmentStart, currentSegmentEnd))
{
for(const auto storeLDSTileOp : nonDirect2LDSStoreLDSTileTops)
{
graph.control.addElement(
Sequence(), {topSetCoordOfLoadLDSTile}, {storeLDSTileOp});
}
}
}
}
//
// Make exchange scale loads happen first!
//
{
auto isExchangePredicate = graph.control.isElemType<Exchange>();
auto bodies = graph.control.getOutputNodeIndices<Body>(forLoop).to<std::vector>();
auto exchanges
= graph.control.findNodes(bodies, isExchangePredicate).to<std::vector>();
std::map<int, int> scaleLoadU;
for(auto const exchangeTag : exchanges)
{
auto prefetchGlobalU
= (m_exchangeSegment[exchangeTag] + numInFlight) % numUnroll;
auto loadTag = getLoadForExchange(exchangeTag, graph);
// When loading pre-swizzled scales from LDS, no
// LoadTiled node exists.
if(!loadTag)
{
Log::debug("No matching load operation found for Exchange({}); assuming it "
"is pre-swizzled from LDS.",
exchangeTag);
continue;
}
auto const search = scaleLoadU.find(loadTag.value());
if(search == scaleLoadU.end() || search->second > prefetchGlobalU)
scaleLoadU[loadTag.value()] = prefetchGlobalU;
}
for(auto const [loadTag, u] : scaleLoadU)
{
std::unordered_set<int> orderBeforeTags;
for(auto const info : loadsByUnroll[u])
orderBeforeTags.insert(info.globalChain);
auto topOp = getTopSetCoordinate(graph, loadTag);
for(auto const orderBeforeTag : orderBeforeTags)
graph.control.addElement(Sequence(), {topOp}, {orderBeforeTag});
}
}
}
void updateExchangeColouring(std::map<int, int>& operationUnroll,
KernelGraph const& graph,
UnrollColouring const& colouring,
int forLoop,
int unrollCoord)
{
auto isMultiplyPredicate = graph.control.isElemType<Multiply>();
auto bodies = graph.control.getOutputNodeIndices<Body>(forLoop).to<std::vector>();
auto multiplyTags
= graph.control.findNodes(bodies, isMultiplyPredicate).to<std::unordered_set>();
for(auto multiplyTag : multiplyTags)
{
auto lhsExchange
= getExchangeForMultiply(graph, multiplyTag, NaryArgument::LHS_SCALE);
auto rhsExchange
= getExchangeForMultiply(graph, multiplyTag, NaryArgument::RHS_SCALE);
if(lhsExchange)
operationUnroll[*lhsExchange] = operationUnroll[multiplyTag];
if(rhsExchange)
operationUnroll[*rhsExchange] = operationUnroll[multiplyTag];
}
}
void AddPrefetchVisitor::stage(KernelGraph const& k)
{
m_prefetchLoops = findPrefetch(k);
auto colouring = colourByUnrollValue(k);
auto isBodyPredicate = k.control.isElemType<Body>();
std::map<int, int> unrollCoordSizes;
{
for(auto unrollTag : k.coordinates.getNodes<Unroll>())
{
auto unroll = k.coordinates.get<Unroll>(unrollTag).value();
unrollCoordSizes[unrollTag] = getUnsignedInt(evaluate(unroll.size));
}
}
// Map: Operation (in loop body) to Unroll coordinate value
std::map<int, int> operationUnroll;
for(auto [forLoop, numUnroll] : m_prefetchLoops)
{
auto forLoopCoord = getForLoopCoords(forLoop, k).first;
auto maybeUnrollCoord = findUnrollNeighbour(k, forLoopCoord);
AssertFatal(maybeUnrollCoord, "Prefetch with no unroll coordinate.");
auto unrollCoord = *maybeUnrollCoord;
Log::debug("AddPrefetch::stage: Unroll coordinate is {}", unrollCoord);
for(auto [opTag, opColours] : colouring.operationColour)
{
if(opColours.contains(unrollCoord))
operationUnroll[opTag] = opColours[unrollCoord];
}
updateExchangeColouring(operationUnroll, k, colouring, forLoop, unrollCoord);
}
auto alreadySeen = std::unordered_set<int>();
//
// Find global loads, LDS stores, and remaining (which
// will be top of unroll bodies)
//
for(auto [forLoop, numUnroll] : m_prefetchLoops)
{
auto bodyEdges
= filter(isBodyPredicate, k.control.getNeighbours<GD::Downstream>(forLoop))
.to<std::vector>();
// Find LDS stores and mark them for detachment
auto isStoreLDSTile = k.control.isElemType<StoreLDSTile>();
for(auto storeLDSTag :
k.control.findNodes(bodyEdges, isStoreLDSTile, GD::Downstream))
{
auto top = getTopSetCoordinate(k, storeLDSTag);
for(auto edge : k.control.getNeighbours(top, GD::Upstream))
m_prefetchDelete[forLoop].insert(edge);
for(auto edge : k.control.getNeighbours(top, GD::Downstream))
{
if(!isBodyPredicate(edge))
m_prefetchDelete[forLoop].insert(edge);
}
auto target = getLDSOperationTarget(k, storeLDSTag);
m_info[forLoop][operationUnroll[storeLDSTag]][target].ldsOperation
= storeLDSTag;
m_info[forLoop][operationUnroll[storeLDSTag]][target].ldsChain = top;
Log::debug("AddPrefetch::stage: LDS store operation {} top {} target {}",
storeLDSTag,
top,
target);
alreadySeen.insert(top);
}
// Find global loads and detach them
auto isLoadTiled = k.control.isElemType<LoadTiled>();
for(auto loadTag : k.control.findNodes(bodyEdges, isLoadTiled, GD::Downstream))
{
// If there isn't an info entry yet, then there
// isn't a matching StoreLDSTile operation. In
// this case, LDS isn't being used for this User
// coordinate; don't try pre-fetching it unless
// it is a direct load (Global Memory -> VGPR).
auto user = k.mapper.get<User>(loadTag);
if(!m_info[forLoop][operationUnroll[loadTag]].contains(user))
{
const auto isDirectLoad = IsDirectLoadToVGPR(k, loadTag);
const auto isExchangeLoad = isLoadForExchange(loadTag, k);
auto ok = m_params->prefetchScale && (isExchangeLoad || isDirectLoad);
if(m_params->prefetchMixMemOps && !ok)
{
Throw<FatalError>(
"AddPrefetch: An unexpected direct load (not through LDS) was "
"detected, "
"and memory-operation mixing is enabled. The AddPrefetch pass "
"can not continue.");
}
if(isDirectLoad && not(isExchangeLoad))
{
if(!operationUnroll.contains(loadTag))
continue;
auto top = getTopSetCoordinate(k, loadTag);
if(alreadySeen.contains(top))
continue;
auto unrollU = operationUnroll[loadTag];
Log::debug("AddPrefetch::stage: Direct load operation {} "
"top {} unroll {}.",
loadTag,
top,
unrollU);
m_directLoads[forLoop][unrollU].insert(
{.user = user, .tag = loadTag, .top = top});
for(auto edge : k.control.getNeighbours(top, GD::Upstream))
m_prefetchDelete[forLoop].insert(edge);
for(auto edge : k.control.getNeighbours(top, GD::Downstream))
{
if(!isBodyPredicate(edge))
m_prefetchDelete[forLoop].insert(edge);
}
alreadySeen.insert(top);
}
else
{
Log::debug(
"AddPrefetch::stage: Skipping global non-LDS load operation {}",
loadTag);
}
continue;
}
auto top = getTopSetCoordinate(k, loadTag);
for(auto edge : k.control.getNeighbours(top, GD::Upstream))
m_prefetchDelete[forLoop].insert(edge);
for(auto edge : k.control.getNeighbours(top, GD::Downstream))
{
if(!isBodyPredicate(edge))
m_prefetchDelete[forLoop].insert(edge);
}
m_info[forLoop][operationUnroll[loadTag]][user].user = user;
m_info[forLoop][operationUnroll[loadTag]][user].globalOperation = loadTag;
m_info[forLoop][operationUnroll[loadTag]][user].globalChain = top;
Log::debug("AddPrefetch::stage: Global load operation {} top {} user {}",
loadTag,
top,
user);
alreadySeen.insert(top);
}
}
//
// Find separator edges and mark for deletion
//
auto inOperationUnroll = [&](int x) -> bool { return operationUnroll.contains(x); };
for(auto [forLoop, numUnroll] : m_prefetchLoops)
{
for(auto bodyTop : k.control.getOutputNodeIndices<Body>(forLoop))
{
for(auto bodyElem : filter(inOperationUnroll,
k.control.depthFirstVisit(bodyTop, GD::Downstream)))
{
for(auto edge : filter(k.control.isElemType<Sequence>(),
k.control.getNeighbours<GD::Downstream>(bodyElem)))
{
auto otherElem
= only(k.control.getNeighbours<GD::Downstream>(edge)).value();
if(operationUnroll.contains(otherElem)
&& operationUnroll[otherElem] != operationUnroll[bodyElem])
{
m_prefetchDelete[forLoop].insert(edge);
}
}
}
}
}
// Find things that haven't been seen yet (so aren't
// global loads or LDS stores) and that aren't in a
// cluster, and mark them as the body-starts
for(auto [forLoop, numUnroll] : m_prefetchLoops)
{
auto headless = [&](int x) {
if(alreadySeen.contains(x))
return false;
for(auto incomingEdge : k.control.getNeighbours<GD::Upstream>(x))
if(!m_prefetchDelete[forLoop].contains(incomingEdge))
return false;
if(!operationUnroll.contains(x))
return false;
return true;
};
auto bodyEdges
= filter(isBodyPredicate, k.control.getNeighbours<GD::Downstream>(forLoop))
.to<std::vector>();
for(auto start : k.control.findNodes(bodyEdges, headless, GD::Downstream))
m_prefetchUnrollBodyStarts[forLoop][operationUnroll[start]].insert(start);
}
//
// Within each segment, determine which LoadLDSTile
// operations should be prefetched.
//
// That is, some LoadLDSTile operation from the next
// segment can be moved into (prefetched) the current
// segment.
//
int splitLDSPrefetchFactor = m_params->prefetchLDSFactor;
for(auto [forLoop, numUnroll] : m_prefetchLoops)
{
auto forLoopCoord = getForLoopCoords(forLoop, k).first;
auto maybeUnrollCoord = findUnrollNeighbour(k, forLoopCoord);
AssertFatal(maybeUnrollCoord, "Prefetch with no unroll coordinate.");
auto prefetchUnrollCoord = *maybeUnrollCoord;
auto bodyEdges
= filter(isBodyPredicate, k.control.getNeighbours<GD::Downstream>(forLoop))
.to<std::vector>();
auto isLoadLDSTile = k.control.isElemType<LoadLDSTile>();
int prefetchLDSUnrollCoord = -1;
{
// LDS prefetch colouring is based on the "small k", not jammed coordinates.
for(auto loadLDSTileTag :
k.control.findNodes(bodyEdges, isLoadLDSTile, GD::Downstream))
{
auto colour = colouring.operationColour[loadLDSTileTag];
for(auto kv : colour)
{
auto unrollCoord = kv.first;
if(unrollCoord == prefetchUnrollCoord)
continue;
auto incoming
= k.coordinates.getInputNodeIndices(unrollCoord, CT::isEdge<Split>)
.to<std::vector>();
auto incomingJammed
= filterCoordinates<JammedWaveTileNumber>(incoming, k);
if(incomingJammed.empty())
{
prefetchLDSUnrollCoord = unrollCoord;
}
}
}
}
AssertFatal(prefetchLDSUnrollCoord != -1, "Can not find LDS prefetch coordinate.");
Log::debug("AddPrefetch::stage: LDS prefetch: factor {} Unroll "
"coordinate {} size {}",
splitLDSPrefetchFactor,
prefetchLDSUnrollCoord,
unrollCoordSizes[prefetchLDSUnrollCoord]);
for(auto loadLDSTileTag :
k.control.findNodes(bodyEdges, isLoadLDSTile, GD::Downstream))
{
auto colour = colouring.operationColour[loadLDSTileTag];
auto u = colour[prefetchUnrollCoord];
auto prefetchLDSUnrollValue = colour[prefetchLDSUnrollCoord];
auto loadLDSTileChain = getTopSetCoordinate(k, loadLDSTileTag);
if(splitLDSPrefetchFactor == 0)
{
// Keep load in same segment
m_loadFromLDSChains[forLoop][u].push_back(loadLDSTileChain);
continue;
}
if(splitLDSPrefetchFactor > 0)
{
if(prefetchLDSUnrollValue
>= unrollCoordSizes[prefetchLDSUnrollCoord] / splitLDSPrefetchFactor)
{
// Keep load in same segment
Log::debug(
"AddPrefetch::stage: LDS load operation {} keep (unroll value {})",
loadLDSTileTag,
prefetchLDSUnrollValue);
m_loadFromLDSChains[forLoop][u].push_back(loadLDSTileChain);
continue;
}
}
// Move load to previous segment (it is prefetchable)
Log::debug("AddPrefetch::stage: LDS load operation {} move (unroll value {})",
loadLDSTileTag,
prefetchLDSUnrollValue);
auto [target, direction] = getOperationTarget(loadLDSTileTag, k);
m_prefetchFromLDSChains[forLoop][u].insert(
{target, prefetchLDSUnrollValue, loadLDSTileChain});
m_prefetchUnrollBodyStarts[forLoop][u].erase(loadLDSTileChain);
for(auto inEdge : k.control.getNeighbours<GD::Upstream>(loadLDSTileChain))
{
m_prefetchDelete[forLoop].insert(inEdge);
}
for(auto outEdge :
filter(k.control.isElemType<Sequence>(),
k.control.getNeighbours<GD::Downstream>(loadLDSTileChain)))
{
if(m_prefetchDelete[forLoop].contains(outEdge))
continue;
auto outNode
= only(k.control.getNeighbours<GD::Downstream>(outEdge)).value();
m_prefetchUnrollBodyStarts[forLoop][u].insert(outNode);
m_prefetchDelete[forLoop].insert(outEdge);
}
}
}
// Mark exchanges
for(auto [forLoop, numUnroll] : m_prefetchLoops)
{
auto isExchangePredicate = k.control.isElemType<Exchange>();
auto bodies = k.control.getOutputNodeIndices<Body>(forLoop).to<std::vector>();
auto exchangeTags
= k.control.findNodes(bodies, isExchangePredicate).to<std::unordered_set>();
for(auto exchangeTag : exchangeTags)
m_exchangeSegment[exchangeTag] = operationUnroll[exchangeTag];
}
//
// Find tail end of each unrolled loop body
//
for(auto [forLoop, numUnroll] : m_prefetchLoops)
{
for(auto u = 0; u < numUnroll; ++u)
{
auto starts = m_prefetchUnrollBodyStarts[forLoop][u];
int fLoop = forLoop;
auto onlyFollowSequenceEdges = [&](int x) -> bool {
auto isSequence = CF::isEdge<Sequence>(k.control.getElement(x));
auto willDelete = m_prefetchDelete[fLoop].contains(x);
return isSequence && !willDelete;
};
for(auto op :
k.control.depthFirstVisit(starts, onlyFollowSequenceEdges, GD::Downstream))
{
auto outgoing = k.control.getNeighbours(op, GD::Downstream);
for(auto tag : m_prefetchDelete[forLoop])
{
std::erase(outgoing, tag);
}
if(outgoing.empty())
{
m_prefetchUnrollBodyEnds[forLoop][u].insert(op);
}
}
}
}
}
ConstraintStatus AcceptablePrefetchNodes(const KernelGraph& k)
{
TIMER(t, "Constraint::AcceptablePrefetchNodes");
ConstraintStatus retval;
for(auto [forLoop, numUnroll] : findPrefetch(k))
{
for(auto bodyTop : k.control.getOutputNodeIndices<Body>(forLoop))
{
for(auto bodyElem : k.control.depthFirstVisit(bodyTop, GD::Downstream))
{
if(k.control.get<Body>(bodyElem) || k.control.get<Sequence>(bodyElem)
|| k.control.get<SetCoordinate>(bodyElem)
|| k.control.get<LoadTiled>(bodyElem)
|| k.control.get<LoadLDSTile>(bodyElem)
|| k.control.get<StoreLDSTile>(bodyElem)
|| k.control.get<Exchange>(bodyElem) || k.control.get<Multiply>(bodyElem)
|| k.control.get<NOP>(bodyElem))
{
continue;
}
auto op = k.control.getNode(bodyElem);
retval.combine(false,
concatenate("Found unsupported node ",
toString(op),
" (",
bodyElem,
") in forloop ",
forLoop,
"."));
}
}
}
return retval;
}
}
}