Skip to content

[mlir][bufferization] Add "bottom-up from terminators" analysis heuristic #83964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,6 @@ struct BufferizationOptions {
DefaultMemorySpaceFn defaultMemorySpaceFn =
[](TensorType t) -> std::optional<Attribute> { return Attribute(); };

/// Seed for the analysis fuzzer. If set to `0`, the fuzzer is deactivated.
/// Should be used only with `testAnalysisOnly = true`.
unsigned analysisFuzzerSeed = 0;

/// If set to `true`, the analysis is skipped. A buffer is copied before every
/// write. This flag cannot be used together with `testAnalysisOnly = true`.
bool copyBeforeWrite = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ class OneShotAnalysisState;

/// Options for analysis-enabled bufferization.
struct OneShotBufferizationOptions : public BufferizationOptions {
enum class AnalysisHeuristic { BottomUp, TopDown };
enum class AnalysisHeuristic {
BottomUp,
TopDown,
BottomUpFromTerminators,
Fuzzer
};

OneShotBufferizationOptions() = default;

Expand All @@ -42,6 +47,11 @@ struct OneShotBufferizationOptions : public BufferizationOptions {
/// Specify the functions that should not be analyzed. copyBeforeWrite will be
/// set to true when bufferizing them.
llvm::ArrayRef<std::string> noAnalysisFuncFilter;

/// Seed for the analysis fuzzer. Used only if the heuristic is set to
/// `AnalysisHeuristic::Fuzzer`. The fuzzer should be used only with
/// `testAnalysisOnly = true`.
unsigned analysisFuzzerSeed = 0;
};

/// State for analysis-enabled bufferization. This class keeps track of alias
Expand Down
18 changes: 18 additions & 0 deletions mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,24 @@ def OneShotBufferize : Pass<"one-shot-bufferize", "ModuleOp"> {
argument is read/written and which returned values are aliasing/equivalent.
For debugging purposes, such information can be printed with
`test-analysis-only`.

The order in which ops are analyzed is important. The analysis is greedy and
ops that are analyzed earlier are more likely to bufferize in-place. The
heuristic can be set with `analysis-heuristic`. At the moment, the following
heuristics are available:

* `bottom-up` (default): Analyze ops from bottom to top.
* `top-down`: Analyze ops from top to bottom.
* `fuzzer`: Randomize the ordering of ops with `analysis-fuzzer-seed`.
* `bottom-up-from-terminators`: Traverse the reverse use-def chains of
tensor IR, starting from region branch terminators (bottom-up). Nested
regions are traversed before enclosing regions. Analyze the traversed ops
first, then analyze the remaining ops bottom-up. This heuristic is useful
for bufferizing loop constructs. One-Shot Bufferize currently supports
only such IR where yielded tensor values bufferize to equivalent region
iter_args, and first analyzing all ops on the path from the "yielding" op
to the beginning of the loop body makes it more likely for the region
iter_args and yielded values to bufferize to equivalent buffers.
}];
let options = [
Option<"allowReturnAllocsFromLoops", "allow-return-allocs-from-loops",
Expand Down
5 changes: 5 additions & 0 deletions mlir/lib/Dialect/Bufferization/Transforms/Bufferize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ parseHeuristicOption(const std::string &s) {
return OneShotBufferizationOptions::AnalysisHeuristic::BottomUp;
if (s == "top-down")
return OneShotBufferizationOptions::AnalysisHeuristic::TopDown;
if (s == "bottom-up-from-terminators")
return OneShotBufferizationOptions::AnalysisHeuristic::
BottomUpFromTerminators;
if (s == "fuzzer")
return OneShotBufferizationOptions::AnalysisHeuristic::Fuzzer;
llvm_unreachable("invalid analysisheuristic option");
}

Expand Down
116 changes: 90 additions & 26 deletions mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/AsmState.h"
#include "mlir/IR/Dominance.h"
#include "mlir/IR/Iterators.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/TypeUtilities.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
Expand Down Expand Up @@ -1094,41 +1095,104 @@ static void equivalenceAnalysis(Operation *op, OneShotAnalysisState &state) {
equivalenceAnalysis(ops, state);
}

LogicalResult OneShotAnalysisState::analyzeOp(Operation *op,
const DominanceInfo &domInfo) {
// Collect ops so we can build our own reverse traversal.
SmallVector<Operation *> ops;
op->walk([&](Operation *op) {
// No tensors => no buffers.
if (!hasTensorSemantics(op))
/// "Bottom-up from terminators" heuristic.
static SmallVector<Operation *>
bottomUpFromTerminatorsHeuristic(Operation *op,
const OneShotAnalysisState &state) {
SetVector<Operation *> traversedOps;

// Find region terminators.
op->walk<WalkOrder::PostOrder>([&](RegionBranchTerminatorOpInterface term) {
if (!traversedOps.insert(term))
return;
ops.push_back(op);
// Follow the reverse SSA use-def chain from each yielded value as long as
// we stay within the same region.
SmallVector<OpResult> worklist;
for (Value v : term->getOperands()) {
if (!isa<TensorType>(v.getType()))
continue;
auto opResult = dyn_cast<OpResult>(v);
if (!opResult)
continue;
worklist.push_back(opResult);
}
while (!worklist.empty()) {
OpResult opResult = worklist.pop_back_val();
Operation *defOp = opResult.getDefiningOp();
if (!traversedOps.insert(defOp))
continue;
if (!term->getParentRegion()->findAncestorOpInRegion(*defOp))
continue;
AliasingOpOperandList aliases = state.getAliasingOpOperands(opResult);
for (auto alias : aliases) {
Value v = alias.opOperand->get();
if (!isa<TensorType>(v.getType()))
continue;
auto opResult = dyn_cast<OpResult>(v);
if (!opResult)
continue;
worklist.push_back(opResult);
}
}
});

if (getOptions().analysisFuzzerSeed) {
// This is a fuzzer. For testing purposes only. Randomize the order in which
// operations are analyzed. The bufferization quality is likely worse, but
// we want to make sure that no assertions are triggered anywhere.
std::mt19937 g(getOptions().analysisFuzzerSeed);
llvm::shuffle(ops.begin(), ops.end(), g);
}
// Analyze traversed ops, then all remaining ops.
SmallVector<Operation *> result(traversedOps.begin(), traversedOps.end());
op->walk<WalkOrder::PostOrder, ReverseIterator>([&](Operation *op) {
if (!traversedOps.contains(op) && hasTensorSemantics(op))
result.push_back(op);
});
return result;
}

LogicalResult OneShotAnalysisState::analyzeOp(Operation *op,
const DominanceInfo &domInfo) {
OneShotBufferizationOptions::AnalysisHeuristic heuristic =
getOptions().analysisHeuristic;
if (heuristic == OneShotBufferizationOptions::AnalysisHeuristic::BottomUp) {
// Default: Walk ops in reverse for better interference analysis.
for (Operation *op : reverse(ops))
if (failed(analyzeSingleOp(op, domInfo)))
return failure();
} else if (heuristic ==
OneShotBufferizationOptions::AnalysisHeuristic::TopDown) {
for (Operation *op : ops)
if (failed(analyzeSingleOp(op, domInfo)))
return failure();

SmallVector<Operation *> orderedOps;
if (heuristic ==
OneShotBufferizationOptions::AnalysisHeuristic::BottomUpFromTerminators) {
orderedOps = bottomUpFromTerminatorsHeuristic(op, *this);
} else {
llvm_unreachable("unsupported heuristic");
op->walk([&](Operation *op) {
// No tensors => no buffers.
if (!hasTensorSemantics(op))
return;
orderedOps.push_back(op);
});
switch (heuristic) {
case OneShotBufferizationOptions::AnalysisHeuristic::BottomUp: {
// Default: Walk ops in reverse for better interference analysis.
std::reverse(orderedOps.begin(), orderedOps.end());
break;
}
case OneShotBufferizationOptions::AnalysisHeuristic::TopDown: {
// Ops are already sorted top-down in `orderedOps`.
break;
}
case OneShotBufferizationOptions::AnalysisHeuristic::Fuzzer: {
assert(getOptions().analysisFuzzerSeed &&
"expected that fuzzer seed it set");
// This is a fuzzer. For testing purposes only. Randomize the order in
// which operations are analyzed. The bufferization quality is likely
// worse, but we want to make sure that no assertions are triggered
// anywhere.
std::mt19937 g(getOptions().analysisFuzzerSeed);
llvm::shuffle(orderedOps.begin(), orderedOps.end(), g);
break;
}
default: {
llvm_unreachable("unsupported heuristic");
}
}
}

// Analyze ops in the computed order.
for (Operation *op : orderedOps)
if (failed(analyzeSingleOp(op, domInfo)))
return failure();

equivalenceAnalysis(op, *this);
return success();
}
Expand Down
6 changes: 3 additions & 3 deletions mlir/test/Dialect/Arith/one-shot-bufferize.mlir
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries" -split-input-file | FileCheck %s

// Run fuzzer with different seeds.
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=23 bufferize-function-boundaries" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=59 bufferize-function-boundaries" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=91 bufferize-function-boundaries" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=23 bufferize-function-boundaries" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=59 bufferize-function-boundaries" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=91 bufferize-function-boundaries" -split-input-file -o /dev/null

// Test bufferization using memref types that have no layout map.
// RUN: mlir-opt %s -one-shot-bufferize="unknown-type-conversion=identity-layout-map function-boundary-type-conversion=identity-layout-map bufferize-function-boundaries" -split-input-file -o /dev/null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// RUN: mlir-opt %s -one-shot-bufferize="allow-unknown-ops" -canonicalize -split-input-file | FileCheck %s

// Run fuzzer with different seeds.
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=23" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=59" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=91" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=23" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=59" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=91" -split-input-file -o /dev/null

// CHECK-LABEL: func @buffer_not_deallocated(
// CHECK-SAME: %[[t:.*]]: tensor<?xf32>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=bottom-up-from-terminators" -split-input-file | FileCheck %s

// CHECK-LABEL: func @simple_test(
func.func @simple_test(%lb: index, %ub: index, %step: index, %f1: f32, %f2: f32) -> (tensor<5xf32>, tensor<5xf32>) {
%c0 = arith.constant 0 : index
%p = arith.constant 0.0 : f32

// Make sure that ops that feed into region terminators bufferize in-place
// (if possible).
// Note: This test case fails to bufferize with a "top-down" or "bottom-up"
// heuristic.

%0 = tensor.empty() : tensor<5xf32>
%1 = scf.for %iv = %lb to %ub step %step iter_args(%t = %0) -> (tensor<5xf32>) {
// CHECK: linalg.fill {__inplace_operands_attr__ = ["none", "false"]}
%2 = linalg.fill ins(%f1 : f32) outs(%t : tensor<5xf32>) -> tensor<5xf32>
// CHECK: linalg.fill {__inplace_operands_attr__ = ["none", "true"]}
%3 = linalg.fill ins(%f2 : f32) outs(%t : tensor<5xf32>) -> tensor<5xf32>
%4 = vector.transfer_read %2[%c0], %p : tensor<5xf32>, vector<5xf32>
vector.print %4 : vector<5xf32>
scf.yield %3 : tensor<5xf32>
}

%5 = tensor.empty() : tensor<5xf32>
%6 = scf.for %iv = %lb to %ub step %step iter_args(%t = %0) -> (tensor<5xf32>) {
// CHECK: linalg.fill {__inplace_operands_attr__ = ["none", "true"]}
%7 = linalg.fill ins(%f1 : f32) outs(%t : tensor<5xf32>) -> tensor<5xf32>
// CHECK: linalg.fill {__inplace_operands_attr__ = ["none", "false"]}
%8 = linalg.fill ins(%f2 : f32) outs(%t : tensor<5xf32>) -> tensor<5xf32>
%9 = vector.transfer_read %8[%c0], %p : tensor<5xf32>, vector<5xf32>
vector.print %9 : vector<5xf32>
scf.yield %7 : tensor<5xf32>
}

return %1, %6 : tensor<5xf32>, tensor<5xf32>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="allow-unknown-ops unknown-type-conversion=identity-layout-map" -split-input-file | FileCheck %s --check-prefix=CHECK-NO-LAYOUT-MAP

// Run fuzzer with different seeds.
// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=23" -split-input-file -o /dev/null
// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=59" -split-input-file -o /dev/null
// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=91" -split-input-file -o /dev/null
// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=23" -split-input-file -o /dev/null
// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=59" -split-input-file -o /dev/null
// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=91" -split-input-file -o /dev/null

// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="dialect-filter=tensor,bufferization allow-unknown-ops" -canonicalize -split-input-file | FileCheck %s --check-prefix=CHECK-TENSOR
// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="dialect-filter=scf,bufferization allow-unknown-ops" -canonicalize -split-input-file | FileCheck %s --check-prefix=CHECK-SCF
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// RUN: mlir-opt %s -one-shot-bufferize="allow-unknown-ops" -verify-diagnostics -split-input-file | FileCheck %s

// Run fuzzer with different seeds.
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=23" -verify-diagnostics -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=59" -verify-diagnostics -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-fuzzer-seed=91" -verify-diagnostics -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=23" -verify-diagnostics -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=59" -verify-diagnostics -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=91" -verify-diagnostics -split-input-file -o /dev/null

// Run with top-down analysis.
// RUN: mlir-opt %s -one-shot-bufferize="allow-unknown-ops analysis-heuristic=top-down" -verify-diagnostics -split-input-file | FileCheck %s --check-prefix=CHECK-TOP-DOWN-ANALYSIS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries=1 " -split-input-file | FileCheck %s --check-prefix=NO-DROP

// Run fuzzer with different seeds.
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries=1 test-analysis-only analysis-fuzzer-seed=23" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries=1 test-analysis-only analysis-fuzzer-seed=59" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries=1 test-analysis-only analysis-fuzzer-seed=91" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries=1 test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=23" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries=1 test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=59" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries=1 test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=91" -split-input-file -o /dev/null

// Test bufferization using memref types that have no layout map.
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries=1 unknown-type-conversion=identity-layout-map function-boundary-type-conversion=identity-layout-map" -split-input-file -o /dev/null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries test-analysis-only" -split-input-file | FileCheck %s

// Run fuzzer with different seeds.
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries test-analysis-only analysis-fuzzer-seed=23" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries test-analysis-only analysis-fuzzer-seed=59" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries test-analysis-only analysis-fuzzer-seed=91" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=23" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=59" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries test-analysis-only analysis-heuristic=fuzzer analysis-fuzzer-seed=91" -split-input-file -o /dev/null

// Try different heuristics. Not checking the result, just make sure that we do
// not crash.
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries test-analysis-only analysis-heuristic=bottom-up-from-terminators" -split-input-file -o /dev/null
// RUN: mlir-opt %s -one-shot-bufferize="bufferize-function-boundaries test-analysis-only analysis-heuristic=top-down" -split-input-file -o /dev/null

// TODO: Extract op-specific test cases and move them to their respective
// dialects.
Expand Down
Loading