summaryrefslogtreecommitdiff
path: root/mlir/lib/Dialect/GPU/TransformOps
diff options
context:
space:
mode:
authorQuinn Dawkins <quinn@nod-labs.com>2023-07-23 16:20:53 -0400
committerQuinn Dawkins <quinn@nod-labs.com>2023-07-25 13:11:32 -0400
commitff8775f3fffea787922eb238c0af3ed5715485d9 (patch)
treeeab59f5cc32135b731c3b41b2f73c93b74a059a3 /mlir/lib/Dialect/GPU/TransformOps
parentcaa35a1ad9afc45079830966258ae24ca8f5ec0a (diff)
[mlir][GPU] Add op for unrolling contractions to a native size
Adds `apply_patterns.gpu.unroll_vectors_subgroup_mma` which allows specifying a native MMA shape of `m`, `n`, and `k` to unroll to, greedily unrolling the inner most dimension of contractions and other vector operations based on expected usage. Differential Revision: https://reviews.llvm.org/D156079
Diffstat (limited to 'mlir/lib/Dialect/GPU/TransformOps')
-rw-r--r--mlir/lib/Dialect/GPU/TransformOps/GPUTransformOps.cpp127
1 files changed, 127 insertions, 0 deletions
diff --git a/mlir/lib/Dialect/GPU/TransformOps/GPUTransformOps.cpp b/mlir/lib/Dialect/GPU/TransformOps/GPUTransformOps.cpp
index 07470c24ae2d..ddbe5d47ff44 100644
--- a/mlir/lib/Dialect/GPU/TransformOps/GPUTransformOps.cpp
+++ b/mlir/lib/Dialect/GPU/TransformOps/GPUTransformOps.cpp
@@ -21,6 +21,7 @@
#include "mlir/Dialect/Transform/IR/TransformInterfaces.h"
#include "mlir/Dialect/Utils/IndexingUtils.h"
#include "mlir/Dialect/Vector/IR/VectorOps.h"
+#include "mlir/Dialect/Vector/Transforms/VectorTransforms.h"
#include "mlir/IR/AffineExpr.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinAttributes.h"
@@ -47,6 +48,132 @@ using namespace mlir::transform::gpu;
#define DBGS_ALIAS() (llvm::dbgs() << '[' << DEBUG_TYPE_ALIAS << "] ")
//===----------------------------------------------------------------------===//
+// ApplyUnrollVectorsSubgroupMmaOp
+//===----------------------------------------------------------------------===//
+
+/// Pick an unrolling order that will allow tensorcore operation to reuse LHS
+/// register.
+static std::optional<SmallVector<int64_t>>
+gpuMmaUnrollOrder(vector::ContractionOp contract) {
+ SmallVector<int64_t> order;
+ // First make reduction the outer dimensions.
+ for (auto [index, iter] : llvm::enumerate(contract.getIteratorTypes())) {
+ if (vector::isReductionIterator(iter)) {
+ order.push_back(index);
+ }
+ }
+
+ llvm::SmallDenseSet<int64_t> dims;
+ for (AffineExpr expr : contract.getIndexingMapsArray()[0].getResults()) {
+ dims.insert(expr.cast<AffineDimExpr>().getPosition());
+ }
+ // Then parallel dimensions that are part of Lhs as we want to re-use Lhs.
+ for (auto [index, iter] : llvm::enumerate(contract.getIteratorTypes())) {
+ if (vector::isParallelIterator(iter) && dims.count(index)) {
+ order.push_back(index);
+ }
+ }
+ // Then the remaining parallel loops.
+ for (auto [index, iter] : llvm::enumerate(contract.getIteratorTypes())) {
+ if (vector::isParallelIterator(iter) && !dims.count(index)) {
+ order.push_back(index);
+ }
+ }
+ return order;
+}
+
+/// Returns the target vector size for the target operation based on the native
+/// vector size specified with `m`, `n`, and `k`.
+static std::optional<SmallVector<int64_t>>
+getSubgroupMmaNativeVectorSize(Operation *op, int64_t m, int64_t n, int64_t k) {
+ if (auto contract = dyn_cast<vector::ContractionOp>(op)) {
+ int64_t contractRank = contract.getIteratorTypes().size();
+ if (contractRank < 3)
+ return std::nullopt;
+ SmallVector<int64_t> nativeSize(contractRank - 3, 1);
+ nativeSize.append({m, n, k});
+ return nativeSize;
+ }
+ if (auto writeOp = dyn_cast<vector::TransferWriteOp>(op)) {
+ int64_t writeRank = writeOp.getVectorType().getRank();
+ if (writeRank < 2)
+ return std::nullopt;
+ SmallVector<int64_t> nativeSize(writeRank - 2, 1);
+ nativeSize.append({m, n});
+ return nativeSize;
+ }
+ if (auto readOp = dyn_cast<vector::TransferReadOp>(op)) {
+ // Transfer read ops may need different shapes based on how they are being
+ // used. For simplicity just match the shape used by the extract strided op.
+ VectorType sliceType;
+ for (Operation *users : op->getUsers()) {
+ auto extract = dyn_cast<vector::ExtractStridedSliceOp>(users);
+ if (!extract)
+ return std::nullopt;
+ auto vecType = extract.getResult().getType().cast<VectorType>();
+ if (sliceType && sliceType != vecType)
+ return std::nullopt;
+ sliceType = vecType;
+ }
+ return llvm::to_vector(sliceType.getShape());
+ }
+ if ((OpTrait::hasElementwiseMappableTraits(op) && op->getNumResults() == 1)) {
+ if (auto vecType = op->getResultTypes()[0].dyn_cast<VectorType>()) {
+ // TODO: The condition for unrolling elementwise should be restricted
+ // only to operations that need unrolling (connected to the contract).
+ if (vecType.getRank() < 2)
+ return std::nullopt;
+
+ // First check whether there is a slice to infer the shape from. This is
+ // required for cases where the accumulator type differs from the input
+ // types, in which case we will see an `arith.ext_` between the contract
+ // and transfer_read which needs to be unrolled.
+ VectorType sliceType;
+ for (Operation *users : op->getUsers()) {
+ auto extract = dyn_cast<vector::ExtractStridedSliceOp>(users);
+ if (!extract)
+ return std::nullopt;
+ auto vecType = extract.getResult().getType().cast<VectorType>();
+ if (sliceType && sliceType != vecType)
+ return std::nullopt;
+ sliceType = vecType;
+ }
+ if (sliceType)
+ return llvm::to_vector(sliceType.getShape());
+
+ // Else unroll for trailing elementwise.
+ SmallVector<int64_t> nativeSize(vecType.getRank() - 2, 1);
+ // Map elementwise ops to the output shape.
+ nativeSize.append({m, n});
+ return nativeSize;
+ }
+ }
+ return std::nullopt;
+}
+
+void transform::ApplyUnrollVectorsSubgroupMmaOp::populatePatterns(
+ RewritePatternSet &patterns) {
+ auto unrollOrder = [](Operation *op) -> std::optional<SmallVector<int64_t>> {
+ auto contract = dyn_cast<vector::ContractionOp>(op);
+ if (!contract)
+ return std::nullopt;
+ return gpuMmaUnrollOrder(contract);
+ };
+
+ int64_t m = getM();
+ int64_t n = getN();
+ int64_t k = getK();
+ auto nativeShapeFn =
+ [m, n, k](Operation *op) -> std::optional<SmallVector<int64_t>> {
+ return getSubgroupMmaNativeVectorSize(op, m, n, k);
+ };
+ vector::populateVectorUnrollPatterns(
+ patterns, vector::UnrollVectorOptions()
+ .setNativeShapeFn(nativeShapeFn)
+ .setUnrollTraversalOrderFn(unrollOrder));
+}
+
+//===----------------------------------------------------------------------===//
// EliminateBarriersOp
//===----------------------------------------------------------------------===//