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

430 lines
17 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
/**
@class AddLDSPadding
@brief Add element padding to LDS buffers.
Padding LDS buffers is used to reduce the number of LDS bank
conflicts.
Padding is added to flat LDS buffers. For each `LDS` node, upstream
`Flatten` edges are transformed to `Join` edges, and downstream
`Tile` edges are transformed to `Split` edges.
Recall that `Join` and `Split` edges honour the `stride` attribute of
upstream/downstream nodes. Padding is accomplished by updating the
`stride` attributes of the upstream/downstream nodes.
In particular, the slow strides are set to the fast size plus a
padding value, and the fast strides are set to 1.
For example:
MacroTileIndex MacroTileIndex
size=256 size=128
\ /
------------------
|
Flatten
|
LDS
size=32,768
|
Tile
|
------------------
/ \
MacroTileIndex MacroTileIndex
size=256 size=128
will be transformed to:
MacroTileIndex MacroTileIndex
size=256 size=128
stride=128+padding stride=1
\ /
------------------
|
Join
|
LDS
size=32,768 + 256*padding
|
Split
|
------------------
/ \
MacroTileIndex MacroTileIndex
size=256 size=128
stride=128+padding stride=1
Note that the size of the LDS allocation is computed in `getNumLDSElements()`.
This LDS padding strategy is well-suited for direct-to-lds loads; as
the assumption is that writes into LDS are contiguous across the
workitems within a workgroup.
*/
#include <rocRoller/CommandSolution.hpp>
#include <rocRoller/Expression.hpp>
#include <rocRoller/ExpressionTransformations.hpp>
#include <rocRoller/KernelGraph/KernelGraph.hpp>
#include <rocRoller/KernelGraph/Transforms/AddLDSPadding.hpp>
#include <rocRoller/KernelGraph/Transforms/AddLDSPadding_detail.hpp>
#include <rocRoller/KernelGraph/Utils.hpp>
namespace rocRoller
{
namespace KernelGraph
{
using GD = Graph::Direction;
using namespace Expression;
using namespace ControlGraph;
using namespace CoordinateGraph;
using namespace AddLDSPaddingDetail;
/**
* @brief Return the number of elements that the fastest thread-tile indexes.
*/
uint GetFastThreadTileIndexElementWidth(KernelGraph const& graph, int tileTag)
{
namespace CT = rocRoller::KernelGraph::CoordinateGraph;
auto isLoadPredicate = [&graph](auto const& x) {
return graph.control.get<LoadTiled>(x.control).has_value()
|| graph.control.get<LoadLDSTile>(x.control).has_value();
};
auto loadOp
= only(filter(isLoadPredicate, graph.mapper.getCoordinateConnections(tileTag)))
.value()
.control;
auto elementNumberTag = graph.mapper.get<CT::ElementNumber>(loadOp, 1);
auto elementNumber = graph.coordinates.get<ElementNumber>(elementNumberTag).value();
return getUnsignedInt(evaluate(elementNumber.size));
}
/**
* @brief Get the layout type and data type of the LDS node.
*
* This will traverse upstream from the LDS node until it finds
* a MacroTile node, and returns the layout type and data type
* of that MacroTile.
*
* If no MacroTile is found, an empty optional is returned.
*/
std::optional<std::pair<LayoutType, DataType>>
GetLayoutTypeAndDataType(KernelGraph const& graph, int ldsTag)
{
namespace CT = rocRoller::KernelGraph::CoordinateGraph;
std::optional<LayoutType> layoutType;
std::optional<DataType> dataType;
auto isDataFlow = [&](int tag) -> bool {
return graph.coordinates.get<CT::DataFlow>(tag).has_value();
};
auto isLoadPredicate = [&graph](int x) {
return graph.control.get<LoadTiled>(x).has_value()
|| graph.control.get<LoadLDSTile>(x).has_value();
};
auto target = ldsTag;
while(true)
{
if(not dataType)
{
for(auto conn : graph.mapper.getCoordinateConnections(target))
{
if(isLoadPredicate(conn.control))
dataType = getVariableType(graph, conn.control).dataType;
}
}
auto edge = only(
filter(isDataFlow, graph.coordinates.getNeighbours<GD::Upstream>(target)));
if(not edge)
break;
target = only(graph.coordinates.getNeighbours<GD::Upstream>(edge.value())).value();
if(not layoutType)
{
auto maybeMacroTile = graph.coordinates.get<MacroTile>(target);
if(maybeMacroTile)
layoutType = maybeMacroTile->layoutType;
}
}
if(layoutType && dataType)
return std::pair<LayoutType, DataType>{layoutType.value(), dataType.value()};
return {};
}
/**
* @brief Get load operation info.
*
* This determines the load width for each load instruction,
* and the number of lanes that "participate" in the load.
* For Direct2LDS loads, this is the workgroup size; for other
* loads, this is the wavefront size.
*/
std::pair<uint, uint> GetLoadAndLaneWidth(KernelGraph const& graph,
int ldsTag,
LayoutType layoutType,
DataType dataType,
ContextPtr context)
{
namespace CT = rocRoller::KernelGraph::CoordinateGraph;
auto isDataFlowEdge = CT::isEdge<CT::DataFlow>;
auto tileTag
= graph.coordinates.getInputNodeIndices(ldsTag, isDataFlowEdge).only().value();
auto tile = graph.coordinates.get<MacroTile>(tileTag).value();
auto isDirect2LDS = tile.memoryType == MemoryType::WAVE_Direct2LDS;
auto workgroupSize = product(context->kernel()->workgroupSize());
auto wavefrontSize = context->kernel()->wavefront_size();
auto loadElementWidth = GetFastThreadTileIndexElementWidth(graph, tileTag);
uint loadWidth = loadElementWidth * DataTypeInfo::Get(dataType).elementBits / 8u;
uint laneWidth = isDirect2LDS ? workgroupSize : wavefrontSize;
return {loadWidth, laneWidth};
}
/**
* @brief Make padded blocks for the given tags.
*/
void MakePaddedBlocks(KernelGraph& graph,
Graph::Direction direction,
int& edge,
std::array<int, 2>& tags,
uint contiguousBytes,
uint paddingBytes,
uint elementBits)
{
auto getCoordinateSize
= [&](int tag) { return getSize(graph.coordinates.getNode(tag)); };
auto slowSize = getCoordinateSize(tags[0]);
auto fastSize = getCoordinateSize(tags[1]);
auto one = literal(1u);
auto linearFlat
= graph.coordinates.addElement(Linear(simplify(slowSize * fastSize), one));
AssertFatal(contiguousBytes * 8u % elementBits == 0,
"Padding mismatch: contiguous-bytes is not divisible by elementBits.",
ShowValue(contiguousBytes),
ShowValue(elementBits));
AssertFatal(paddingBytes * 8u % elementBits == 0,
"Padding mismatch: padding-bytes is not divisible by elementBits.",
ShowValue(paddingBytes),
ShowValue(elementBits));
auto newFastSize = literal(contiguousBytes * 8u / elementBits);
auto newSlowSize = simplify(slowSize * fastSize / newFastSize);
auto newSlowStride = simplify(newFastSize + literal(paddingBytes * 8u / elementBits));
auto linearSlow = graph.coordinates.addElement(Linear(newSlowSize, newSlowStride));
auto linearFast = graph.coordinates.addElement(Linear(newFastSize, one));
Log::debug("KernelGraph::AddLDSPadding::MakePaddedBlocks: "
"slowSize {}, fastSize {}, newSlowSize {}, newSlowStride {}, newFastSize {}",
toString(slowSize),
toString(fastSize),
toString(newSlowSize),
toString(newSlowStride),
toString(newFastSize));
// If tags are upstream...
if(direction == GD::Upstream)
{
auto downstreamTag = *only(graph.coordinates.getNeighbours<GD::Downstream>(edge));
graph.coordinates.deleteElement(edge);
graph.coordinates.addElement(Flatten(), {tags[0], tags[1]}, {linearFlat});
graph.coordinates.addElement(Tile(), {linearFlat}, {linearSlow, linearFast});
edge = graph.coordinates.addElement(
Join(), {linearSlow, linearFast}, {downstreamTag});
}
else
{
auto upstreamTag = *only(graph.coordinates.getNeighbours<GD::Upstream>(edge));
graph.coordinates.deleteElement(edge);
edge = graph.coordinates.addElement(
Split(), {upstreamTag}, {linearSlow, linearFast});
graph.coordinates.addElement(Flatten(), {linearSlow, linearFast}, {linearFlat});
graph.coordinates.addElement(Tile(), {linearFlat}, {tags[0], tags[1]});
}
// Update tags to point to the new Linear nodes, these will be padded
tags = {linearSlow, linearFast};
}
/**
* @brief Add LDS padding transformer.
*/
struct AddLDSPaddingVisitor
{
AddLDSPaddingVisitor(ContextPtr context, CommandParametersPtr params)
: m_context(context)
, m_params(params)
{
}
void stage(KernelGraph const&, int);
void commit(KernelGraph&);
private:
std::pair<uint, uint> getLDSPaddingElements(KernelGraph const&,
LDSPaddingInfo const&) const;
ContextPtr m_context;
CommandParametersPtr m_params;
std::map<int, LDSPaddingInfo> m_ldsTags;
};
/**
* @brief Get the number of padding elements for a given LDS
* node.
*
* If the padding is set to -1, this will compute a default
* padding value.
*/
std::pair<uint, uint>
AddLDSPaddingVisitor::getLDSPaddingElements(KernelGraph const& graph,
LDSPaddingInfo const& info) const
{
if(m_params->ldsPadding.contains(info.layoutType))
{
int contiguousBytes, paddingBytes;
std::tie(contiguousBytes, paddingBytes) = m_params->ldsPadding.at(info.layoutType);
if(contiguousBytes == -1)
{
Throw<FatalError>("Automatic padding not implemented yet.");
// Note: For direct-to-lds, this is correct:
//
// contiguousBytes = info.loadInstructionByteWidth * info.loadLaneWidth;
//
// For other loads, this makes blocks as wide as a
// wave will load, which might not be optimal. It
// may not be proportional to the loadLaneWidth.
}
if(paddingBytes == -1)
{
Throw<FatalError>("Automatic padding not implemented yet.");
}
return {contiguousBytes, paddingBytes};
}
return {0u, 0u};
}
/**
* @brief Stage LDS nodes that are flattened/tiled.
*/
void AddLDSPaddingVisitor::stage(KernelGraph const& graph, int ldsTag)
{
using GD = Graph::Direction;
auto flattenEdgeTag = GetEdgeTag<GD::Upstream, Flatten>(graph, ldsTag);
auto tileEdgeTag = GetEdgeTag<GD::Downstream, Tile>(graph, ldsTag);
if((not flattenEdgeTag) or (not tileEdgeTag))
return;
auto maybeLayoutTypeAndDataType = GetLayoutTypeAndDataType(graph, ldsTag);
if(!maybeLayoutTypeAndDataType)
{
Log::debug("KernelGraph::AddLDSPadding: "
"Could not determine layout type and data type for LDS tag {}",
ldsTag);
return;
}
auto const& [loadWidth, laneWidth]
= GetLoadAndLaneWidth(graph,
ldsTag,
maybeLayoutTypeAndDataType->first,
maybeLayoutTypeAndDataType->second,
m_context);
auto upstreamTags
= graph.coordinates.getNeighbours<GD::Upstream>(flattenEdgeTag.value());
auto downstreamTags
= graph.coordinates.getNeighbours<GD::Downstream>(tileEdgeTag.value());
m_ldsTags[ldsTag] = LDSPaddingInfo{ldsTag,
flattenEdgeTag.value(),
tileEdgeTag.value(),
{upstreamTags[0], upstreamTags[1]},
{downstreamTags[0], downstreamTags[1]},
maybeLayoutTypeAndDataType->second,
maybeLayoutTypeAndDataType->first,
loadWidth,
laneWidth};
}
/**
* @brief Commit LDS padding changes to the graph.
*
* This will change the upstream Flatten edge to a Join edge,
* and the downstream Tile edge to a Split edge.
*/
void AddLDSPaddingVisitor::commit(KernelGraph& graph)
{
for(auto& [ldsTag, info] : m_ldsTags)
{
auto [contiguousBytes, paddingBytes] = getLDSPaddingElements(graph, info);
Log::debug("KernelGraph::AddLDSPadding: ldsTag {}, upstreamEdge {}, "
"downstreamEdge {}, contiguousBytes {} paddingBytes {}",
info.ldsTag,
info.upstreamEdge,
info.downstreamEdge,
contiguousBytes,
paddingBytes);
if(contiguousBytes == 0)
continue;
MakePaddedBlocks(graph,
GD::Downstream,
info.downstreamEdge,
info.downstreamTags,
contiguousBytes,
paddingBytes,
DataTypeInfo::Get(info.dataType).elementBits);
MakePaddedBlocks(graph,
GD::Upstream,
info.upstreamEdge,
info.upstreamTags,
contiguousBytes,
paddingBytes,
DataTypeInfo::Get(info.dataType).elementBits);
}
}
KernelGraph AddLDSPadding::apply(KernelGraph const& original)
{
TIMER(t, "KernelGraph::AddLDSPadding");
auto graph = original;
auto visitor = AddLDSPaddingVisitor(m_context, m_params);
for(auto ldsTag : graph.coordinates.getNodes<LDS>())
visitor.stage(graph, ldsTag);
visitor.commit(graph);
return graph;
}
}
}