MLIR NOTE

前端转MLIR的基本流程

前端可以是一个模型model,也可以是特定领域语言DSL。
如果为model,则需要经过以下3步:

  1. 需要将模型文件进行解析,得到相应的计算图以及权重参数
  2. 根据计算图生成相应的MLIR表示

如果为DSL转换成MLIR的表示,需要经过以下几个步骤:

  1. 对DSL进行语义分析和语法分析,得到其AST(参考toy1)
  2. 解析AST生成MLIR代码(参考toy2)

总而言之,无论是哪一种前端,接入到mlir中后,其后续的操作都是相同的。

创建MLIR项目

mlir项目的创建可分为是否自定义dialect;若没有自定义dialect,则只需要链接相应的libs即可;若含有自定义dialect,则一般使用ODS框架进行自动生成;

无自定义dialect的项目创建

# 项目结构
mlir-project
├── CMakeLists.txt
└── main.cpp
# CMakeLists.txt文件格式
cmake_minimum_required(VERSION 3.13.4)
project(mlir-project VERSION 0.0.0)
# C++标准
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
# 导入mlirconfig.cmake文件,初始化mlir
set(MLIR_CONFIG_PATH ./llvm-project/build/lib/cmake/mlir)
find_package(MLIR REQUIRED CONFIG PATHS ${MLIR_CONFIG_PATH})
# 将与mlir和llvm相关的.cmake文件添加到CMAKE_MODULE_PATH变量
list(APPEND CMAKE_MODULE_PATH "${MLIR_CMAKE_DIR}")
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
# 直接include导入.cmake文件
include(TableGen)
include(AddLLVM)
include(AddMLIR)
include(HandleLLVMOptions)
# 链接生成可执行文件
include_directories(${LLVM_INCLUDE_DIRS} ${MLIR_INCLUDE_DIRS})
add_executable(demo main.cpp)
target_link_libraries(
	demo
	MLIRIR
	MLIRParser
	MLIRFuncDialect  # 这里使用了funcDialect和arithDialect
	MLIRArithDialect
)
#include "mlir/IR/AsmState.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/Visitors.h"
#include "mlir/Parser/Parser.h"
#include "mlir/Support/FileUtilities.h"

#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/raw_ostream.h"

#include "llvm/ADT/DenseMap.h"

using namespace mlir;
using namespace llvm;

int main(int argc, char ** argv) {
  MLIRContext ctx;
  ctx.loadDialect<func::FuncDialect, arith::ArithDialect>();
  // 创建 OpBuilder
  OpBuilder builder(&ctx);
  auto mod = builder.create<ModuleOp>(builder.getUnknownLoc());
  // 设置插入点
  builder.setInsertionPointToEnd(mod.getBody());
  // 创建 func
  auto i32 = builder.getI32Type();
  auto funcType = builder.getFunctionType({i32, i32}, {i32});
  auto func = builder.create<func::FuncOp>(builder.getUnknownLoc(), "test", funcType);
  // 添加基本块
  auto entry = func.addEntryBlock();
  auto args = entry->getArguments();
  // 设置插入点
  builder.setInsertionPointToEnd(entry);
  // 创建 arith.addi
  auto addi = builder.create<arith::AddIOp>(builder.getUnknownLoc(), args[0], args[1]);
  // 创建 func.return
  builder.create<func::ReturnOp>(builder.getUnknownLoc(), ValueRange({addi}));
  mod->print(llvm::outs());
  return 0;
}
// 生成的 mlir 代码
module {
  func.func @test(%arg0: i32, %arg1: i32) -> i32 {
    %0 = arith.addi %arg0, %arg1 : i32
    return %0 : i32
  }
}

这是最简单的一种mlir项目的创建方式。

有自定义dialect的项目创建

创建自定义dialect主要就是创建其所需要的特定operator,之后就可以使用自定义的dialect接入到MLIR中。
含有自定义dialect的项目需要创建声明dialect以及operator的tablegen文件(dialect和operator的声明可以写在一个tablegen文件中),在编译过程中DOS会根据tablegen文件,在build目录下生成dialect和op定义与实现的.{h,cpp}.inc文件,后续的使用需要include进自己的项目中。

