1 //===- Fusion.cpp - Implementation of linalg Fusion -----------------------===// 2 // 3 // Copyright 2019 The MLIR Authors. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 // ============================================================================= 17 // 18 // This file implements the linalg dialect Fusion pass. 19 // 20 //===----------------------------------------------------------------------===// 21 22 #include "mlir/EDSC/Helpers.h" 23 #include "mlir/IR/AffineExpr.h" 24 #include "mlir/IR/AffineMap.h" 25 #include "mlir/IR/OpImplementation.h" 26 #include "mlir/Dialect/Linalg/Analysis/DependenceAnalysis.h" 27 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 28 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 29 #include "mlir/Dialect/Linalg/Passes.h" 30 #include "mlir/Dialect/Linalg/Utils/Intrinsics.h" 31 #include "mlir/Dialect/Linalg/Utils/Utils.h" 32 #include "mlir/Pass/Pass.h" 33 #include "mlir/Support/LLVM.h" 34 #include "mlir/Support/STLExtras.h" 35 #include "mlir/Transforms/FoldUtils.h" 36 37 #include "llvm/ADT/SetVector.h" 38 #include "llvm/Support/CommandLine.h" 39 #include "llvm/Support/Debug.h" 40 41 #define DEBUG_TYPE "linalg-fusion" 42 43 using namespace mlir; 44 using namespace mlir::edsc; 45 using namespace mlir::edsc::intrinsics; 46 using namespace mlir::linalg; 47 using namespace mlir::linalg::intrinsics; 48 49 using llvm::dbgs; 50 51 /// Implements a simple high-level fusion pass of linalg library operations. 52 /// 53 /// In each block, linalg ops are processed in reverse textual order. 54 /// Given a linalg op, fusion occurs by: 55 /// 1. tiling the op by a given multi-dimensional tile size; 56 /// 2. inspecting the linalg ops that write into the views read by the op in 57 /// step 1. This uses the SSA value of the views to determine producer- 58 /// consumer dependences: only identical SSA views are considered for 59 /// fusion at this point; 60 /// 3. greedily fuse the producing linalg ops into the consuming loop tiles; 61 /// 4. inspect the fused ops and determine whether they have other remaining 62 /// LinalgOp uses. If not, then erase the original producing linalg op. 63 /// 64 /// More advanced use cases, analyses as well as profitability heuristics are 65 /// left for future work. 66 67 static llvm::cl::OptionCategory clOptionsCategory(DEBUG_TYPE " options"); 68 static llvm::cl::list<unsigned> clTileSizes( 69 "linalg-fusion-tile-sizes", 70 llvm::cl::desc( 71 "Tile sizes by which to tile linalg operations during linalg fusion"), 72 llvm::cl::ZeroOrMore, llvm::cl::MiscFlags::CommaSeparated, 73 llvm::cl::cat(clOptionsCategory)); 74 75 // Return a cloned version of `op` that operates on `loopRanges`, assumed to be 76 // a subset of the original loop ranges of `op`. 77 // This is achieved by applying the `loopToOperandRangesMaps` permutation maps 78 // to the `loopRanges` in order to obtain view ranges. 79 static LinalgOp cloneWithLoopRanges(OpBuilder &b, Location loc, LinalgOp op, 80 ArrayRef<SubViewOp::Range> loopRanges, 81 OperationFolder &state) { 82 ScopedContext scope(b, loc); 83 84 auto maps = loopToOperandRangesMaps(op); 85 SmallVector<Value *, 8> clonedViews; 86 clonedViews.reserve(op.getNumInputsAndOutputs()); 87 // Iterate over the inputs and outputs in order. 88 // Extract the subranges from the linearized ranges. 89 SmallVector<Value *, 8> ios(op.getInputsAndOutputs()); 90 for (auto en : llvm::enumerate(ios)) { 91 unsigned idx = en.index(); 92 auto map = maps[idx]; 93 LLVM_DEBUG(dbgs() << "map: " << map << "\n"); 94 Value *view = en.value(); 95 SmallVector<SubViewOp::Range, 8> viewRanges(map.getNumResults()); 96 for (auto en2 : llvm::enumerate(map.getResults())) { 97 unsigned d = en2.index(); 98 // loopToOperandRangesMaps are permutations-only. 99 unsigned loopPos = en2.value().cast<AffineDimExpr>().getPosition(); 100 viewRanges[d] = loopRanges[loopPos]; 101 LLVM_DEBUG(dbgs() << "\ni,j: " << en.index() << ", " << en2.index() 102 << "\t" 103 << "loopPos: " << loopPos << "\t" << viewRanges[d]); 104 } 105 // TODO(ntv) opportunities for folding/CSE here rather than build new IR. 106 clonedViews.push_back(b.create<SubViewOp>(loc, view, viewRanges)); 107 } 108 auto operands = getAssumedNonViewOperands(op); 109 clonedViews.append(operands.begin(), operands.end()); 110 return op.create(b, loc, clonedViews, op.getAttrs()); 111 } 112 113 struct ViewDimension { 114 Value *view; 115 unsigned dimension; 116 }; 117 118 static ViewDimension getViewDefiningLoopRange(LinalgOp op, unsigned loopDepth) { 119 auto maps = loopToOperandRangesMaps(op); 120 SmallVector<Value *, 8> clonedViews; 121 clonedViews.reserve(op.getNumInputsAndOutputs()); 122 // Iterate over the inputs and outputs in order. 123 // Extract the subranges from the linearized ranges. 124 SmallVector<Value *, 8> ios(op.getInputsAndOutputs()); 125 for (auto en : llvm::enumerate(ios)) { 126 unsigned idx = en.index(); 127 auto map = maps[idx]; 128 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange I/O idx: " << idx << "\n"); 129 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange map: " << map << "\n"); 130 Value *view = en.value(); 131 SmallVector<Value *, 8> viewRanges(map.getNumResults(), nullptr); 132 for (auto en2 : llvm::enumerate(map.getResults())) { 133 if (loopDepth == en2.value().cast<AffineDimExpr>().getPosition()) { 134 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange loopDepth: " << loopDepth 135 << "\n"); 136 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange view: " << *view 137 << "\n"); 138 return ViewDimension{view, static_cast<unsigned>(en2.index())}; 139 } 140 } 141 } 142 llvm_unreachable("Expect to be able to extract a view defining loop range"); 143 } 144 145 static Optional<LinalgOp> fuse(Value *producedView, LinalgOp producer, 146 LinalgOp consumer, LinalgOp tiledConsumer, 147 OperationFolder &state) { 148 auto maybeConsumerIdx = consumer.getIndexOfInput(producedView); 149 if (!maybeConsumerIdx.hasValue()) 150 return llvm::None; 151 unsigned consumerIdx = maybeConsumerIdx.getValue(); 152 153 auto maybeProducerIdx = producer.getIndexOfOutput(producedView); 154 if (!maybeProducerIdx.hasValue()) 155 return llvm::None; 156 unsigned producerIdx = maybeProducerIdx.getValue(); 157 158 // If the view is the same between consumer and tiledConsumer, this means we 159 // don't have loops and the producer cannot be fused at this level. 160 if (consumer.getInput(consumerIdx) == tiledConsumer.getInput(consumerIdx)) 161 return llvm::None; 162 163 auto tiledConsumerSubView = dyn_cast_or_null<SubViewOp>( 164 tiledConsumer.getInput(consumerIdx)->getDefiningOp()); 165 166 // If we don't have a slice, this also means we don't have loops and the 167 // producer cannot be fused at this level. 168 if (!tiledConsumerSubView) 169 return llvm::None; 170 171 // loopToOperandRangesMaps are permutations-only by construction: 172 // we can always identify a data dimension with a (at least one) loop 173 // dimension. 174 AffineMap producerMap = 175 loopToOperandRangesMaps(producer)[producer.getNumInputs() + producerIdx]; 176 LLVM_DEBUG(dbgs() << "Consumer Idx: " << consumerIdx << ", consumer map: " 177 << loopToOperandRangesMaps(consumer)[consumerIdx] << "\n"); 178 LLVM_DEBUG(dbgs() << "Producer Idx: " << producerIdx 179 << ", producer map: " << producerMap << "\n"); 180 181 unsigned nPar = producer.getNumParallelLoops(); 182 unsigned nRed = producer.getNumReductionLoops(); 183 unsigned nWin = producer.getNumWindowLoops(); 184 SmallVector<SubViewOp::Range, 8> loopRanges(nPar + nRed + nWin); 185 186 // Iterate over dimensions identified by the producer map for `producerIdx`. 187 // This defines a subset of the loop ranges that we need to complete later. 188 for (auto en : llvm::enumerate(producerMap.getResults())) { 189 unsigned posInProducerLoop = en.value().cast<AffineDimExpr>().getPosition(); 190 loopRanges[posInProducerLoop] = tiledConsumerSubView.getRange(en.index()); 191 } 192 193 OpBuilder b(tiledConsumer.getOperation()); 194 auto loc = tiledConsumer.getLoc(); 195 // Iterate over all dimensions. For the dimensions not identified by the 196 // producer map for `producerIdx`, we need to explicitly compute the view that 197 // defines the loop ranges using the `producer`. 198 for (unsigned i = 0, nLoops = loopRanges.size(); i < nLoops; ++i) { 199 if (loopRanges[i].min) 200 LLVM_DEBUG(llvm::dbgs() 201 << "existing LoopRange: " << loopRanges[i] << "\n"); 202 else { 203 auto viewDim = getViewDefiningLoopRange(producer, i); 204 loopRanges[i] = SubViewOp::Range{ 205 state.create<ConstantIndexOp>(b, loc, 0), 206 linalg::intrinsics::dim(viewDim.view, viewDim.dimension), 207 state.create<ConstantIndexOp>(b, loc, 1)}; 208 LLVM_DEBUG(llvm::dbgs() << "new LoopRange: " << loopRanges[i] << "\n"); 209 } 210 } 211 212 return cloneWithLoopRanges(b, loc, producer, loopRanges, state); 213 } 214 215 // Encode structural fusion safety preconditions. 216 // Some of these will be lifted in the future with better analysis. 217 static bool isStructurallyFusableProducer(LinalgOp producer, Value *readView, 218 LinalgOp consumer) { 219 // If a producer has multiple outputs, the analysis needs to take the tiling 220 // of other outputs into account. 221 if (producer.getNumOutputs() != 1) 222 return false; 223 // Until subview analysis is available, same SSA value is required for fusion. 224 if (producer.getOutput(0) != readView) 225 return false; 226 // No control-flow divergence supported. Only straightline op fusion allowed. 227 // TODO(ntv) allow fusion when a dominance relation exists. 228 if (producer.getOperation()->getBlock() != 229 consumer.getOperation()->getBlock()) 230 return false; 231 return true; 232 } 233 234 static void fuseLinalgOps(FuncOp f, ArrayRef<int64_t> tileSizes) { 235 OperationFolder state; 236 DenseSet<Operation *> eraseSet; 237 238 LLVM_DEBUG(f.print(dbgs() << "\nBefore linalg-fusion: \n")); 239 240 // 1. Record the linalg ops so we can traverse them in reverse order. 241 SmallVector<Operation *, 8> linalgOps; 242 f.walk<LinalgOp>( 243 [&](LinalgOp op) { linalgOps.push_back(op.getOperation()); }); 244 245 // 2. Setup the dependences graph, aliases are populated lazily. 246 Aliases aliases; 247 LinalgDependenceGraph G(aliases, linalgOps); 248 249 // 2. For each original linalg op (in reverse order to allow chained 250 // fusions). 251 for (auto *op : llvm::reverse(linalgOps)) { 252 auto consumer = cast<LinalgOp>(op); 253 LLVM_DEBUG(dbgs() << "\n******\nStart processing:\t" << *op); 254 // 3. If marked for erasure, it has already been fused. Skip fusing op. 255 if (eraseSet.count(op) > 0) { 256 LLVM_DEBUG(dbgs() << "\nAlready fused and marked for erasure, skip."); 257 continue; 258 } 259 260 // 4. Apply loop tiling to enable fusion. If unsuccessful, skip fusing op. 261 auto tiledOp = tileLinalgOp(op, tileSizes, state); 262 if (!tiledOp) { 263 LLVM_DEBUG(dbgs() << "\nTile sizes did not produce loops, skip."); 264 continue; 265 } 266 267 // 5. For now, we only fuse RAW dependences. 268 SmallVector<Operation *, 8> fusedProducers; 269 SmallVector<Value *, 8> fusedViews; 270 for (auto dependence : G.getDependencesInto( 271 consumer, LinalgDependenceGraph::DependenceType::RAW)) { 272 auto producer = cast<LinalgOp>(dependence.dependentOpView.op); 273 LLVM_DEBUG(dbgs() << "\n***Consider producer:\t" 274 << *producer.getOperation() << "\n"); 275 276 // a. For now we require fusion on identical SSA values, this allows us to 277 // not worry about partial writes etc. 278 // TODO(ntv) support more elaborate fusion with non identical SSA values. 279 auto *view = dependence.indexingView; 280 if (view != dependence.dependentOpView.view) { 281 LLVM_DEBUG(dbgs() << "\nviews are different SSA values, skip."); 282 continue; 283 } 284 // b. Make some simple structural checks that alleviate the need for more 285 // complex analyses. 286 if (!isStructurallyFusableProducer(producer, view, op)) { 287 LLVM_DEBUG(dbgs() << "\n***Not fusable:\t" << *producer.getOperation()); 288 continue; 289 } 290 // c. Check for fusion-preventing write that would violate dependences. 291 // `view` is a producer write that cannot bypass any other write or read. 292 bool preventFusion = false; 293 for (auto *op : G.findCoveringDependences(producer, consumer)) 294 if (eraseSet.count(op) == 0) { 295 preventFusion = true; 296 LLVM_DEBUG(dbgs() << "\n***Found fusion preventing dep via: " << *op); 297 break; 298 } 299 if (preventFusion) 300 continue; 301 302 // 6. Try to fuse `producer` just before `tiledOp`. 303 LLVM_DEBUG(f.print(dbgs() << "\nBefore tiledOp-fusion: \n")); 304 305 auto tOp = tiledOp->op; 306 OpBuilder builder(tOp.getOperation()); 307 ScopedContext scope(builder, tOp.getLoc()); 308 LLVM_DEBUG(dbgs() << "Try fuse into tiled consumer: " << *tOp << "\n"); 309 auto maybeFusedProducer = fuse(view, producer, op, tOp, state); 310 if (!maybeFusedProducer) { 311 LLVM_DEBUG(dbgs() << "\nFusion did not do anything, skip."); 312 continue; 313 } 314 315 fusedProducers.push_back(producer.getOperation()); 316 fusedViews.push_back(view); 317 } 318 319 // 7. If no fusion occurred, or a drop the outer tiled loop which undoes 320 // everything we did. 321 if (fusedProducers.empty()) { 322 tiledOp->loops[0].erase(); 323 continue; 324 } 325 326 eraseSet.insert(op); 327 eraseSet.insert(fusedProducers.begin(), fusedProducers.end()); 328 } 329 330 for (auto *op : eraseSet) 331 op->erase(); 332 333 LLVM_DEBUG(f.print(dbgs() << "\nAfter linalg-fusion: \n")); 334 } 335 336 namespace { 337 struct LinalgFusionPass : public FunctionPass<LinalgFusionPass> { 338 LinalgFusionPass() = default; 339 LinalgFusionPass(ArrayRef<int64_t> sizes); 340 341 void runOnFunction() { fuseLinalgOps(getFunction(), tileSizes); } 342 343 SmallVector<int64_t, 8> tileSizes; 344 }; 345 } // namespace 346 347 LinalgFusionPass::LinalgFusionPass(ArrayRef<int64_t> sizes) 348 : LinalgFusionPass() { 349 if (!sizes.empty()) 350 this->tileSizes.assign(sizes.begin(), sizes.end()); 351 } 352 353 std::unique_ptr<FunctionPassBase> 354 mlir::linalg::createLinalgFusionPass(ArrayRef<int64_t> tileSizes) { 355 return std::make_unique<LinalgFusionPass>(tileSizes); 356 } 357 358 static PassRegistration<LinalgFusionPass> 359 pass("linalg-fusion", "Fuse operations in the linalg dialect", [] { 360 auto pass = std::make_unique<LinalgFusionPass>(); 361 pass->tileSizes.assign(clTileSizes.begin(), clTileSizes.end()); 362 return pass; 363 }); 364