Skip to content

Commit 9259e18

Browse files
committed
Add const shadows for forward mode AD on RegionBranchOpInterface
When an operand has const activity, we may still need to create a shadow for it (particularly in scf.for, where a const iter_arg may still have to be shadowed if the result and the terminator are active)
1 parent bf7e813 commit 9259e18

6 files changed

Lines changed: 169 additions & 36 deletions

File tree

enzyme/Enzyme/MLIR/Implementations/CoreDialectsAutoDiffImplementations.cpp

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -318,15 +318,14 @@ LogicalResult mlir::enzyme::detail::controlFlowForwardHandler(
318318
// add the shadow as operand.
319319
auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op);
320320
if (!regionBranchOp) {
321-
op->emitError() << " RegionBranchOpInterface not implemented for " << *op
322-
<< "\n";
323-
return failure();
321+
return op->emitError() << " RegionBranchOpInterface not implemented for "
322+
<< *op << "\n";
324323
}
325324
auto iface = dyn_cast<ControlFlowAutoDiffOpInterface>(op);
326325
if (!iface) {
327-
op->emitError() << " ControlFlowAutoDiffOpInterface not implemented for "
328-
<< *op << "\n";
329-
return failure();
326+
return op->emitError()
327+
<< " ControlFlowAutoDiffOpInterface not implemented for " << *op
328+
<< "\n";
330329
}
331330

