Enzyme main
Loading...
Searching...
No Matches
EnzymeAutoDiffOpInterfaceImpl.cpp
Go to the documentation of this file.
1//===- ArithAutoDiffOpInterfaceImpl.cpp - Interface external model --------===//
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 contains the external model implementation of the automatic
10// differentiation op interfaces for the upstream MLIR arithmetic dialect.
11//
12//===----------------------------------------------------------------------===//
13
18#include "mlir/IR/DialectRegistry.h"
19#include "mlir/Support/LogicalResult.h"
20
21#include "Dialect/Dialect.h"
22#include "Dialect/Ops.h"
23#include "mlir/Dialect/Affine/IR/AffineOps.h"
24#include "mlir/IR/TypeSupport.h"
25
26using namespace mlir;
27using namespace mlir::enzyme;
28
29namespace {
30#include "Implementations/EnzymeDerivatives.inc"
31
32// The primal op atomically performs old = m[i]; m[i] = old + v and yields
33// old. Backward, the location's adjoint serves both reads: the added value
34// takes it (d_v += dm[i]), and it keeps flowing to whatever wrote m[i]
35// before -- the atomic changed the value, not which value the location
36// stands for. A used result read the pre-add value, so its adjoint joins
37// the location's, atomically for the same reason the primal add was atomic.
38// The load of the location's adjoint is plain: the reverse sweep mirrors
39// the forward structure, so the barriers that ordered the primal's atomics
40// against this thread's reads order the shadow's atomics against this load.
41struct AffineAtomicRMWOpInterfaceReverse
42 : public ReverseAutoDiffOpInterface::ExternalModel<
43 AffineAtomicRMWOpInterfaceReverse, enzyme::AffineAtomicRMWOp> {
44 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
46 SmallVector<Value> caches) const {
47 auto rmwOp = cast<enzyme::AffineAtomicRMWOp>(op);
48 Value memref = rmwOp.getMemref();
49 Value val = rmwOp.getValue();
50
51 if (gutils->isConstantValue(memref)) {
52 // Inactive memory: the value folds into it and the result read from it
53 // carries nothing out.
54 if (!gutils->isConstantValue(rmwOp.getResult()))
55 gutils->zeroDiffe(rmwOp.getResult(), builder);
56 return success();
57 }
58
59 if (rmwOp.getKind() != arith::AtomicRMWKind::addf)
60 return op->emitError()
61 << "could not compute the adjoint of a non-add atomic rmw " << *op;
62
63 auto iface = dyn_cast<AutoDiffTypeInterface>(val.getType());
64 if (!iface)
65 return op->emitError()
66 << "could not compute the adjoint of an atomic rmw on a type "
67 "without autodiff semantics "
68 << *op;
69
70 Value memrefGradient = gutils->popCache(caches.front(), builder);
71 SmallVector<Value> retrievedArguments;
72 for (Value cache : ValueRange(caches).drop_front(1))
73 retrievedArguments.push_back(gutils->popCache(cache, builder));
74
75 auto alignAttr = rmwOp.getAlignmentAttr();
76 if (!gutils->isConstantValue(val)) {
77 auto loadedGradient = affine::AffineLoadOp::create(
78 builder, rmwOp.getLoc(), memrefGradient, rmwOp.getMap(),
79 ArrayRef<Value>(retrievedArguments));
80 if (alignAttr)
81 loadedGradient->setAttr("alignment", alignAttr);
82 gutils->addToDiffe(val, loadedGradient, builder);
83 }
84
85 if (!gutils->isConstantValue(rmwOp.getResult())) {
86 Value gradient = gutils->diffe(rmwOp.getResult(), builder);
87 gutils->zeroDiffe(rmwOp.getResult(), builder);
88 // The adjoint accumulates where the primal did, so it is ordered as
89 // the primal was.
90 setDerivativeFastMath(enzyme::AffineAtomicRMWOp::create(
91 builder, rmwOp.getLoc(), gradient.getType(),
92 arith::AtomicRMWKind::addf, rmwOp.getOrdering(), gradient,
93 memrefGradient, retrievedArguments, rmwOp.getMap(), alignAttr));
94 }
95
96 return success();
97 }
98
99 SmallVector<Value> cacheValues(Operation *op,
100 MGradientUtilsReverse *gutils) const {
101 auto rmwOp = cast<enzyme::AffineAtomicRMWOp>(op);
102 if (gutils->isConstantValue(rmwOp.getMemref()))
103 return {};
104 OpBuilder cacheBuilder(gutils->getNewFromOriginal(op));
105 SmallVector<Value> caches;
106 caches.push_back(gutils->initAndPushCache(
107 gutils->invertPointerM(rmwOp.getMemref(), cacheBuilder), cacheBuilder));
108 for (Value v : rmwOp.getIndices())
109 caches.push_back(gutils->initAndPushCache(gutils->getNewFromOriginal(v),
110 cacheBuilder));
111 return caches;
112 }
113
114 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
115 MGradientUtilsReverse *gutils) const {
116 return success();
117 }
118};
119
120// Forward mode mirrors the primal on the shadows: the tangent of the value
121// joins the location's tangent through the same atomic add, and what the
122// primal read out (the pre-add value) has as its tangent what the shadow
123// location held before the shadow's add -- exactly what the mirrored atomic
124// returns.
125struct AffineAtomicRMWOpForwardInterface
126 : public AutoDiffOpInterface::ExternalModel<
127 AffineAtomicRMWOpForwardInterface, enzyme::AffineAtomicRMWOp> {
128 LogicalResult createForwardModeTangent(Operation *op, OpBuilder &builder,
129 MGradientUtils *gutils) const {
130 auto rmwOp = cast<enzyme::AffineAtomicRMWOp>(op);
131 auto iface = dyn_cast<AutoDiffTypeInterface>(rmwOp.getValue().getType());
132 if (!iface)
133 return op->emitError()
134 << "could not compute the tangent of an atomic rmw on a type "
135 "without autodiff semantics "
136 << *op;
137
138 if (gutils->isConstantValue(rmwOp.getMemref())) {
139 // Inactive memory: the value's tangent folds into it and the result
140 // read from it carries a zero tangent out.
141 if (!gutils->isConstantValue(rmwOp.getResult()))
142 gutils->setDiffe(rmwOp.getResult(),
143 iface.createNullValue(builder, op->getLoc()), builder);
144 return success();
145 }
146
147 if (rmwOp.getKind() != arith::AtomicRMWKind::addf)
148 return op->emitError()
149 << "could not compute the tangent of a non-add atomic rmw " << *op;
150
151 Value shadowMemref = gutils->invertPointerM(rmwOp.getMemref(), builder);
152 Value shadowVal = gutils->isConstantValue(rmwOp.getValue())
153 ? iface.createNullValue(builder, op->getLoc())
154 : gutils->invertPointerM(rmwOp.getValue(), builder);
155 auto newOp =
156 cast<enzyme::AffineAtomicRMWOp>(gutils->getNewFromOriginal(op));
157 SmallVector<Value> shadowIndices;
158 for (Value v : newOp.getIndices())
159 shadowIndices.push_back(v);
160 auto shadowOp = enzyme::AffineAtomicRMWOp::create(
161 builder, rmwOp.getLoc(), shadowVal.getType(),
162 arith::AtomicRMWKind::addf, rmwOp.getOrdering(), shadowVal,
163 shadowMemref, shadowIndices, rmwOp.getMap(), rmwOp.getAlignmentAttr());
164 shadowOp->setAttr("fastmath", newOp.getFastmathAttr());
165 if (!gutils->isConstantValue(rmwOp.getResult()))
166 gutils->setDiffe(rmwOp.getResult(), shadowOp.getResult(), builder);
167 return success();
168 }
169};
170
171} // namespace
172
174 DialectRegistry &registry) {
175 registry.addExtension(+[](MLIRContext *context, enzyme::EnzymeDialect *) {
176 registerInterfaces(context);
177 // The rules build affine loads and atomic adds over affine maps.
178 context->getOrLoadDialect<affine::AffineDialect>();
179 enzyme::AffineAtomicRMWOp::attachInterface<
180 AffineAtomicRMWOpInterfaceReverse>(*context);
181 enzyme::AffineAtomicRMWOp::attachInterface<
182 AffineAtomicRMWOpForwardInterface>(*context);
183 });
184}
mlir::Value diffe(mlir::Value origv, mlir::OpBuilder &builder)
void zeroDiffe(mlir::Value origv, mlir::OpBuilder &builder)
Value popCache(Value cache, OpBuilder &builder)
Value initAndPushCache(Value v, OpBuilder &builder)
void addToDiffe(mlir::Value oldGradient, mlir::Value addedGradient, OpBuilder &builder)
void setDiffe(mlir::Value origv, mlir::Value newv, mlir::OpBuilder &builder)
mlir::Value invertPointerM(mlir::Value v, OpBuilder &Builder2)
SmallVector< mlir::Value, 1 > getNewFromOriginal(ValueRange originst) const
bool isConstantValue(mlir::Value v) const
void registerEnzymeDialectAutoDiffInterface(DialectRegistry &registry)
void setDerivativeFastMath(Operation *op)
Give an operation built for a derivative the fast-math flags the LLVM path gives everything it builds...