19#include "mlir/Analysis/AliasAnalysis.h"
20#include "mlir/Dialect/MemRef/IR/MemRef.h"
21#include "mlir/IR/Builders.h"
22#include "mlir/Interfaces/FunctionInterfaces.h"
23#include "mlir/Interfaces/SideEffectInterfaces.h"
24#include "llvm/ADT/DenseMap.h"
26#define DEBUG_TYPE "enzyme-diff-batch"
27#define ENZYME_DBGS llvm::dbgs() << "[" << DEBUG_TYPE << "]"
31using namespace enzyme;
35#define GEN_PASS_DEF_BATCHDIFFPASS
36#include "Passes/Passes.h.inc"
42struct BatchDiffPass :
public enzyme::impl::BatchDiffPassBase<BatchDiffPass> {
43 void runOnOperation()
override;
45 void mergeFwddiffCalls(SymbolTableCollection &symbolTable,
46 FunctionOpInterface op) {
49 llvm::DenseMap<FunctionOpInterface,
50 SmallVector<MemoryEffects::EffectInstance>>
53 OpBuilder builder(op);
55 op->walk([&](Block *blk) {
57 SmallVector<enzyme::ForwardDiffOp>>
60 for (
auto fwdOp : blk->getOps<enzyme::ForwardDiffOp>()) {
61 auto fnOp = dyn_cast_or_null<FunctionOpInterface>(
62 symbolTable.lookupNearestSymbolFrom(fwdOp, fwdOp.getFnAttr()));
69 diffMergeSet[key].push_back(fwdOp);
72 for (
auto &pair : diffMergeSet) {
73 auto key = pair.first;
74 auto allDiffs = pair.second;
75 if (allDiffs.size() < 2)
79 if (!innerEffectCache.contains(key.function)) {
80 innerEffectCache[key.function] =
84 SmallVector<MemoryEffects::EffectInstance> &calleeEffects =
85 innerEffectCache[key.function];
88 bool skipMergeEntry =
false;
103 llvm::DenseMap<ForwardDiffOp,
104 SmallVector<MemoryEffects::EffectInstance>>
107 for (
auto &eff : calleeEffects) {
108 if (!isa<MemoryEffects::Read>(eff.getEffect()) &&
109 !isa<MemoryEffects::Write>(eff.getEffect())) {
111 skipMergeEntry =
true;
115 Value effVal = eff.getValue();
118 skipMergeEntry =
true;
123 size_t primalArgPos = 0;
124 bool foundPrimal =
false;
125 if (
auto effBA = dyn_cast<BlockArgument>(effVal)) {
126 if (llvm::is_contained(key.function.getArguments(), effBA)) {
128 primalArgPos = effBA.getArgNumber();
136 skipMergeEntry =
true;
141 Value primalVal = key.inputs[primalArgPos];
142 for (
auto dop : allDiffs) {
144 primalVal, eff.getEffect(), eff.getResource()));
153 (key.inActivity[primalArgPos] == Activity::enzyme_dup) ||
154 (key.inActivity[primalArgPos] == Activity::enzyme_dupnoneed);
157 size_t gradArgPos = 0;
158 for (
auto [idx, act] : llvm::enumerate(key.inActivity)) {
161 if (idx == primalArgPos)
164 if (act == Activity::enzyme_dup ||
165 act == Activity::enzyme_dupnoneed) {
170 for (
auto dop : allDiffs) {
171 Value dVal = dop.getInputs()[gradArgPos];
173 dVal, eff.getEffect(), eff.getResource()));
181 SmallVector<ForwardDiffOp> prunedSources =
185 symbolTable, key, prunedSources, callerEffectMap, innerEffectCache);
188 SmallVector<enzyme::ForwardDiffOp> &allOps = legalMerge;
189 int64_t width = allOps.size();
193 auto firstDiffOp = allOps.front();
194 IRRewriter::InsertionGuard insertGuard(builder);
195 builder.setInsertionPoint(firstDiffOp);
196 auto loc = firstDiffOp->getLoc();
197 auto context = builder.getContext();
199 SmallVector<mlir::Value> in_args;
200 SmallVector<ActivityAttr, 2> inActivityAttrs;
201 SmallVector<ActivityAttr, 2> retActivityAttrs;
202 SmallVector<mlir::Type, 2> out_ty;
204 for (
auto [idx, act] : llvm::enumerate(key.inActivity)) {
205 ActivityAttr iattr = ActivityAttr::get(context, act);
206 inActivityAttrs.push_back(iattr);
207 in_args.push_back(key.inputs[idx]);
210 SmallVector<mlir::Value> derivList;
211 if (act == Activity::enzyme_dup ||
212 act == Activity::enzyme_dupnoneed) {
213 for (
auto uop : allOps) {
214 derivList.push_back(uop.getInputs()[in_idx]);
217 mlir::Value batchedDeriv =
219 in_args.push_back(batchedDeriv);
225 for (
auto [idx, ract] : llvm::enumerate(key.retActivity)) {
226 ActivityAttr iattr = ActivityAttr::get(context, ract);
227 retActivityAttrs.push_back(iattr);
229 case Activity::enzyme_active: {
230 mlir::Value res = firstDiffOp.getOutputs()[out_idx];
231 out_ty.push_back(res.getType());
235 case Activity::enzyme_const: {
236 mlir::Value res = firstDiffOp.getOutputs()[out_idx];
237 out_ty.push_back(res.getType());
241 case Activity::enzyme_dupnoneed: {
243 mlir::Value dres = firstDiffOp.getOutputs()[out_idx];
248 case Activity::enzyme_dup: {
249 mlir::Value res = firstDiffOp.getOutputs()[out_idx];
250 out_ty.push_back(res.getType());
253 mlir::Value dres = firstDiffOp.getOutputs()[out_idx];
258 case Activity::enzyme_constnoneed: {
261 case Activity::enzyme_activenoneed: {
262 mlir::Value res = firstDiffOp.getOutputs()[out_idx];
263 out_ty.push_back(res.getType());
269 "unknown activity value encountered for ret_activity");
273 ArrayAttr newInActivity = ArrayAttr::get(
274 context, llvm::ArrayRef<Attribute>(inActivityAttrs.begin(),
275 inActivityAttrs.end()));
276 ArrayAttr newRetActivity = ArrayAttr::get(
277 context, llvm::ArrayRef<Attribute>(retActivityAttrs.begin(),
278 retActivityAttrs.end()));
279 IntegerAttr newWidthAttr =
280 IntegerAttr::get(firstDiffOp.getWidthAttr().getType(), width);
281 auto newDiffOp = ForwardDiffOp::create(
282 builder, loc, out_ty, firstDiffOp.getFnAttr(), in_args,
283 newInActivity, newRetActivity, newWidthAttr,
284 firstDiffOp.getStrongZeroAttr());
290 for (
auto [idx, ract] : llvm::enumerate(key.retActivity)) {
292 case Activity::enzyme_constnoneed:
295 case Activity::enzyme_active:
296 case Activity::enzyme_activenoneed:
297 case Activity::enzyme_const: {
298 auto new_out = newDiffOp.getOutputs()[out_idx];
299 for (
auto dop : allOps) {
300 dop.getOutputs()[out_idx].replaceAllUsesWith(new_out);
306 case Activity::enzyme_dupnoneed: {
308 auto batch_dout = newDiffOp.getOutputs()[out_idx];
309 for (
auto [dop_idx, dop] : llvm::enumerate(allOps)) {
310 auto old_dout = dop.getOutputs()[out_idx];
311 auto doutTy = old_dout.getType();
315 old_dout.replaceAllUsesWith(new_dout);
321 case Activity::enzyme_dup: {
322 mlir::Value new_out = newDiffOp.getOutputs()[out_idx];
323 for (ForwardDiffOp dop : allOps) {
324 dop.getOutputs()[out_idx].replaceAllUsesWith(new_out);
329 auto batch_dout = newDiffOp.getOutputs()[out_idx];
330 for (
auto [dop_idx, dop] : llvm::enumerate(allOps)) {
331 auto old_dout = dop.getOutputs()[out_idx];
332 auto doutTy = old_dout.getType();
336 old_dout.replaceAllUsesWith(new_dout);
343 "unknown activity value encountered for ret_activity");
348 for (
auto dop : allOps) {
356 void mergeRevdiffCalls(SymbolTableCollection &symbolTable,
357 FunctionOpInterface op) {
363 llvm::DenseMap<FunctionOpInterface,
364 SmallVector<MemoryEffects::EffectInstance>>
367 OpBuilder builder(op);
369 op->walk([&](Block *blk) {
372 SmallVector<enzyme::AutoDiffOp>>
375 for (
auto revOp : blk->getOps<enzyme::AutoDiffOp>()) {
376 auto fnOp = dyn_cast_or_null<FunctionOpInterface>(
377 symbolTable.lookupNearestSymbolFrom(revOp, revOp.getFnAttr()));
384 diffMergeSet[key].push_back(revOp);
387 for (
auto &pair : diffMergeSet) {
388 auto key = pair.first;
389 auto allDiffs = pair.second;
390 if (allDiffs.size() < 2)
394 if (!innerEffectCache.contains(key.
function)) {
399 SmallVector<MemoryEffects::EffectInstance> &calleeEffects =
403 bool skipMergeEntry =
false;
405 llvm::DenseMap<AutoDiffOp, SmallVector<MemoryEffects::EffectInstance>>
408 for (
auto &eff : calleeEffects) {
409 if (!isa<MemoryEffects::Read>(eff.getEffect()) &&
410 !isa<MemoryEffects::Write>(eff.getEffect())) {
412 skipMergeEntry =
true;
416 Value effVal = eff.getValue();
419 skipMergeEntry =
true;
424 size_t primalArgPos = 0;
425 bool foundPrimal =
false;
426 if (
auto effBA = dyn_cast<BlockArgument>(effVal)) {
427 if (llvm::is_contained(key.
function.getArguments(), effBA)) {
429 primalArgPos = effBA.getArgNumber();
437 skipMergeEntry =
true;
442 Value primalVal = key.
inputs[primalArgPos];
443 for (
auto dop : allDiffs) {
445 primalVal, eff.getEffect(), eff.getResource()));
454 (key.
inActivity[primalArgPos] == Activity::enzyme_dup) ||
455 (key.
inActivity[primalArgPos] == Activity::enzyme_dupnoneed);
458 size_t gradArgPos = 0;
459 for (
auto [idx, act] : llvm::enumerate(key.
inActivity)) {
462 if (idx == primalArgPos)
465 if (act == Activity::enzyme_dup ||
466 act == Activity::enzyme_dupnoneed) {
471 for (
auto dop : allDiffs) {
472 Value dVal = dop.getInputs()[gradArgPos];
474 dVal, MemoryEffects::Write::get(), eff.getResource()));
476 dVal, MemoryEffects::Read::get(), eff.getResource()));
484 SmallVector<AutoDiffOp> prunedSources =
488 symbolTable, key, prunedSources, callerEffectMap, innerEffectCache);
493 SmallVector<enzyme::AutoDiffOp> &allOps = legalMerge;
494 int64_t width = allOps.size();
499 auto firstDiffOp = allOps.front();
500 IRRewriter::InsertionGuard insertGuard(builder);
501 builder.setInsertionPoint(firstDiffOp);
502 auto loc = firstDiffOp->getLoc();
503 auto context = builder.getContext();
507 SmallVector<mlir::Value> in_args;
508 SmallVector<ActivityAttr, 2> inActivityAttrs;
509 SmallVector<ActivityAttr, 2> retActivityAttrs;
510 SmallVector<mlir::Type, 2> out_ty;
514 for (
auto [idx, act] : llvm::enumerate(key.
inActivity)) {
515 auto iattr = ActivityAttr::get(context, act);
516 inActivityAttrs.push_back(iattr);
517 in_args.push_back(key.
inputs[idx]);
520 if (act == Activity::enzyme_dup ||
521 act == Activity::enzyme_dupnoneed) {
523 SmallVector<mlir::Value> derivList;
524 for (
auto uop : allOps) {
525 derivList.push_back(uop.getInputs()[call_idx]);
529 in_args.push_back(b_din);
535 if (call_idx == firstDiffOp.getInputs().size()) {
542 auto iattr = ActivityAttr::get(context, ract);
543 retActivityAttrs.push_back(iattr);
546 if (ract == Activity::enzyme_constnoneed ||
547 ract == Activity::enzyme_dupnoneed) {
552 if (ract == Activity::enzyme_active ||
553 ract == Activity::enzyme_activenoneed) {
554 SmallVector<mlir::Value> derivList;
555 for (
auto uop : allOps) {
556 derivList.push_back(uop.getInputs()[call_idx]);
560 in_args.push_back(batch_dout);
565 if (ract == Activity::enzyme_active ||
566 ract == Activity::enzyme_const ||
567 ract == Activity::enzyme_dup) {
568 Value out = firstDiffOp.getOutputs()[out_idx];
569 out_ty.push_back(out.getType());
576 if (act == Activity::enzyme_active) {
577 Value din = firstDiffOp.getOutputs()[out_idx];
583 ArrayAttr newInActivity = ArrayAttr::get(
584 context, llvm::ArrayRef<Attribute>(inActivityAttrs.begin(),
585 inActivityAttrs.end()));
587 ArrayAttr newRetActivity = ArrayAttr::get(
588 context, llvm::ArrayRef<Attribute>(retActivityAttrs.begin(),
589 retActivityAttrs.end()));
591 IntegerAttr newWidthAttr =
592 IntegerAttr::get(firstDiffOp.getWidthAttr().getType(), width);
594 auto newDiffOp = AutoDiffOp::create(
595 builder, loc, out_ty, firstDiffOp.getFnAttr(), in_args,
596 newInActivity, newRetActivity, newWidthAttr,
597 firstDiffOp.getStrongZeroAttr(), firstDiffOp.getAtomicAddAttr());
602 if (ract == Activity::enzyme_active ||
603 ract == Activity::enzyme_const ||
604 ract == Activity::enzyme_dup) {
605 Value new_out = newDiffOp.getOutputs()[out_idx];
606 for (
auto dop : allOps) {
607 dop.getOutputs()[out_idx].replaceAllUsesWith(new_out);
614 if (act == Activity::enzyme_active) {
615 Value batch_din = newDiffOp.getOutputs()[out_idx];
616 for (
auto [dop_idx, dop] : llvm::enumerate(allOps)) {
617 Value old_din = dop.getOutputs()[out_idx];
618 auto dinTy = old_din.getType();
622 old_din.replaceAllUsesWith(new_din);
627 for (
auto dop : allOps) {
638void BatchDiffPass::runOnOperation() {
639 SymbolTableCollection symbolTable;
640 symbolTable.getSymbolTable(getOperation());
641 getOperation()->walk([&](FunctionOpInterface op) {
642 mergeFwddiffCalls(symbolTable, op);
643 mergeRevdiffCalls(symbolTable, op);
BatchDiffCacheKey createDiffCacheKey(SourceOp uop, FunctionOpInterface fn)
llvm::SmallVector< SourceOp, 2 > pruneGradDefs(BatchDiffCacheKey &key, SmallVector< SourceOp > &allDiffs)
llvm::SmallVector< SourceOp > pruneMemoryEffects(SymbolTableCollection &symbolTable, BatchDiffCacheKey &key, SmallVector< SourceOp > &prunedSources, DenseMap< SourceOp, SmallVector< MemoryEffects::EffectInstance > > &callerEffectMap, llvm::DenseMap< FunctionOpInterface, SmallVector< MemoryEffects::EffectInstance > > &innerEffectCache)
SmallVector< MemoryEffects::EffectInstance > collectFnEffects(FunctionOpInterface fnOp)
MemoryEffects::EffectInstance getEffectOfVal(Value val, MemoryEffects::Effect *effect, SideEffects::Resource *resource)
Type getConcatType(Value val, int64_t width)
Value getConcatValue(OpBuilder &builder, Location loc, ArrayRef< Value > argList)
Value getExtractValue(OpBuilder &builder, Location loc, Type argTy, Value val, int64_t index)
SmallVector< enzyme::Activity > retActivity
SmallVector< mlir::Value > inputs
SmallVector< enzyme::Activity > inActivity
FunctionOpInterface function