Enzyme main
Loading...
Searching...
No Matches
ArithAutoDiffOpInterfaceImpl.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/Dialect/Arith/IR/Arith.h"
19#include "mlir/Dialect/Math/IR/Math.h"
20#include "mlir/Dialect/SCF/IR/SCF.h"
21#include "mlir/Dialect/Tensor/IR/Tensor.h"
22#include "mlir/IR/DialectRegistry.h"
23#include "mlir/Support/LogicalResult.h"
24
25#include "Dialect/Ops.h"
26#include "mlir/IR/TypeSupport.h"
27
28using namespace mlir;
29using namespace mlir::enzyme;
30
31namespace {
32
33struct ArithConstantOpBatchInterface
34 : public BatchOpInterface::ExternalModel<ArithConstantOpBatchInterface,
35 arith::ConstantOp> {
36
37 mlir::LogicalResult createBatch(Operation *src, OpBuilder &builder,
38 IRMapping &mapper,
39 ArrayRef<int64_t> batchSizes) const {
40
41 SmallVector<Type> resultTypes(src->getResultTypes().begin(),
42 src->getResultTypes().end());
43 for (auto &Ty : resultTypes) {
44 auto T = cast<TensorType>(Ty);
45 SmallVector<int64_t> shape(batchSizes.begin(), batchSizes.end());
46 shape.append(T.getShape().begin(), T.getShape().end());
47 Ty = T.clone(shape);
48 }
49 mlir::NamedAttrList attrs;
50 for (auto attr : src->getAttrs()) {
51 auto eattr = cast<DenseElementsAttr>(attr.getValue());
52 attr.setValue(eattr.resizeSplat(cast<ShapedType>(resultTypes[0])));
53 attrs.append(attr);
54 }
55 auto cop = mlir::Operation::create(
56 src->getLoc(), src->getName(), resultTypes, {}, std::move(attrs),
57 mlir::PropertyRef(), mlir::BlockRange(), 0);
58 builder.insert(cop);
59 mapper.map(src->getResult(0), cop->getResult(0));
60 return success();
61 }
62};
63
64struct ArithAddFSimplifyMathInterface
65 : public MathSimplifyInterface::ExternalModel<
66 ArithAddFSimplifyMathInterface, arith::AddFOp> {
67 mlir::LogicalResult simplifyMath(Operation *src,
68 PatternRewriter &rewriter) const {
69 auto op = cast<arith::AddFOp>(src);
70
71 if (matchPattern(op.getLhs(), m_AnyZeroFloat())) {
72 rewriter.replaceOp(op, op.getRhs());
73 return success();
74 }
75
76 if (matchPattern(op.getRhs(), m_AnyZeroFloat())) {
77 rewriter.replaceOp(op, op.getLhs());
78 return success();
79 }
80
81 return failure();
82 }
83};
84
85struct ArithSubFSimplifyMathInterface
86 : public MathSimplifyInterface::ExternalModel<
87 ArithSubFSimplifyMathInterface, arith::SubFOp> {
88 mlir::LogicalResult simplifyMath(Operation *src,
89 PatternRewriter &rewriter) const {
90 auto op = cast<arith::SubFOp>(src);
91
92 if (matchPattern(op.getRhs(), m_AnyZeroFloat())) {
93 rewriter.replaceOp(op, op.getLhs());
94 return success();
95 }
96
97 if (matchPattern(op.getLhs(), m_AnyZeroFloat())) {
98 rewriter.replaceOpWithNewOp<arith::NegFOp>(op, op.getRhs(),
99 op.getFastmathAttr());
100 return success();
101 }
102
103 return failure();
104 }
105};
106
107// An active operand contributes its shadow; a constant immutable operand a
108// zero. A constant mutable operand has no shadow of its own -- hand back the
109// primal and warn, pending proper runtime activity handling.
110static Value operandTangent(Value v, Operation *op, OpBuilder &builder,
111 MGradientUtils *gutils) {
112 if (!gutils->isConstantValue(v))
113 return gutils->invertPointerM(v, builder);
114 auto iface = dyn_cast<AutoDiffTypeInterface>(getShadowType(v.getType()));
115 if (iface && !iface.isMutable())
116 return iface.createNullValue(builder, op->getLoc());
117 op->emitWarning()
118 << "constant mutable operand of an active select is passed through "
119 "as its own shadow pending runtime activity handling";
120 return gutils->getNewFromOriginal(v);
121}
122
123// The condition carries no tangent; the result's tangent follows the same
124// choice between the branch tangents, a constant branch contributing zero.
125// Reverse-generated adjoints are full of these selects, and forward-over-
126// reverse differentiates them again.
127struct SelectOpFwdInterface
128 : public AutoDiffOpInterface::ExternalModel<SelectOpFwdInterface,
129 arith::SelectOp> {
130 LogicalResult createForwardModeTangent(Operation *op, OpBuilder &builder,
131 MGradientUtils *gutils) const {
132 auto selectOp = cast<arith::SelectOp>(op);
133 gutils->eraseIfUnused(op);
134 if (gutils->isConstantInstruction(op))
135 return success();
136 // The same rule serves values and pointers: a value's tangent and a
137 // pointer's shadow both follow the choice the primal made.
138 Value cond = gutils->getNewFromOriginal(selectOp.getCondition());
139 Value dTrue = operandTangent(selectOp.getTrueValue(), op, builder, gutils);
140 Value dFalse =
141 operandTangent(selectOp.getFalseValue(), op, builder, gutils);
142 Value tangent =
143 arith::SelectOp::create(builder, op->getLoc(), cond, dTrue, dFalse);
144 gutils->setDiffe(selectOp.getResult(), tangent, builder);
145 return success();
146 }
147};
148
149struct SelectOpInterfaceReverse
150 : public ReverseAutoDiffOpInterface::ExternalModel<SelectOpInterfaceReverse,
151 arith::SelectOp> {
152
153 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
154 MGradientUtilsReverse *gutils,
155 SmallVector<Value> caches) const {
156 auto selectOp = cast<arith::SelectOp>(op);
157 // TODO: ideally make part of tablegen, handling both float and pointer
158 // values
159 if (!gutils->isConstantValue(selectOp.getResult())) {
160 auto iface =
161 dyn_cast<AutoDiffTypeInterface>(selectOp.getResult().getType());
162 if (iface && iface.isMutable()) {
163 return success();
164 }
165
166 Value condition = gutils->popCache(caches.front(), builder);
167
168 auto siface =
169 cast<AutoDiffTypeInterface>(getShadowType(selectOp.getType()));
170 Value zero = siface.createNullValue(builder, selectOp.getLoc());
171 Value dret = gutils->diffe(selectOp.getResult(), builder);
172 gutils->zeroDiffe(selectOp.getResult(), builder);
173 if (!gutils->isConstantValue(selectOp.getTrueValue())) {
174 Value trueSelect = arith::SelectOp::create(builder, selectOp.getLoc(),
175 condition, dret, zero);
176
177 gutils->addToDiffe(selectOp.getTrueValue(), trueSelect, builder);
178 }
179 if (!gutils->isConstantValue(selectOp.getFalseValue())) {
180 Value falseSelect = arith::SelectOp::create(builder, selectOp.getLoc(),
181 condition, zero, dret);
182 gutils->addToDiffe(selectOp.getFalseValue(), falseSelect, builder);
183 }
184 }
185 return success();
186 }
187
188 SmallVector<Value> cacheValues(Operation *op,
189 MGradientUtilsReverse *gutils) const {
190 auto selectOp = cast<arith::SelectOp>(op);
191 SmallVector<Value> caches;
192 if (!gutils->isConstantValue(selectOp.getResult())) {
193 OpBuilder cacheBuilder(gutils->getNewFromOriginal(op));
194 auto iface =
195 dyn_cast<AutoDiffTypeInterface>(selectOp.getResult().getType());
196 if (iface && iface.isMutable()) {
197 return caches;
198 }
199
200 caches.push_back(gutils->initAndPushCache(
201 gutils->getNewFromOriginal(selectOp.getCondition()), cacheBuilder));
202 }
203 return caches;
204 }
205 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
206 MGradientUtilsReverse *gutils) const {
207 auto selectOp = cast<arith::SelectOp>(op);
208 if (gutils->isConstantValue(selectOp.getResult()))
209 return success();
210
211 auto iface =
212 dyn_cast<AutoDiffTypeInterface>(selectOp.getResult().getType());
213 if (iface && iface.isMutable()) {
214 auto shadowOp = arith::SelectOp::create(
215 builder, selectOp.getLoc(),
216 gutils->getNewFromOriginal(selectOp.getCondition()),
217 gutils->invertPointerM(selectOp.getTrueValue(), builder),
218 gutils->invertPointerM(selectOp.getFalseValue(), builder));
219 gutils->setInvertedPointer(selectOp.getResult(), shadowOp.getResult());
220 }
221 return success();
222 }
223};
224
225struct SelectActivityInterface
226 : public ActivityOpInterface::ExternalModel<SelectActivityInterface,
227 arith::SelectOp> {
228 bool isInactive(Operation *op) const { return false; }
229 bool isArgInactive(Operation *op, size_t idx) const {
230 // arith.select is not inactive in general, but the condition is always
231 // inactive.
232 return idx == 0;
233 }
234};
235
236#include "Implementations/ArithDerivatives.inc"
237} // namespace
238
240 DialectRegistry &registry) {
241 registry.addExtension(+[](MLIRContext *context, arith::ArithDialect *) {
242 registerInterfaces(context);
243 arith::SelectOp::attachInterface<SelectActivityInterface>(*context);
244 arith::SelectOp::attachInterface<SelectOpFwdInterface>(*context);
245 arith::SelectOp::attachInterface<SelectOpInterfaceReverse>(*context);
246 arith::ConstantOp::attachInterface<ArithConstantOpBatchInterface>(*context);
247 arith::AddFOp::attachInterface<ArithAddFSimplifyMathInterface>(*context);
248 arith::SubFOp::attachInterface<ArithSubFSimplifyMathInterface>(*context);
249 });
250}
251
253 DialectRegistry &registry) {
254 registry.addExtension(+[](MLIRContext *context, tensor::TensorDialect *) {
255 registerInterfaces(context);
256 });
257}
Type getShadowType(Type type, unsigned width)
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)
void eraseIfUnused(Operation *op, bool erase=true, bool check=true)
SmallVector< mlir::Value, 1 > getNewFromOriginal(ValueRange originst) const
bool isConstantInstruction(mlir::Operation *v) const
void setInvertedPointer(mlir::Value origv, mlir::Value newv)
bool isConstantValue(mlir::Value v) const
void registerArithDialectAutoDiffInterface(DialectRegistry &registry)
void registerTensorDialectAutoDiffInterface(DialectRegistry &registry)