# 项目结构
toy-dialect
├── CMakeLists.txt           # 控制其他各个部分的 CMakeLists(1)
├── include
│   ├── CMakeLists.txt       # add_subdirectory(toy)
│   ├── IR.h                 # 当前项目所有.h文件的include
│   └── toy
│       ├── CMakeLists.txt   # 控制 Dialect 定义的 CMakeLists(2)
│       ├── ToyDialect.h     # Dialect 头文件
│       ├── ToyDialect.td    # Dialect TableGen 文件
│       ├── ToyOps.h         # Op 头文件
│       ├── ToyOps.td        # Op TableGen 文件
│       └── Toy.td           # 把 ToyDialect.td 和 ToyOps.td include 到一起,用于 tablegen
└── src
    ├── CMakeLists.txt       # 生成可执行文件的 CMakeLists(4)
    ├── main.cpp             # 主函数,使用dialect
    └── toy
        ├── CMakeLists.txt   # 生成MLIRToy等依赖的CMakeLists(3)
        └── toy.cpp          # Dialect library

toy.td文件可以将ToyDialect.td和ToyOps.td文件include到一起,所以toy.td文件可以代表dialect和ops两个;那么就等同于可以将ToyDialect.td和ToyOps.td文件的内容全都写到一个toy.td文件中。
下面对cmakelists文件进行分析(对照上面目录树):

  • (1)CMakeLists.txt文件(./toy-dialect/CMakeLists.txt)
cmake_minimum_required(VERSION 3.13.4)
project(test LANGUAGES CXX C)

# C++ 标准
set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to conform to")
set(CMAKE_CXX_STANDARD_REQUIRED YES)

set(LLVM_BUILD_LIBRARY_DIR /home/xiebaokang/projects/mlir/llvm-project/build/lib)
set(MLIR_CONFIG_PATH /home/xiebaokang/projects/mlir/llvm-project/build/lib/cmake/mlir)
find_package(MLIR REQUIRED CONFIG PATHS ${MLIR_CONFIG_PATH})

# llvm构建中生成的runtime和lib存储位置
set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/bin)
set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/lib)

# 将llvm和mlir相关的所有.cmake文件路径添加到CMAKE_MODULE_PATH变量中,后续可直接include
list(APPEND CMAKE_MODULE_PATH "${MLIR_CMAKE_DIR}")
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
include(TableGen)
include(AddLLVM)
include(AddMLIR)
include(HandleLLVMOptions)

# 链接1 - include
include_directories(${LLVM_INCLUDE_DIRS} ${MLIR_INCLUDE_DIRS})
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${PROJECT_BINARY_DIR}/include)

link_directories(${LLVM_BUILD_LIBRARY_DIR})

SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_subdirectory(include)
add_subdirectory(src)
  • (2)CMakeLists.txt文件(./toy-dialect/include/toy/CMakeLists.txt)**
# 添加dialect,Toy为Toy.td的名称;toy为dialect的名称
add_mlir_dialect(Toy toy)

注:add_mlir_dialect函数其实封装了mlir_tablegen函数以及add_public_tablegen_target函数,代码如下:

add_mlir_dialect(Toy toy)
# 等价于
set(LLVM_TARGET_DEFINITIONS Toy.td)        // 指定主要文件
mlir_tablegen(Toy.h.inc -gen-op-decls)
mlir_tablegen(Toy.cpp.inc -gen-op-defs)
mlir_tablegen(ToyDialect.h.inc -gen-dialect-decls)
mlir_tablegen(ToyDialect.cpp.inc -gen-dialect-defs)
add_public_tablegen_target(MLIRToyIncGen)    // MLIRToyIncGen 依赖
  • (3)CMakeLists.txt文件(./toy-dialect/src/toy/CMakeLists.txt)**
