Enzyme main
Loading...
Searching...
No Matches
EnzymeMLIRPass.cpp
Go to the documentation of this file.
1//===- EnzymeMLIRPass.cpp - Replace calls with their derivatives ------------ //
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements a pass to lower gpu kernels in NVVM/gpu dialects into
10// a generic parallel for representation
11//===----------------------------------------------------------------------===//
12
13#include "Dialect/Ops.h"
15#include "PassDetails.h"
16#include "Passes/Passes.h"
17
19#include "mlir/Dialect/Func/IR/FuncOps.h"
20#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
21#include "mlir/Dialect/Math/IR/Math.h"
22#include "mlir/IR/Builders.h"
23#include "mlir/Interfaces/FunctionInterfaces.h"
24#include "mlir/Pass/PassManager.h"
25
26#define DEBUG_TYPE "enzyme"
27
28using namespace mlir;
29using namespace mlir::enzyme;
30using namespace enzyme;
31
32namespace mlir {
33namespace enzyme {
34#define GEN_PASS_DEF_DIFFERENTIATEPASS
35#include "Passes/Passes.h.inc"
36} // namespace enzyme
37} // namespace mlir
38
39namespace {
40struct DifferentiatePass
41 : public enzyme::impl::DifferentiatePassBase<DifferentiatePass> {
42 using DifferentiatePassBase::DifferentiatePassBase;
43
44 void runOnOperation() override;
45
46 void getDependentDialects(DialectRegistry &registry) const override {
47 mlir::OpPassManager pm;
48 mlir::LogicalResult result = mlir::parsePassPipeline(postpasses, pm);
49 if (!mlir::failed(result)) {
50 pm.getDependentDialects(registry);
51 }
52
53 // llvm_ext is what a clone of a pointer is written in -- see
54 // PointerClonableTypeInterface -- so this pass builds ops of it and has to
55 // say so. In a pipeline that raised through llvm_ext it is already loaded,
56 // which is why only running this pass on its own ever noticed.
57 // math is used by derivative rules of ops from other dialects (e.g. the
58 // signed-zero handling of arith.minimumf/arith.maximumf builds
59 // math.copysign), so it may be built even if the input has no math ops.
60 registry.insert<mlir::arith::ArithDialect, mlir::complex::ComplexDialect,
61 mlir::cf::ControlFlowDialect, mlir::tensor::TensorDialect,
62 mlir::memref::MemRefDialect, mlir::math::MathDialect,
63 mlir::enzyme::EnzymeDialect, mlir::LLVM::LLVMDialect,
64 mlir::enzyme::llvm_ext::LLVMExtDialect>();
65 }
66
67 static std::vector<DIFFE_TYPE> mode_from_fn(FunctionOpInterface fn,
68 DerivativeMode mode) {
69 std::vector<DIFFE_TYPE> retTypes;
70 for (auto ty : fn.getResultTypes()) {
71 if (isa<IntegerType>(ty)) {
72 retTypes.push_back(DIFFE_TYPE::CONSTANT);
73 continue;
74 }
75
77 retTypes.push_back(DIFFE_TYPE::OUT_DIFF);
78 else
79 retTypes.push_back(DIFFE_TYPE::DUP_ARG);
80 }
81 return retTypes;
82 }
83
84 template <typename T>
85 LogicalResult HandleAutoDiff(MEnzymeLogic &Logic,
86 SymbolTableCollection &symbolTable, T CI) {
87 std::vector<DIFFE_TYPE> constants;
88 SmallVector<mlir::Value, 2> args;
89
90 size_t truei = 0;
91 auto activityAttr = CI.getActivity();
92
93 for (unsigned i = 0; i < CI.getInputs().size(); ++i) {
94 mlir::Value res = CI.getInputs()[i];
95
96 auto mop = activityAttr[truei];
97 auto iattr = cast<mlir::enzyme::ActivityAttr>(mop);
98 DIFFE_TYPE ty;
99
100 switch (iattr.getValue()) {
101 case mlir::enzyme::Activity::enzyme_active:
103 break;
104 case mlir::enzyme::Activity::enzyme_dup:
106 break;
107 case mlir::enzyme::Activity::enzyme_const:
109 break;
110 case mlir::enzyme::Activity::enzyme_dupnoneed:
112 break;
113 case mlir::enzyme::Activity::enzyme_activenoneed:
115 assert(0 && "unsupported arg activenoneed");
116 break;
117 case mlir::enzyme::Activity::enzyme_constnoneed:
119 assert(0 && "unsupported arg constnoneed");
120 break;
121 }
122
123 constants.push_back(ty);
124 args.push_back(res);
125 if (ty == DIFFE_TYPE::DUP_ARG || ty == DIFFE_TYPE::DUP_NONEED) {
126 ++i;
127 res = CI.getInputs()[i];
128 args.push_back(res);
129 }
130
131 truei++;
132 }
133
134 auto *symbolOp = symbolTable.lookupNearestSymbolFrom(CI, CI.getFnAttr());
135 auto fn = cast<FunctionOpInterface>(symbolOp);
136
137 auto mode = DerivativeMode::ForwardMode;
138 std::vector<DIFFE_TYPE> retType;
139
140 std::vector<bool> returnPrimals;
141 for (auto act : CI.getRetActivity()) {
142 auto iattr = cast<mlir::enzyme::ActivityAttr>(act);
143 auto val = iattr.getValue();
144 DIFFE_TYPE ty;
145 bool primalNeeded = true;
146 switch (val) {
147 case mlir::enzyme::Activity::enzyme_active:
149 break;
150 case mlir::enzyme::Activity::enzyme_dup:
152 break;
153 case mlir::enzyme::Activity::enzyme_const:
155 break;
156 case mlir::enzyme::Activity::enzyme_dupnoneed:
158 primalNeeded = false;
159 break;
160 case mlir::enzyme::Activity::enzyme_activenoneed:
162 primalNeeded = false;
163 break;
164 case mlir::enzyme::Activity::enzyme_constnoneed:
166 primalNeeded = false;
167 break;
168 }
169 retType.push_back(ty);
170 returnPrimals.push_back(primalNeeded);
171 }
172
173 MTypeAnalysis TA;
174 auto type_args = TA.getAnalyzedTypeInfo(fn);
175 bool freeMemory = true;
176 bool omp = false;
177 size_t width = CI.getWidth();
178
179 std::vector<bool> overwritten_args;
180 for (auto &a : fn.getFunctionBody().getArguments()) {
181 (void)a;
182 overwritten_args.push_back(
184 }
185
186 FunctionOpInterface newFunc = Logic.CreateForwardDiff(
187 fn, retType, constants, TA, returnPrimals, mode, freeMemory, width,
188 /*addedType*/ nullptr, type_args, overwritten_args,
189 /*augmented*/ nullptr, omp, postpasses, verifyPostPasses,
190 CI.getStrongZero());
191 if (!newFunc)
192 return failure();
193
194 OpBuilder builder(CI);
195 // Ask the function how it is called, as the reverse handler does: what is
196 // being differentiated is often an llvm.func, and a func.call to one of
197 // those is not a call at all.
198 auto iface = dyn_cast<AutoDiffFunctionInterface>(newFunc.getOperation());
199 if (!iface) {
200 newFunc.getOperation()->emitError()
201 << "this function operation does not implement "
202 "AutoDiffFunctionInterface";
203 return failure();
204 }
205 Operation *dCI = iface.createCall(builder, CI.getLoc(), args);
206 if (dCI->getNumResults() != CI.getNumResults()) {
207 CI.emitError() << "Incorrect number of results for enzyme operation: "
208 << *CI << " expected " << *dCI;
209 return failure();
210 }
211 CI.replaceAllUsesWith(dCI);
212 CI->erase();
213 return success();
214 }
215
216 template <typename T>
217 LogicalResult HandleAutoDiffReverse(MEnzymeLogic &Logic,
218 SymbolTableCollection &symbolTable,
219 T CI) {
220
221 auto *symbolOp = symbolTable.lookupNearestSymbolFrom(CI, CI.getFnAttr());
222 auto fn = cast<FunctionOpInterface>(symbolOp);
223 assert(fn);
224 if (CI.getActivity().size() != fn.getNumArguments()) {
225 llvm::errs() << "Incorrect number of argument activities on autodiff op"
226 << "CI: " << CI << ", expected " << fn.getNumArguments()
227 << " found " << CI.getActivity().size() << "\n";
228 return failure();
229 }
230 if (CI.getRetActivity().size() != fn.getNumResults()) {
231 llvm::errs() << "Incorrect number of result activities on autodiff op"
232 << "CI: " << CI << ", expected " << fn.getNumResults()
233 << " found " << CI.getRetActivity().size() << "\n";
234 return failure();
235 }
236
237 std::vector<DIFFE_TYPE> arg_activities;
238 SmallVector<mlir::Value, 2> args;
239
240 size_t call_idx = 0;
241 {
242 for (auto act : CI.getActivity()) {
243 if (call_idx >= CI.getInputs().size()) {
244 llvm::errs() << "Too few arguments to autodiff op"
245 << "CI: " << CI << "\n";
246 return failure();
247 }
248 mlir::Value res = CI.getInputs()[call_idx];
249 ++call_idx;
250
251 auto iattr = cast<mlir::enzyme::ActivityAttr>(act);
252 auto val = iattr.getValue();
253 DIFFE_TYPE ty;
254 switch (val) {
255 case mlir::enzyme::Activity::enzyme_active:
257 break;
258 case mlir::enzyme::Activity::enzyme_dup:
260 break;
261 case mlir::enzyme::Activity::enzyme_const:
263 break;
264 case mlir::enzyme::Activity::enzyme_dupnoneed:
266 break;
267 case mlir::enzyme::Activity::enzyme_activenoneed:
269 assert(0 && "unsupported arg activenoneed");
270 break;
271 case mlir::enzyme::Activity::enzyme_constnoneed:
273 assert(0 && "unsupported arg constnoneed");
274 break;
275 }
276 arg_activities.push_back(ty);
277 args.push_back(res);
278 if (ty == DIFFE_TYPE::DUP_ARG || ty == DIFFE_TYPE::DUP_NONEED) {
279 if (call_idx >= CI.getInputs().size()) {
280 llvm::errs() << "Too few arguments to autodiff op"
281 << "CI: " << CI << "\n";
282 return failure();
283 }
284 res = CI.getInputs()[call_idx];
285 ++call_idx;
286 args.push_back(res);
287 }
288 }
289 }
290
291 bool omp = false;
293 std::vector<DIFFE_TYPE> retType;
294 std::vector<bool> returnPrimals;
295 std::vector<bool> returnShadows;
296
297 // Add the return gradient
298 for (auto act : CI.getRetActivity()) {
299 auto iattr = cast<mlir::enzyme::ActivityAttr>(act);
300 auto val = iattr.getValue();
301 DIFFE_TYPE ty;
302 bool primalNeeded = true;
303 switch (val) {
304 case mlir::enzyme::Activity::enzyme_active:
306 break;
307 case mlir::enzyme::Activity::enzyme_dup:
309 break;
310 case mlir::enzyme::Activity::enzyme_const:
312 break;
313 case mlir::enzyme::Activity::enzyme_dupnoneed:
315 primalNeeded = false;
316 break;
317 case mlir::enzyme::Activity::enzyme_activenoneed:
319 primalNeeded = false;
320 break;
321 case mlir::enzyme::Activity::enzyme_constnoneed:
323 primalNeeded = false;
324 break;
325 }
326 retType.push_back(ty);
327 returnPrimals.push_back(primalNeeded);
328 returnShadows.push_back(false);
329 if (ty == DIFFE_TYPE::OUT_DIFF) {
330 if (call_idx >= CI.getInputs().size()) {
331 llvm::errs() << "Too few arguments to autodiff op"
332 << "CI: " << CI << "\n";
333 return failure();
334 }
335 mlir::Value res = CI.getInputs()[call_idx];
336 ++call_idx;
337 args.push_back(res);
338 }
339 }
340
341 MTypeAnalysis TA;
342 auto type_args = TA.getAnalyzedTypeInfo(fn);
343 bool freeMemory = true;
344 size_t width = CI.getWidth();
345
346 std::vector<bool> overwritten_args;
347 for (auto &a : fn.getFunctionBody().getArguments()) {
348 (void)a;
349 overwritten_args.push_back(
351 }
352
353 FunctionOpInterface newFunc = Logic.CreateReverseDiff(
354 fn, retType, arg_activities, TA, returnPrimals, returnShadows, mode,
355 freeMemory, CI.getAtomicAdd(), width,
356 /*addedType*/ nullptr, type_args, overwritten_args,
357 /*augmented*/ nullptr, omp, postpasses, verifyPostPasses,
358 CI.getStrongZero(), markReadonly);
359 if (!newFunc)
360 return failure();
361
362 OpBuilder builder(CI);
363 if (auto iface =
364 dyn_cast<AutoDiffFunctionInterface>(newFunc.getOperation())) {
365 auto dCI = iface.createCall(builder, CI.getLoc(), args);
366 CI.replaceAllUsesWith(dCI);
367 } else {
368 newFunc.getOperation()->emitError()
369 << "this function operation does not implement "
370 "AutoDiffFunctionInterface";
371 return failure();
372 }
373 CI->erase();
374 return success();
375 }
376
377 void lowerEnzymeCalls(MEnzymeLogic &Logic, SymbolTableCollection &symbolTable,
378 FunctionOpInterface op) {
379 {
380 SmallVector<Operation *> toLower;
381 op->walk([&](enzyme::ForwardDiffOp dop) {
382 auto *symbolOp =
383 symbolTable.lookupNearestSymbolFrom(dop, dop.getFnAttr());
384 auto callableOp = cast<FunctionOpInterface>(symbolOp);
385
386 lowerEnzymeCalls(Logic, symbolTable, callableOp);
387 toLower.push_back(dop);
388 });
389
390 for (auto T : toLower) {
391 if (auto F = dyn_cast<enzyme::ForwardDiffOp>(T)) {
392 auto res = HandleAutoDiff(Logic, symbolTable, F);
393 if (!res.succeeded()) {
394 signalPassFailure();
395 return;
396 }
397 } else {
398 llvm_unreachable("Illegal type");
399 }
400 }
401 };
402
403 {
404 SmallVector<Operation *> toLower;
405 op->walk([&](enzyme::AutoDiffOp dop) {
406 auto *symbolOp =
407 symbolTable.lookupNearestSymbolFrom(dop, dop.getFnAttr());
408 auto callableOp = cast<FunctionOpInterface>(symbolOp);
409
410 lowerEnzymeCalls(Logic, symbolTable, callableOp);
411 toLower.push_back(dop);
412 });
413
414 for (auto T : toLower) {
415 if (auto F = dyn_cast<enzyme::AutoDiffOp>(T)) {
416 auto res = HandleAutoDiffReverse(Logic, symbolTable, F);
417 if (!res.succeeded()) {
418 signalPassFailure();
419 return;
420 }
421 } else {
422 llvm_unreachable("Illegal type");
423 }
424 }
425 }
426 };
427};
428
429} // end anonymous namespace
430
431void DifferentiatePass::runOnOperation() {
432 MEnzymeLogic Logic(dataflowActivity);
433 SymbolTableCollection symbolTable;
434 symbolTable.getSymbolTable(getOperation());
435 getOperation()->walk([&](FunctionOpInterface op) {
436 lowerEnzymeCalls(Logic, symbolTable, op);
437 });
438 getOperation()->walk([&](FunctionOpInterface op) { removeSummaries(op); });
439}
DIFFE_TYPE
Potential differentiable argument classifications.
DerivativeMode
Definition Utils.h:524
FunctionOpInterface CreateForwardDiff(FunctionOpInterface fn, std::vector< DIFFE_TYPE > retType, std::vector< DIFFE_TYPE > constants, MTypeAnalysis &TA, std::vector< bool > returnPrimals, DerivativeMode mode, bool freeMemory, size_t width, mlir::Type addedType, MFnTypeInfo type_args, std::vector< bool > overwritten_args, void *augmented, bool omp, llvm::StringRef postpasses, bool verifyPostPasses, bool strongZero)
FunctionOpInterface CreateReverseDiff(FunctionOpInterface fn, std::vector< DIFFE_TYPE > retType, std::vector< DIFFE_TYPE > constants, MTypeAnalysis &TA, std::vector< bool > returnPrimals, std::vector< bool > returnShadows, DerivativeMode mode, bool freeMemory, bool atomicAdd, size_t width, mlir::Type addedType, MFnTypeInfo type_args, std::vector< bool > overwritten_args, void *augmented, bool omp, llvm::StringRef postpasses, bool verifyPostPasses, bool strongZero, bool markReadonly)
MFnTypeInfo getAnalyzedTypeInfo(FunctionOpInterface op) const
Definition EnzymeLogic.h:25
void removeSummaries(Operation *op)