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 pass. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "PassDetail.h" 14 #include "mlir/Dialect/Affine/IR/AffineOps.h" 15 #include "mlir/Dialect/Linalg/Analysis/DependenceAnalysis.h" 16 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 17 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 18 #include "mlir/Dialect/Linalg/Passes.h" 19 #include "mlir/Dialect/Linalg/Transforms/Transforms.h" 20 #include "mlir/Dialect/Linalg/Utils/Utils.h" 21 #include "mlir/Dialect/MemRef/IR/MemRef.h" 22 #include "mlir/Dialect/Tensor/IR/Tensor.h" 23 #include "mlir/IR/AffineExpr.h" 24 #include "mlir/IR/AffineMap.h" 25 #include "mlir/IR/Dominance.h" 26 #include "mlir/Support/LLVM.h" 27 #include "mlir/Transforms/GreedyPatternRewriteDriver.h" 28 #include "mlir/Transforms/RegionUtils.h" 29 #include "llvm/ADT/MapVector.h" 30 #include "llvm/ADT/ScopeExit.h" 31 #include "llvm/Support/CommandLine.h" 32 #include "llvm/Support/Debug.h" 33 34 #include <set> 35 36 #define DEBUG_TYPE "linalg-fusion" 37 38 using namespace mlir; 39 using namespace mlir::linalg; 40 41 using llvm::dbgs; 42 43 /// Implements a simple high-level fusion pass on linalg structured operations. 44 /// 45 /// In each block, linalg ops are processed in reverse textual order. 46 /// Given a linalg op `O`, fusion occurs by: 47 /// 1. inspecting the linalg ops that write into the views read by `O`. There 48 /// are 2 cases: 49 /// a) buffer case: use the SSA value of the views and a simple alias 50 /// analysis on subview ops to determine producer-consumer dependences; 51 /// b) tensor case: use SSA use-def chains on subtensor ops; 52 /// 2. greedily fuse the linalg ops that produce the subview/subtensor. 53 /// 3. inspect the fused ops and determine whether they have other remaining 54 /// LinalgOp uses. If not, then erase the original producing linalg op. 55 /// 56 /// More advanced use cases, analyses as well as profitability heuristics are 57 /// left for future work. 58 59 struct ShapeDimension { 60 Value shape; 61 unsigned dimension; 62 }; 63 64 // Given an `op`, returns the first (`shape`, `dimension`) pair that identifies 65 // the loop range at `loopDepth`. The semantics of the loopToOperandRangesMaps 66 // guarantees at least one such dimension is found. If multiple candidates exist 67 // they must agree by construction (i.e. have the same size) and we just return 68 // the first one. 69 static ShapeDimension 70 getShapeDefiningLoopRange(LinalgOp op, unsigned loopDepth, 71 bool fromSubViewOpOnly = false) { 72 auto maps = op.indexing_maps(); 73 // Iterate over the inputs and outputs in order. 74 // Extract the subranges from the linearized ranges. 75 for (auto en : llvm::enumerate(op.getShapedOperands())) { 76 // The method `getRangeFromOperandShape` requires using SubViewOp or 77 // SubTensorOps. If the value isnt defined from there continue. 78 // todo: The method should be adapted to get the values from 79 // `ViewInterface`. The interface needs a `getOrCreateRanges` method which 80 // currently returns a `linalg.range`. The fix here is to move this op to 81 // `std` dialect and add the method to `ViewInterface`. 82 if (fromSubViewOpOnly && !isa_and_nonnull<memref::SubViewOp, SubTensorOp>( 83 en.value().getDefiningOp())) 84 continue; 85 86 unsigned idx = en.index(); 87 auto map = maps[idx].cast<AffineMapAttr>().getValue(); 88 LLVM_DEBUG(llvm::dbgs() 89 << "getShapeDefiningLoopRange I/O idx: " << idx << "\n"); 90 LLVM_DEBUG(llvm::dbgs() 91 << "getShapeDefiningLoopRange map: " << map << "\n"); 92 Value shape = en.value(); 93 SmallVector<Value, 8> shapeRanges(map.getNumResults(), nullptr); 94 for (auto en2 : llvm::enumerate(map.getResults())) { 95 auto dimExpr = en2.value().dyn_cast<AffineDimExpr>(); 96 if (!dimExpr) 97 continue; 98 if (loopDepth == en2.value().cast<AffineDimExpr>().getPosition()) { 99 LLVM_DEBUG(llvm::dbgs() << "getShapeDefiningLoopRange loopDepth: " 100 << loopDepth << "\n"); 101 LLVM_DEBUG(llvm::dbgs() 102 << "getShapeDefiningLoopRange shape: " << shape << "\n"); 103 return ShapeDimension{shape, static_cast<unsigned>(en2.index())}; 104 } 105 } 106 } 107 llvm_unreachable("Expect to be able to extract a shape defining loop range"); 108 } 109 110 // Return tiled operands for the fused producer op. When fusing into 111 // `linalg.tiled_loop` one has to update `input` and `output` arguments of the 112 // loop correspondingly. 113 // Each input tensor of the producer op has to be added to `inputs` of the 114 // `tiled_loop` if it is not present there already. Each output tensor has to 115 // be added either to `inputs` or to `outputs` of `linalg.tiled_loop` depending 116 // on whether the correponding result is an input or an output to the loop. 117 // 118 // NOTE: This way of updating the arguments of the `tiled_loop` assumes that the 119 // intermediate result is not used by any other operation but the consumer. A 120 // more generic way is to append all missing output tensors of the producer to 121 // the tiled loop outputs and hence modify the number of the results, since we 122 // would need to add the intermediate results to `linalg.yield`. After that a 123 // canonicalization pass would move the unused output args of the `tiled_loop` 124 // to the `input` section. 125 static SmallVector<Value, 4> getTiledOperands(OpBuilder &b, LinalgOp producer) { 126 auto tiledLoop = dyn_cast<TiledLoopOp>(b.getBlock()->getParentOp()); 127 if (!tiledLoop) 128 return llvm::to_vector<4>(producer.getShapedOperands()); 129 130 SmallVector<Value, 4> tiledOperands; 131 assert(producer.hasTensorSemantics() && 132 "only fusion on tensors is currently supported for TiledLinalgOp"); 133 134 for (auto producerInput : producer.getInputTensors()) { 135 OpOperand *addedInput = tiledLoop.findInputOperand(producerInput); 136 if (addedInput == nullptr) 137 addedInput = &tiledLoop.appendInputOperand(b, producerInput); 138 BlockArgument addedBlockArg = tiledLoop.getTiedBlockArgument(*addedInput); 139 tiledOperands.push_back(addedBlockArg); 140 } 141 for (auto &en : llvm::enumerate(producer.getOutputTensors())) { 142 Value producerOutput = en.value(); 143 144 Value result = producer->getResult(en.index()); 145 OpOperand *resultInputOperand = tiledLoop.findInputOperand(result); 146 OpOperand *resultOutputOperand = tiledLoop.findOutputOperand(result); 147 assert((resultInputOperand != nullptr) ^ (resultOutputOperand != nullptr) && 148 "The result should be present in `input` or `output` args of " 149 "`tiled_loop"); 150 151 bool isInput = resultInputOperand; 152 int opNumber = isInput ? resultInputOperand->getOperandNumber() 153 : resultOutputOperand->getOperandNumber(); 154 155 OpOperand *addedOutput = tiledLoop.findOutputOperand(producerOutput); 156 if (addedOutput == nullptr) 157 addedOutput = isInput ? &tiledLoop.appendInputOperand(b, producerOutput) 158 : &tiledLoop.appendOutputOperand(b, producerOutput); 159 160 OpOperand &resultOperand = tiledLoop->getOpOperand(opNumber); 161 auto addedBlockArg = tiledLoop.getTiedBlockArgument(*addedOutput); 162 auto resultOperandBlockArg = tiledLoop.getTiedBlockArgument(resultOperand); 163 resultOperandBlockArg.replaceAllUsesWith(addedBlockArg); 164 tiledLoop.eraseOperand(b, resultOperand); 165 tiledOperands.push_back(addedBlockArg); 166 } 167 return tiledOperands; 168 } 169 170 /// Fuses the producer by cloning the `producer`. The `fusedLoopsAndRanges` 171 /// provides the loop range information for the fused loops. The rest are 172 /// obtained from the producer itself, since they are not tiled + fused. 173 static LinalgOp fuse(OpBuilder &b, LinalgOp producer, 174 const DenseMap<unsigned, Range> &fusedLoopsAndRanges) { 175 SmallVector<Value, 8> ivs, tileSizes, sizeBounds; 176 SmallVector<Range, 8> loopRanges; 177 Location loc = producer.getLoc(); 178 auto zero = b.create<ConstantIndexOp>(loc, 0); 179 auto one = b.create<ConstantIndexOp>(loc, 1); 180 181 for (unsigned i = 0, e = producer.getNumLoops(); i < e; ++i) { 182 auto it = fusedLoopsAndRanges.find(i); 183 if (it != fusedLoopsAndRanges.end()) { 184 ivs.push_back(it->second.offset); 185 tileSizes.push_back(it->second.size); 186 sizeBounds.push_back(nullptr); 187 loopRanges.push_back(it->second); 188 LLVM_DEBUG(llvm::dbgs() << "tiled loop#" << i << " with LoopRange " 189 << loopRanges.back() << "\n"); 190 } else { 191 auto shapeDim = getShapeDefiningLoopRange(producer, i); 192 Value dim = b.createOrFold<memref::DimOp>(loc, shapeDim.shape, 193 shapeDim.dimension); 194 tileSizes.push_back(zero); 195 sizeBounds.push_back(dim); 196 loopRanges.push_back(Range{zero, dim, one}); 197 LLVM_DEBUG(llvm::dbgs() << "full loop#" << i << " with LoopRange " 198 << loopRanges.back() << "\n"); 199 } 200 } 201 202 SmallVector<Value, 8> clonedShapes; 203 clonedShapes.reserve(producer.getNumShapedOperands()); 204 205 // Compute subranges for all tensor input/output operands. 206 clonedShapes.append(makeTiledShapes(b, loc, producer, 207 getTiledOperands(b, producer), ivs, 208 tileSizes, sizeBounds)); 209 210 // Append the other operands. 211 auto operands = producer.getAssumedNonShapedOperands(); 212 clonedShapes.append(operands.begin(), operands.end()); 213 214 // Iterate over the results in order. 215 // Extract the subtensor type from the linearized range. 216 // Since we do not enforce any canonicalizations on the fly, this is always 217 // fully dynamic at construction time. 218 SmallVector<Type, 4> resultTypes; 219 resultTypes.reserve(producer->getNumResults()); 220 for (RankedTensorType t : producer.getOutputTensorTypes()) { 221 unsigned rank = t.getRank(); 222 SmallVector<int64_t, 4> staticOffsetsVector( 223 rank, ShapedType::kDynamicStrideOrOffset); 224 SmallVector<int64_t, 4> staticSizesVector(rank, ShapedType::kDynamicSize); 225 SmallVector<int64_t, 4> staticStridesVector( 226 rank, ShapedType::kDynamicStrideOrOffset); 227 resultTypes.push_back(SubTensorOp::inferResultType( 228 t.cast<RankedTensorType>(), staticOffsetsVector, staticSizesVector, 229 staticStridesVector)); 230 } 231 232 Operation *clonedOp = producer.clone(b, loc, resultTypes, clonedShapes); 233 // When the producer has index semantics, we have to transform the indices of 234 // the producer according to the tiling of the consumer, i.e. offset them by 235 // the values computed in `loopRanges`. 236 assert(!isa<IndexedGenericOp>(producer) && "unexpected op"); 237 if (producer.hasIndexSemantics()) { 238 assert(clonedOp->getNumRegions() == 1 && 239 clonedOp->getRegion(0).getBlocks().size() == 1 && 240 "expected producer to have one block."); 241 // Shift all indices by the tile offset. 242 Block &block = clonedOp->getRegion(0).front(); 243 for (IndexOp indexOp : block.getOps<IndexOp>()) { 244 OpBuilder::InsertionGuard g(b); 245 b.setInsertionPointAfter(indexOp); 246 AffineExpr index, offset; 247 bindDims(b.getContext(), index, offset); 248 AffineApplyOp applyOp = b.create<AffineApplyOp>( 249 indexOp.getLoc(), index + offset, 250 ValueRange{indexOp.getResult(), loopRanges[indexOp.dim()].offset}); 251 indexOp.getResult().replaceAllUsesExcept(applyOp, applyOp); 252 } 253 } 254 255 return clonedOp; 256 } 257 258 /// Get the loop range for a dimension `dim` based on the `shapedOperand`. It is 259 /// expected to be defined by a subview op or a subtensor op. 260 static Range getRangeFromOperandShape(OpBuilder &b, Location loc, 261 Value shapedOperand, unsigned dim) { 262 Operation *shapeProducingOp = shapedOperand.getDefiningOp(); 263 if (auto subViewOp = dyn_cast<memref::SubViewOp>(shapeProducingOp)) 264 return subViewOp.getOrCreateRanges(b, loc)[dim]; 265 if (auto subTensorOp = dyn_cast<SubTensorOp>(shapeProducingOp)) 266 return subTensorOp.getOrCreateRanges(b, loc)[dim]; 267 llvm_unreachable("SubviewOp or SubTensorOp expected"); 268 } 269 270 /// Fuses the producer of `producerIdx` into the loop immediately enclosing 271 /// `consumer`. This is achieved by "recomputing" the `producer` at the time it 272 /// is needed just before the `consumer. 273 /// 274 /// Depending on the type of `consumer.getShapedOperand(consumerIdx)`, there are 275 /// 2 cases: 276 /// 1. Buffer case: `producerIdx` is the index of the buffer in 277 /// `producer.getOutputBuffers()`. 278 /// 2. Tensor case: `producerIdx` is the index of the tensor in 279 /// `producer.getResults()`. 280 static LinalgOp fuse(OpBuilder &b, LinalgOp producerOp, AffineMap producerMap, 281 OpOperand &consumerOpOperand) { 282 LLVM_DEBUG(llvm::dbgs() << "Producer map: " << producerMap << "\n"); 283 DenseMap<unsigned, Range> fusedLoopsAndRanges; 284 Value shapedOperand = consumerOpOperand.get(); 285 for (auto en : llvm::enumerate(producerMap.getResults())) { 286 unsigned posInProducerLoop = en.value().cast<AffineDimExpr>().getPosition(); 287 fusedLoopsAndRanges[posInProducerLoop] = getRangeFromOperandShape( 288 b, consumerOpOperand.getOwner()->getLoc(), shapedOperand, en.index()); 289 } 290 return fuse(b, producerOp, fusedLoopsAndRanges); 291 } 292 293 // Encode structural fusion safety preconditions. 294 // Some of these will be lifted in the future with better analysis. 295 static bool isStructurallyFusableProducer(LinalgOp producer, Value consumedView, 296 LinalgOp consumer) { 297 assert(producer.hasBufferSemantics() && 298 "expected linalg op with buffer semantics"); 299 assert(consumer.hasBufferSemantics() && 300 "expected linalg op with buffer semantics"); 301 if (producer.getNumOutputs() != 1) { 302 LLVM_DEBUG(llvm::dbgs() << "\nNot structurally fusable (multi-output)"); 303 return false; 304 } 305 // Only fuse when the producer block dominates. 306 DominanceInfo dom(producer.getOperation()); 307 if (!dom.dominates(producer->getBlock(), consumer->getBlock())) { 308 LLVM_DEBUG( 309 llvm::dbgs() 310 << "\nNot structurally fusable (producer block does not dominate)"); 311 return false; 312 } 313 return true; 314 } 315 316 bool mlir::linalg::isProducerLastWriteOfView(const LinalgDependenceGraph &graph, 317 LinalgOp consumer, 318 Value consumedView, 319 LinalgOp producer) { 320 assert(producer.hasBufferSemantics() && 321 "expected linalg op with buffer semantics"); 322 assert(consumer.hasBufferSemantics() && 323 "expected linalg op with buffer semantics"); 324 // Make some simple structural checks that alleviate the need for more 325 // complex analyses. 326 if (!isStructurallyFusableProducer(producer, consumedView, consumer)) { 327 LLVM_DEBUG(llvm::dbgs() << "\n***Not static last write due to structure:\t" 328 << *producer.getOperation()); 329 return false; 330 } 331 // Check for any interleaved write to consumedView. 332 if (!graph.findCoveringWrites(producer, consumer, consumedView).empty()) { 333 LLVM_DEBUG(llvm::dbgs() << "\n***Not fusable due to interleaved write:\t" 334 << *producer.getOperation()); 335 return false; 336 } 337 return true; 338 } 339 340 bool mlir::linalg::isFusableInto(const LinalgDependenceGraph &graph, 341 LinalgOp consumer, Value consumedView, 342 LinalgOp producer) { 343 assert(producer.hasBufferSemantics() && 344 "expected linalg op with buffer semantics"); 345 assert(consumer.hasBufferSemantics() && 346 "expected linalg op with buffer semantics"); 347 if (!isProducerLastWriteOfView(graph, consumer, consumedView, producer)) 348 return false; 349 // Check for any fusion-preventing dependence to any shape read/written that 350 // would violate dependences. 351 if (!graph.findCoveringDependences(producer, consumer).empty()) { 352 LLVM_DEBUG(llvm::dbgs() 353 << "\n***Not fusable due to an interleaved dependence:\t" 354 << *producer.getOperation()); 355 return false; 356 } 357 if (auto convOp = dyn_cast<linalg::ConvOp>(producer.getOperation())) { 358 // TODO: add a level of indirection to linalg.generic. 359 if (convOp.padding()) 360 return false; 361 } 362 if (auto convOp = dyn_cast<linalg::ConvOp>(consumer.getOperation())) { 363 // TODO: add a level of indirection to linalg.generic. 364 if (convOp.padding()) 365 return false; 366 } 367 return true; 368 } 369 370 /// For `consumer` with buffer semantics, find the Linalg operation on buffers 371 /// that is the last writer of `consumerOpOperand`. For now the fusable 372 /// dependence is returned as an instance of the `dependenceGraph`. 373 static Optional<LinalgDependenceGraph::LinalgDependenceGraphElem> 374 findFusableProducer(OpOperand &consumerOpOperand, 375 const LinalgDependenceGraph &dependenceGraph) { 376 LLVM_DEBUG(llvm::dbgs() << "findFusableProducer for: " 377 << consumerOpOperand.get() << " @" 378 << consumerOpOperand.getOperandNumber() << " in " 379 << *consumerOpOperand.getOwner() << "\n"); 380 LinalgOp consumerOp = dyn_cast<LinalgOp>(consumerOpOperand.getOwner()); 381 if (!consumerOp) 382 return {}; 383 384 // Only consider RAW and WAW atm. 385 for (auto depType : { 386 LinalgDependenceGraph::DependenceType::RAW, 387 LinalgDependenceGraph::DependenceType::WAW, 388 }) { 389 LLVM_DEBUG(llvm::dbgs() 390 << "Dependencies into: " << *consumerOp.getOperation() << "\n"); 391 for (auto dependence : llvm::make_filter_range( 392 dependenceGraph.getDependencesInto(consumerOp, depType), 393 [&](LinalgDependenceGraph::LinalgDependenceGraphElem elem) { 394 LLVM_DEBUG(llvm::dbgs() << "Inspect dependence btw: " 395 << elem.getIndexingValue() << " and " 396 << elem.getDependentValue() << "\n"); 397 Value v = elem.getIndexingValue(); 398 Optional<unsigned> operandNum = 399 elem.getIndexingOpViewOperandNum(); 400 return isa<LinalgOp>(elem.getDependentOp()) && 401 v == consumerOpOperand.get() && operandNum && 402 operandNum.getValue() == 403 consumerOpOperand.getOperandNumber(); 404 })) { 405 // Consumer consumes this view, `isStructurallyFusableProducer` also 406 // checks whether it is a strict subview of the producer view. 407 auto producer = cast<LinalgOp>(dependence.getDependentOp()); 408 LLVM_DEBUG(llvm::dbgs() 409 << "\n" 410 << LinalgDependenceGraph::getDependenceTypeStr(depType) 411 << "producer: " << *dependence.getDependentOp() 412 << " view: " << dependence.getDependentValue() << "\n"); 413 414 // If the producer and consumer have tensor semantics, the only dependence 415 // between them is through a RAW dependence and they are fusable by 416 // construction. For buffer semantics need additional checks. 417 if (producer.hasBufferSemantics() && consumerOp.hasBufferSemantics() && 418 isFusableInto(dependenceGraph, consumerOp, consumerOpOperand.get(), 419 producer)) 420 return dependence; 421 if (producer.hasTensorSemantics() && consumerOp.hasTensorSemantics()) { 422 assert(dependence.dependenceType == 423 LinalgDependenceGraph::DependenceType::RAW); 424 return dependence; 425 } 426 } 427 } 428 return {}; 429 } 430 431 Optional<FusionInfo> 432 mlir::linalg::fuseProducerOfBuffer(OpBuilder &b, OpOperand &consumerOpOperand, 433 const LinalgDependenceGraph &graph) { 434 Optional<LinalgDependenceGraph::LinalgDependenceGraphElem> fusableDependence = 435 findFusableProducer(consumerOpOperand, graph); 436 if (!fusableDependence) 437 return llvm::None; 438 439 // Canonicalize indexed generic ops before fusion. 440 if (isa<IndexedGenericOp>(fusableDependence->getDependentOp())) 441 return llvm::None; 442 443 LinalgOp producerOp = dyn_cast<LinalgOp>(fusableDependence->getDependentOp()); 444 if (!producerOp) 445 return llvm::None; 446 447 // If producer is already in the same block as consumer, we are done. 448 if (consumerOpOperand.get().getParentBlock() == 449 fusableDependence->getDependentValue().getParentBlock()) 450 return llvm::None; 451 452 Optional<AffineMap> producerMap = 453 fusableDependence->getDependentOpViewIndexingMap(); 454 if (!producerMap) 455 return llvm::None; 456 457 // Must be a subview or a slice to guarantee there are loops we can fuse 458 // into. 459 auto subView = consumerOpOperand.get().getDefiningOp<memref::SubViewOp>(); 460 if (!subView) { 461 LLVM_DEBUG(llvm::dbgs() << "\nNot fusable (not a subview)"); 462 return llvm::None; 463 } 464 465 // Fuse `producer` just before `consumer`. 466 OpBuilder::InsertionGuard g(b); 467 b.setInsertionPoint(consumerOpOperand.getOwner()); 468 LLVM_DEBUG(llvm::dbgs() << "Fuse into consumer: " 469 << *consumerOpOperand.getOwner() << "\n"); 470 471 auto fusedProducer = fuse(b, producerOp, *producerMap, consumerOpOperand); 472 return FusionInfo{producerOp, fusedProducer}; 473 } 474 475 /// Walk back use-def chain through scf::For yields. 476 /// Sets `producer` and `outputIndex` if it finds a producer LinalgOp 477 478 // TODO(ravishankarm, ntv): This can be moved into the dependence graphs 479 // dependence tracking since the dependence tracking is similar to what is done 480 // w.r.t to buffers. 481 static void getProducerOfTensor(Value tensor, OpResult &opResult) { 482 if (!tensor.getType().isa<RankedTensorType>()) 483 return; 484 485 while (true) { 486 LLVM_DEBUG(llvm::dbgs() << "\ngetProducerOfTensor: " << tensor); 487 if (auto linalgOp = tensor.getDefiningOp<LinalgOp>()) { 488 opResult = tensor.cast<OpResult>(); 489 return; 490 } 491 if (auto subTensorOp = tensor.getDefiningOp<SubTensorOp>()) { 492 tensor = subTensorOp.source(); 493 continue; 494 } 495 if (auto blockArg = tensor.dyn_cast<BlockArgument>()) { 496 if (auto forOp = blockArg.getDefiningOp<scf::ForOp>()) { 497 tensor = *(forOp.getIterOperands().begin() + blockArg.getArgNumber()); 498 continue; 499 } 500 } 501 return; 502 } 503 } 504 505 Optional<FusionInfo> 506 mlir::linalg::fuseProducerOfTensor(OpBuilder &b, OpOperand &consumerOpOperand) { 507 Value inputTensor = consumerOpOperand.get(); 508 OpResult producerOpResult; 509 getProducerOfTensor(inputTensor, producerOpResult); 510 if (!producerOpResult) { 511 LLVM_DEBUG(llvm::dbgs() << "\nUnable to find producer"); 512 return {}; 513 } 514 return fuseProducerOfTensor(b, producerOpResult, consumerOpOperand); 515 } 516 517 Optional<FusionInfo> 518 mlir::linalg::fuseProducerOfTensor(OpBuilder &b, OpResult producerOpResult, 519 OpOperand &consumerOpOperand) { 520 // Canonicalize indexed generic ops before fusion. 521 if (isa<IndexedGenericOp>(producerOpResult.getOwner())) 522 return llvm::None; 523 524 auto producerOp = dyn_cast<LinalgOp>(producerOpResult.getOwner()); 525 if (!producerOp) 526 return llvm::None; 527 528 LinalgOp consumerOp = dyn_cast<LinalgOp>(consumerOpOperand.getOwner()); 529 if (!consumerOp) 530 return llvm::None; 531 532 Value inputTensor = consumerOpOperand.get(); 533 534 // Must be a subtensor to guarantee there are loops we can fuse into. 535 auto subTensor = inputTensor.getDefiningOp<SubTensorOp>(); 536 if (!subTensor) { 537 LLVM_DEBUG(llvm::dbgs() 538 << "\nNot fusable, not a subtensor: " << inputTensor); 539 return {}; 540 } 541 542 // If producer is already in the same block as consumer, we are done. 543 if (consumerOpOperand.get().getParentBlock() == 544 producerOpResult.getParentBlock()) 545 return {}; 546 547 // Insert fused `producer` just before `consumer`. 548 OpBuilder::InsertionGuard g(b); 549 b.setInsertionPoint(consumerOp); 550 LLVM_DEBUG(llvm::dbgs() << "Fuse into consumer: " << *consumerOp << "\n"); 551 LinalgOp fusedProducer = 552 fuse(b, producerOp, 553 producerOp.getOutputIndexingMap(producerOpResult.getResultNumber()), 554 consumerOpOperand); 555 556 // Replace use. 557 // Canonicalizations are not guaranteed to have happened before constructing 558 // `fusedProducer`. In the tensor case this can result in temporary type 559 // mismatches. Insert a `tensor.cast` op to propagate the transformation 560 // invariant that types are compatible. 561 Value def = fusedProducer->getResult(producerOpResult.getResultNumber()); 562 Type consumerType = consumerOpOperand.get().getType(); 563 if (consumerType != def.getType()) 564 def = b.create<tensor::CastOp>(fusedProducer.getLoc(), consumerType, def); 565 consumerOpOperand.set(def); 566 return FusionInfo{cast<LinalgOp>(producerOpResult.getOwner()), fusedProducer}; 567 } 568 569 /// Prune all dimensions that are of reduction iterator type from `map`. 570 static AffineMap pruneReductionDimsFromMap(ArrayRef<Attribute> iteratorTypes, 571 AffineMap map) { 572 llvm::SmallDenseSet<unsigned> projectedDims; 573 for (auto attr : llvm::enumerate(iteratorTypes)) { 574 if (!isParallelIterator(attr.value())) 575 projectedDims.insert(attr.index()); 576 } 577 return getProjectedMap(map, projectedDims); 578 } 579 580 /// Returns the mapping from iterations in the consumer that write to the same 581 /// location as the iterations in the producer. To do so use 582 /// - indexing map of the fused view in the consumer : consumerIndexMap 583 /// - indexing map of the fused view in the producer : producerIndexMap 584 /// consumerLoopToProducerLoop = 585 /// inverse(producerIndexMap).compose(consumerIndexMap) 586 static Optional<AffineMap> getConsumerLoopToProducerLoopMap( 587 LinalgDependenceGraph::LinalgDependenceGraphElem dependence) { 588 auto producer = dyn_cast<LinalgOp>(dependence.getDependentOp()); 589 if (!producer) 590 return None; 591 592 Optional<AffineMap> producerIndexingMap = 593 dependence.getDependentOpViewIndexingMap(); 594 Optional<AffineMap> consumerIndexingMap = 595 dependence.getIndexingOpViewIndexingMap(); 596 if (!producerIndexingMap || !consumerIndexingMap) 597 return None; 598 599 AffineMap prunedProducerIndexingMap = pruneReductionDimsFromMap( 600 producer.iterator_types().getValue(), *producerIndexingMap); 601 if (!prunedProducerIndexingMap.isPermutation()) 602 return None; 603 604 if (consumerIndexingMap->getNumResults() != 605 prunedProducerIndexingMap.getNumResults()) 606 return None; 607 608 LLVM_DEBUG({ 609 llvm::dbgs() << "\t producerMap : "; 610 producerIndexingMap->print(llvm::dbgs()); 611 llvm::dbgs() << " pruned : "; 612 prunedProducerIndexingMap.print(llvm::dbgs()); 613 llvm::dbgs() << "\n"; 614 llvm::dbgs() << "\t consumerMap : "; 615 consumerIndexingMap->print(llvm::dbgs()); 616 llvm::dbgs() << "\n"; 617 }); 618 619 AffineMap invProducerIndexMap = inversePermutation(prunedProducerIndexingMap); 620 if (!invProducerIndexMap) 621 return None; 622 623 return invProducerIndexMap.compose(*consumerIndexingMap); 624 } 625 626 /// Given a projected permutation `map`, returns true if the map changes the 627 /// order in which the fused loop dimension appear. 628 static bool doesTransposeAccess(AffineMap map, 629 const std::set<unsigned> &fusableLoops) { 630 Optional<unsigned> lastFusableLoop; 631 for (unsigned pos : llvm::map_range(map.getResults(), [](AffineExpr expr) { 632 return expr.cast<AffineDimExpr>().getPosition(); 633 })) { 634 if (!fusableLoops.count(pos)) 635 continue; 636 if (!lastFusableLoop) { 637 lastFusableLoop = pos; 638 continue; 639 } 640 if (pos <= lastFusableLoop.getValue()) 641 return true; 642 lastFusableLoop = pos; 643 } 644 return false; 645 } 646 647 /// Returns the positions of the loop in `op` that can be tiled based on the 648 /// operations that are to be fused with it. For example, in a 649 /// 650 /// linalg.matmul ins(%a, %b : ...) outs(%c : ...) 651 /// 652 /// if the producer of %a needs to be fused with this op, only the `i` loop of 653 /// the matmul can be tiled while fusing. If producer of %a, and %b are to be 654 /// fused, then no loops can be tiled while fusing. The conditions used are: 655 /// 1. Only parallel loops can be used for tile + fuse. Find the number of 656 /// common outer parallel loops between the op and its producers being fused. 657 /// 2. Of the parallel loops only some can be fused. Only those loops can be 658 /// fused such where the fusable loops iteration space only touches one tile 659 /// of the fused operation. This is because the producer (which is writing 660 /// the fused subview) has update semantics. 661 /// 662 /// Since an inverse computation is needed, we need to consider the projection 663 /// of the producerIndexMap w.r.t the parallel loops. The actual fusable loops 664 /// are the dimensions of the consumerLoopToProducerLoop map that correspond to 665 /// parallel loops and appear in the result of the map 666 /// 667 /// Example 1: 668 /// linalg.fill(%c, %cst) 669 /// linalg.matmul ins(%a, %b) outs(%c) 670 /// Number of parallel loops : 2 671 /// producerIndexMap = affine_map<(i, j) ->(i , j)> 672 /// consumerIndexMap = affine_map<(i, j, k) -> (i, j)> 673 /// consumerLoopToProducerLoop = affine_map<(i, j, k) -> (i, j)> 674 /// Fused dimensions : i, j 675 /// 676 /// Example 2: 677 /// linalg.matmul ins(%a, %b) outs(%c) 678 /// linalg.generic {indexing_maps = [affine_map<(i, j) -> (j, i)>, ... 679 /// iterator_types = ["parallel", "parallel"]} 680 /// ins(%c) ... 681 /// 682 /// Number of parallel loops = 2: 683 /// producerIndexMap (projected to parallel loops) = 684 /// affine_map<(i, j) -> (i, j)> 685 /// consumerLoopToProducerLoop2 = affine_map<(i, j) -> (j, i)> 686 /// Fused dimensions : i, j 687 /// 688 /// Example 3: 689 /// linalg.copy(%s, %b) 690 /// linalg.matmul ins(%a, %b) outs(%c) 691 /// 692 /// Number of parallel loops = 2 693 /// produceIndexMap : affine_map<(i, j) -> (i, j)> 694 /// consumerLoopToProduceLoops = affine_map<(i, j, k) -> (k, j)> 695 /// submap with only parallel loops = affine_map<(i, j) -> (j)> 696 /// Fused dimensions : j 697 static std::set<unsigned> 698 collectFusableLoops(ArrayRef<LinalgOp> ops, 699 const FusableOpDependencesTy &fusableDependences) { 700 assert(!ops.empty()); 701 auto getNumOuterParallelLoops = [](LinalgOp linalgOp) { 702 return linalgOp.iterator_types() 703 .getValue() 704 .take_while([](Attribute attr) -> bool { 705 return attr.cast<StringAttr>().getValue() == 706 getParallelIteratorTypeName(); 707 }) 708 .size(); 709 }; 710 711 size_t numOuterParallelLoops = getNumOuterParallelLoops(ops.back()); 712 for (auto op : ops.drop_back()) { 713 numOuterParallelLoops = 714 std::min(numOuterParallelLoops, getNumOuterParallelLoops(op)); 715 } 716 717 std::set<unsigned> fusableLoops; 718 auto range = llvm::seq<unsigned>(0, numOuterParallelLoops); 719 fusableLoops.insert(range.begin(), range.end()); 720 721 for (auto op : reverse(ops)) { 722 for (auto dependence : fusableDependences.lookup(op)) { 723 LLVM_DEBUG({ 724 llvm::dbgs() << "\t fusable :"; 725 for (unsigned i : fusableLoops) 726 llvm::dbgs() << " " << i; 727 llvm::dbgs() << "\n"; 728 }); 729 730 Optional<AffineMap> consumerLoopToProducerLoop = 731 getConsumerLoopToProducerLoopMap(dependence); 732 if (!consumerLoopToProducerLoop) { 733 op.emitRemark("failed to get map from consumer loop to producer loop"); 734 return {}; 735 } 736 // todo: This condition is only an implementation limitation. When fusing 737 // the operation, if the accesses in the producer/consumer are transposes 738 // of each other, the loop bounds for the tiled producer can be 739 // manipulated accordingly. This requires some additional bookkeeping in 740 // the implementation of tile+fuse that is deferred to later. 741 if (doesTransposeAccess(*consumerLoopToProducerLoop, fusableLoops)) { 742 op.emitRemark("unhandled fusion when fusion requires permutation"); 743 return {}; 744 } 745 746 std::set<unsigned> candidates; 747 for (AffineExpr expr : consumerLoopToProducerLoop->getResults()) { 748 unsigned position = expr.cast<AffineDimExpr>().getPosition(); 749 if (fusableLoops.count(position)) 750 candidates.insert(position); 751 } 752 LLVM_DEBUG({ 753 llvm::dbgs() << "\t candidates :"; 754 for (unsigned i : candidates) 755 llvm::dbgs() << " " << i; 756 llvm::dbgs() << "\n"; 757 }); 758 if (candidates.empty()) 759 return {}; 760 std::swap(candidates, fusableLoops); 761 } 762 } 763 764 return fusableLoops; 765 } 766 767 /// Find all dependences that are fusable. 768 FusableOpDependencesTy mlir::linalg::findAllFusableDependences( 769 ArrayRef<LinalgOp> ops, const LinalgDependenceGraph &dependenceGraph) { 770 FusableOpDependencesTy fusableDependences; 771 DenseMap<Operation *, SmallVector<AffineMap, 1>> fusedProducerIndexingMap; 772 for (LinalgOp op : reverse(ops)) { 773 for (OpOperand &opOperand : op.getShapedOpOperands()) { 774 Optional<LinalgDependenceGraph::LinalgDependenceGraphElem> 775 fusableDependence = findFusableProducer(opOperand, dependenceGraph); 776 if (!fusableDependence) 777 continue; 778 // Canonicalize indexed generic ops before fusion. 779 if (isa<IndexedGenericOp>(fusableDependence->getDependentOp())) 780 continue; 781 LinalgOp producerOp = 782 dyn_cast<LinalgOp>(fusableDependence->getDependentOp()); 783 if (!producerOp) 784 continue; 785 // Do not fuse dependences that are to operations not in the same basic 786 // block. This avoid moving fused operations across loops that might 787 // themselves carry dependency making the fusion illegal. 788 if (producerOp->getBlock() != op->getBlock()) 789 continue; 790 791 // Make sure that the indexing map of the view used for fusion in the 792 // producer is a projected permutation. 793 Optional<AffineMap> producerMap = 794 fusableDependence->getDependentOpViewIndexingMap(); 795 Optional<AffineMap> consumerMap = 796 fusableDependence->getIndexingOpViewIndexingMap(); 797 assert( 798 consumerMap && 799 "unable to find indexing map of operand/result of indexing OpView"); 800 fusedProducerIndexingMap[producerOp.getOperation()].push_back( 801 *consumerMap); 802 if (!producerMap || !producerMap->isProjectedPermutation() || 803 !consumerMap->isProjectedPermutation()) 804 continue; 805 806 fusableDependences[producerOp.getOperation()].push_back( 807 *fusableDependence); 808 } 809 } 810 // TODO: Currently fusion would not be legal if the fusable dependence is to 811 // the same producer but different indexing map in the consumer. Fix this, but 812 // in the meanwhile disallow such a fusion. 813 for (auto useIndexingMapsList : fusedProducerIndexingMap) { 814 AffineMap map1 = useIndexingMapsList.second.front(); 815 for (AffineMap map2 : 816 ArrayRef<AffineMap>(useIndexingMapsList.second).drop_front()) { 817 if (map1 != map2) { 818 fusableDependences.erase(useIndexingMapsList.first); 819 break; 820 } 821 } 822 } 823 return fusableDependences; 824 } 825 826 /// Tile the fused loops in the root operation, by setting the tile sizes for 827 /// all other loops to zero (those will be tiled later). 828 static Optional<TiledLinalgOp> 829 tileRootOperation(OpBuilder &b, LinalgOp op, ArrayRef<Value> tileSizeVector, 830 const LinalgTilingOptions &options, 831 const std::set<unsigned> &fusedLoops) { 832 SmallVector<Value, 4> tileSizes(tileSizeVector.begin(), tileSizeVector.end()); 833 auto zero = b.create<ConstantIndexOp>(op.getLoc(), 0); 834 for (unsigned i = 0, e = tileSizes.size(); i != e; ++i) 835 if (!fusedLoops.count(i)) 836 tileSizes[i] = zero; 837 LinalgTilingOptions tileFusedLoopsOptions = options; 838 tileFusedLoopsOptions.setTileSizes(tileSizes); 839 return tileLinalgOp(b, op, tileFusedLoopsOptions); 840 } 841 842 /// Fuse the operations in `fusionCandidates` with `tiledOp`. Latter is expected 843 /// to be a tiled operation such that it is valid to fuse all operations in 844 /// `fusionCandidates`, i.e. move the operation within the inter-tile loops of 845 /// `tiledOp`. 846 static SmallVector<LinalgOp, 1> 847 fuseOperations(OpBuilder &b, LinalgOp rootOp, TiledLinalgOp tiledLinalgOp, 848 ArrayRef<LinalgOp> fusionCandidates, 849 const FusableOpDependencesTy &fusableDependences, 850 const std::set<unsigned> &fusedLoops) { 851 LinalgOp tiledOp = tiledLinalgOp.op; 852 OpBuilder::InsertionGuard guard(b); 853 b.setInsertionPoint(tiledOp); 854 855 DenseMap<unsigned, Range> fusedLoopsAndRanges; 856 for (unsigned loop : fusedLoops) { 857 ShapeDimension shapeDim = getShapeDefiningLoopRange(tiledOp, loop, true); 858 fusedLoopsAndRanges[loop] = getRangeFromOperandShape( 859 b, tiledOp.getLoc(), shapeDim.shape, shapeDim.dimension); 860 } 861 862 SmallVector<LinalgOp, 1> fusedOps(fusionCandidates.size()); 863 DenseMap<Operation *, LinalgOp> origOpToFusedOp; 864 origOpToFusedOp[rootOp.getOperation()] = tiledOp; 865 for (auto candidate : enumerate(llvm::reverse(fusionCandidates))) { 866 LinalgOp origOp = candidate.value(); 867 LinalgOp fusedOp = fuse(b, origOp, fusedLoopsAndRanges); 868 origOpToFusedOp[origOp.getOperation()] = fusedOp; 869 fusedOps[fusionCandidates.size() - candidate.index() - 1] = fusedOp; 870 871 // Prepare the builder for the next insertion point. 872 auto guard = llvm::make_scope_exit([&]() { b.setInsertionPoint(fusedOp); }); 873 if (!origOp.hasTensorSemantics()) 874 continue; 875 876 // If the producer consumer operations are linalg operations on tensors, the 877 // dependence is due to value produced (as a return tensor) by the producer 878 // and used in the consumer. The returned value of the fused op needs to be 879 // made the operand of the tiled/fused consumer operation. By construction 880 // the value returned by the producer is the value used by the consumer. 881 for (auto &dependence : fusableDependences.lookup(origOp.getOperation())) { 882 if (dependence.dependenceType != 883 LinalgDependenceGraph::DependenceType::RAW) 884 continue; 885 886 unsigned resultIndex = 887 dependence.getDependentOpViewResultNum().getValue(); 888 LinalgOp consumer = origOpToFusedOp.lookup(dependence.getIndexingOp()); 889 if (!consumer) 890 continue; 891 892 Value replacementValue = fusedOp.getOperation()->getResult(resultIndex); 893 consumer.getOperation()->setOperand( 894 dependence.getIndexingOpViewOperandNum().getValue(), 895 replacementValue); 896 } 897 898 // At this point, all Linalg uses of the tensors produced by `origOp` have 899 // been replaced. However, there may still be "output tensor"-like uses 900 // coming from WAW dependencies. 901 // All these uses are iter_args of the outermost loop (TODO: add a check). 902 // Such iter_args uses serve 2 purposes: 903 // 1. give a shape to the output 904 // 2. encode destructive updates that may be inplaceable by bufferization. 905 // To keep the second type of information while letting the unfused op die 906 // unused, we need to forward the producer output operand. 907 if (auto forOp = dyn_cast<scf::ForOp>(tiledLinalgOp.loops.front())) { 908 for (auto &operand : forOp.getIterOpOperands()) 909 if (auto opResult = operand.get().dyn_cast<OpResult>()) 910 if (opResult.getOwner() == origOp) 911 operand.set(origOp.getOutputTensors()[opResult.getResultNumber()]); 912 } 913 } 914 return fusedOps; 915 } 916 917 static Optional<TiledAndFusedLinalgOps> 918 tileAndFuseLinalgOpsImpl(OpBuilder &b, ArrayRef<LinalgOp> ops, 919 const LinalgDependenceGraph &dependenceGraph, 920 const LinalgTilingOptions &tilingOptions) { 921 if (ops.size() < 2) 922 return llvm::None; 923 LinalgOp rootOp = ops.back(); 924 if (!llvm::all_of( 925 ops, 926 [](LinalgOp linalgOp) { return linalgOp.hasBufferSemantics(); }) && 927 !llvm::all_of(ops, [](LinalgOp linalgOp) { 928 return linalgOp.hasTensorSemantics(); 929 })) { 930 rootOp.emitError( 931 "unable to fuse operations that have tensor semantics with operations " 932 "that have buffer semantics and viceversa."); 933 return llvm::None; 934 } 935 // TODO: Support interchange with tile + fuse. This might actually help do 936 // better fusion. 937 if (!tilingOptions.interchangeVector.empty()) { 938 rootOp.emitRemark("unable to handle tile and fuse with interchange"); 939 return llvm::None; 940 } 941 942 OpBuilder::InsertionGuard guard(b); 943 b.setInsertionPoint(rootOp); 944 945 // Find all the producers. 946 LLVM_DEBUG(llvm::dbgs() << "findAllFusableDependences\n"); 947 FusableOpDependencesTy fusableDependences = 948 findAllFusableDependences(ops, dependenceGraph); 949 if (fusableDependences.empty()) { 950 LLVM_DEBUG(llvm::dbgs() << "no fusable dependencies found\n"); 951 return llvm::None; 952 } 953 954 TiledAndFusedLinalgOps ret; 955 // Find the loops that can be tiled and fused. 956 LLVM_DEBUG(llvm::dbgs() << "collectFusableLoops\n"); 957 ret.fusedLoopDims = collectFusableLoops(ops, fusableDependences); 958 959 // If there are no fusable dependences or there are no tile+fusable loops, 960 // just return. 961 if (ret.fusedLoopDims.empty()) { 962 LLVM_DEBUG(llvm::dbgs() << "no fusable loops found\n"); 963 return llvm::None; 964 } 965 966 // Tile the fused loops in the last operation in the list. 967 SmallVector<Value, 4> tileSizeVector = 968 tilingOptions.tileSizeComputationFunction(b, rootOp); 969 Optional<TiledLinalgOp> tiledRootOp = tileRootOperation( 970 b, rootOp, tileSizeVector, tilingOptions, ret.fusedLoopDims); 971 if (!tiledRootOp) { 972 rootOp.emitRemark("failed to tile the fused loops"); 973 return llvm::None; 974 } 975 ret.op = tiledRootOp->op; 976 ret.fusedLoops.assign(tiledRootOp->loops.begin(), tiledRootOp->loops.end()); 977 978 // Fuse the other operations into the fused inter-tile loops produced above. 979 ret.fusedProducers = fuseOperations(b, rootOp, *tiledRootOp, ops.drop_back(), 980 fusableDependences, ret.fusedLoopDims); 981 982 return ret; 983 } 984 985 Optional<TiledAndFusedLinalgOps> 986 mlir::linalg::tileAndFuseLinalgOps(OpBuilder &b, ArrayRef<LinalgOp> ops, 987 const LinalgDependenceGraph &dependenceGraph, 988 const LinalgTilingOptions &tilingOptions) { 989 switch (tilingOptions.loopType) { 990 case LinalgTilingLoopType::Loops: 991 case LinalgTilingLoopType::ParallelLoops: 992 case LinalgTilingLoopType::TiledLoops: 993 return tileAndFuseLinalgOpsImpl(b, ops, dependenceGraph, tilingOptions); 994 default:; 995 } 996 return llvm::None; 997 } 998