add_mlir_dialect_library(
  MLIRToy                               # 生成依赖的名称lib
  toy.cpp                               # 包含了对ToyDialect.h/ToyOps.h、{}.cpp.inc文件,以及dialect的初始化

  ADDITIONAL_HEADER_DIRS
  ${PROJECT_SOURCE_DIR}/include
  ${PROJECT_SOURCE_DIR}/build/include

  DEPENDS
  MLIRToyIncGen                         # IncGen的依赖

  LINK_COMPONENTS
  Core

  LINK_LIBS PUBLIC
  MLIRIR
  MLIRInferTypeOpInterface
)
  • (4)CMakeLists.txt文件(./toy-dialect/src/CMakeLists.txt)
add_subdirectory(toy)                    # 先运行toy下的cmakelist生成MLIRToy依赖
file(GLOB SRC_FILE ./*.cpp)              # 找到主文件main.cpp
add_executable(test ${SRC_FILE})         # 生成可执行文件

set(LLVM_LINK_COMPONENTS                 # 设置llvm需要的组件
        Core
        Support
        nativecodegen
        OrcJIT
        )
# dialect_libs == MLIR_DIALECT_LIBS 依赖libs文件
get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)
get_property(conversion_libs GLOBAL PROPERTY MLIR_CONVERSION_LIBS)
get_property(translation_libs GLOBAL PROPERTY MLIR_TRANSLATION_LIBS)

target_link_libraries(test               # 可执行文件链接libs
        PUBLIC
        ${dialect_libs}
        ${conversion_libs}
        ${translation_libs}
        MLIRToy
        MLIRAnalysis
        MLIRCallInterfaces
        MLIRCastInterfaces
        MLIRExecutionEngine
        MLIRIR
        MLIRLLVMCommonConversion
        MLIRLLVMToLLVMIRTranslation
        MLIRMemRefDialect
        MLIRParser
        MLIRPass
        MLIRSideEffectInterfaces
        MLIRTargetLLVMIRExport
        MLIRTransforms
        MLIRNVVMToLLVMIRTranslation
        MLIRToLLVMIRTranslationRegistration
        MLIRTargetLLVMIRImport
        MLIRTargetLLVMIRExport
        MLIRFuncToLLVM
        MLIRSupport
        MLIROptLib
        )

这样的项目结构,make编译时,ODS框架会根据tablegen文件事先生成.{h,cpp}.inc文件(dialect/ops/type),然后(2)cmakelists中的add_mlir_dialect_library会生成MLIRtoy的lib,最后由(4)cmakelists的target_link_libraries链接MLIRtoy的lib生成可执行文件。

模式匹配和重写pass(ch3)

优化的一种方式,对某种op进行匹配,然后重新写满足要求的op。上述项目的目录需要更新:

# 项目结构
toy-dialect
├── CMakeLists.txt           # 控制其他各个部分的 CMakeList(1)
├── include
│   ├── CMakeLists.txt       # add_subdirectory(toy)
│   ├── IR.h                 # 当前项目所有.h文件的include
│   └── toy
│       ├── CMakeLists.txt   # 控制 Dialect 定义的 CMakeList(2)
│       ├── ToyDialect.h     # Dialect 头文件
│       ├── ToyDialect.td    # Dialect TableGen 文件
│       ├── ToyOps.h         # Op 头文件
│       ├── ToyOps.td        # Op TableGen 文件
│       ├── Toy.td           # 把 ToyDialect.td 和 ToyOps.td include 到一起,用于 tablegen
│       └── ToyCombine.td    # 重写匹配的ddr声明----------new add
└── src
    ├── CMakeLists.txt       # 生成可执行文件的 CMakeList(4)
    ├── main.cpp             # 主函数,使用dialect
    └── toy
        ├── CMakeLists.txt   # 生成MLIRToy等依赖的CMakeLists(3)
        ├── toy.cpp          # Dialect library
        └── toyCombine.cpp   # 匹配重写的手写类以及重写登记处----------new add

匹配重写的优化过程

首先说明,匹配重写优化的步骤;
第一步,无论是DDR生成,还是手写C++,都需要有匹配重写的C++代码,格式如下:

// xxx为当前匹配重写的类名,xxxOp需要匹配出来的Op
struct xxx : public mlir::OpRewritePattern<xxxOp> {
  xxx(mlir::MLIRContext *context)
  	 : OpRewritePattern<xxxOp>(context, /*benefit=*/1) {}
  
  // 重写函数,这里重写符合要求的op
  mlir::LogicalResult matchAndRewrite(xxxOp op,
                  mlir::PatternRewriter &rewriter) const override {
    // 这里干你想干的事情,比如冗余检测等
    return success();
  }
};
// 登记为 canonicalization 模式,op的优化才会生效
void xxxOp::getCanonicalizationPatterns(RewritePatternSet &results,
                                              MLIRContext *context) {
  results.add<xxx,xxx...>(context);
}

