Skip to content

Commit 6f5c8fe

Browse files
authored
[MLIR][SparseTensor] Dense Outer Loop Ordering Strategy (llvm#160168)
This PR builds upon the infrastructure set up for Sparse Tensor Loop Ordering Heuristics (llvm#154656) by adding a preference to have dense loops outer and sparse loops inner. As always I'd love to get feedback and know if there's any other direction to go with this work that might be better.
1 parent 39e7712 commit 6f5c8fe

File tree

3 files changed

+77
-4
lines changed

3 files changed

+77
-4
lines changed

mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ enum class SparseEmitStrategy {
5858
namespace sparse_tensor {
5959

6060
/// Defines a strategy for loop ordering during sparse code generation.
61+
/// See Passes.td for strategy descriptions.
6162
enum class LoopOrderingStrategy : unsigned {
62-
kDefault, ///< Default strategy (eagerly selects last loop in topological
63-
///< sort).
63+
kDefault,
64+
kDenseOuter,
6465
};
6566

6667
} // namespace sparse_tensor

mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.td

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ def SparseReinterpretMap : Pass<"sparse-reinterpret-map", "ModuleOp"> {
8585
"mlir::sparse_tensor::LoopOrderingStrategy::kDefault",
8686
"Set the loop ordering strategy for sparse code generation", [{llvm::cl::values(
8787
clEnumValN(mlir::sparse_tensor::LoopOrderingStrategy::kDefault, "default",
88-
"Default strategy (eagerly selects last loop in topological sort)"))}]>,
88+
"Default strategy (eagerly selects last loop in topological sort)"),
89+
clEnumValN(mlir::sparse_tensor::LoopOrderingStrategy::kDenseOuter, "dense-outer",
90+
"Prefer dense, then compressed, then singleton dimensions outermost"))}]>,
8991
];
9092
}
9193

mlir/lib/Dialect/SparseTensor/Transforms/Utils/IterationGraphSorter.cpp

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,53 @@ inline static bool includesDenseOutput(SortMask mask) {
8080
return includesAny(mask, SortMask::kIncludeDenseOutput);
8181
}
8282

83+
/// Returns a sparsity rank for loop ordering: lower values indicate
84+
/// dimensions that should be placed in outer loops.
85+
/// 0 = Dense, 1 = Compressed, 2 = Singleton, 3 = Other/Unknown.
86+
static unsigned getLoopSparsityRank(unsigned loop, ArrayRef<Value> allTensors,
87+
ArrayRef<AffineMap> allMaps) {
88+
// Start with highest rank.
89+
unsigned minRank = 3;
90+
91+
for (auto [tensor, map] : llvm::zip(allTensors, allMaps)) {
92+
// Check if this loop accesses this tensor.
93+
bool loopAccessesTensor = false;
94+
unsigned tensorDim = 0;
95+
for (AffineExpr expr : map.getResults()) {
96+
if (auto dimExpr = dyn_cast<AffineDimExpr>(expr)) {
97+
if (dimExpr.getPosition() == loop) {
98+
loopAccessesTensor = true;
99+
break;
100+
}
101+
}
102+
tensorDim++;
103+
}
104+
105+
if (loopAccessesTensor) {
106+
const auto enc = getSparseTensorEncoding(tensor.getType());
107+
if (!enc) {
108+
// Dense tensor - lowest rank.
109+
return 0;
110+
} else {
111+
// Sparse tensor - check the level type for this dimension.
112+
auto lvlTypes = enc.getLvlTypes();
113+
if (tensorDim < lvlTypes.size()) {
114+
auto lvlType = lvlTypes[tensorDim];
115+
if (isDenseLT(lvlType)) {
116+
return 0; // Dense level.
117+
} else if (isCompressedLT(lvlType)) {
118+
minRank = std::min(minRank, 1u); // Compressed level.
119+
} else if (isSingletonLT(lvlType)) {
120+
minRank = std::min(minRank, 2u); // Singleton level.
121+
}
122+
}
123+
}
124+
}
125+
}
126+
127+
return minRank;
128+
}
129+
83130
AffineMap IterationGraphSorter::topoSort() {
84131
// The sorted result will put the first Reduction iterator to the
85132
// latest possible position.
@@ -107,10 +154,33 @@ AffineMap IterationGraphSorter::topoSort() {
107154
case sparse_tensor::LoopOrderingStrategy::kDefault:
108155
src = it.back();
109156
break;
157+
case sparse_tensor::LoopOrderingStrategy::kDenseOuter: {
158+
// Prefer dense, then compressed, then singleton dimensions outermost.
159+
// Create combined tensor and map lists for analysis.
160+
SmallVector<Value> allTensors = ins;
161+
allTensors.push_back(out);
162+
SmallVector<AffineMap> allMaps = loop2InsLvl;
163+
allMaps.push_back(loop2OutLvl);
164+
165+
// Find loop with minimum (lowest) sparsity rank.
166+
unsigned minLoop = it[0];
167+
unsigned minRank = getLoopSparsityRank(minLoop, allTensors, allMaps);
168+
169+
for (auto candidateLoop : it) {
170+
unsigned rank = getLoopSparsityRank(candidateLoop, allTensors, allMaps);
171+
if (rank < minRank || (rank == minRank && candidateLoop < minLoop)) {
172+
minLoop = candidateLoop;
173+
minRank = rank;
174+
}
175+
}
176+
src = minLoop;
177+
break;
178+
}
110179
}
111180

112181
loopOrder.push_back(src);
113-
it.pop_back();
182+
// Remove the selected loop from the worklist.
183+
it.erase(std::find(it.begin(), it.end(), src));
114184
// Update in-degree, and push 0-degree node into worklist.
115185
for (unsigned dst = 0; dst < numLoops; dst++) {
116186
if (itGraph[src][dst] && --inDegree[dst] == 0) {

0 commit comments

Comments
 (0)