[Mlir-commits] [mlir] 6f5c8fe - [MLIR][SparseTensor] Dense Outer Loop Ordering Strategy (#160168)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Mon Nov 17 09:01:48 PST 2025
Author: Govind Malasani
Date: 2025-11-17T09:01:44-08:00
New Revision: 6f5c8fe1c1d24604d3328b82f5a1ed348e59326f
URL: https://github.com/llvm/llvm-project/commit/6f5c8fe1c1d24604d3328b82f5a1ed348e59326f
DIFF: https://github.com/llvm/llvm-project/commit/6f5c8fe1c1d24604d3328b82f5a1ed348e59326f.diff
LOG: [MLIR][SparseTensor] Dense Outer Loop Ordering Strategy (#160168)
This PR builds upon the infrastructure set up for Sparse Tensor Loop
Ordering Heuristics (#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.
Added:
Modified:
mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.h
mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.td
mlir/lib/Dialect/SparseTensor/Transforms/Utils/IterationGraphSorter.cpp
Removed:
################################################################################
diff --git a/mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.h b/mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.h
index af64370a62dd7..419ecda80e9a5 100644
--- a/mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.h
+++ b/mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.h
@@ -58,9 +58,10 @@ enum class SparseEmitStrategy {
namespace sparse_tensor {
/// Defines a strategy for loop ordering during sparse code generation.
+/// See Passes.td for strategy descriptions.
enum class LoopOrderingStrategy : unsigned {
- kDefault, ///< Default strategy (eagerly selects last loop in topological
- ///< sort).
+ kDefault,
+ kDenseOuter,
};
} // namespace sparse_tensor
diff --git a/mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.td b/mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.td
index 75e77d67db1b3..0b8562e484f51 100644
--- a/mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/SparseTensor/Transforms/Passes.td
@@ -85,7 +85,9 @@ def SparseReinterpretMap : Pass<"sparse-reinterpret-map", "ModuleOp"> {
"mlir::sparse_tensor::LoopOrderingStrategy::kDefault",
"Set the loop ordering strategy for sparse code generation", [{llvm::cl::values(
clEnumValN(mlir::sparse_tensor::LoopOrderingStrategy::kDefault, "default",
- "Default strategy (eagerly selects last loop in topological sort)"))}]>,
+ "Default strategy (eagerly selects last loop in topological sort)"),
+ clEnumValN(mlir::sparse_tensor::LoopOrderingStrategy::kDenseOuter, "dense-outer",
+ "Prefer dense, then compressed, then singleton dimensions outermost"))}]>,
];
}
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/IterationGraphSorter.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/IterationGraphSorter.cpp
index ffa8b402e0b6b..99048034b4f0c 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/IterationGraphSorter.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/IterationGraphSorter.cpp
@@ -80,6 +80,53 @@ inline static bool includesDenseOutput(SortMask mask) {
return includesAny(mask, SortMask::kIncludeDenseOutput);
}
+/// Returns a sparsity rank for loop ordering: lower values indicate
+/// dimensions that should be placed in outer loops.
+/// 0 = Dense, 1 = Compressed, 2 = Singleton, 3 = Other/Unknown.
+static unsigned getLoopSparsityRank(unsigned loop, ArrayRef<Value> allTensors,
+ ArrayRef<AffineMap> allMaps) {
+ // Start with highest rank.
+ unsigned minRank = 3;
+
+ for (auto [tensor, map] : llvm::zip(allTensors, allMaps)) {
+ // Check if this loop accesses this tensor.
+ bool loopAccessesTensor = false;
+ unsigned tensorDim = 0;
+ for (AffineExpr expr : map.getResults()) {
+ if (auto dimExpr = dyn_cast<AffineDimExpr>(expr)) {
+ if (dimExpr.getPosition() == loop) {
+ loopAccessesTensor = true;
+ break;
+ }
+ }
+ tensorDim++;
+ }
+
+ if (loopAccessesTensor) {
+ const auto enc = getSparseTensorEncoding(tensor.getType());
+ if (!enc) {
+ // Dense tensor - lowest rank.
+ return 0;
+ } else {
+ // Sparse tensor - check the level type for this dimension.
+ auto lvlTypes = enc.getLvlTypes();
+ if (tensorDim < lvlTypes.size()) {
+ auto lvlType = lvlTypes[tensorDim];
+ if (isDenseLT(lvlType)) {
+ return 0; // Dense level.
+ } else if (isCompressedLT(lvlType)) {
+ minRank = std::min(minRank, 1u); // Compressed level.
+ } else if (isSingletonLT(lvlType)) {
+ minRank = std::min(minRank, 2u); // Singleton level.
+ }
+ }
+ }
+ }
+ }
+
+ return minRank;
+}
+
AffineMap IterationGraphSorter::topoSort() {
// The sorted result will put the first Reduction iterator to the
// latest possible position.
@@ -107,10 +154,33 @@ AffineMap IterationGraphSorter::topoSort() {
case sparse_tensor::LoopOrderingStrategy::kDefault:
src = it.back();
break;
+ case sparse_tensor::LoopOrderingStrategy::kDenseOuter: {
+ // Prefer dense, then compressed, then singleton dimensions outermost.
+ // Create combined tensor and map lists for analysis.
+ SmallVector<Value> allTensors = ins;
+ allTensors.push_back(out);
+ SmallVector<AffineMap> allMaps = loop2InsLvl;
+ allMaps.push_back(loop2OutLvl);
+
+ // Find loop with minimum (lowest) sparsity rank.
+ unsigned minLoop = it[0];
+ unsigned minRank = getLoopSparsityRank(minLoop, allTensors, allMaps);
+
+ for (auto candidateLoop : it) {
+ unsigned rank = getLoopSparsityRank(candidateLoop, allTensors, allMaps);
+ if (rank < minRank || (rank == minRank && candidateLoop < minLoop)) {
+ minLoop = candidateLoop;
+ minRank = rank;
+ }
+ }
+ src = minLoop;
+ break;
+ }
}
loopOrder.push_back(src);
- it.pop_back();
+ // Remove the selected loop from the worklist.
+ it.erase(std::find(it.begin(), it.end(), src));
// Update in-degree, and push 0-degree node into worklist.
for (unsigned dst = 0; dst < numLoops; dst++) {
if (itGraph[src][dst] && --inDegree[dst] == 0) {
More information about the Mlir-commits
mailing list