第二步,在定义完类后,需要登记为 canonicalization 模式;

// 登记为 canonicalization 模式,op的优化才会生效
void xxxOp::getCanonicalizationPatterns(RewritePatternSet &results,
                                              MLIRContext *context) {
  results.add<xxx,xxx...>(context);
}

注:既然属于创建时的优化,则需要生成和MLIRToy lib相同的操作,也就是在(3)CMakeLists.txt文件中的add_mlir_dialect_library需要添加cpp文件,如果使用DDR直接生成的匹配重写类,则需要添加incgen依赖。

第三步,在此xxxOp的声明文件(xxOps.td)中,op的声明处添加 “let hasCanonicalizer = 1;” 声明,确保启用规范化框架,应用 canonicalization pass;

def xxxOp : Toy_Op<"xxx", [NoSideEffect]> {
...
  // 确保启用规范化框架,应用 canonicalization pass
  let hasCanonicalizer = 1;
...
}

第四步,向passmanager中添加此类pass

mlir::PassManager pm(&context);
mlir::applyPassManagerCLOptions(pm);  // 通用优化pipline
pm.addNestedPass<mlir::toy::FuncOp>(mlir::createCanonicalizerPass());  // 添加pass
pm.run(*module); // 运行pass

注:注意addNestedPass表示只在指定的op下运行此pass,createCanonicalizerPass这个pass由官方提供,在上面我们已经将我们自己的模式匹配重写的类登记为 canonicalization 模式,所以直接passrun的就可以应用此pass。

DDR生成匹配重写类

DDR的直接生成我只说一点,也就是cmake文件的格式。
因为,采用的tablegen文件声明匹配重写的类,所以CMakeLists.txt中肯定需要一个像add_mlir_dialect一样的函数来生成inc文件以及incgen的依赖,所以(2)CMakeLists.txt(./toy-dialect/include/toy/CMakeLists.txt)修改为:

    add_mlir_dialect(Toy toy)
    # 将ToyCombine.td文件与Toy.td文件放在一个目录下
    set(LLVM_TARGET_DEFINITIONS ToyCombine.td)
    mlir_tablegen(ToyCombine.inc -gen-rewriters)
    add_public_tablegen_target(MLIRToyCombineIncGen)    // 这里会生成ToyCombineIncGen的incgen依赖

这一步在make的时候会生成inc文件,以及ToyCombineIncGen依赖;而且,ToyCombine.cpp文件中有对匹配重写类的canonicalization 登记,所以(3)CMakeLists.txt文件(./toy-dialect/src/toy/CMakeLists.txt)代码修改为:

add_mlir_dialect_library(
  MLIRToy                               # 生成依赖的名称lib
  toy.cpp                               # 包含了对ToyDialect.h/ToyOps.h、{}.cpp.inc文件,以及dialect的初始化
  toyCombine.cpp                        # canonicalization 登记处(手写类处)------new add
	
  ADDITIONAL_HEADER_DIRS
  ${PROJECT_SOURCE_DIR}/include
  ${PROJECT_SOURCE_DIR}/build/include

  DEPENDS
  MLIRToyIncGen                         # IncGen的依赖
  MLIRToyCombineIncGen                  # 模式匹配重写的incgen依赖------new add

  LINK_COMPONENTS
  Core

  LINK_LIBS PUBLIC
  MLIRIR
  MLIRInferTypeOpInterface
)

最后,至于ddr声明的书写方式,参考官网:Table-driven Declarative Rewrite Rule (DDR)

内联和形状推断pass(ch4)