332331
// TODO: we may need to record, for every successor, which of its inputs
@@ -352,8 +351,37 @@ LogicalResult mlir::enzyme::detail::controlFlowForwardHandler(
352351
// operands.
353352
for (auto &&[i, regionValue, operand] :
354353
llvm::enumerate(targetValues, operandRange)) {
355-
if (gutils->isConstantValue(regionValue))
356-
continue;
354+
355+
// if all the possible predecessors for this value are also const, then
356+
// we can skip creating a shadow. Else we need to create a shadow for
357+
// activity correctness
358+
if (gutils->isConstantValue(regionValue)) {
359+
SmallVector<Value> possibleActivePreds;
360+
SmallVector<RegionBranchPoint> predecessors;
361+
regionBranchOp.getPredecessors(successor, predecessors);
362+
for (RegionBranchPoint predecessor : predecessors) {
363+
if (predecessor.isParent()) {
364+
// if the predecessor is the parent itself, then it's just
365+
// `operand`
366+
possibleActivePreds.push_back(operand);
367+
continue;
368+
}
369+
auto terminator = predecessor.getTerminatorPredecessorOrNull();
370+
auto predecessorOperands = terminator.getSuccessorOperands(successor);
371+
if (i < predecessorOperands.size())
372+
possibleActivePreds.push_back(predecessorOperands[i]);
373+
}
374+
375+
bool skipOpShadow = true;
376+
for (auto pv : possibleActivePreds) {
377+
if (!skipOpShadow)
378+
break;
379+
skipOpShadow = skipOpShadow && gutils->isConstantValue(pv);
380+
};
381+
if (skipOpShadow)
382+
continue;
383+
// if there's any possible active predecessor, we create a shadow for it
384+
}
357385
operandPositionsToShadow.insert(operandRange.getBeginOperandIndex() + i);
358386
if (successor.isParent())
359387
resultPositionsToShadow.insert(i);
@@ -388,33 +416,87 @@ LogicalResult mlir::enzyme::detail::controlFlowForwardHandler(
388416
continue;
389417
auto typeIface = dyn_cast<AutoDiffTypeInterface>(result.getType());
390418
if (!typeIface) {
391-
op->emitError() << " AutoDiffTypeInterface not implemented for "
392-
<< result.getType() << "\n";
393-
return failure();
419+
return op->emitError() << " AutoDiffTypeInterface not implemented for "
420+
<< result.getType() << "\n";
394421
}
395422
newOpResultTypes.push_back(typeIface.getShadowType(gutils->width));
396423
}
397424

398425
SmallVector<Value> newOperands;
399426
newOperands.reserve(op->getNumOperands() + operandPositionsToShadow.size());
427+
428+
auto iface = dyn_cast<ControlFlowAutoDiffOpInterface>(op);
429+
if (!iface) {
430+
return op->emitError()
431+
<< " ControlFlowAutoDiffOpInterface not implemented for " << *op
432+
<< "\n";
433+
}
434+
435+
auto regionBranchOp = cast<RegionBranchOpInterface>(op);
436+
SmallVector<RegionSuccessor> entrySuccessors;
437+
regionBranchOp.getEntrySuccessorRegions(
438+
SmallVector<Attribute>(op->getNumOperands(), Attribute()),
439+
entrySuccessors);
400440
for (OpOperand &operand : op->getOpOperands()) {
401441
newOperands.push_back(gutils->getNewFromOriginal(operand.get()));
402-
if (operandPositionsToShadow.contains(operand.getOperandNumber()))
403-
newOperands.push_back(gutils->invertPointerM(operand.get(), builder));
442+
if (operandPositionsToShadow.contains(operand.getOperandNumber())) {
443+
Value shadowValue = nullptr;
444+
if (!gutils->isConstantValue(operand.get()))
445+
shadowValue = gutils->invertPointerM(operand.get(), builder);
446+
else {
447+
auto Ty = operand.get().getType();
448+
auto shadowType =
449+
cast<AutoDiffTypeInterface>(Ty).getShadowType(gutils->width);
450+
shadowValue = cast<AutoDiffTypeInterface>(shadowType)
451+
.createNullValue(builder, operand.get().getLoc());
452+
453+
// modify block arguments for entry successors to newOp, since
454+
// forceAugmentedReturns will not shadow const operands. No need to add
455+
// to the invertPointers map since `operand` is const (the shadow will
456+
// be unused)
457+
for (const RegionSuccessor &successor : entrySuccessors) {
458+
if (successor.isParent())
459+
continue;
460+
auto &newOpRegion =
461+
newOp->getRegion(successor.getSuccessor()->getRegionNumber());
462+
OperandRange succOperands =
463+
iface.getSuccessorOperands(regionBranchOp, successor);
464+
ValueRange succInputs = regionBranchOp.getSuccessorInputs(successor);
465+
466+
if (succOperands.empty())
467+
continue;
468+
469+
auto succInputPos =
470+
operand.getOperandNumber() - succOperands.getBeginOperandIndex();
471+
472+
if (succInputPos >= 0 && succInputPos < succInputs.size()) {
473+
auto oldRegionInput =
474+
dyn_cast<BlockArgument>(succInputs[succInputPos]);
475+
if (!oldRegionInput)
476+
continue;
477+
if (gutils->invertedPointers.contains(oldRegionInput))
478+
continue;
479+
auto newOpBlockVal =
480+
cast<BlockArgument>(gutils->getNewFromOriginal(oldRegionInput));
481+
auto i = newOpBlockVal.getArgNumber();
482+
if (i == newOpRegion.getNumArguments() - 1) {
483+
newOpRegion.addArgument(shadowType, newOpBlockVal.getLoc());
484+
} else {
485+
newOpRegion.insertArgument(newOpRegion.args_begin() + i + 1,
486+
shadowType, newOpBlockVal.getLoc());
487+
}
488+
}
489+
}
490+
}
491+
newOperands.push_back(shadowValue);
492+
}
404493
}
405494
// We are assuming the op can forward additional operands, listed
406495
// immediately after the original operands, to the same regions.
407496
// ^^
408497
// Our interface guarantees this.
409498
// We also assume that the region-holding op returns all of the values
410499
// yielded by terminators, and only those values.
411-
412-
auto iface = dyn_cast<ControlFlowAutoDiffOpInterface>(op);
413-
if (!iface) {
414-
op->emitError() << " ControlFlowAutoDiffOpInterface not implemented for "
415-
<< *op << "\n";
416-
return failure();
417-
}
418500
Operation *replacement = iface.createWithShadows(
419501
builder, gutils, op, newOperands, newOpResultTypes);
420502
assert(replacement->getNumResults() == newOpResultTypes.size());

enzyme/test/MLIR/ForwardMode/affine.mlir

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ module {
1515
}
1616
// CHECK: @fwddiffeloop
1717
// CHECK: (%[[arg0:.+]]: f64, %[[arg1:.+]]: f64)
18-
// CHECK: %[[cst:.+]] = arith.constant 0.000000e+00 : f64
19-
// CHECK: %[[cst_0:.+]] = arith.constant 1.000000e+01 : f64
20-
// CHECK: %[[r0:.+]]:2 = affine.for %{{.*}} = 0 to 10 iter_args(%[[arg3:.+]] = %[[cst_0]], %[[arg4:.+]] = %[[cst]]) -> (f64, f64) {
18+
// CHECK-DAG: %[[TEN:.+]] = arith.constant 1.000000e+01 : f64
19+
// CHECK-DAG: %[[ZERO:.+]] = arith.constant 0.000000e+00 : f64
20+
// CHECK: %[[r0:.+]]:2 = affine.for %{{.*}} = 0 to 10 iter_args(%[[arg3:.+]] = %[[TEN]], %[[arg4:.+]] = %[[ZERO]]) -> (f64, f64) {
2121
// CHECK: %[[v1:.+]] = arith.addf %[[arg4]], %[[arg1]] : f64
2222
// CHECK: %[[v2:.+]] = arith.addf %[[arg3]], %[[arg0]] : f64
2323
// CHECK: affine.yield %[[v2]], %[[v1]] : f64, f64
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// RUN: %eopt --enzyme --split-input-file %s | FileCheck %s
2+
3+
module {
4+
func.func @carry_mismatch_scf(%x : f64) -> f64 {
5+
%zero = arith.constant 0.0 : f64
6+
%c0 = arith.constant 0 : index
7+
%c1 = arith.constant 1 : index
8+
%c10 = arith.constant 10 : index
9+
%r = scf.for %i = %c0 to %c10 step %c1 iter_args(%acc = %zero) -> (f64) {
10+
scf.yield %x : f64
11+
}
12+
return %r : f64
13+
}
14+
15+
func.func @dcarry_mismatch_scf(%x : f64, %dx : f64) -> f64 {
16+
%r = enzyme.fwddiff @carry_mismatch_scf(%x, %dx) { activity=[#enzyme<activity enzyme_dup>], ret_activity=[#enzyme<activity enzyme_dupnoneed>] } : (f64, f64) -> (f64)
17+
return %r : f64
18+
}
19+
}
20+
21+
// CHECK-LABEL: func.func private @fwddiffecarry_mismatch_scf(
22+
// CHECK-DAG: %[[C0:.+]] = arith.constant 0 : index
23+
// CHECK-DAG: %[[C1:.+]] = arith.constant 1 : index
24+
// CHECK-DAG: %[[C10:.+]] = arith.constant 10 : index
25+
// CHECK: %[[LOOP:.+]]:2 = scf.for %[[IV:.+]] = %[[C0]] to %[[C10]] step %[[C1]] iter_args(%[[ACC:.+]] = %{{.+}}, %[[DACC:.+]] = %{{.+}}) -> (f64, f64) {
26+
// CHECK-NEXT: scf.yield %[[ARG0:.+]], %[[ARG1:.+]] : f64, f64
27+
// CHECK-NEXT: }
28+
// CHECK-NEXT: return %[[LOOP]]#1 : f64
29+
30+
// -----
31+
32+
module {
33+
func.func @carry_mismatch_affine(%x : f64) -> f64 {
34+
%zero = arith.constant 0.0 : f64
35+
%r = affine.for %i = 0 to 10 iter_args(%acc = %zero) -> (f64) {
36+
affine.yield %x : f64
37+
}
38+
return %r : f64
39+
}
40+
41+
func.func @dcarry_mismatch_affine(%x : f64, %dx : f64) -> f64 {
42+
%r = enzyme.fwddiff @carry_mismatch_affine(%x, %dx) { activity=[#enzyme<activity enzyme_dup>], ret_activity=[#enzyme<activity enzyme_dupnoneed>] } : (f64, f64) -> (f64)
43+
return %r : f64
44+
}
45+
}
46+
47+
// CHECK-LABEL: func.func private @fwddiffecarry_mismatch_affine(
48+
// CHECK: %[[ALOOP:.+]]:2 = affine.for %[[AIV:.+]] = 0 to 10 iter_args(%[[AACC:.+]] = %{{.+}}, %[[ADACC:.+]] = %{{.+}}) -> (f64, f64) {
49+
// CHECK-NEXT: affine.yield %[[ARG0:.+]], %[[ARG1:.+]] : f64, f64
50+
// CHECK-NEXT: }
51+
// CHECK-NEXT: return %[[ALOOP]]#1 : f64

enzyme/test/MLIR/ForwardMode/parallel.mlir

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ module {
3232

3333
// CHECK: @fwddiffematvec(%[[arg0:.+]]: memref<?x?xf64>, %[[arg1:.+]]: memref<?x?xf64>, %[[arg2:.+]]: memref<?xf64>, %[[arg3:.+]]: memref<?xf64>, %[[arg4:.+]]: memref<?xf64>, %[[arg5:.+]]: memref<?xf64>) {
3434
// CHECK: %[[cst:.+]] = arith.constant 0.000000e+00 : f64
35-
// CHECK: %[[cst_0:.+]] = arith.constant 0.000000e+00 : f64
3635
// CHECK: %[[c1:.+]] = arith.constant 1 : index
3736
// CHECK: %[[c0:.+]] = arith.constant 0 : index
3837
// CHECK: %[[dim:.+]] = memref.dim %[[arg0:.+]], %[[c0]] : memref<?x?xf64>
3938
// CHECK: %[[dim_1:.+]] = memref.dim %[[arg0:.+]], %[[c1]] : memref<?x?xf64>
4039
// CHECK: scf.parallel (%[[arg6:.+]]) = (%[[c0]]) to (%dim) step (%[[c1]]) {
41-
// CHECK: %[[x0:.+]]:2 = scf.for %[[arg7:.+]] = %[[c0]] to %dim_1 step %[[c1]] iter_args(%[[arg8:.+]] = %[[cst_0]], %[[arg9:.+]] = %[[cst]]) -> (f64, f64) {
40+
// CHECK: %[[cst_0:.+]] = arith.constant 0.000000e+00 : f64
41+
// CHECK: %[[x0:.+]]:2 = scf.for %[[arg7:.+]] = %[[c0]] to %[[dim_1]] step %[[c1]] iter_args(%[[arg8:.+]] = %[[cst]], %[[arg9:.+]] = %[[cst_0]]) -> (f64, f64) {
4242
// CHECK: %[[x1:.+]] = memref.load %[[arg1]][%[[arg6]], %[[arg7]]] : memref<?x?xf64>
4343
// CHECK: %[[x2:.+]] = memref.load %[[arg0]][%[[arg6]], %[[arg7]]] : memref<?x?xf64>
4444
// CHECK: %[[x3:.+]] = memref.load %[[arg3]][%[[arg7]]] : memref<?xf64>

enzyme/test/MLIR/ForwardMode/parallel_reduce.mlir

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ module {
2727
}
2828

2929
// CHECK: @fwddiffenrm2(%[[arg0:.+]]: memref<?xf64>, %[[arg1:.+]]: memref<?xf64>) -> f64 {
30-
// CHECK: %[[c0:.+]] = arith.constant 0 : index
31-
// CHECK: %[[c1:.+]] = arith.constant 1 : index
32-
// CHECK: %[[cst:.+]] = arith.constant 0.000000e+00 : f64
30+
// CHECK-DAG: %[[c0:.+]] = arith.constant 0 : index
31+
// CHECK-DAG: %[[c1:.+]] = arith.constant 1 : index
32+
// CHECK-DAG: %[[cst:.+]] = arith.constant 0.000000e+00 : f64
33+
// CHECK-DAG: %[[dim:.+]] = memref.dim %[[arg0]], %[[c0]] : memref<?xf64>
3334
// CHECK: %[[cst_0:.+]] = arith.constant 0.000000e+00 : f64
34-
// CHECK: %[[dim:.+]] = memref.dim %[[arg0]], %[[c0]] : memref<?xf64>
35-
// CHECK: %[[x0:.+]]:2 = scf.parallel (%[[arg2:.+]]) = (%[[c0]]) to (%dim) step (%[[c1]]) init (%[[cst_0]], %[[cst]]) -> (f64, f64) {
35+
// CHECK: %[[x0:.+]]:2 = scf.parallel (%[[arg2:.+]]) = (%[[c0]]) to (%[[dim]]) step (%[[c1]]) init (%[[cst]], %[[cst_0]]) -> (f64, f64) {
3636
// CHECK: %[[x1:.+]] = memref.load %[[arg1]][%[[arg2]]] : memref<?xf64>
3737
// CHECK: %[[x2:.+]] = memref.load %[[arg0]][%[[arg2]]] : memref<?xf64>
3838
// CHECK: %[[x3:.+]] = arith.mulf %[[x1]], %[[x2]] : f64

enzyme/test/MLIR/ForwardMode/while.mlir

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ module {
2424
}
2525
// CHECK: @fwddiffewhile
2626
// CHECK: (%[[arg0:.+]]: f64, %[[arg1:.+]]: f64) -> f64 {
27-
// CHECK: %[[cst:.+]] = arith.constant 0.000000e+00 : f64
28-
// CHECK: %[[cst_0:.+]] = arith.constant 1.000000e+01 : f64
29-
// CHECK: %[[c0:.+]] = arith.constant 0 : index
30-
// CHECK: %[[c1:.+]] = arith.constant 1 : index
31-
// CHECK: %[[c10:.+]] = arith.constant 10 : index
32-
// CHECK: %[[r0:.+]]:3 = scf.while (%[[arg2:.+]] = %[[c0]], %[[arg3:.+]] = %[[cst_0]], %[[arg4:.+]] = %[[cst]]) : (index, f64, f64) -> (index, f64, f64) {
27+
// CHECK-DAG: %[[TEN:.+]] = arith.constant 1.000000e+01 : f64
28+
// CHECK-DAG: %[[ZERO:.+]] = arith.constant 0.000000e+00 : f64
29+
// CHECK-DAG: %[[c0:.+]] = arith.constant 0 : index
30+
// CHECK-DAG: %[[c1:.+]] = arith.constant 1 : index
31+
// CHECK-DAG: %[[c10:.+]] = arith.constant 10 : index
32+
// CHECK: %[[r0:.+]]:3 = scf.while (%[[arg2:.+]] = %[[c0]], %[[arg3:.+]] = %[[TEN]], %[[arg4:.+]] = %[[ZERO]]) : (index, f64, f64) -> (index, f64, f64) {
3333
// CHECK: %[[v1:.+]] = arith.cmpi slt, %[[arg2]], %[[c10]] : index
3434
// CHECK: scf.condition(%[[v1]]) %[[arg2]], %[[arg3]], %[[arg4]] : index, f64, f64
3535
// CHECK: } do {

0 commit comments

Comments
 (0)