1 //===- Fusion.cpp - Implementation of linalg Fusion -----------------------===// 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 implements the linalg dialect Fusion on tensors operations pass. 10 // 11 //===----------------------------------------------------------------------===// 12 #include "PassDetail.h" 13 #include "mlir/Dialect/Affine/IR/AffineOps.h" 14 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 15 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 16 #include "mlir/Dialect/Linalg/Passes.h" 17 #include "mlir/Dialect/Linalg/Transforms/Transforms.h" 18 #include "mlir/Dialect/Linalg/Utils/Utils.h" 19 #include "mlir/IR/AffineExpr.h" 20 #include "mlir/IR/AffineMap.h" 21 #include "mlir/IR/PatternMatch.h" 22 #include "mlir/Support/LLVM.h" 23 24 using namespace mlir; 25 using namespace mlir::linalg; 26 27 namespace { 28 29 /// Implementation of fusion of generic ops and indexed_generic ops. 30 struct FuseGenericOpsOnTensors { 31 static bool isFusible(LinalgOp producer, LinalgOp consumer, 32 unsigned consumerIdx) { 33 // Producer and consumer must have tensor semantics. 34 if (!producer.hasTensorSemantics() || !consumer.hasTensorSemantics()) 35 return false; 36 37 // Verify that 38 // - the producer has all "parallel" iterator type. 39 if (producer.getNumParallelLoops() != producer.getNumLoops()) 40 return false; 41 42 // Get the consumer index map. The number of results of the consumer index 43 // map must match the number of loops of the producer. 44 AffineMap consumerIndexMap = consumer.getIndexingMap(consumerIdx); 45 if (consumerIndexMap.getNumResults() != producer.getNumLoops()) 46 return false; 47 48 // Finally the index_map for the result must be invertible. For now just 49 // verify it is a permutation. 50 AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0); 51 return producerResultIndexMap.isPermutation(); 52 } 53 54 static LinalgOp fuse(LinalgOp producer, LinalgOp consumer, 55 unsigned consumerIdx, PatternRewriter &rewriter, 56 OperationFolder *folder = nullptr) { 57 if (!isFusible(producer, consumer, consumerIdx)) 58 return nullptr; 59 60 unsigned numFusedOperands = producer.getOperation()->getNumOperands() + 61 consumer.getOperation()->getNumOperands() - 1; 62 63 // Compute the fused operands list, 64 SmallVector<Value, 2> fusedOperands; 65 fusedOperands.reserve(numFusedOperands); 66 auto consumerOperands = consumer.getOperation()->getOperands(); 67 auto producerOperands = producer.getOperation()->getOperands(); 68 fusedOperands.assign(consumerOperands.begin(), 69 std::next(consumerOperands.begin(), consumerIdx)); 70 fusedOperands.append(producerOperands.begin(), producerOperands.end()); 71 fusedOperands.append(std::next(consumerOperands.begin(), consumerIdx + 1), 72 consumerOperands.end()); 73 74 // Compute indexing_maps for the fused operation. The indexing_maps for the 75 // operands of the consumers that arent fused are the same. The 76 // indexing_maps for the producers need to be computed based on the 77 // indexing_map of the operand at consumerIdx in the consumer. 78 SmallVector<Attribute, 4> fusedIndexMaps; 79 auto consumerIndexMaps = consumer.indexing_maps(); 80 fusedIndexMaps.reserve(fusedOperands.size() + 81 consumer.getOperation()->getNumResults()); 82 fusedIndexMaps.assign(consumerIndexMaps.begin(), 83 std::next(consumerIndexMaps.begin(), consumerIdx)); 84 // Compute indexing maps for the producer args in the fused operation. 85 computeProducerOperandIndex( 86 producer, consumer.getInputIndexingMap(consumerIdx), fusedIndexMaps); 87 88 // Append the indexing maps for the remaining consumer operands. 89 fusedIndexMaps.append(std::next(consumerIndexMaps.begin(), consumerIdx + 1), 90 consumerIndexMaps.end()); 91 92 // Generate the fused op. 93 // Tensor-level fusion is only on ops without initTensors and outputBuffers. 94 LinalgOp fusedOp; 95 if (isa<GenericOp>(producer.getOperation()) && 96 isa<GenericOp>(consumer.getOperation())) { 97 fusedOp = 98 rewriter 99 .create<GenericOp>(consumer.getLoc(), 100 consumer.getOperation()->getResultTypes(), 101 /*inputs=*/fusedOperands, 102 /*outputBuffers=*/ValueRange{}, 103 /*initTensors=*/ValueRange{}, 104 rewriter.getArrayAttr(fusedIndexMaps), 105 consumer.iterator_types(), 106 /*doc=*/nullptr, 107 /*library_call=*/nullptr, 108 /*symbol_source=*/nullptr) 109 .getOperation(); 110 } else { 111 fusedOp = 112 rewriter 113 .create<IndexedGenericOp>( 114 consumer.getLoc(), consumer.getOperation()->getResultTypes(), 115 /*inputs=*/fusedOperands, 116 /*outputBuffers=*/ValueRange{}, 117 /*initTensors=*/ValueRange{}, 118 rewriter.getArrayAttr(fusedIndexMaps), 119 consumer.iterator_types(), 120 /*doc=*/nullptr, 121 /*library_call=*/nullptr, 122 /*symbol_source=*/nullptr) 123 .getOperation(); 124 } 125 126 // Construct an AffineMap from consumer loops to producer loops. 127 // consumer loop -> tensor index 128 AffineMap consumerResultIndexMap = 129 consumer.getInputIndexingMap(consumerIdx); 130 // producer loop -> tensor index 131 AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0); 132 // tensor index -> producer loop 133 AffineMap invProducerResultIndexMap = 134 inversePermutation(producerResultIndexMap); 135 assert(invProducerResultIndexMap && 136 "expected producer result indexig map to be invertible"); 137 // consumer loop -> producer loop 138 AffineMap consumerToProducerLoopsMap = 139 invProducerResultIndexMap.compose(consumerResultIndexMap); 140 141 generateFusedRegion(rewriter, fusedOp, producer, consumer, 142 consumerToProducerLoopsMap, consumerIdx, 143 consumer.getNumLoops()); 144 return fusedOp; 145 } 146 147 private: 148 /// Append to `fusedOpIndexingMapAttrs` the indexing maps for the operands of 149 /// the `producer` to use in the fused operation given the indexing map of the 150 /// result of the producer in the consumer. 151 static void computeProducerOperandIndex( 152 LinalgOp producer, AffineMap fusedConsumerArgIndexMap, 153 SmallVectorImpl<Attribute> &fusedOpIndexingMapAttrs) { 154 // The indexing map in the consumer op (fusedConsumerArgIndexMap) is a map 155 // from consumer loop -> consumer arg tensor index/producer result tensor 156 // index. The fused loop is same as the consumer loop. For each producer arg 157 // the indexing map to be computed is a map from consumer loop -> producer 158 // arg tensor index. 159 160 AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0); 161 // producerResultIndexMap is a map from producer loop -> tensor index. 162 // Compute the inverse to get map from tensor index -> producer loop. 163 // The inverse is a map from producer result tensor index -> producer loop. 164 AffineMap invProducerResultIndexMap = 165 inversePermutation(producerResultIndexMap); 166 assert(invProducerResultIndexMap && 167 "expected producer result indexig map to be invertible"); 168 for (unsigned argNum : llvm::seq<unsigned>(0, producer.getNumInputs())) { 169 // argMap is a map from producer loop -> producer arg tensor index. 170 AffineMap argMap = producer.getInputIndexingMap(argNum); 171 172 // Compose argMap with invProducerResultIndexMap to get a map from 173 // producer result tensor index -> producer arg tensor index. 174 AffineMap t1 = argMap.compose(invProducerResultIndexMap); 175 176 // Compose t1 with fusedConsumerArgIndexMap gives an indexing map from 177 // consumer loop/ fused loop -> producer arg tensor index. 178 AffineMap indexingMap = t1.compose(fusedConsumerArgIndexMap); 179 fusedOpIndexingMapAttrs.push_back(AffineMapAttr::get(indexingMap)); 180 } 181 } 182 183 /// Generate the region of the fused operation. The region of the fused op 184 /// must be empty. 185 static void generateFusedRegion(PatternRewriter &rewriter, Operation *fusedOp, 186 LinalgOp producer, LinalgOp consumer, 187 AffineMap consumerToProducerLoopsMap, 188 unsigned consumerIdx, unsigned nloops) { 189 // Build the region of the fused op. 190 Block &producerBlock = producer.getOperation()->getRegion(0).front(); 191 Block &consumerBlock = consumer.getOperation()->getRegion(0).front(); 192 Block *fusedBlock = new Block(); 193 fusedOp->getRegion(0).push_back(fusedBlock); 194 BlockAndValueMapping mapper; 195 OpBuilder::InsertionGuard guard(rewriter); 196 rewriter.setInsertionPointToStart(fusedBlock); 197 198 // The block arguments are 199 // [index_0, index_1, ... , 200 // consumer_operand_0, ... , consumer_operand_(`consumerIdx`-1), 201 // producer_operand_0, ... , producer_operand_(n-1)], 202 // consumer_operand_(`consumerIdx`), .. consumer_operand_(m-1)] 203 // , where n is the number of producer's operand and m is the number 204 // consumer's operand. 205 // If both `numProducerIndices` and `numConsumerIndices` are zero, this is a 206 // generic op. In this case, there are no indices in block arguments. 207 unsigned numProducerIndices = 208 isa<IndexedGenericOp>(producer.getOperation()) ? nloops : 0; 209 unsigned numConsumerIndices = 210 isa<IndexedGenericOp>(consumer.getOperation()) ? nloops : 0; 211 // Firstly, add all the indices to the block arguments. 212 for (unsigned i = 0, e = std::max(numProducerIndices, numConsumerIndices); 213 i < e; ++i) 214 fusedBlock->addArgument(rewriter.getIndexType()); 215 // Map the arguments for the unmodified args from the consumer. 216 for (auto consumerArg : llvm::enumerate(consumerBlock.getArguments())) { 217 if (consumerArg.index() == consumerIdx + numConsumerIndices) { 218 // Map the arguments for the args from the producer. 219 for (auto producerArg : llvm::enumerate(producerBlock.getArguments())) { 220 // If producer is an indexed_generic op, map the indices from consumer 221 // loop to producer loop (because the fusedOp is built based on 222 // consumer's perspective). 223 if (producerArg.index() < numProducerIndices) { 224 auto newIndex = rewriter.create<mlir::AffineApplyOp>( 225 producer.getLoc(), 226 consumerToProducerLoopsMap.getSubMap(producerArg.index()), 227 fusedBlock->getArguments().take_front(nloops)); 228 mapper.map(producerArg.value(), newIndex); 229 } else { 230 mapper.map(producerArg.value(), 231 fusedBlock->addArgument(producerArg.value().getType())); 232 } 233 } 234 continue; 235 } 236 237 // If consumer is an indexed_generic op, map the indices to the block 238 // arguments directly. Otherwise, add the same type of arugment and map to 239 // it. 240 if (consumerArg.index() < numConsumerIndices) { 241 mapper.map(consumerArg.value(), 242 fusedBlock->getArgument(consumerArg.index())); 243 } else { 244 mapper.map(consumerArg.value(), 245 fusedBlock->addArgument(consumerArg.value().getType())); 246 } 247 } 248 249 // Add operations from producer (except the yield operation) to the fused 250 // op. 251 for (auto &op : producerBlock.getOperations()) { 252 if (auto yieldOp = dyn_cast<linalg::YieldOp>(op)) { 253 // Lookup the value the yield operation is mapped to. 254 Value yieldVal = yieldOp.getOperand(0); 255 if (Value clonedVal = mapper.lookupOrNull(yieldVal)) 256 mapper.map( 257 consumerBlock.getArgument(consumerIdx + numConsumerIndices), 258 clonedVal); 259 continue; 260 } 261 rewriter.clone(op, mapper); 262 } 263 for (auto &op : consumerBlock.getOperations()) 264 rewriter.clone(op, mapper); 265 } 266 }; 267 } // namespace 268 269 /// Linearize the expressions in `sourceMap` based on the `reassociationMaps` 270 /// provided, given the shape of the source tensor that corresponds to the 271 /// `sourceMap`. Note that this implicitly assumes that the tensors dimensions 272 /// are "row-major" ordered logically. 273 /// 274 /// For example: 275 /// 276 /// %0 = op ... : tensor<?x?x4x5xf32> 277 /// with output index_map `affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>` 278 /// 279 /// and reshape: 280 /// %1 = linalg.tensor_reshape %0 [affine_map<(i, j, k, l) -> (i)>, 281 /// affine_map<(i, j, k, l) -> (j, k, l)>] : 282 /// tensor<?x?x4x5xf32> into tensor<?x?xf32> 283 /// 284 /// would be rewritten into: 285 /// %0 = op ... : tensor<?x?x4x5xf32> 286 /// with output index_map 287 /// `affine_map<(d0, d1, d2, d3) -> (d0, d1 * 20 + d2 * 5 + d3)>` 288 static AffineMap linearizeCollapsedDims(AffineMap sourceMap, 289 ArrayRef<int64_t> sourceShape, 290 ArrayRef<AffineMap> reassociationMaps) { 291 SmallVector<AffineExpr, 4> resultExprs; 292 resultExprs.reserve(reassociationMaps.size()); 293 ArrayRef<AffineExpr> sourceExprs = sourceMap.getResults(); 294 MLIRContext *context = sourceMap.getContext(); 295 296 // Compute the result exprs based on the reassociation maps. 297 for (AffineMap map : reassociationMaps) { 298 ArrayRef<AffineExpr> collapsedDims = map.getResults(); 299 // Assume that they are in-order and contiguous (already checked in 300 // verifier). 301 assert(!collapsedDims.empty()); 302 unsigned startDim = 303 collapsedDims.front().cast<AffineDimExpr>().getPosition(); 304 AffineExpr linearizedExpr = makeCanonicalStridedLayoutExpr( 305 sourceShape.slice(startDim, collapsedDims.size()), 306 sourceExprs.slice(startDim, collapsedDims.size()), context); 307 resultExprs.push_back(linearizedExpr); 308 } 309 return AffineMap::get(sourceMap.getNumDims(), sourceMap.getNumSymbols(), 310 resultExprs, context); 311 } 312 313 /// Checks if the `reshapeOp` can be fused with it consumer (if `asProducer` is 314 /// true) or its producer (if `asProducer` is false) given the indexing map at 315 /// its use. 316 static bool isTensorReshapeOpFusible(TensorReshapeOp reshapeOp, 317 AffineMap useIndexMap, bool asProducer) { 318 RankedTensorType returnType = reshapeOp.getResultType(); 319 RankedTensorType operandType = reshapeOp.getSrcType(); 320 // Reshape is fusible with its consumer (i.e. reshape as a producer) when its 321 // operand is of lesser rank than the result. Fusing when operand has higher 322 // rank will require use of mods and divs in the indexing maps of the fused op 323 // which would make it non-invertible. Similarly reshape is fused with its 324 // producer (i.e. reshape as consumer) only if the return type has lesser 325 // rank. 326 if ((asProducer && returnType.getRank() < operandType.getRank()) || 327 (!asProducer && operandType.getRank() < returnType.getRank())) 328 return false; 329 return useIndexMap.isIdentity(); 330 } 331 332 /// Based on the type of `op` create a linalg op of the same type, i.e. if `op` 333 /// is a linalg.generic operation, the create a `linalg.generic` operation with 334 /// the given `args`. Expects `op` to be `linalg.generic` or 335 /// `linalg.indexed_generic`. 336 template <typename... Args> 337 static LinalgOp createLinalgOpOfSameType(LinalgOp op, PatternRewriter &rewriter, 338 Args... args) { 339 if (isa<GenericOp>(op.getOperation())) 340 return cast<LinalgOp>(rewriter.create<GenericOp>(args...).getOperation()); 341 if (isa<IndexedGenericOp>(op.getOperation())) 342 return cast<LinalgOp>( 343 rewriter.create<IndexedGenericOp>(args...).getOperation()); 344 llvm_unreachable( 345 "expected only linalg.generic or linalg.indexed_generic ops"); 346 return nullptr; 347 } 348 349 namespace { 350 351 /// Implementation of fusion on tensor ops when producer is a TensorReshapeOp. 352 struct FuseTensorReshapeOpAsProducer { 353 static bool isFusible(TensorReshapeOp producer, LinalgOp consumer, 354 unsigned consumerIdx) { 355 return isa<GenericOp, IndexedGenericOp>(consumer.getOperation()) && 356 consumer.hasTensorSemantics() && 357 isTensorReshapeOpFusible(producer, 358 consumer.getInputIndexingMap(consumerIdx), 359 /*asProducer=*/true); 360 } 361 362 static LinalgOp fuse(TensorReshapeOp producer, LinalgOp consumer, 363 unsigned consumerIdx, PatternRewriter &rewriter, 364 OperationFolder *folder = nullptr) { 365 if (producer.src().getDefiningOp<ConstantOp>()) 366 return nullptr; 367 368 if (!isFusible(producer, consumer, consumerIdx)) 369 return nullptr; 370 371 // Compute the fused operands list, 372 Operation *consumerOp = consumer.getOperation(); 373 SmallVector<Value, 2> fusedOperands(consumerOp->getOperands()); 374 fusedOperands[consumerIdx] = producer.src(); 375 376 // Compute indexing_maps for the fused operation. The indexing_maps for the 377 // operands of the consumers that arent fused are the same. 378 SmallVector<AffineMap, 4> fusedIndexMaps = 379 llvm::to_vector<4>(llvm::map_range( 380 consumer.indexing_maps(), [](Attribute attr) -> AffineMap { 381 return attr.cast<AffineMapAttr>().getValue(); 382 })); 383 384 // Compute the indexing map to use for the operand of the producer. 385 AffineMap modifiedMap = linearizeCollapsedDims( 386 fusedIndexMaps[consumerIdx], producer.getResultType().getShape(), 387 producer.getReassociationMaps()); 388 for (AffineExpr expr : modifiedMap.getResults()) { 389 if (!expr.isPureAffine()) 390 return nullptr; 391 } 392 fusedIndexMaps[consumerIdx] = modifiedMap; 393 394 // Further check that the resulting index maps can be fused and 395 // inverted. Without this the resultant op is not legal. 396 if (!inversePermutation(concatAffineMaps(fusedIndexMaps))) 397 return nullptr; 398 399 SmallVector<Attribute, 4> indexMapAttrs = llvm::to_vector<4>( 400 llvm::map_range(fusedIndexMaps, [](AffineMap map) -> Attribute { 401 return AffineMapAttr::get(map); 402 })); 403 LinalgOp fusedOp = createLinalgOpOfSameType( 404 consumer, rewriter, rewriter.getUnknownLoc(), 405 consumerOp->getResultTypes(), 406 /*inputs=*/fusedOperands, 407 /*outputBuffers=*/ValueRange{}, 408 /*initTensors=*/ValueRange{}, // no init tensors for now. 409 rewriter.getArrayAttr(indexMapAttrs), consumer.iterator_types(), 410 /*doc=*/nullptr, 411 /*library_call=*/nullptr, 412 /*symbol_source=*/nullptr); 413 auto &fusedRegion = fusedOp.getOperation()->getRegion(0); 414 rewriter.cloneRegionBefore(consumerOp->getRegion(0), fusedRegion, 415 fusedRegion.begin()); 416 return fusedOp; 417 } 418 }; 419 420 /// Implementation of fusion on tensor ops when consumer is a TensorReshapeOp. 421 struct FuseTensorReshapeOpAsConsumer { 422 static bool isCollapsingAndFusible(LinalgOp producer, 423 TensorReshapeOp consumer, 424 unsigned consumerIdx) { 425 return isa<GenericOp, IndexedGenericOp>(producer.getOperation()) && 426 producer.hasTensorSemantics() && 427 isTensorReshapeOpFusible(consumer, producer.getOutputIndexingMap(0), 428 /*asProducer=*/false); 429 } 430 431 static LinalgOp fuseCollapsingCase(LinalgOp producer, 432 TensorReshapeOp consumer, 433 unsigned consumerIdx, 434 PatternRewriter &rewriter) { 435 // The indexing_maps for the operands of the fused operation are same as 436 // those for the operands of the producer. 437 SmallVector<AffineMap, 4> fusedIndexMaps = 438 llvm::to_vector<4>(llvm::map_range( 439 producer.indexing_maps(), [](Attribute attr) -> AffineMap { 440 return attr.cast<AffineMapAttr>().getValue(); 441 })); 442 // Compute the indexing map to use for the operand of the producer. 443 AffineMap modifiedMap = linearizeCollapsedDims( 444 producer.getOutputIndexingMap(0), consumer.getSrcType().getShape(), 445 consumer.getReassociationMaps()); 446 for (AffineExpr expr : modifiedMap.getResults()) { 447 if (!expr.isPureAffine()) 448 return nullptr; 449 } 450 fusedIndexMaps.back() = modifiedMap; 451 452 // Further check that the resulting index maps can be fused and 453 // inverted. Without this the resultant op is not legal. 454 if (!inversePermutation(concatAffineMaps(fusedIndexMaps))) 455 return nullptr; 456 457 SmallVector<Attribute, 4> indexMapAttrs = llvm::to_vector<4>( 458 llvm::map_range(fusedIndexMaps, [](AffineMap map) -> Attribute { 459 return AffineMapAttr::get(map); 460 })); 461 462 Operation *producerOp = producer.getOperation(); 463 LinalgOp fusedOp = createLinalgOpOfSameType( 464 producer, rewriter, rewriter.getUnknownLoc(), consumer.getResultType(), 465 /*inputs=*/producerOp->getOperands(), 466 /*outputBuffers=*/ValueRange{}, 467 /*initTensors=*/ValueRange{}, // no init tensors for now. 468 rewriter.getArrayAttr(indexMapAttrs), producer.iterator_types(), 469 /*doc=*/nullptr, 470 /*library_call=*/nullptr, 471 /*symbol_source=*/nullptr); 472 auto &fusedRegion = fusedOp.getOperation()->getRegion(0); 473 rewriter.cloneRegionBefore(producerOp->getRegion(0), fusedRegion, 474 fusedRegion.begin()); 475 return fusedOp; 476 } 477 478 static bool isExpandingAndFusible(LinalgOp producer, TensorReshapeOp consumer, 479 unsigned consumerIdx) { 480 // Is fusible only if: 481 // 1) The producer is a generic op. 482 // 2) The producer has tensor semantics. 483 // 3) The tensor reshape op is a expanding case. 484 // 4) All the shapes are the same for the generic op. 485 // 5) All the indexing maps in producer are identity. 486 // 6) All the loops in producer are parallel loops. 487 // 7) The producer has a single user. 488 auto types = producer.getInputOutputShapedTypes(); 489 assert(!types.empty()); 490 return isa<GenericOp>(producer.getOperation()) && 491 producer.hasTensorSemantics() && 492 consumer.getSrcType().getRank() < 493 consumer.getResultType().getRank() && 494 std::equal(types.begin() + 1, types.end(), types.begin()) && 495 llvm::all_of(producer.getIndexingMaps(), 496 [](AffineMap map) { return map.isIdentity(); }) && 497 llvm::all_of(producer.iterator_types(), 498 [](Attribute attr) { 499 return attr.cast<StringAttr>().getValue() == 500 getParallelIteratorTypeName(); 501 }) && 502 producer.getOperation()->hasOneUse(); 503 } 504 505 static LinalgOp fuseExpandingCase(LinalgOp producer, TensorReshapeOp consumer, 506 unsigned consumerIdx, 507 PatternRewriter &rewriter) { 508 Location loc = producer.getLoc(); 509 auto dstShape = consumer.getResultType().cast<ShapedType>().getShape(); 510 SmallVector<Value, 4> args; 511 for (auto arg : producer.getOperation()->getOperands()) { 512 auto type = RankedTensorType::get( 513 dstShape, arg.getType().cast<ShapedType>().getElementType()); 514 args.push_back(rewriter.createOrFold<linalg::TensorReshapeOp>( 515 loc, type, arg, consumer.reassociation())); 516 } 517 518 SmallVector<Type, 4> resultTypes; 519 for (auto t : producer.getOutputTensorTypes()) { 520 Type type = RankedTensorType::get(dstShape, 521 t.cast<ShapedType>().getElementType()); 522 resultTypes.push_back(type); 523 } 524 525 int rank = dstShape.size(); 526 auto genericOp = rewriter.create<linalg::GenericOp>( 527 loc, resultTypes, /*inputs=*/args, 528 /*outputBuffers=*/ValueRange{}, 529 /*initTensors=*/ValueRange{}, 530 SmallVector<AffineMap, 3>(args.size() + resultTypes.size(), 531 rewriter.getMultiDimIdentityMap(rank)), 532 SmallVector<StringRef, 3>(rank, getParallelIteratorTypeName())); 533 Region ®ion = genericOp.getRegion(); 534 rewriter.cloneRegionBefore(producer.getOperation()->getRegion(0), region, 535 region.begin()); 536 return cast<LinalgOp>(genericOp.getOperation()); 537 } 538 539 static LinalgOp fuse(LinalgOp producer, TensorReshapeOp consumer, 540 unsigned consumerIdx, PatternRewriter &rewriter, 541 OperationFolder *folder = nullptr) { 542 if (isCollapsingAndFusible(producer, consumer, consumerIdx)) 543 return fuseCollapsingCase(producer, consumer, consumerIdx, rewriter); 544 if (isExpandingAndFusible(producer, consumer, consumerIdx)) 545 return fuseExpandingCase(producer, consumer, consumerIdx, rewriter); 546 return nullptr; 547 } 548 }; 549 550 /// Implementation of fusion on tensor ops when producer is a splat constant. 551 struct FuseConstantOpAsProducer { 552 static bool isFusible(ConstantOp producer, LinalgOp consumer, 553 unsigned consumerIdx) { 554 return isa<GenericOp, IndexedGenericOp>(consumer.getOperation()) && 555 consumer.hasTensorSemantics() && 556 producer.getResult().getType().isa<RankedTensorType>() && 557 producer.value().cast<DenseElementsAttr>().isSplat(); 558 } 559 560 static LinalgOp fuse(ConstantOp producer, LinalgOp consumer, 561 unsigned consumerIdx, PatternRewriter &rewriter, 562 OperationFolder *folder = nullptr) { 563 if (!isFusible(producer, consumer, consumerIdx)) 564 return nullptr; 565 566 // The indexing_maps for the operands of the fused operation are same as 567 // those for the operands of the consumer without the indexing map at 568 // consumerIdx 569 SmallVector<AffineMap, 4> fusedIndexMaps = 570 llvm::to_vector<4>(llvm::map_range( 571 consumer.indexing_maps(), [](Attribute attr) -> AffineMap { 572 return attr.cast<AffineMapAttr>().getValue(); 573 })); 574 fusedIndexMaps.erase(std::next(fusedIndexMaps.begin(), consumerIdx)); 575 576 // The operands list is same as the consumer with the argument for constant 577 // index dropped. 578 Operation *consumerOp = consumer.getOperation(); 579 SmallVector<Value, 4> fusedOperands(consumerOp->getOperands()); 580 fusedOperands.erase(std::next(fusedOperands.begin(), consumerIdx)); 581 582 // Create a constant scalar value from the splat constant. 583 Value scalarConstant = rewriter.create<ConstantOp>( 584 producer.getLoc(), 585 producer.value().cast<DenseElementsAttr>().getSplatValue()); 586 587 LinalgOp fusedOp = createLinalgOpOfSameType( 588 consumer, rewriter, rewriter.getUnknownLoc(), 589 consumerOp->getResultTypes(), 590 /*inputs=*/fusedOperands, 591 /*outputBuffers=*/ValueRange{}, 592 /*initTensors=*/ValueRange{}, // no init tensors for now. 593 rewriter.getAffineMapArrayAttr(fusedIndexMaps), 594 consumer.iterator_types(), 595 /*doc=*/nullptr, 596 /*library_call=*/nullptr, 597 /*symbol_source=*/nullptr); 598 599 // Map the block argument corresponding to the replaced argument with the 600 // scalar constant. 601 Region &consumerRegion = consumerOp->getRegion(0); 602 Block &entryBlock = *consumerRegion.begin(); 603 unsigned argIndex = entryBlock.getNumArguments() - 604 consumerOp->getNumOperands() + consumerIdx; 605 BlockAndValueMapping mapping; 606 mapping.map(entryBlock.getArgument(argIndex), scalarConstant); 607 Region &fusedRegion = fusedOp.getOperation()->getRegion(0); 608 rewriter.cloneRegionBefore(consumerRegion, fusedRegion, fusedRegion.begin(), 609 mapping); 610 return fusedOp; 611 } 612 }; 613 } // namespace 614 615 Operation *mlir::linalg::fuseTensorOps(PatternRewriter &rewriter, 616 Operation *consumer, 617 unsigned consumerIdx, 618 OperationFolder *folder) { 619 if (consumerIdx >= consumer->getNumOperands()) 620 return nullptr; 621 Operation *producer = consumer->getOperand(consumerIdx).getDefiningOp(); 622 if (!producer || producer->getNumResults() != 1) 623 return nullptr; 624 625 // Fuse when consumer is GenericOp or IndexedGenericOp. 626 if (isa<GenericOp, IndexedGenericOp>(consumer)) { 627 if (isa<GenericOp, IndexedGenericOp>(producer)) 628 return FuseGenericOpsOnTensors::fuse(cast<LinalgOp>(producer), 629 cast<LinalgOp>(consumer), 630 consumerIdx, rewriter, folder); 631 if (auto reshapeOpProducer = dyn_cast<TensorReshapeOp>(producer)) 632 return FuseTensorReshapeOpAsProducer::fuse(reshapeOpProducer, 633 cast<LinalgOp>(consumer), 634 consumerIdx, rewriter, folder); 635 if (auto constantOpProducer = dyn_cast<ConstantOp>(producer)) 636 return FuseConstantOpAsProducer::fuse(constantOpProducer, 637 cast<LinalgOp>(consumer), 638 consumerIdx, rewriter, folder); 639 return nullptr; 640 } 641 642 if (isa<GenericOp, IndexedGenericOp>(producer)) { 643 // Fuse when consumer is a TensorReshapeOp. 644 if (TensorReshapeOp reshapeOp = dyn_cast<TensorReshapeOp>(consumer)) { 645 return FuseTensorReshapeOpAsConsumer::fuse( 646 cast<LinalgOp>(producer), reshapeOp, consumerIdx, rewriter, folder); 647 } 648 } 649 650 return nullptr; 651 } 652 653 namespace { 654 /// Patterns to fuse a generic op, with the producer of its operands. 655 template <typename LinalgOpTy> 656 struct FuseTensorOps : public OpRewritePattern<LinalgOpTy> { 657 using OpRewritePattern<LinalgOpTy>::OpRewritePattern; 658 659 LogicalResult matchAndRewrite(LinalgOpTy op, 660 PatternRewriter &rewriter) const override { 661 // Find the first operand that is defined by another generic op on tensors. 662 for (auto operandNum : 663 llvm::seq<unsigned>(0, op.getOperation()->getNumOperands())) { 664 Operation *producer = 665 op.getOperation()->getOperand(operandNum).getDefiningOp(); 666 if (Operation *fusedOp = fuseTensorOps(rewriter, op, operandNum)) { 667 rewriter.replaceOp(op, fusedOp->getResults()); 668 if (producer && llvm::all_of(producer->getResults(), 669 [](Value val) { return val.use_empty(); })) 670 rewriter.eraseOp(producer); 671 return success(); 672 } 673 } 674 return failure(); 675 } 676 }; 677 678 /// Pass that fuses generic ops on tensors. Used only for testing. 679 struct FusionOfTensorOpsPass 680 : public LinalgFusionOfTensorOpsBase<FusionOfTensorOpsPass> { 681 void runOnOperation() override { 682 OwningRewritePatternList patterns; 683 Operation *op = getOperation(); 684 populateLinalgTensorOpsFusionPatterns(op->getContext(), patterns); 685 applyPatternsAndFoldGreedily(op->getRegions(), patterns); 686 }; 687 }; 688 } // namespace 689 690 void mlir::populateLinalgTensorOpsFusionPatterns( 691 MLIRContext *context, OwningRewritePatternList &patterns) { 692 patterns.insert<FuseTensorOps<GenericOp>, FuseTensorOps<IndexedGenericOp>, 693 FuseTensorOps<TensorReshapeOp>>(context); 694 } 695 696 std::unique_ptr<Pass> mlir::createLinalgFusionOfTensorOpsPass() { 697 return std::make_unique<FusionOfTensorOpsPass>(); 698 } 699