Enzyme main
Loading...
Searching...
No Matches
MemRefAutoDiffOpInterfaceImpl.cpp
Go to the documentation of this file.
1//===- MemRefAutoDiffOpInterfaceImpl.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 memref dialect.
11//
12//===----------------------------------------------------------------------===//
13
20#include "Interfaces/Utils.h"
21
22#include "mlir/Dialect/MemRef/IR/MemRef.h"
23#include "mlir/IR/DialectRegistry.h"
24#include "mlir/IR/Matchers.h"
25#include "mlir/Support/LogicalResult.h"
26
27using namespace mlir;
28using namespace mlir::enzyme;
29
30namespace {
31#include "Implementations/MemRefDerivatives.inc"
32
33struct SubViewOpOffsetInterface
34 : public OffsetViewInterface::ExternalModel<SubViewOpOffsetInterface,
35 memref::SubViewOp> {
36 bool isZeroOffset(Operation *op) const {
37 return cast<memref::SubViewOp>(op).hasZeroOffset();
38 }
39};
40
41struct ViewOpOffsetInterface
42 : public OffsetViewInterface::ExternalModel<ViewOpOffsetInterface,
43 memref::ViewOp> {
44 bool isZeroOffset(Operation *op) const {
45 return matchPattern(cast<memref::ViewOp>(op).getByteShift(), m_Zero());
46 }
47};
48
49// Lets activity analysis treat memref.store generically via StoreLikeInterface.
50struct MemRefStoreLike
51 : public StoreLikeInterface::ExternalModel<MemRefStoreLike,
52 memref::StoreOp> {
53 Value getStoredValue(Operation *op) const {
54 return cast<memref::StoreOp>(op).getValueToStore();
55 }
56 Value getStoredPointer(Operation *op) const {
57 return cast<memref::StoreOp>(op).getMemRef();
58 }
59};
60
61struct LoadOpInterfaceReverse
62 : public ReverseAutoDiffOpInterface::ExternalModel<LoadOpInterfaceReverse,
63 memref::LoadOp> {
64 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
66 SmallVector<Value> caches) const {
67 auto loadOp = cast<memref::LoadOp>(op);
68 Value memref = loadOp.getMemref();
69
70 if (auto iface = dyn_cast<AutoDiffTypeInterface>(loadOp.getType())) {
71 // A mutable type's derivative is a shadow, not a value to add into: there
72 // is nothing to accumulate here, the same way the store adjoint below has
73 // nothing to take back out. Loading a pointer is the case in hand.
74 if (!gutils->isConstantValue(loadOp) &&
75 !gutils->isConstantValue(memref) && !iface.isMutable()) {
76 Value gradient = gutils->diffe(loadOp, builder);
77 Value memrefGradient = gutils->popCache(caches.front(), builder);
78
79 SmallVector<Value> retrievedArguments;
80 for (Value cache : ValueRange(caches).drop_front(1)) {
81 Value retrievedValue = gutils->popCache(cache, builder);
82 retrievedArguments.push_back(retrievedValue);
83 }
84
85 if (!gutils->AtomicAdd) {
86 auto loadedGradientOp =
87 memref::LoadOp::create(builder, loadOp.getLoc(), memrefGradient,
88 ArrayRef<Value>(retrievedArguments));
89 loadedGradientOp.setAlignmentAttr(loadOp.getAlignmentAttr());
90 Value addedGradient = iface.createAddOp(builder, loadOp.getLoc(),
91 loadedGradientOp, gradient);
92 auto storeGradientOp = memref::StoreOp::create(
93 builder, loadOp.getLoc(), addedGradient, memrefGradient,
94 ArrayRef<Value>(retrievedArguments));
95 storeGradientOp.setAlignmentAttr(loadOp.getAlignmentAttr());
96 } else {
97 setDerivativeFastMath(enzyme::AtomicRMWOp::create(
98 builder, loadOp.getLoc(), gradient.getType(),
99 arith::AtomicRMWKind::addf, Ordering::monotonic, gradient,
100 memrefGradient, retrievedArguments, loadOp.getAlignmentAttr()));
101 }
102 }
103 }
104 return success();
105 }
106
107 SmallVector<Value> cacheValues(Operation *op,
108 MGradientUtilsReverse *gutils) const {
109 auto loadOp = cast<memref::LoadOp>(op);
110 Value memref = loadOp.getMemref();
111 ValueRange indices = loadOp.getIndices();
112 if (auto iface = dyn_cast<AutoDiffTypeInterface>(loadOp.getType())) {
113 if (!gutils->isConstantValue(loadOp) &&
114 !gutils->isConstantValue(memref)) {
115 OpBuilder cacheBuilder(gutils->getNewFromOriginal(op));
116 SmallVector<Value> caches;
117 caches.push_back(gutils->initAndPushCache(
118 gutils->invertPointerM(memref, cacheBuilder), cacheBuilder));
119 for (Value v : indices) {
120 caches.push_back(gutils->initAndPushCache(
121 gutils->getNewFromOriginal(v), cacheBuilder));
122 }
123 return caches;
124 }
125 }
126 return SmallVector<Value>();
127 }
128
129 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
130 MGradientUtilsReverse *gutils) const {
131 auto loadOp = cast<memref::LoadOp>(op);
132 Value memref = loadOp.getMemref();
133 auto iface = dyn_cast<AutoDiffTypeInterface>(loadOp.getType());
134 // What a load of a mutable type reads is itself a handle on active memory,
135 // so what stands for it is the handle held at the same place in the shadow:
136 // the same load, off the shadow memref. Reading it out is the whole of the
137 // derivative -- see the adjoint above, which leaves it alone.
138 if (!iface)
139 return op->emitError()
140 << "could not compute the shadow of a load of a type without "
141 "autodiff semantics "
142 << *op;
143 // An immutable load's derivative is the adjoint's to accumulate.
144 if (!iface.isMutable())
145 return success();
146 if (gutils->isConstantValue(loadOp))
147 return success();
148 // Cannot load a non-constant value out of a constant memref: there is no
149 // shadow to read the handle from, so the claimed activity cannot be
150 // honored.
151 if (gutils->isConstantValue(memref))
152 return op->emitError()
153 << "cannot load a non-constant value out of a constant memref "
154 << *op;
155 Value memrefShadow = gutils->invertPointerM(memref, builder);
156 auto newLoad = cast<memref::LoadOp>(gutils->getNewFromOriginal(op));
157 auto shadowLoad = cast<memref::LoadOp>(builder.clone(*newLoad));
158 shadowLoad.getMemrefMutable().assign(memrefShadow);
159 gutils->setInvertedPointer(loadOp.getResult(), shadowLoad.getResult());
160 return success();
161 }
162};
163
164struct StoreOpInterfaceReverse
165 : public ReverseAutoDiffOpInterface::ExternalModel<StoreOpInterfaceReverse,
166 memref::StoreOp> {
167 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
168 MGradientUtilsReverse *gutils,
169 SmallVector<Value> caches) const {
170 auto storeOp = cast<memref::StoreOp>(op);
171 Value val = storeOp.getValue();
172 Value memref = storeOp.getMemref();
173 // ValueRange indices = storeOp.getIndices();
174
175 auto iface = cast<AutoDiffTypeInterface>(val.getType());
176
177 if (!gutils->isConstantValue(memref)) {
178 Value memrefGradient = gutils->popCache(caches.front(), builder);
179
180 SmallVector<Value> retrievedArguments;
181 for (Value cache : ValueRange(caches).drop_front(1)) {
182 Value retrievedValue = gutils->popCache(cache, builder);
183 retrievedArguments.push_back(retrievedValue);
184 }
185
186 if (!iface.isMutable()) {
187 if (!gutils->isConstantValue(val)) {
188 auto loadedGradientOp =
189 memref::LoadOp::create(builder, storeOp.getLoc(), memrefGradient,
190 ArrayRef<Value>(retrievedArguments));
191 loadedGradientOp.setAlignmentAttr(storeOp.getAlignmentAttr());
192 gutils->addToDiffe(val, loadedGradientOp, builder);
193 }
194
195 auto zero =
196 cast<AutoDiffTypeInterface>(gutils->getShadowType(val.getType()))
197 .createNullValue(builder, op->getLoc());
198
199 auto zeroStoreOp = memref::StoreOp::create(
200 builder, storeOp.getLoc(), zero, memrefGradient,
201 ArrayRef<Value>(retrievedArguments));
202 zeroStoreOp.setAlignmentAttr(storeOp.getAlignmentAttr());
203 }
204 }
205
206 // A store into memory whose primal contents the caller declared
207 // unneeded (enzyme_dupnoneed) need not happen in the augmented
208 // forward pass either.
209 if (gutils->primalStoreElidable(storeOp.getMemref()))
210 gutils->erase(gutils->getNewFromOriginal(op));
211 return success();
212 }
213
214 SmallVector<Value> cacheValues(Operation *op,
215 MGradientUtilsReverse *gutils) const {
216 auto storeOp = cast<memref::StoreOp>(op);
217 Value memref = storeOp.getMemref();
218 ValueRange indices = storeOp.getIndices();
219 Value val = storeOp.getValue();
220 if (auto iface = dyn_cast<AutoDiffTypeInterface>(val.getType())) {
221 if (!gutils->isConstantValue(memref)) {
222 OpBuilder cacheBuilder(gutils->getNewFromOriginal(op));
223 SmallVector<Value> caches;
224 caches.push_back(gutils->initAndPushCache(
225 gutils->invertPointerM(memref, cacheBuilder), cacheBuilder));
226 for (Value v : indices) {
227 caches.push_back(gutils->initAndPushCache(
228 gutils->getNewFromOriginal(v), cacheBuilder));
229 }
230 return caches;
231 }
232 }
233 return SmallVector<Value>();
234 }
235
236 // A store of a mutable value -- a pointer, an inner memref -- has no float
237 // adjoint to accumulate; its derivative story is structural: the shadow
238 // memory must hold the shadow value at the same spot, so shadow loads
239 // traverse shadow structures. A value nothing differentiates is its own
240 // shadow, keeping inactive fields readable through the shadow object.
241 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
242 MGradientUtilsReverse *gutils) const {
243 auto storeOp = cast<memref::StoreOp>(op);
244 Value val = storeOp.getValue();
245 Value memref = storeOp.getMemref();
246 auto iface = dyn_cast<AutoDiffTypeInterface>(val.getType());
247 if (!iface)
248 return op->emitError()
249 << "could not compute the shadow of a store of a type without "
250 "autodiff semantics "
251 << *op;
252 if (gutils->isConstantValue(memref))
253 return success();
254 // Immutable values' shadows live in the reverse sweep's adjoint, which
255 // accumulates and zeroes the slot; only mutable values need the forward
256 // sweep to place their shadow.
257 if (!iface.isMutable())
258 return success();
259 Value memrefShadow = gutils->invertPointerM(memref, builder);
260 Value valShadow =
261 gutils->isConstantValue(val)
262 ? oputils::inactiveStoredValueShadow(op, *gutils, val, builder)
263 : gutils->invertPointerM(val, builder);
264 auto newOp = cast<memref::StoreOp>(gutils->getNewFromOriginal(op));
265 auto shadowOp = cast<memref::StoreOp>(builder.clone(*newOp));
266 shadowOp.getValueMutable().assign(valShadow);
267 shadowOp.getMemrefMutable().assign(memrefShadow);
268 return success();
269 }
270};
271
272struct SubViewOpInterfaceReverse
273 : public ReverseAutoDiffOpInterface::ExternalModel<
274 SubViewOpInterfaceReverse, memref::SubViewOp> {
275 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
276 MGradientUtilsReverse *gutils,
277 SmallVector<Value> caches) const {
278 return success();
279 }
280
281 SmallVector<Value> cacheValues(Operation *op,
282 MGradientUtilsReverse *gutils) const {
283 return SmallVector<Value>();
284 }
285
286 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
287 MGradientUtilsReverse *gutils) const {
288 auto subviewOp = cast<memref::SubViewOp>(op);
289 auto newSubviewOp = cast<memref::SubViewOp>(gutils->getNewFromOriginal(op));
290 if (!gutils->isConstantValue(subviewOp.getSource())) {
291 Value shadow = memref::SubViewOp::create(
292 builder, op->getLoc(), newSubviewOp.getType(),
293 gutils->invertPointerM(subviewOp.getSource(), builder),
294 newSubviewOp.getMixedOffsets(), newSubviewOp.getMixedSizes(),
295 newSubviewOp.getMixedStrides());
296 gutils->setInvertedPointer(subviewOp, shadow);
297 }
298 return success();
299 }
300};
301
302class MemRefClonableTypeInterface
303 : public ClonableTypeInterface::ExternalModel<MemRefClonableTypeInterface,
304 MemRefType> {
305
306public:
307 mlir::Value cloneValue(mlir::Type self, OpBuilder &builder,
308 Value value) const {
309 MemRefType MT = cast<MemRefType>(self);
310 SmallVector<Value> dynamicSizes;
311
312 for (auto [i, s] : llvm::enumerate(MT.getShape())) {
313 if (s == ShapedType::kDynamic) {
314 Value dim = arith::ConstantIndexOp::create(builder, value.getLoc(), i);
315 dynamicSizes.push_back(
316 memref::DimOp::create(builder, value.getLoc(), value, dim));
317 }
318 }
319
320 // Pass the MemRefType, not `self`: with a plain Type the generic
321 // (TypeRange, ValueRange) builder is selected, which appends the dynamic
322 // sizes as operands without setting operandSegmentSizes -- producing an
323 // invalid memref.alloc as soon as the type has a dynamic dimension.
324 auto clone =
325 memref::AllocOp::create(builder, value.getLoc(), MT, dynamicSizes);
326 memref::CopyOp::create(builder, value.getLoc(), value, clone);
327
328 return clone;
329 }
330
331 void copyValue(mlir::Type self, OpBuilder &builder, Value dst,
332 Value src) const {
333 // memref.copy carries the extents in its operand types, so there is no
334 // extent to recover here and dynamic shapes work unchanged.
335 memref::CopyOp::create(builder, src.getLoc(), src, dst);
336 }
337
338 void freeClonedValue(mlir::Type self, OpBuilder &builder, Value value) const {
339 memref::DeallocOp::create(builder, value.getLoc(), value);
340 };
341
342 bool implementsBatchAllocation(Type self, Value base,
343 OpFoldResult size) const {
344 return true;
345 }
346
347 Value deriveSubElement(Type self, OpBuilder &builder, Location loc,
348 Value base, Value multiel, Value index) const {
349 auto MT = cast<MemRefType>(base.getType());
350
351 SmallVector<Value> dynSizes, offsets;
352
353 offsets.push_back(index);
354
355 SmallVector<int64_t> static_offsets(MT.getRank() + 1, 0);
356 static_offsets[0] = ShapedType::kDynamic;
357
358 SmallVector<int64_t> static_sizes;
359 static_sizes.push_back(1);
360 static_sizes.append(MT.getShape().begin(), MT.getShape().end());
361 SmallVector<int64_t> static_strides(MT.getRank() + 1, 1);
362
363 for (auto [i, sz] : llvm::enumerate(MT.getShape())) {
364 if (sz == ShapedType::kDynamic) {
365 dynSizes.push_back(memref::DimOp::create(builder, loc, base, i));
366 }
367 }
368
369 auto RT = memref::SubViewOp::inferRankReducedResultType(
370 MT.getShape(), cast<MemRefType>(multiel.getType()), static_offsets,
371 static_sizes, static_strides);
372 auto sv = memref::SubViewOp::create(
373 builder, loc, RT, multiel, offsets, dynSizes, /*strides=*/ValueRange(),
374 /*static_offsets=*/builder.getDenseI64ArrayAttr(static_offsets),
375 /*static_sizes=*/builder.getDenseI64ArrayAttr(static_sizes),
376 /*static_strides=*/builder.getDenseI64ArrayAttr(static_strides));
377
378 return sv;
379 }
380
381 Value batchAllocate(Type self, OpBuilder &builder, Location loc, Value base,
382 OpFoldResult numel) const {
383 auto MT = cast<MemRefType>(self);
384
385 SmallVector<int64_t> shape;
386 SmallVector<Value> dynSizes;
387
388 if (auto attr = dyn_cast<Attribute>(numel)) {
389 shape.push_back(cast<IntegerAttr>(attr).getValue().getSExtValue());
390 } else {
391 shape.push_back(ShapedType::kDynamic);
392 dynSizes.push_back(cast<Value>(numel));
393 }
394
395 for (auto [i, sz] : llvm::enumerate(MT.getShape())) {
396 shape.push_back(sz);
397
398 if (sz == ShapedType::kDynamic) {
399 dynSizes.push_back(memref::DimOp::create(
400 builder, loc, base,
401 arith::ConstantIndexOp::create(builder, loc, i)));
402 }
403 }
404
405 Value alloc = memref::AllocOp::create(
406 builder, loc, MemRefType::get(shape, MT.getElementType()), dynSizes);
407
408 return alloc;
409 }
410};
411
412class MemRefAutoDiffTypeInterface
413 : public AutoDiffTypeInterface::ExternalModel<MemRefAutoDiffTypeInterface,
414 MemRefType> {
415public:
416 mlir::Attribute createNullAttr(mlir::Type self) const {
417 llvm_unreachable("Cannot create null of memref (todo polygeist null)");
418 }
419 mlir::Value createNullValue(mlir::Type self, OpBuilder &builder,
420 Location loc) const {
421 // Create a memref of the given type with the required number of
422 // dynamic dimensions, all set to 0
423 MemRefType MT = cast<MemRefType>(self);
424 unsigned numDynamicDims = MT.getNumDynamicDims();
425 SmallVector<mlir::Value> dynamicSizes(numDynamicDims);
426 for (unsigned i = 0; i < numDynamicDims; ++i) {
427 dynamicSizes[i] = builder.create<mlir::arith::ConstantIndexOp>(loc, 0);
428 }
429 return mlir::memref::AllocOp::create(builder, loc, MT, dynamicSizes);
430 }
431
432 Value createAddOp(Type self, OpBuilder &builder, Location loc, Value a,
433 Value b) const {
434 llvm_unreachable("TODO");
435 }
436
437 Type getShadowType(Type self, unsigned width) const {
438 assert(width == 1 && "unsupported width != 1");
439 return self;
440 }
441
442 Value createConjOp(Type self, OpBuilder &builder, Location loc,
443 Value a) const {
444 llvm_unreachable("TODO");
445 }
446
447 bool isMutable(Type self) const { return true; }
448
449 LogicalResult zeroInPlace(Type self, OpBuilder &builder, Location loc,
450 Value val) const {
451 auto MT = cast<MemRefType>(self);
452 if (auto iface = dyn_cast<AutoDiffTypeInterface>(MT.getElementType())) {
453 if (!iface.isMutable()) {
454 enzyme::FillZeroOp::create(builder, loc, val);
455 }
456 } else {
457 return failure();
458 }
459 return success();
460 }
461
462 bool isZero(Type self, Value val) const { return false; }
463 bool isZeroAttr(Type self, Attribute val) const { return false; }
464};
465
466struct MemRefAllocOpInterface
467 : public MultidimensionalAllocInterface::ExternalModel<
468 MemRefAllocOpInterface, memref::AllocOp> {
469 Value allocate(Operation *op, OpBuilder &rewriter, Location loc, Type newType,
470 ValueRange dynamicDims) const {
471 return memref::AllocOp::create(rewriter, loc, cast<MemRefType>(newType),
472 dynamicDims);
473 }
474
475 void deallocate(Operation *op, OpBuilder &rewriter, Location loc,
476 Value val) const {
477 memref::DeallocOp::create(rewriter, loc, val);
478 }
479
480 bool isDeallocation(Operation *op, Operation *user) const {
481 return isa<memref::DeallocOp>(user);
482 }
483};
484
485} // namespace
486
488 DialectRegistry &registry) {
489 registry.addExtension(+[](MLIRContext *context, memref::MemRefDialect *) {
490 registerInterfaces(context);
491 MemRefType::attachInterface<MemRefAutoDiffTypeInterface>(*context);
492 MemRefType::attachInterface<MemRefClonableTypeInterface>(*context);
493
494 memref::StoreOp::attachInterface<MemRefStoreLike>(*context);
495 memref::LoadOp::attachInterface<LoadOpInterfaceReverse>(*context);
496 memref::StoreOp::attachInterface<StoreOpInterfaceReverse>(*context);
497 memref::SubViewOp::attachInterface<SubViewOpInterfaceReverse>(*context);
498 memref::AllocOp::attachInterface<MemRefAllocOpInterface>(*context);
499
500 memref::CastOp::attachInterface<OffsetViewInterface::Model<memref::CastOp>>(
501 *context);
502 memref::MemorySpaceCastOp::attachInterface<
503 OffsetViewInterface::Model<memref::MemorySpaceCastOp>>(*context);
504 memref::ReshapeOp::attachInterface<
505 OffsetViewInterface::Model<memref::ReshapeOp>>(*context);
506 memref::ExpandShapeOp::attachInterface<
507 OffsetViewInterface::Model<memref::ExpandShapeOp>>(*context);
508 memref::CollapseShapeOp::attachInterface<
509 OffsetViewInterface::Model<memref::CollapseShapeOp>>(*context);
510 memref::TransposeOp::attachInterface<
511 OffsetViewInterface::Model<memref::TransposeOp>>(*context);
512 memref::SubViewOp::attachInterface<SubViewOpOffsetInterface>(*context);
513 memref::ViewOp::attachInterface<ViewOpOffsetInterface>(*context);
514 });
515}
Type getShadowType(Type type, unsigned width)
Operation * clone(Operation *src, IRMapping &mapper, Operation::CloneOptions options, std::map< Operation *, Operation * > &opMap)
static bool isZero(llvm::Constant *cst)
static bool isMutable(Type type)
Definition Ops.cpp:311
mlir::Value diffe(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)
bool primalStoreElidable(mlir::Value ptr)
True when a primal store through this pointer may be omitted: declaring the base enzyme_dupnoneed is ...
mlir::Type getShadowType(mlir::Type T)
void erase(Operation *op)
mlir::Value invertPointerM(mlir::Value v, OpBuilder &Builder2)
SmallVector< mlir::Value, 1 > getNewFromOriginal(ValueRange originst) const
void setInvertedPointer(mlir::Value origv, mlir::Value newv)
bool isConstantValue(mlir::Value v) const
Value inactiveStoredValueShadow(Operation *orig, MGradientUtils &gutils, Value stored, OpBuilder &builder)
Definition Utils.cpp:104
void setDerivativeFastMath(Operation *op)
Give an operation built for a derivative the fast-math flags the LLVM path gives everything it builds...
void registerMemRefDialectAutoDiffInterface(DialectRegistry &registry)