Enzyme main
Loading...
Searching...
No Matches
EnzymeBatchPass.cpp
Go to the documentation of this file.
1//===- EnzymeBatchPass.cpp - Replace calls with their batched versions
2//------------ //
3//
4// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5// See https://llvm.org/LICENSE.txt for license information.
6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7//
8//===----------------------------------------------------------------------===//
9//
10// This file implements a pass to lower gpu kernels in NVVM/gpu dialects into
11// a generic parallel for representation
12//===----------------------------------------------------------------------===//
13
15#include "Dialect/Ops.h"
17#include "PassDetails.h"
18#include "Passes/Passes.h"
19
20#include "mlir/Dialect/Func/IR/FuncOps.h"
21#include "mlir/IR/Builders.h"
22#include "mlir/Interfaces/FunctionInterfaces.h"
23
24#define DEBUG_TYPE "enzyme-batch"
25
26using namespace mlir;
27using namespace mlir::enzyme;
28using namespace enzyme;
29
30namespace mlir {
31namespace enzyme {
32#define GEN_PASS_DEF_BATCHPASS
33#include "Passes/Passes.h.inc"
34} // namespace enzyme
35} // namespace mlir
36
37namespace mlir {
38namespace enzyme {
39namespace batchutils {
40
41mlir::TensorType applyBatchSizes(mlir::Type Ty,
42 llvm::ArrayRef<int64_t> batchSizes) {
43 auto T = dyn_cast<TensorType>(Ty);
44 if (!T) {
45 return RankedTensorType::get(batchSizes, Ty);
46 }
47
48 SmallVector<int64_t> shape(batchSizes.begin(), batchSizes.end());
49 shape.append(T.getShape().begin(), T.getShape().end());
50 auto T2 = T.clone(shape);
51 return T2;
52}
53
54LogicalResult handleCallLikeOp(
55 Operation *callLike, StringRef symbolAttrName, OpBuilder &builder,
56 IRMapping &mapper, llvm::ArrayRef<int64_t> batchSizes,
57 std::map<BatchCacheKey, FunctionOpInterface> &batchedFunctionCache) {
58 auto calledFunc = SymbolTable::lookupNearestSymbolFrom<FunctionOpInterface>(
59 callLike, callLike->getAttrOfType<SymbolRefAttr>(symbolAttrName));
60
61 if (!calledFunc) {
62 return failure();
63 }
64
65 std::string fnName = "batched_" + calledFunc.getName().str();
66 FunctionOpInterface batchedFunc = batchCloneFunction(
67 builder, calledFunc, fnName, batchSizes, batchedFunctionCache);
68
69 if (!batchedFunc) {
70 return failure();
71 }
72
73 // Create new call operation to the batched function
74 SmallVector<Value> newOperands;
75 for (auto operand : callLike->getOperands())
76 newOperands.push_back(mapper.lookup(operand));
77
78 SmallVector<Type> resultTypes =
79 llvm::map_to_vector(callLike->getResultTypes(), [&](Type Ty) -> Type {
80 return applyBatchSizes(Ty, batchSizes);
81 });
82
83 Operation *newCallLike =
84 builder.create(callLike->getLoc(), callLike->getName().getIdentifier(),
85 newOperands, resultTypes, callLike->getAttrs());
86
87 newCallLike->setAttr(symbolAttrName, SymbolRefAttr::get(batchedFunc));
88
89 for (auto [oldRes, newRes] :
90 llvm::zip_equal(callLike->getResults(), newCallLike->getResults())) {
91 mapper.map(oldRes, newRes);
92 }
93
94 return success();
95}
96
97LogicalResult handleCallOp(
98 func::CallOp callOp, OpBuilder &builder, IRMapping &mapper,
99 llvm::ArrayRef<int64_t> batchSizes,
100 std::map<BatchCacheKey, FunctionOpInterface> &batchedFunctionCache) {
101 return handleCallLikeOp(callOp, "callee", builder, mapper, batchSizes,
102 batchedFunctionCache);
103}
104
106 OpBuilder &builder, Block *blk, IRMapping &mapper,
107 llvm::ArrayRef<int64_t> batchSizes,
108 std::map<BatchCacheKey, FunctionOpInterface> &batchedFunctionCache,
109 bool withoutTerminator) {
110 for (auto &src : *blk) {
111 if (auto callOp = dyn_cast<func::CallOp>(&src)) {
112 if (succeeded(handleCallOp(callOp, builder, mapper, batchSizes,
113 batchedFunctionCache)))
114 continue;
115 }
116
117 if (isa<enzyme::ForwardDiffOp, enzyme::AutoDiffOp>(&src)) {
118 if (succeeded(handleCallLikeOp(&src, "fn", builder, mapper, batchSizes,
119 batchedFunctionCache)))
120 continue;
121 }
122
123 if (auto ifaceOp = dyn_cast<BatchOpInterface>(&src)) {
124 auto res = ifaceOp.createBatch(builder, mapper, batchSizes);
125 if (res.succeeded())
126 continue;
127 }
128
129 SmallVector<Value, 8> operands;
130 SmallVector<Block *, 2> successors;
131
132 // Remap the operands.
133 operands.reserve(src.getNumOperands());
134 for (auto opValue : src.getOperands())
135 operands.push_back(mapper.lookup(opValue));
136
137 if (withoutTerminator && src.hasTrait<OpTrait::IsTerminator>()) {
138 // map the operands and the results
139 for (unsigned i = 0, e = src.getNumResults(); i != e; ++i)
140 mapper.map(src.getResult(i), operands[i]);
141 continue;
142 }
143
144 // Remap the successors.
145 successors.reserve(src.getNumSuccessors());
146 for (Block *successor : src.getSuccessors())
147 successors.push_back(mapper.lookup(successor));
148
149 SmallVector<Type> resultTypes(src.getResultTypes().begin(),
150 src.getResultTypes().end());
151 for (auto &Ty : resultTypes) {
152 Ty = applyBatchSizes(Ty, batchSizes);
153 }
154
155 Operation *newOp = Operation::create(
156 src.getLoc(), src.getName(), resultTypes, operands, src.getAttrs(),
157 mlir::PropertyRef(), successors, src.getNumRegions());
158
159 // Clone the regions.
160 for (auto &&[oldReg, newReg] :
161 llvm::zip(src.getRegions(), newOp->getRegions())) {
162 batchCloneRegion(builder, &oldReg, &newReg, mapper, batchSizes,
163 batchedFunctionCache);
164 }
165
166 // Remember the mapping of any results.
167 for (unsigned i = 0, e = src.getNumResults(); i != e; ++i)
168 mapper.map(src.getResult(i), newOp->getResult(i));
169
170 builder.insert(newOp);
171 }
172}
173
175 OpBuilder &builder, Region *src, Region *dest, IRMapping &mapper,
176 llvm::ArrayRef<int64_t> batchSizes,
177 std::map<BatchCacheKey, FunctionOpInterface> &batchedFunctionCache) {
178 // For each block in src, generate a corresponding block in the dest region.
179 for (auto &blk : *src) {
180 auto newBlk = new Block();
181 dest->push_back(newBlk);
182
183 mapper.map(&blk, newBlk);
184
185 for (auto arg : blk.getArguments()) {
186 Value newArg = newBlk->addArgument(
187 applyBatchSizes(arg.getType(), batchSizes), arg.getLoc());
188 mapper.map(arg, newArg);
189 }
190 }
191
192 for (auto &&[blk, newBlk] : llvm::zip(*src, *dest)) {
193 IRRewriter::InsertionGuard insertGuard(builder);
194 builder.setInsertionPointToEnd(&newBlk);
195 batchCloneBlock(builder, &blk, mapper, batchSizes, batchedFunctionCache,
196 false);
197 }
198}
199
200FunctionOpInterface batchCloneFunction(
201 OpBuilder &builder, FunctionOpInterface F, Twine name,
202 llvm::ArrayRef<int64_t> batchSizes,
203 std::map<BatchCacheKey, FunctionOpInterface> &batchedFunctionCache) {
204 assert(!F.getFunctionBody().empty());
205
206 // Add the function to the cache BEFORE processing its body to support
207 // recursion.
208 BatchCacheKey key{F,
209 SmallVector<int64_t>(batchSizes.begin(), batchSizes.end())};
210
211 auto it = batchedFunctionCache.find(key);
212 if (it != batchedFunctionCache.end())
213 return it->second;
214
215 auto FTy = cast<FunctionType>(F.getFunctionType());
216
217 llvm::SmallVector<mlir::Type> RetTypes;
218 RetTypes.reserve(FTy.getNumResults());
219
220 for (auto Ty : FTy.getResults()) {
221 RetTypes.push_back(applyBatchSizes(Ty, batchSizes));
222 }
223
224 SmallVector<mlir::Type, 4> ArgTypes;
225 ArgTypes.reserve(FTy.getNumInputs());
226
227 for (auto Ty : FTy.getInputs()) {
228 ArgTypes.push_back(applyBatchSizes(Ty, batchSizes));
229 }
230
231 FunctionType newFTy = builder.getFunctionType(ArgTypes, RetTypes);
232
233 auto NewF = cast<FunctionOpInterface>(F->cloneWithoutRegions());
234 SymbolTable::setSymbolName(NewF, name.str());
235 NewF.setType(newFTy);
236
237 Operation *parent = F->getParentWithTrait<OpTrait::SymbolTable>();
238 SymbolTable table(parent);
239 table.insert(NewF);
240 SymbolTable::setSymbolVisibility(NewF, SymbolTable::Visibility::Private);
241
242 batchedFunctionCache[key] = NewF;
243
244 auto &origReg = F.getFunctionBody();
245 auto &newReg = NewF.getFunctionBody();
246
247 IRMapping mapper;
248 batchCloneRegion(builder, &origReg, &newReg, mapper, batchSizes,
249 batchedFunctionCache);
250
251 return NewF;
252}
253
254template <typename T>
256 OpBuilder &builder, T CI, FunctionOpInterface fn,
257 std::map<BatchCacheKey, FunctionOpInterface> &batchedFunctionCache) {
259 fn, SmallVector<int64_t>(CI.getBatchShape().begin(),
260 CI.getBatchShape().end())};
261
262 // Check if we already have a batched version
263 auto it = batchedFunctionCache.find(key);
264 FunctionOpInterface newFunc;
265
266 if (it != batchedFunctionCache.end()) {
267 return it->second;
268 } else {
269 // Create new batched function and store in cache
270 std::string newFnName = "batched_" + fn.getName().str();
271 newFunc = batchCloneFunction(builder, fn, newFnName, CI.getBatchShape(),
272 batchedFunctionCache);
273 return newFunc;
274 }
275}
276
277} // namespace batchutils
278} // namespace enzyme
279} // namespace mlir
280
281namespace {
282
283struct BatchPass : public enzyme::impl::BatchPassBase<BatchPass> {
284 void runOnOperation() override;
285
286 // Cache mapping original function and batch sizes to batched function
287 std::map<enzyme::batchutils::BatchCacheKey, FunctionOpInterface>
288 batchedFunctionCache;
289
290 void lowerEnzymeBatchCalls(SymbolTableCollection &symbolTable,
291 FunctionOpInterface op) {
292 {
293 SmallVector<Operation *> toLower;
294 op->walk([&](enzyme::BatchOp dop) { toLower.push_back(dop); });
295
296 OpBuilder builder(op);
297
298 for (auto T : toLower) {
299 if (auto F = dyn_cast<enzyme::BatchOp>(T)) {
300 auto res = enzyme::batchutils::batchOperation(symbolTable, builder, F,
301 batchedFunctionCache);
302 if (!res.succeeded()) {
303 signalPassFailure();
304 return;
305 }
306 } else {
307 llvm_unreachable("Illegal type");
308 }
309 }
310 };
311 };
312};
313
314} // end anonymous namespace
315
316void BatchPass::runOnOperation() {
317 SymbolTableCollection symbolTable;
318 symbolTable.getSymbolTable(getOperation());
319 getOperation()->walk(
320 [&](FunctionOpInterface op) { lowerEnzymeBatchCalls(symbolTable, op); });
321}
FunctionOpInterface batchOperationWithoutInsertingCallOp(OpBuilder &builder, T CI, FunctionOpInterface fn, std::map< BatchCacheKey, FunctionOpInterface > &batchedFunctionCache)
LogicalResult batchOperation(SymbolTableCollection &symbolTable, OpBuilder &builder, T CI, std::map< BatchCacheKey, FunctionOpInterface > &batchedFunctionCache)
mlir::TensorType applyBatchSizes(mlir::Type Ty, llvm::ArrayRef< int64_t > batchSizes)
FunctionOpInterface batchCloneFunction(OpBuilder &builder, FunctionOpInterface F, Twine name, llvm::ArrayRef< int64_t > batchSizes, std::map< BatchCacheKey, FunctionOpInterface > &batchedFunctionCache)
void batchCloneBlock(OpBuilder &builder, Block *blk, IRMapping &mapper, llvm::ArrayRef< int64_t > batchSizes, std::map< BatchCacheKey, FunctionOpInterface > &batchedFunctionCache, bool withoutTerminator)
LogicalResult handleCallOp(func::CallOp callOp, OpBuilder &builder, IRMapping &mapper, llvm::ArrayRef< int64_t > batchSizes, std::map< BatchCacheKey, FunctionOpInterface > &batchedFunctionCache)
void batchCloneRegion(OpBuilder &builder, Region *src, Region *dest, IRMapping &mapper, llvm::ArrayRef< int64_t > batchSizes, std::map< BatchCacheKey, FunctionOpInterface > &batchedFunctionCache)
LogicalResult handleCallLikeOp(Operation *callLike, StringRef symbolAttrName, OpBuilder &builder, IRMapping &mapper, llvm::ArrayRef< int64_t > batchSizes, std::map< BatchCacheKey, FunctionOpInterface > &batchedFunctionCache)