| # 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. |
| |
| ### Write the script for testing interestingness |
| |
| As mentioned, 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. The sample script, |
| |
| ```shell |
| mlir-opt -convert-vector-to-spirv $1 | grep "failed to materialize" |
| if [[ $? -eq 1 ]]; then |
| exit 1 |
| else |
| exit 0 |
| fi |
| ``` |
| |
| The sample usage will be like, note that the `test` argument is part of the mode |
| argument. |
| |
| ```shell |
| mlir-reduce $INPUT -reduction-tree='traversal-mode=0 test=$TEST_SCRIPT' |
| ``` |
| |
| ## 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 { |
| 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. |