CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
185 lines
6.3 KiB
Markdown
185 lines
6.3 KiB
Markdown
# MLIR Reduce
|
|
|
|
[TOC]
|
|
|
|
An MLIR input may trigger bugs after series of transformations. To root cause
|
|
the problem or help verification after fixes, developers want to be able to
|
|
reduce the size of a reproducer for a bug. This document describes
|
|
`mlir-reduce`, which is similar to
|
|
[bugpoint](https://llvm.org/docs/CommandGuide/bugpoint.html), a tool that can
|
|
reduce the size of the input needed to trigger the error.
|
|
|
|
`mlir-reduce` supports reducing the input in several ways, including simply
|
|
deleting code not required to reproduce an error, applying the reducer
|
|
patterns heuristically or run with optimization passes to reduce the input. To
|
|
use it, the first thing you need to do is, provide a command which tells if an
|
|
input is interesting, e.g., exhibits the characteristics that you would like to
|
|
focus on. For example, you may want to see if `mlir-opt` invocation fails after
|
|
it runs on the certain MLIR input. Afterwards, select your reduction strategy
|
|
then `mlir-reduce` will do the remaining works for you.
|
|
|
|
## How to Use it
|
|
|
|
`mlir-reduce` adopts the reduction-tree algorithm to reduce the input. It
|
|
generates several reduced outputs and further reduces in between them according
|
|
to the tree traversal strategy. The different strategies may lead to different
|
|
results and different time complexity. You can run as
|
|
`-reduction-tree='traversal-mode=0'` to select the mode for example.
|
|
|
|
### Example MLIR input
|
|
|
|
```mlir
|
|
// query-test.mlir
|
|
func.func @func1() {
|
|
// A func can be pruned if it's not relevant to the error.
|
|
return
|
|
}
|
|
|
|
func.func @func2(%arg0: i1) -> f32 {
|
|
%0 = arith.constant 1 : i32
|
|
%1 = arith.constant 2 : i32
|
|
%2 = arith.constant 2.2 : f32
|
|
%3 = arith.constant 5.3 : f32
|
|
%4 = arith.addi %0, %1 : i32
|
|
%5 = arith.addf %2, %3 : f32
|
|
%6 = arith.muli %4, %4 : i32
|
|
%7 = arith.subi %6, %4 : i32
|
|
%8 = arith.select %arg0, %5, %2 : f32
|
|
%9 = arith.addf %2, %8 : f32
|
|
return %9 : f32
|
|
}
|
|
```
|
|
|
|
### Write the script for testing interestingness
|
|
|
|
You need to provide a command to `mlir-reduce` which identifies cases you're
|
|
interested in. For each intermediate output generated during reduction,
|
|
`mlir-reduce` will run the command over the it, the script should returns 1 for
|
|
interesting case, 0 otherwise. For the IR above, a sample script might simply
|
|
look for the presence of the `arith.fptosi` operation. A more realistic script
|
|
would check for the presence of a particular kind of error message in stderr.
|
|
|
|
```shell
|
|
#!/bin/bash
|
|
#
|
|
# query-test.sh
|
|
# `2>&1` redirects stderr (where errors and diagnostics are printed) to stdout
|
|
# so it can be piped to grep.
|
|
#
|
|
# Note mlir-opt needs to be on your path, or else replace mlir-opt with the
|
|
# absolute path to the binary. If mlir-opt cannot be found, this interestingness
|
|
# test will report "uninteresting on the first run, and mlir-reduce will
|
|
# report that the input is not marked as interesting.
|
|
#
|
|
mlir-opt $1 2>&1 | grep "arith.select"
|
|
|
|
# grep's exit code is 0 if the queried string is found
|
|
if [[ $? -eq 0 ]]; then
|
|
exit 1
|
|
else
|
|
exit 0
|
|
fi
|
|
```
|
|
|
|
### Running the example
|
|
|
|
The sample usage will be as follows, noting that the `test` argument is part of
|
|
the mode argument.
|
|
|
|
```shell
|
|
mlir-reduce query-test.mlir -reduction-tree='traversal-mode=0 test=query-test.sh'
|
|
```
|
|
|
|
The output:
|
|
|
|
```
|
|
module {
|
|
func.func @func2(%arg0: i1) -> f32 {
|
|
%cst = arith.constant 2.200000e+00 : f32
|
|
%cst_0 = arith.constant 7.500000e+00 : f32
|
|
%0 = arith.select %arg0, %cst_0, %cst : f32
|
|
%1 = arith.addf %0, %cst : f32
|
|
return %1 : f32
|
|
}
|
|
}
|
|
```
|
|
|
|
## Available reduction strategies
|
|
|
|
### Operation elimination
|
|
|
|
`mlir-reduce` will try to remove the operations directly. This is the most
|
|
aggressive reduction as it may result in an invalid output as long as it ends up
|
|
retaining the error message that the test script is interesting. To avoid that,
|
|
`mlir-reduce` always checks the validity and it expects the user will provide a
|
|
valid input as well.
|
|
|
|
### Rewrite patterns into simpler forms
|
|
|
|
In some cases, rewrite an operation into a simpler or smaller form can still
|
|
retain the interestingness. For example, `mlir-reduce` will try to rewrite a
|
|
`tensor<?xindex>` with unknown rank into a constant rank one like
|
|
`tensor<1xi32>`. Not only produce a simpler operation, it may introduce further
|
|
reduction chances because of precise type information.
|
|
|
|
MLIR supports dialects and `mlir-reduce` supports rewrite patterns for every
|
|
dialect as well. Which means you can have the dialect specific rewrite patterns.
|
|
To do that, you need to implement the `DialectReductionPatternInterface`. For
|
|
example,
|
|
|
|
```c++
|
|
#include "mlir/Reducer/ReductionPatternInterface.h"
|
|
|
|
struct MyReductionPatternInterface : public DialectReductionPatternInterface {
|
|
MyReductionPatternInterface(Dialect *dialect)
|
|
: DialectReductionPatternInterface(dialect) {};
|
|
|
|
virtual void
|
|
populateReductionPatterns(RewritePatternSet &patterns) const final {
|
|
populateMyReductionPatterns(patterns);
|
|
}
|
|
}
|
|
```
|
|
|
|
`mlir-reduce` will call `populateReductionPatterns` to collect the reduction
|
|
rewrite patterns provided by each dialect. Here's a hint, if you use
|
|
[DRR](../DeclarativeRewrites.md) to write the reduction patterns, you can
|
|
leverage the method `populateWithGenerated` generated by `mlir-tblgen`.
|
|
|
|
### Reduce with built-in optimization passes
|
|
|
|
MLIR provides amount of transformation passes and some of them are useful for
|
|
reducing the input size, e.g., Symbol-DCE. `mlir-reduce` will schedule them
|
|
along with above two strategies.
|
|
|
|
## Build a custom mlir-reduce
|
|
|
|
In the cases of, 1. have defined a custom syntax, 2. the failure is specific to
|
|
certain dialects or 3. there's a dialect specific reducer patterns, you need to
|
|
build your own `mlir-reduce`. Link it with `MLIRReduceLib` and implement it
|
|
like,
|
|
|
|
```c++
|
|
#include "mlir/Tools/mlir-reduce/MlirReduceMain.h"
|
|
using namespace mlir;
|
|
|
|
int main(int argc, char **argv) {
|
|
DialectRegistry registry;
|
|
registerMyDialects(registry);
|
|
// Register the DialectReductionPatternInterface if any.
|
|
MLIRContext context(registry);
|
|
return failed(mlirReduceMain(argc, argv, context));
|
|
}
|
|
|
|
```
|
|
|
|
## Future works
|
|
|
|
`mlir-reduce` is missing several features,
|
|
|
|
* `-reduction-tree` now only supports `Single-Path` traversal mode, extends it
|
|
with different traversal strategies may reduce the input better.
|
|
* Produce the optimal result when interrupted. The reduction process may take
|
|
a quite long time, it'll be better to get an optimal result so far while an
|
|
interrupt is triggered.
|