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