MLIR 24.0.0git
ShardingInterfaceImpl.cpp
Go to the documentation of this file.
1//===- ShardingInterfaceImpl.cpp - Linalg sharding models -----------------===//
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
10
23#include "mlir/IR/AffineExpr.h"
25#include "mlir/IR/IRMapping.h"
26#include "mlir/IR/MLIRContext.h"
28#include "mlir/IR/Operation.h"
29#include "mlir/IR/SymbolTable.h"
30#include "mlir/IR/Value.h"
31#include "llvm/ADT/STLExtras.h"
32#include "llvm/ADT/SmallVector.h"
33#include "llvm/ADT/TypeSwitch.h"
34#include <numeric>
35#include <optional>
36
37namespace mlir::linalg {
39using ReductionKind = shard::ReductionKind;
42using GridOp = shard::GridOp;
43
44// Returns the corresponding grid reduction kind for the given arith op.
47 // Floating-point operations.
48 .Case([](arith::AddFOp op) { return ReductionKind::Sum; })
49 .Case([](arith::MulFOp op) { return ReductionKind::Product; })
50 // TODO: handle maxnumf and minnumf.
51 .Case([](arith::MaximumFOp op) { return ReductionKind::Max; })
52 .Case([](arith::MinimumFOp op) { return ReductionKind::Min; })
53 // Integer operations.
54 .Case([](arith::AddIOp op) { return ReductionKind::Sum; })
55 .Case([](arith::OrIOp op) { return ReductionKind::BitwiseOr; })
56 .Case([](arith::XOrIOp op) { return ReductionKind::BitwiseXor; })
57 .Case([](arith::AndIOp op) { return ReductionKind::BitwiseAnd; })
58 // TODO: handle signless, signed and unsigned types properly.
59 // It is assumed that the element type of the collective operands and
60 // result drive the meaning of the reduction kind, whether it is signed
61 // or unsigned.
62 // The reduction op inside the linalg op may have different result type
63 // from the element type of the linalg op's result.
64 // Also signed and unsigned Arith dialect ops may accept signed, unsigned
65 // or signless operands.
66 // Maybe expand the reduction kinds.
67 .Case([](arith::MaxUIOp op) { return ReductionKind::Max; })
68 .Case([](arith::MinUIOp op) { return ReductionKind::Min; })
69 .Case([](arith::MaxSIOp op) { return ReductionKind::Max; })
70 .Case([](arith::MinSIOp op) { return ReductionKind::Min; })
71 .Case([](arith::MulIOp op) { return ReductionKind::Product; })
72 .Default([](Operation *op) { return ReductionKind::Generic; });
73}
74
75static std::optional<Operation *> getCombinerOp(LinalgOp op) {
76 SmallVector<Operation *> combinerOps;
77 Value reducedValue = matchReduction(op.getRegionOutputArgs(), 0, combinerOps);
78 if (!reducedValue || combinerOps.size() != 1) {
79 return std::nullopt;
80 }
81
82 return combinerOps[0];
83}
84
86 std::optional<Operation *> reductionOp = getCombinerOp(op);
87 if (!reductionOp) {
88 return ReductionKind::Generic;
89 }
90 [[maybe_unused]] Type resultElementType =
91 llvm::cast<RankedTensorType>(op->getResult(0).getType()).getElementType();
92 // TODO: handle case when result type of the reduction op does not match the
93 // element type of the result tensor.
94 // Would it makes sense at all?
95 assert(resultElementType == reductionOp.value()->getResult(0).getType());
96 return getReductionKind(reductionOp.value());
97}
98
99static GridOp getGrid(Operation *op, ArrayRef<Sharding> operandShardings,
100 ArrayRef<Sharding> resultShardings,
101 SymbolTableCollection &symbolTable) {
102 for (const Sharding &sharding : operandShardings) {
103 if (sharding) {
104 return shard::getGrid(op, sharding.getGridAttr(), symbolTable);
105 }
106 }
107
108 for (const Sharding &sharding : resultShardings) {
109 if (sharding) {
110 return shard::getGrid(op, sharding.getGridAttr(), symbolTable);
111 }
112 }
113
114 assert(false);
115 return nullptr;
116}
117
118// Choose the operand based on the current process index along the reduction
119// grid axes.
120// We need to use the initial value only once to avoid including it in the
121// reduction multiple times.
122// In each process group only the leading process with linear index 0 would use
123// the original operand.
124// The other processes would use the reduction operation neutral tensor.
126 LinalgOp op, int operandNumber, Value partitionedOperand,
127 ArrayRef<GridAxis> reductionGridAxes, GridOp gridOp,
128 ImplicitLocOpBuilder &builder) {
129 Value processLinearIndexInReductionGroup = shard::createProcessLinearIndex(
130 builder, gridOp.getSymName(), reductionGridAxes);
131 Value zero = arith::ConstantIndexOp::create(builder, 0);
132 Value isLeadProcess = arith::CmpIOp::create(
133 builder, builder.getI1Type(), arith::CmpIPredicate::eq,
134 processLinearIndexInReductionGroup, zero);
135 scf::IfOp ifOp = scf::IfOp::create(builder, partitionedOperand.getType(),
136 isLeadProcess, true, true);
137 // Then block.
138 {
139 OpBuilder::InsertionGuard insertionGuard(builder);
140 builder.setInsertionPointToEnd(&ifOp.getThenRegion().front());
141 scf::YieldOp::create(builder, partitionedOperand);
142 }
143
144 // Else block.
145 {
146 OpBuilder::InsertionGuard insertionGuard(builder);
147 builder.setInsertionPointToEnd(&ifOp.getElseRegion().front());
149 tensor::getMixedSizes(builder, builder.getLoc(), partitionedOperand);
150
151 SmallVector<Operation *> combinerOps;
152 matchReduction(op.getRegionOutputArgs(), operandNumber, combinerOps);
153 assert(combinerOps.size() == 1);
154 std::optional<TypedAttr> neutralEl =
155 arith::getNeutralElement(combinerOps[0]);
156
157 Value init = tensor::EmptyOp::create(builder, op.getLoc(), shape,
158 neutralEl.value().getType());
159 Value constant =
160 arith::ConstantOp::create(builder, op.getLoc(), neutralEl.value());
161 Value fill = linalg::FillOp::create(builder, op.getLoc(), constant, init)
162 .getResult(0);
163
164 scf::YieldOp::create(builder, fill);
165 }
166 return ifOp.getResult(0);
167}
168
169// Create the DPS init operands for the partitioned Linalg op.
170// Return all the new partitioned operands.
172 LinalgOp op, GridOp gridOp, ArrayRef<Value> partitionedOperands,
173 ArrayRef<GridAxis> reductionGridAxes, IRMapping &partitionMap,
174 ImplicitLocOpBuilder &builder) {
175 // TODO: add support for multiple destination passing style initial value
176 // operands.
177 assert(op.getNumDpsInits() == 1 && "Multiple initial values not supported.");
178 SmallVector<Value> newOperands = llvm::to_vector(partitionedOperands);
179 auto operandIdx = op.getDpsInitOperand(0)->getOperandNumber();
180 Value partitionedInitOperand =
181 partitionMap.lookup(op->getOperands()[operandIdx]);
182 newOperands[operandIdx] = createDestinationPassingStyleInitOperand(
183 op, 0, partitionedInitOperand, reductionGridAxes, gridOp, builder);
184 return newOperands;
185}
186
188 LinalgOp unshardedOp, ArrayRef<GridAxis> opReductionGridAxes,
189 ArrayRef<Sharding> resultShardings, IRMapping &partitionMap,
190 ImplicitLocOpBuilder &builder) {
191 ReductionKind reductionKind = getReductionKindOfLinalgOp(unshardedOp);
192 for (auto [unshardedLinalgOpResult, resultSharding] :
193 llvm::zip_equal(unshardedOp->getResults(), resultShardings)) {
194 Value partitionedLinalgOpResult =
195 partitionMap.lookup(unshardedLinalgOpResult);
196 Value reducedValue = shard::AllReduceOp::create(
197 builder, partitionedLinalgOpResult, resultSharding.getGrid(),
198 opReductionGridAxes, reductionKind);
199 partitionMap.map(unshardedLinalgOpResult, reducedValue);
200 }
201}
202
204 LinalgOp op, ArrayRef<Value> partitionedOperands,
205 ArrayRef<Sharding> operandShardings, ArrayRef<Sharding> resultShardings,
206 ArrayRef<utils::IteratorType> loopIteratorTypes,
207 ArrayRef<SmallVector<GridAxis>> gridAxisAssignmentForLoopIterators,
208 IRMapping &partitionMap, SymbolTableCollection &symbolTable,
209 ImplicitLocOpBuilder &builder) {
210 GridOp grid = getGrid(op, operandShardings, resultShardings, symbolTable);
212 loopIteratorTypes, gridAxisAssignmentForLoopIterators);
213 SmallVector<Value> partitionedLinalgOpOperands =
214 createDestinationPassingStyleInitOperands(op, grid, partitionedOperands,
215 reductionGridAxes, partitionMap,
216 builder);
217 // We must not change the operand mappings of the original partitionMap as
218 // they are the mappings for the whole partition blob and may be used by
219 // others.
220 IRMapping internalPartitionMap;
221 for (auto [unshardedOperand, partitionedOperand] :
222 llvm::zip_equal(op->getOperands(), partitionedLinalgOpOperands)) {
223 internalPartitionMap.map(unshardedOperand, partitionedOperand);
224 }
225 partitionTriviallyShardableOperation(
226 *op, partitionedLinalgOpOperands, operandShardings, resultShardings,
227 internalPartitionMap, symbolTable, builder);
228 for (Value result : op->getResults()) {
229 partitionMap.map(result, internalPartitionMap.lookup(result));
230 }
231
232 // Handle partial shardings.
234 op, reductionGridAxes, resultShardings, partitionMap, builder);
235}
236
237namespace {
238// ShardingInterface for ops that implement LinalgStructuredInterface.
239// The supported ops are only those where the indexing maps are projected
240// permutations.
241template <typename Op>
242struct StructuredOpShardingInterface
243 : public shard::ShardingInterface::ExternalModel<
244 StructuredOpShardingInterface<Op>, Op> {
245 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op) const {
246 return llvm::cast<LinalgOp>(op).getIteratorTypesArray();
247 }
248
249 SmallVector<AffineMap> getIndexingMaps(Operation *op) const {
250 LinalgOp linalgOp = llvm::cast<LinalgOp>(op);
251 SmallVector<AffineMap> res = linalgOp.getIndexingMapsArray();
252
253 // Results must have the same indexing as destination passing style initial
254 // operands.
255 for (int64_t i = 0; i < linalgOp.getNumDpsInits(); ++i) {
256 res.push_back(res[linalgOp.getDpsInitOperand(i)->getOperandNumber()]);
257 }
258
259 return res;
260 }
261
262 SmallVector<ReductionKind>
263 getReductionLoopIteratorKinds(Operation *op) const {
264 LinalgOp linalgOp = llvm::cast<LinalgOp>(op);
265 SmallVector<utils::IteratorType> iteratorTypes =
266 linalgOp.getIteratorTypesArray();
267 unsigned reductionItersCount = llvm::accumulate(
268 iteratorTypes, 0u, [](unsigned count, utils::IteratorType iter) {
269 return count + (iter == utils::IteratorType::reduction);
270 });
271 shard::ReductionKind reductionKind = getReductionKindOfLinalgOp(linalgOp);
272 return SmallVector<ReductionKind>(reductionItersCount, reductionKind);
273 }
274
275 LogicalResult partition(Operation *op, ArrayRef<Value> partitionedOperands,
276 ArrayRef<Sharding> operandShardings,
277 ArrayRef<Sharding> resultShardings,
278 IRMapping &partitionMap,
279 SymbolTableCollection &symbolTable,
280 OpBuilder &builder) const {
281 LinalgOp linalgOp = llvm::cast<LinalgOp>(op);
282
283 SmallVector<AffineMap> indexingMaps = linalgOp.getIndexingMapsArray();
284 bool allIndexingMapsAreProjectedPermutation =
285 llvm::all_of(indexingMaps, [](AffineMap map) {
286 return map.isProjectedPermutation();
287 });
288 if (!allIndexingMapsAreProjectedPermutation) {
289 // TODO: handle non-projected permutations.
290 return op->emitOpError()
291 << "supports indexing maps that are only projected permutation.";
292 }
293
294 SmallVector<utils::IteratorType> loopIteratorTypes =
295 linalgOp.getIteratorTypesArray();
296 ShardingArray gridAxisAssignmentForLoopIterators =
297 getGridAxisAssignmentForLoopIterators(operandShardings, resultShardings,
298 loopIteratorTypes, indexingMaps);
300 loopIteratorTypes, gridAxisAssignmentForLoopIterators)) {
301 ImplicitLocOpBuilder implicitLocBuilder(op->getLoc(), builder);
303 linalgOp, partitionedOperands, operandShardings, resultShardings,
304 loopIteratorTypes, gridAxisAssignmentForLoopIterators, partitionMap,
305 symbolTable, implicitLocBuilder);
306 } else {
307 partitionTriviallyShardableOperation(*op, partitionedOperands,
308 operandShardings, resultShardings,
309 partitionMap, symbolTable, builder);
310 }
311
312 return success();
313 }
314};
315} // namespace
316
317template <typename OpType>
318static void registerOne(MLIRContext *ctx) {
319 OpType::template attachInterface<StructuredOpShardingInterface<OpType>>(*ctx);
320}
321
322/// Variadic helper function.
323template <typename... OpTypes>
324static void registerAll(MLIRContext *ctx) {
325 (registerOne<OpTypes>(ctx), ...);
326}
327
329 registry.addExtension(+[](MLIRContext *ctx, LinalgDialect *dialect) {
330 DialectRegistry registry;
331 registry.insert<affine::AffineDialect, arith::ArithDialect, scf::SCFDialect,
332 tensor::TensorDialect>();
333 ctx->appendDialectRegistry(registry);
334 for (StringRef name : registry.getRegisteredDialectNames())
335 ctx->getOrLoadDialect(name);
336
339#define GET_OP_LIST
340#include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc"
341 >(ctx);
342 });
343}
344} // namespace mlir::linalg
return success()
IntegerType getI1Type()
Definition Builders.cpp:61
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
SmallVector< StringRef > getRegisteredDialectNames() const
Return the names of dialects registered in this registry with an allocator function.
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
auto lookup(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:72
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
ImplicitLocOpBuilder maintains a 'current location', allowing use of the create<> method without spec...
Definition Builders.h:632
Location getLoc() const
Accessors for the implied location.
Definition Builders.h:665
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:71
void appendDialectRegistry(const DialectRegistry &registry)
Append the contents of the given dialect registry to the registry associated with this context.
T * getOrLoadDialect()
Get (or create) a dialect for the given derived dialect type.
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Definition Builders.h:439
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
This class represents a collection of SymbolTables.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
std::optional< TypedAttr > getNeutralElement(Operation *op)
Return the identity numeric value associated to the give op.
shard::Sharding Sharding
static std::optional< Operation * > getCombinerOp(LinalgOp op)
static void partitionLinalgOpWithShardedReduction(LinalgOp op, ArrayRef< Value > partitionedOperands, ArrayRef< Sharding > operandShardings, ArrayRef< Sharding > resultShardings, ArrayRef< utils::IteratorType > loopIteratorTypes, ArrayRef< SmallVector< GridAxis > > gridAxisAssignmentForLoopIterators, IRMapping &partitionMap, SymbolTableCollection &symbolTable, ImplicitLocOpBuilder &builder)
static ReductionKind getReductionKind(Operation *op)
shard::ShardingArray ShardingArray
static Value createDestinationPassingStyleInitOperand(LinalgOp op, int operandNumber, Value partitionedOperand, ArrayRef< GridAxis > reductionGridAxes, GridOp gridOp, ImplicitLocOpBuilder &builder)
static void createAllReduceForResultsWithoutPartialShardings(LinalgOp unshardedOp, ArrayRef< GridAxis > opReductionGridAxes, ArrayRef< Sharding > resultShardings, IRMapping &partitionMap, ImplicitLocOpBuilder &builder)
static void registerOne(MLIRContext *ctx)
static void registerAll(MLIRContext *ctx)
Variadic helper function.
shard::ReductionKind ReductionKind
shard::GridAxis GridAxis
static SmallVector< Value > createDestinationPassingStyleInitOperands(LinalgOp op, GridOp gridOp, ArrayRef< Value > partitionedOperands, ArrayRef< GridAxis > reductionGridAxes, IRMapping &partitionMap, ImplicitLocOpBuilder &builder)
void registerShardingInterfaceExternalModels(DialectRegistry &registry)
static ReductionKind getReductionKindOfLinalgOp(LinalgOp op)
static GridOp getGrid(Operation *op, ArrayRef< Sharding > operandShardings, ArrayRef< Sharding > resultShardings, SymbolTableCollection &symbolTable)
bool isAtLeastOneReductionIteratorSharded(ArrayRef< utils::IteratorType > loopIteratorTypes, ArrayRef< SmallVector< GridAxis > > gridAxisAssignmentForLoopIterators)
int16_t GridAxis
Definition ShardOps.h:26
void partitionTriviallyShardableOperation(Operation &op, ArrayRef< Value > partitionedOperands, ArrayRef< Sharding > operandShardings, ArrayRef< Sharding > resultShardings, IRMapping &partitionMap, SymbolTableCollection &symbolTable, OpBuilder &builder)
TypedValue< IndexType > createProcessLinearIndex(ImplicitLocOpBuilder &builder, StringRef grid, ArrayRef< GridAxis > gridAxes={})
SmallVector< GridAxis > getReductionGridAxes(ArrayRef< utils::IteratorType > loopIteratorTypes, ArrayRef< SmallVector< GridAxis > > gridAxisAssignmentForLoopIterators)
shard::GridOp getGrid(Operation *op, FlatSymbolRefAttr gridSymbol, SymbolTableCollection &symbolTableCollection)
Definition ShardOps.h:130
ShardingArray getGridAxisAssignmentForLoopIterators(ArrayRef< Sharding > operandShardings, ArrayRef< Sharding > resultShardings, ArrayRef< utils::IteratorType > loopIteratorTypes, ArrayRef< AffineMap > indexingMaps)
SmallVector< SmallVector< GridAxis > > ShardingArray
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:91
Value matchReduction(ArrayRef< BlockArgument > iterCarriedArgs, unsigned redPos, SmallVectorImpl< Operation * > &combinerOps)
Utility to match a generic reduction given a list of iteration-carried arguments, iterCarriedArgs and...