通过使用 Dialect,MLIR 可以表示多种不同等级的抽象。尽管这些不同的 Dialect 表示不同的抽象,但某些操作的算法机制十分相似,为了减少代码重复,MLIR 提供了一组通用的转换和分析也就是这一类型的pass,mlir已经实现了接口。

内联(inline)

适用于函数调用op(callop),将一些简单的函数嵌入到调用处,,以储存空间为代价换取运行速度。
mlir中已经实现了通用函数内联的pass,我们构建一个属于toy dialect的内联函数接口,需要继承DialectInlinerInterface来编写自己的内联接口;

形状推断(shape inference)

需要使用ODS框架来声明定义ShapeInference 的接口(写一个shapeInference.td文件,放在Toy.td相同目录下)
将此接口添加到需要的operation的声明处(ops.td),然后这些operation就具有形状推断的接口了,就需要在这些 operation 中定义对应的形状推断函数,独立定义可以保证 ShapeInferencePass 会独立地作用于该 operation。
最后添加进入passmanage中。

mlir::applyPassManagerCLOptions(pm);    // 通用pipeline
pm.addPass(mlir::createInlinerPass());   // 内联pass
mlir::OpPassManager &optPM = pm.nest<mlir::toy::FuncOp>();
optPM.addPass(mlir::toy::createShapeInferencePass());   // 形状推断pass
optPM.addPass(mlir::createCanonicalizerPass());    // 匹配重写
optPM.addPass(mlir::createCSEPass());  // 公共子表达式消除(直接调用就行)

详细的规则过程参考链接1参考链接2

现在主要讲解项目构建,因为现在新加入一个tablegen文件(ShapeInferenceInterface.td)用于形状推断,且tablegen文件放在与Toy.td文件相同的路径下,所以有:
(2)CMakeLists.txt文件(./toy-dialect/include/toy/CMakeLists.txt)

    add_mlir_dialect(Toy toy)
    # 将ToyCombine.td文件与Toy.td文件放在一个目录下
    set(LLVM_TARGET_DEFINITIONS ToyCombine.td)
    mlir_tablegen(ToyCombine.inc -gen-rewriters)
    add_public_tablegen_target(MLIRToyCombineIncGen)    // 这里会生成ToyCombineIncGen的incgen依赖
    # 将ShapeInferenceInterface.td文件与Toy.td文件放在一个目录下
    set(LLVM_TARGET_DEFINITIONS ShapeInferenceInterface.td)
    mlir_tablegen(ShapeInferenceOpInterfaces.h.inc -gen-op-interface-decls)  # 这个需要生成.h文件
    mlir_tablegen(ShapeInferenceOpInterfaces.cpp.inc -gen-op-interface-defs)
    add_public_tablegen_target(MLIRToyShapeInferenceInterfaceIncGen)

(3)CMakeLists.txt文件(./toy-dialect/src/toy/CMakeLists.txt)

add_mlir_dialect_library(
  MLIRToy                               # 生成依赖的名称lib
  toy.cpp                               # 包含了对ToyDialect.h/ToyOps.h、{}.cpp.inc文件,以及dialect的初始化
  toyCombine.cpp                        # canonicalization 登记处(手写类处)
  ShapeInferencePass.cpp                # 包括推断的详细过程 ------new add
	
  ADDITIONAL_HEADER_DIRS
  ${PROJECT_SOURCE_DIR}/include
  ${PROJECT_SOURCE_DIR}/build/include

  DEPENDS
  MLIRToyIncGen                         # IncGen的依赖
  MLIRToyCombineIncGen                  # 模式匹配重写的incgen依赖
  MLIRToyShapeInferenceInterfaceIncGen  # 形状推断的incgen依赖------new add

  LINK_COMPONENTS
  Core

  LINK_LIBS PUBLIC
  MLIRIR
  MLIRInferTypeOpInterface
)

注:与上述匹配重写不同的点在于,这个类似于定义新的op,所以还需要一个ShapeInferenceInterface.h文件

lowwering

lowwering也是使用pass实现,pass还是自己实现。

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瘦瘦无感

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值