Enzyme main
Loading...
Searching...
No Matches
EnzymeActivityOpt.cpp
Go to the documentation of this file.
1//===- EnzymeAcitivtyOpt.cpp - Optimize activity for differentiation ------===//
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
11#include "Dialect/Ops.h"
15#include "PassDetails.h"
16#include "Passes/Passes.h"
17
18#include "mlir/Analysis/DataFlow/ConstantPropagationAnalysis.h"
19#include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h"
20#include "mlir/Analysis/DataFlowFramework.h"
21#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
22#include "mlir/IR/PatternMatch.h"
23#include "mlir/Interfaces/FunctionInterfaces.h"
24#include "mlir/Support/LLVM.h"
25#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
26
27#include "llvm/Support/raw_ostream.h"
28
30#include <deque>
31
32#define DEBUG_TYPE "enzyme-activity-opt"
33#define ENZYME_DBGS llvm::dbgs() << "[" << DEBUG_TYPE << "]"
34
35using namespace mlir;
36using namespace mlir::enzyme;
37using namespace mlir::dataflow;
38
39namespace mlir {
40namespace enzyme {
41#define GEN_PASS_DEF_ENZYMEACTIVITYOPT
42#include "Passes/Passes.h.inc"
43} // namespace enzyme
44} // namespace mlir
45
46namespace {
47
48struct TaggedActivity {
49 Attribute tag;
50 bool isConstant;
51};
52
53struct ActivityOptimization {
54 SmallVector<Activity> argumentActivity;
55 SmallVector<Activity> resultActivity;
56 SmallVector<TaggedActivity> taggedActivities;
57};
58
59static void
60printTaggedActivityAnalysis(FunctionOpInterface callee,
61 ArrayRef<TaggedActivity> taggedActivities) {
62 llvm::errs() << FlatSymbolRefAttr::get(callee) << ":\n";
63 for (const TaggedActivity &activity : taggedActivities) {
64 llvm::errs() << " " << activity.tag << ": "
65 << (activity.isConstant ? "Constant" : "Active") << "\n";
66 }
67}
68
69// NOTE: Currently, we only optimize for return activities in the classical
70// activity analyzer. This is because the ActivityAnalyzer computes and runs
71// activity analysis based on a fixed assignment to argument activities
72static FailureOr<ActivityOptimization> computeClassicalActivityOptimization(
73 Operation *diffOp, FunctionOpInterface callee,
74 ArrayRef<Activity> argumentActivity, ArrayRef<Activity> resultActivity,
75 SymbolTableCollection &symbolTable) {
77 SmallPtrSet<Block *, 4> blocksNotForAnalysis;
78 SmallPtrSet<mlir::Value, 1> constargvals;
79 SmallPtrSet<mlir::Value, 1> activeargvals;
80
81 // TODO: check if we need to specialize here for fwddiff and autodiff
82 for (auto &&[arg, act] :
83 llvm::zip(callee.getFunctionBody().getArguments(), argumentActivity)) {
84 if (act == Activity::enzyme_const)
85 constargvals.insert(arg);
86 else
87 activeargvals.insert(arg);
88 }
89
90 SmallVector<DIFFE_TYPE> ReturnActivity;
91 for (auto act : resultActivity) {
92 if (act != Activity::enzyme_const)
93 ReturnActivity.push_back(DIFFE_TYPE::DUP_ARG);
94 else
95 ReturnActivity.push_back(DIFFE_TYPE::CONSTANT);
96 }
97
98 DenseMap<Operation *, bool> readOnlyCache;
99 enzyme::ActivityAnalyzer activityAnalyzer(blocksNotForAnalysis, readOnlyCache,
100 constargvals, activeargvals,
101 ReturnActivity);
102
103 // walk thru all return ops and set activity
104 SmallVector<bool> isRetValueConstant(resultActivity.size(), true);
105 callee.walk([&](Operation *op) {
107 op->getParentOp() != callee.getOperation())
108 return;
109
110 for (const auto &[idx, opret] : llvm::enumerate(op->getOperands())) {
111 bool icv = activityAnalyzer.isConstantValue(TR, opret);
112 isRetValueConstant[idx] &= icv;
113 }
114 });
115
116 // Initialize activity results to be the same as that of the callsite
117 ActivityOptimization optimized;
118 optimized.argumentActivity.assign(argumentActivity.begin(),
119 argumentActivity.end());
120 optimized.resultActivity.assign(resultActivity.begin(), resultActivity.end());
121
122 for (auto &&[idx, ract] : llvm::enumerate(optimized.resultActivity)) {
123 if (isRetValueConstant[idx] && ract != Activity::enzyme_const &&
124 ract != Activity::enzyme_constnoneed) {
125 if (ract == Activity::enzyme_dup || ract == Activity::enzyme_active) {
126 ract = Activity::enzyme_const;
127 } else if (ract == Activity::enzyme_activenoneed ||
128 ract == Activity::enzyme_dupnoneed) {
129 ract = Activity::enzyme_constnoneed;
130 }
131 }
132 }
133
134 // set return activity based on final inferred activity
135 return optimized;
136}
137
138static FailureOr<ActivityOptimization> computeDataFlowActivityOptimization(
139 Operation *diffOp, FunctionOpInterface callee,
140 ArrayRef<Activity> argumentActivity, ArrayRef<Activity> resultActivity,
141 SymbolTableCollection &symbolTable, bool collectActivityAnalysis = false) {
142
143 Region &body = callee.getFunctionBody();
144 if (body.empty() || body.front().empty())
145 return failure();
146 if (callee.getArguments().size() != argumentActivity.size()) {
147 diffOp->emitError()
148 << "callee argument count does not match activity count";
149 return failure();
150 }
151
152 DataFlowSolver solver;
153 solver.load<enzyme::PointsToPointerAnalysis>();
154 solver.load<enzyme::AliasAnalysis>(callee.getContext());
156 solver.load<enzyme::DenseForwardActivityAnalysis>(&body.front(),
157 argumentActivity);
158 solver.load<enzyme::SparseBackwardActivityAnalysis>(symbolTable);
160 symbolTable, callee, argumentActivity, resultActivity);
161 solver.load<DeadCodeAnalysis>();
162 solver.load<SparseConstantPropagation>();
163
164 for (const auto &[arg, activity] :
165 llvm::zip(callee.getArguments(), argumentActivity)) {
166 // we should only initialize for enzyme_active, enzyme_dup, enzyme_dupnoneed
167 if (activity != Activity::enzyme_active &&
168 activity != Activity::enzyme_dup &&
169 activity != Activity::enzyme_dupnoneed)
170 continue;
171 auto *argLattice =
172 solver.getOrCreateState<enzyme::ForwardValueActivity>(arg);
173 (void)argLattice->join(ValueActivity::getActiveVal());
174 }
175
176 SmallVector<Operation *> returnOps;
177 bool invalidReturn = false;
178 callee->walk(
179 [&](Operation *op) {
181 op->getParentOp() != callee.getOperation())
182 return;
183
184 if (op->getNumOperands() != resultActivity.size()) {
185 invalidReturn = true;
186 diffOp->emitError()
187 << "return operand count does not match ret_activity count";
188 return;
189 }
190
191 returnOps.push_back(op);
192 for (const auto &[operand, activity] :
193 llvm::zip(op->getOperands(), resultActivity)) {
194 // Seed scalar return activity here; dense memory activity handles
195 // pointed-to data for duplicated memory returns.
196 auto *returnLattice =
197 solver.getOrCreateState<enzyme::BackwardValueActivity>(operand);
198 if (activity == Activity::enzyme_active ||
199 activity == Activity::enzyme_activenoneed ||
200 activity == Activity::enzyme_dup ||
201 activity == Activity::enzyme_dupnoneed) {
202 (void)returnLattice->meet(ValueActivity::getActiveVal());
203 }
204 }
205 });
206
207 if (invalidReturn)
208 return failure();
209
210 if (returnOps.empty())
211 return failure();
212
213 Operation *analysisRoot = callee->getParentOfType<ModuleOp>();
214 if (!analysisRoot)
215 analysisRoot = callee.getOperation();
216 if (failed(solver.initializeAndRun(analysisRoot))) {
217 diffOp->emitError() << "dataflow activity analysis failed";
218 return failure();
219 }
220
221 auto isActiveData = [&](Value value) {
222 auto *forward = solver.lookupState<enzyme::ForwardValueActivity>(value);
223 auto *backward = solver.lookupState<enzyme::BackwardValueActivity>(value);
224 bool forwardActive = forward && forward->getValue().isActiveVal();
225 bool backwardActive = backward && backward->getValue().isActiveVal();
226 return forwardActive && backwardActive;
227 };
228
229 auto isConstantPointer = [&](Value value) {
230 auto *backwardMemory = solver.lookupState<enzyme::BackwardMemoryActivity>(
231 solver.getProgramPointBefore(&body.front().front()));
232 auto *aliasClassLattice =
233 solver.lookupState<enzyme::AliasClassLattice>(value);
234
235 if (!backwardMemory || !aliasClassLattice ||
236 aliasClassLattice->isUndefined() || aliasClassLattice->isUnknown())
237 return false;
238
239 auto scheduleVisit = [](std::deque<DistinctAttr> &frontier,
240 DenseSet<DistinctAttr> &visited,
241 const enzyme::AliasClassSet &aliasClasses) {
242 bool known = true;
243 (void)aliasClasses.foreachElement(
244 [&](DistinctAttr neighbor, enzyme::AliasClassSet::State state) {
245 if (state != enzyme::AliasClassSet::State::Defined || !neighbor) {
246 known = false;
247 return ChangeResult::NoChange;
248 }
249 if (!visited.contains(neighbor)) {
250 visited.insert(neighbor);
251 frontier.push_back(neighbor);
252 }
253 return ChangeResult::NoChange;
254 });
255 return known;
256 };
257
258 for (Operation *returnOp : returnOps) {
259 auto *forwardMemory = solver.lookupState<enzyme::ForwardMemoryActivity>(
260 solver.getProgramPointAfter(returnOp));
261 const enzyme::PointsToSets *pointsToSets =
262 solver.lookupState<enzyme::PointsToSets>(
263 solver.getProgramPointAfter(returnOp));
264 if (!forwardMemory || !pointsToSets)
265 return false;
266
267 std::deque<DistinctAttr> frontier;
268 DenseSet<DistinctAttr> visited;
269 if (!scheduleVisit(frontier, visited,
270 aliasClassLattice->getAliasClassesObject()))
271 return false;
272
273 while (!frontier.empty()) {
274 DistinctAttr aliasClass = frontier.front();
275 frontier.pop_front();
276
277 if (forwardMemory->hasActiveData(aliasClass) &&
278 backwardMemory->activeDataFlowsOut(aliasClass))
279 return false;
280
281 const enzyme::AliasClassSet &pointsTo =
282 pointsToSets->getPointsTo(aliasClass);
283 if (pointsTo.isUndefined() || pointsTo.isUnknown())
284 return false;
285 if (!scheduleVisit(frontier, visited, pointsTo))
286 return false;
287 }
288 }
289
290 return true;
291 };
292
293 auto isConstantValue = [&](Value value) {
294 if (isa<LLVM::LLVMPointerType, MemRefType, UnrankedMemRefType>(
295 value.getType()))
296 return isConstantPointer(value);
297 return !isActiveData(value);
298 };
299
300 // Initialize activity results to be the same as that of the callsite
301 ActivityOptimization optimized;
302 optimized.argumentActivity.assign(argumentActivity.begin(),
303 argumentActivity.end());
304 optimized.resultActivity.assign(resultActivity.begin(), resultActivity.end());
305
306 for (const auto &[index, arg, activity] :
307 llvm::enumerate(callee.getArguments(), argumentActivity)) {
308 if (isConstantValue(arg)) {
309 if (activity == Activity::enzyme_active ||
310 activity == Activity::enzyme_dup ||
311 activity == Activity::enzyme_dupnoneed)
312 optimized.argumentActivity[index] = Activity::enzyme_const;
313 }
314 }
315
316 // Conservatively combine all the activities of all ReturnLike ops as a
317 // summary for ret_activity originating from the callsite autodiff op
318 SmallVector<bool> allReturnValuesConstant(resultActivity.size(), true);
319 for (Operation *returnOp : returnOps) {
320 for (const auto &[index, operand] :
321 llvm::enumerate(returnOp->getOperands())) {
322 allReturnValuesConstant[index] =
323 allReturnValuesConstant[index] && isConstantValue(operand);
324 }
325 }
326
327 for (const auto &[index, activity] : llvm::enumerate(resultActivity)) {
328 if (allReturnValuesConstant[index]) {
329 if (activity == Activity::enzyme_active ||
330 activity == Activity::enzyme_dup)
331 optimized.resultActivity[index] = Activity::enzyme_const;
332 else if (activity == Activity::enzyme_activenoneed ||
333 activity == Activity::enzyme_dupnoneed)
334 optimized.resultActivity[index] = Activity::enzyme_constnoneed;
335 }
336 }
337
338 if (collectActivityAnalysis) {
339 for (BlockArgument arg : callee.getArguments()) {
340 if (Attribute tag = callee.getArgAttr(arg.getArgNumber(), "enzyme.tag"))
341 optimized.taggedActivities.push_back({tag, isConstantValue(arg)});
342 }
343
344 callee.walk([&](Operation *op) {
345 Attribute tag = op->getAttr("tag");
346 if (!tag)
347 return;
348 for (OpResult result : op->getResults())
349 optimized.taggedActivities.push_back({tag, isConstantValue(result)});
350 });
351 }
352
353 return optimized;
354}
355
356class MinimizeForwardActivity : public OpRewritePattern<ForwardDiffOp> {
357public:
358 MinimizeForwardActivity(MLIRContext *context, bool dataflow,
359 bool printActivityAnalysis,
360 PatternBenefit benefit = 1)
361 : OpRewritePattern(context, benefit), dataflow(dataflow),
362 printActivityAnalysis(printActivityAnalysis) {}
363
364 LogicalResult matchAndRewrite(ForwardDiffOp op,
365 PatternRewriter &rewriter) const override {
366 auto *ctx = rewriter.getContext();
367 SymbolTableCollection symbolTable;
368 FunctionOpInterface callee = dyn_cast_or_null<FunctionOpInterface>(
369 symbolTable.lookupNearestSymbolFrom(op, op.getFnAttr()));
370 if (!callee)
371 return failure();
372
373 SmallVector<Activity> argumentActivity =
374 llvm::to_vector(op.getActivity().getAsValueRange<ActivityAttr>());
375 SmallVector<Activity> resultActivity =
376 llvm::to_vector(op.getRetActivity().getAsValueRange<ActivityAttr>());
377
378 FailureOr<ActivityOptimization> optimized =
379 dataflow
380 ? computeDataFlowActivityOptimization(op, callee, argumentActivity,
381 resultActivity, symbolTable,
382 printActivityAnalysis)
383 : computeClassicalActivityOptimization(op, callee, argumentActivity,
384 resultActivity, symbolTable);
385
386 if (failed(optimized))
387 return failure();
388
389 // debug printing activity
390 if (printActivityAnalysis && dataflow)
391 printTaggedActivityAnalysis(callee, optimized->taggedActivities);
392
393 if (llvm::equal(argumentActivity, optimized->argumentActivity) &&
394 llvm::equal(resultActivity, optimized->resultActivity))
395 return failure();
396
397 // reconstruct op according to modified activity
398 SmallVector<Value> in_args;
399 auto in_idx = 0;
400 for (const auto &[idx, act, nact] :
401 llvm::enumerate(argumentActivity, optimized->argumentActivity)) {
402 Value inp = op.getInputs()[in_idx];
403 if (act == nact) {
404 in_args.push_back(inp);
405 in_idx++;
406 if (act == Activity::enzyme_dup || act == Activity::enzyme_dupnoneed) {
407 inp = op.getInputs()[in_idx];
408 in_args.push_back(inp);
409 in_idx++;
410 }
411 } else {
412 // Handle const demotion for enzyme_dup/enzyme_dupnoneed/enzyme_active
413 in_args.push_back(inp);
414 in_idx++;
415 // we skip the duplicated derivative arg
416 if ((act == Activity::enzyme_dup ||
417 act == Activity::enzyme_dupnoneed) &&
418 nact == Activity::enzyme_const)
419 in_idx++;
420 }
421 }
422
423 SmallVector<Value> out_args;
424 auto out_idx = 0;
425 for (const auto &[idx, act, nact] :
426 llvm::enumerate(resultActivity, optimized->resultActivity)) {
427 if (act == Activity::enzyme_constnoneed)
428 continue;
429
430 Value out = op.getOutputs()[out_idx];
431 if (act == nact) {
432 switch (act) {
433 case Activity::enzyme_active:
434 case Activity::enzyme_activenoneed:
435 case Activity::enzyme_const:
436 out_args.push_back(out);
437 out_idx++;
438 break;
439
440 case Activity::enzyme_dup:
441 case Activity::enzyme_dupnoneed:
442 out_args.push_back(out);
443 out_idx++;
444
445 // handle derivative
446 out = op.getOutputs()[out_idx];
447 out_args.push_back(out);
448 out_idx++;
449 break;
450
451 default:
452 llvm_unreachable("unexpected return activity arg");
453 }
454 } else {
455 // NOTE: These are the only possible activity demotions
456 // enzyme_active -> enzyme_const
457 // enzyme_dup -> enzyme_const
458 // enzyme_activenoneed -> enzyme_constnoneed
459 // enzyme_dupnoneed -> enzyme_constnoneed
460 switch (act) {
461 case Activity::enzyme_active:
462 case Activity::enzyme_dup:
463 out_args.push_back(out);
464 // we skip the derivative
465 out_idx += 2;
466 break;
467
468 case Activity::enzyme_dupnoneed:
469 case Activity::enzyme_activenoneed:
470 // the primal is already skipped. We also skip the derivative.
471 out_idx++;
472 break;
473
474 default:
475 llvm_unreachable("unexpected demotion for return activity");
476 }
477 }
478 }
479
480 TypeRange out_ty = ValueRange(out_args).getTypes();
481 ArrayAttr newInActivity = ArrayAttr::get(
482 ctx, llvm::map_to_vector(optimized->argumentActivity,
483 [&](enzyme::Activity act) -> Attribute {
484 return ActivityAttr::get(ctx, act);
485 }));
486
487 ArrayAttr newOutActivity = ArrayAttr::get(
488 ctx, llvm::map_to_vector(optimized->resultActivity,
489 [&](enzyme::Activity act) -> Attribute {
490 return ActivityAttr::get(ctx, act);
491 }));
492
493 ForwardDiffOp newOp = ForwardDiffOp::create(
494 rewriter, op->getLoc(), out_ty, op.getFnAttr(), in_args, newInActivity,
495 newOutActivity, op.getWidthAttr(), op.getStrongZeroAttr());
496
497 // Map old uses of op to newOp
498 auto oldIdx = 0;
499 auto newIdx = 0;
500 for (auto [idx, old_val, new_val] :
501 llvm::enumerate(resultActivity, optimized->resultActivity)) {
502
503 if (old_val == new_val) {
504 // don't index into op if its a const_noneed
505 if (old_val == Activity::enzyme_constnoneed) {
506 continue;
507 }
508 // replace use
509 op.getOutputs()[oldIdx++].replaceAllUsesWith(
510 newOp.getOutputs()[newIdx++]);
511 if (old_val == Activity::enzyme_dup) {
512 // 2nd replacement for derivative
513 op.getOutputs()[oldIdx++].replaceAllUsesWith(
514 newOp.getOutputs()[newIdx++]);
515 }
516 } else {
517 // handle all substitutions
518 if (new_val == Activity::enzyme_constnoneed &&
519 old_val == Activity::enzyme_dupnoneed) {
520 ++oldIdx; // skip gradient too
521 } else if (new_val == Activity::enzyme_const &&
522 old_val == Activity::enzyme_dup) {
523 op.getOutputs()[oldIdx++].replaceAllUsesWith(
524 newOp.getOutputs()[newIdx++]);
525 ++oldIdx; // skip derivative
526 } else {
527 llvm_unreachable("unexpected demotion for return activity");
528 }
529 }
530 }
531
532 rewriter.eraseOp(op);
533 return success();
534 }
535
536private:
537 bool dataflow;
538 bool printActivityAnalysis;
539};
540
541class MinimizeReverseActivity : public OpRewritePattern<AutoDiffOp> {
542public:
543 MinimizeReverseActivity(MLIRContext *context, bool dataflow,
544 bool printActivityAnalysis,
545 PatternBenefit benefit = 1)
546 : OpRewritePattern(context, benefit), dataflow(dataflow),
547 printActivityAnalysis(printActivityAnalysis) {}
548
549 LogicalResult matchAndRewrite(AutoDiffOp op,
550 PatternRewriter &rewriter) const override {
551 auto *ctx = rewriter.getContext();
552 SymbolTableCollection symbolTable;
553 FunctionOpInterface callee = dyn_cast_or_null<FunctionOpInterface>(
554 symbolTable.lookupNearestSymbolFrom(op, op.getFnAttr()));
555 if (!callee)
556 return failure();
557
558 SmallVector<Activity> argumentActivity =
559 llvm::to_vector(op.getActivity().getAsValueRange<ActivityAttr>());
560 SmallVector<Activity> resultActivity =
561 llvm::to_vector(op.getRetActivity().getAsValueRange<ActivityAttr>());
562
563 FailureOr<ActivityOptimization> optimized =
564 dataflow
565 ? computeDataFlowActivityOptimization(op, callee, argumentActivity,
566 resultActivity, symbolTable,
567 printActivityAnalysis)
568 : computeClassicalActivityOptimization(op, callee, argumentActivity,
569 resultActivity, symbolTable);
570
571 if (failed(optimized))
572 return failure();
573
574 // debug printing activity
575 if (printActivityAnalysis && dataflow)
576 printTaggedActivityAnalysis(callee, optimized->taggedActivities);
577
578 if (llvm::equal(argumentActivity, optimized->argumentActivity) &&
579 llvm::equal(resultActivity, optimized->resultActivity))
580 return failure();
581
582 // Construct new autodiff op based on optimized activities
583 SmallVector<Value> in_args;
584 auto in_idx = 0;
585 for (const auto &[idx, act, nact] :
586 llvm::enumerate(argumentActivity, optimized->argumentActivity)) {
587 mlir::Value res = op.getInputs()[in_idx];
588 if (act == nact) {
589 // no change in activity
590 in_args.push_back(res);
591 ++in_idx;
592
593 if (act == Activity::enzyme_dup || act == Activity::enzyme_dupnoneed) {
594 mlir::Value dres = op.getInputs()[in_idx];
595 in_args.push_back(dres);
596 ++in_idx;
597 }
598 } else {
599 // we can only demote input args to enzyme_const. Anything else should
600 // return in an error
601 if (nact != Activity::enzyme_const)
602 return failure();
603 switch (act) {
604 case Activity::enzyme_active:
605 in_args.push_back(res);
606 ++in_idx;
607 break;
608 case Activity::enzyme_dup:
609 case Activity::enzyme_dupnoneed:
610 in_args.push_back(res);
611 // we skip over the derivative
612 in_idx += 2;
613 break;
614 default:
615 llvm_unreachable(
616 "unexpected demotion for input activity in reverse mode");
617 }
618 }
619 }
620
621 // If the autodiff operation is non differentiable then we just stop any
622 // more transforms
623 if (in_idx == op.getInputs().size())
624 return failure();
625
626 SmallVector<Value> out_args;
627 SmallVector<Type> out_ty;
628 auto out_idx = 0;
629 for (const auto &[idx, act, nact] :
630 llvm::enumerate(resultActivity, optimized->resultActivity)) {
631 // we do not index into any output if the return activity is constnoneed
632 if (act == Activity::enzyme_constnoneed) {
633 if (nact != act)
634 return op->emitError() << "unknown activity demotion";
635 continue;
636 }
637
638 if (act == nact) {
639 // check if this retun value has an active differential return which is
640 // an input to the autodiff op
641 if (act == Activity::enzyme_active ||
642 act == Activity::enzyme_activenoneed) {
643 mlir::Value diffret = op.getInputs()[in_idx];
644 in_args.push_back(diffret);
645 ++in_idx;
646 }
647
648 // we do not index into the output if the return activity is
649 // activenoneed
650 if (act == Activity::enzyme_activenoneed) {
651 continue;
652 }
653
654 // Reverse mode only returns primal values for return activities.
655 // Return cotangents are inputs for active/activenoneed returns, not
656 // shadow results for dup/dupnoneed returns.
657 switch (act) {
658 case Activity::enzyme_active:
659 case Activity::enzyme_dup:
660 case Activity::enzyme_const: {
661 mlir::Value res = op.getOutputs()[out_idx];
662 out_args.push_back(res);
663 out_ty.push_back(res.getType());
664 ++out_idx;
665 break;
666 }
667
668 case Activity::enzyme_dupnoneed: {
669 break;
670 }
671
672 default:
673 llvm_unreachable("unknown reverse mode activity");
674 }
675
676 } else {
677 // NOTE:handle return activity demotions
678 // 1.activenoneed->constnoneed (in which case we will not have a diffret
679 // input for in_args)
680 // 2. active -> const, in which case we will again not have diffret
681 // input, but we retain the output args
682 // 3. dup -> const
683 // 4. dupnn -> constnn
684 // act cannot be const or constnn
685
686 bool validDemotion =
687 (act == Activity::enzyme_active &&
688 nact == Activity::enzyme_const) ||
689 (act == Activity::enzyme_activenoneed &&
690 nact == Activity::enzyme_constnoneed) ||
691 (act == Activity::enzyme_dup && nact == Activity::enzyme_const) ||
692 (act == Activity::enzyme_dupnoneed &&
693 nact == Activity::enzyme_constnoneed);
694 if (!validDemotion)
695 return failure();
696
697 if (act == Activity::enzyme_activenoneed &&
698 nact == Activity::enzyme_constnoneed) {
699 ++in_idx;
700 continue;
701 }
702
703 switch (act) {
704 case Activity::enzyme_active: {
705 ++in_idx; // skip diffret for active arg in inputs
706 mlir::Value res = op.getOutputs()[out_idx];
707 out_args.push_back(res);
708 out_ty.push_back(res.getType());
709 ++out_idx;
710 break;
711 }
712 case Activity::enzyme_dup: {
713 mlir::Value res = op.getOutputs()[out_idx];
714 out_args.push_back(res);
715 out_ty.push_back(res.getType());
716 ++out_idx;
717 break;
718 }
719 case Activity::enzyme_dupnoneed: {
720 break;
721 }
722
723 default:
724 llvm_unreachable("unknown return activity demotion");
725 }
726 }
727 }
728
729 // handle differential returns
730 for (const auto &[idx, act, nact] :
731 llvm::enumerate(argumentActivity, optimized->argumentActivity)) {
732 if (act == nact) {
733 if (act == Activity::enzyme_active) {
734 mlir::Value res = op.getOutputs()[out_idx];
735 out_args.push_back(res);
736 out_ty.push_back(res.getType());
737 ++out_idx;
738 }
739 } else {
740 // we skip over the output if active has been demoted to const
741 if (act == Activity::enzyme_active && nact == Activity::enzyme_const) {
742 ++out_idx;
743 }
744 }
745 }
746
747 ArrayAttr newInActivity = ArrayAttr::get(
748 ctx, llvm::map_to_vector(optimized->argumentActivity,
749 [&](enzyme::Activity act) -> Attribute {
750 return ActivityAttr::get(ctx, act);
751 }));
752
753 ArrayAttr newOutActivity = ArrayAttr::get(
754 ctx, llvm::map_to_vector(optimized->resultActivity,
755 [&](enzyme::Activity act) -> Attribute {
756 return ActivityAttr::get(ctx, act);
757 }));
758
759 AutoDiffOp newOp = AutoDiffOp::create(
760 rewriter, op->getLoc(), out_ty, op.getFnAttr(), in_args, newInActivity,
761 newOutActivity, op.getWidthAttr(), op.getStrongZeroAttr(),
762 op.getAtomicAddAttr());
763
764 // Map old uses of op to newOp
765 auto oldIdx = 0;
766 auto newIdx = 0;
767 for (auto [idx, old_val, new_val] :
768 llvm::enumerate(resultActivity, optimized->resultActivity)) {
769
770 if (old_val == new_val) {
771 // These return activities have no reverse-mode op result.
772 if (old_val == Activity::enzyme_constnoneed ||
773 old_val == Activity::enzyme_activenoneed ||
774 old_val == Activity::enzyme_dupnoneed) {
775 continue;
776 }
777 // replace use
778 op.getOutputs()[oldIdx++].replaceAllUsesWith(
779 newOp.getOutputs()[newIdx++]);
780 } else {
781 // handle all substitutions
782 if (new_val == Activity::enzyme_constnoneed &&
783 old_val == Activity::enzyme_dupnoneed) {
784 continue;
785 } else if (new_val == Activity::enzyme_const &&
786 old_val == Activity::enzyme_dup) {
787 op.getOutputs()[oldIdx++].replaceAllUsesWith(
788 newOp.getOutputs()[newIdx++]);
789 } else if (old_val == Activity::enzyme_active &&
790 new_val == Activity::enzyme_const) {
791 op.getOutputs()[oldIdx++].replaceAllUsesWith(
792 newOp.getOutputs()[newIdx++]);
793 } else if (old_val == Activity::enzyme_activenoneed &&
794 new_val == Activity::enzyme_constnoneed) {
795 continue;
796 } else {
797 llvm_unreachable("unexpected demotion for return activity");
798 }
799 }
800 }
801
802 // only replace active differential returns
803 for (auto [idx, old_val, new_val] :
804 llvm::enumerate(argumentActivity, optimized->argumentActivity)) {
805 if (old_val == new_val) {
806 if (old_val == Activity::enzyme_active) {
807 op.getOutputs()[oldIdx++].replaceAllUsesWith(
808 newOp.getOutputs()[newIdx++]);
809 }
810 } else {
811 if (old_val == Activity::enzyme_active &&
812 new_val == Activity::enzyme_const) {
813 ++oldIdx; // skip diffret
814 }
815 }
816 }
817
818 rewriter.eraseOp(op);
819 return success();
820 }
821
822private:
823 bool dataflow;
824 bool printActivityAnalysis;
825};
826
827struct EnzymeActivityOptPass
828 : public enzyme::impl::EnzymeActivityOptBase<EnzymeActivityOptPass> {
829 using EnzymeActivityOptBase::EnzymeActivityOptBase;
830
831 void runOnOperation() override {
832 RewritePatternSet patterns(&getContext());
833 patterns.add<MinimizeForwardActivity, MinimizeReverseActivity>(
834 &getContext(), dataflow, printActivityAnalysis);
835
836 GreedyRewriteConfig config;
837 config.enableFolding();
838 if (failed(applyPatternsGreedily(getOperation(), std::move(patterns),
839 config))) {
840 signalPassFailure();
841 }
842 }
843};
844
845} // namespace
Helper class to analyze the differential activity.
This analysis implements interprocedural alias analysis.
const AliasClassSet & getPointsTo(DistinctAttr id) const
Sparse activity analysis reasons about activity by traversing forward down the def-use chains startin...
bool isFunctionReturnLike(Operation *op)