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