MLIR知识点

MLIR知识点

社蕙 9 2025-08-01

MLIR 入门完全指南

1. MLIR 是什么?

1.1 核心定义

  • 定位:通用编译器基础设施(Unified Compiler Infrastructure)
  • 核心思想
    • 混合IR:支持多级别抽象(从高层计算图到低层硬件指令)
    • 可扩展性:允许自定义领域特定IR(如TensorFlow IR、HLO、GPU方言等)

1.2 设计目标

  • 解决传统编译器的问题:
    • 不同框架(TensorFlow/PyTorch)和硬件(CPU/GPU/TPU)需要重复开发编译器
    • 优化过程碎片化(如XLA/LLVM各自为政)
  • 统一基础设施:覆盖从计算图优化到芯片指令生成的完整流程

2. MLIR 核心功能

2.1 关键能力

  1. 数据流图表示
    • 支持动态形状、用户自定义算子(Op)、变量(如TF Variables)
    • 示例:TensorFlow的计算图可直接转换为MLIR方言
  2. 跨层级优化
    • 高层优化(如算子融合、常量折叠)
    • 低层优化(循环分块、内存布局变换、向量化)
  3. 硬件特定支持
    • 加速器专用操作(如TPU指令、GPU Warp操作)
    • 显式内存管理(DMA插入、缓存控制)
  4. 渐进式Lowering
    • 从抽象操作逐步降级到硬件指令(如 linalg.matmul → loops → vector → llvm

3. MLIR 核心概念

3.1 基础术语

术语解释示例
SSA静态单赋值:每个变量只赋值一次%x = add %a, %b
CSE公共子表达式消除消除重复计算的 %x + %x
DCE死代码消除删除未被引用的 %unused = ...
Invariant循环不变量可提到循环外的计算
Legalization非法操作转换为合法操作sin(x)展开为泰勒级数

3.2 核心数据结构

  1. Operation (Op)

    • 语义最小单元,可包含属性、操作数、结果和区域
    %result = "dialect.op_name"(%arg1, %arg2) {attr = 42} : (i32, i32) -> i64
    
  2. Block (^bb)

    • 顺序执行的Op序列,必须以终止符(如 return)结尾
    ^bb0(%arg: i32):
      %1 = "add"(%arg, %arg) : (i32, i32) -> i32
      "return"(%1) : (i32) -> ()
    
  3. Region

    • 由多个Block组成的子图,用于表示控制流(如 if/while的then/else分支)
  4. Module

    • 顶级容器,包含所有Op(类似C++的命名空间)

4. MLIR 工作流程

4.1 典型编译流程

  1. 前端转换:框架计算图 → MLIR方言(如 mhlo
  2. 中端优化
    • 通用优化(CSE/DCE)
    • 领域优化(量化、算子融合 Fusion)
  3. 硬件Lowering
    • 逐步降级到低级方言(如 vectorllvmnvvm
  4. 代码生成:目标二进制(CPU/GPU机器码)

4.2 示例:矩阵乘法优化

// 原始HLO级表示
%result = "mhlo.dot"(%A, %B) : (tensor<1024x1024xf32>, tensor<1024x1024xf32>) -> tensor<1024x1024xf32>

// 降级为循环嵌套
%result = "linalg.matmul"(%A, %B) : (memref<1024x1024xf32>, memref<1024x1024xf32>) -> memref<1024x1024xf32>

// 分块+向量化
"transform.vectorize"(%matmul) : !transform.any_op

以下是关于 MLIR Builtin Dialect 的详细介绍,适合编译器初学者系统学习:

MLIR Builtin Dialect 完全解析

Builtin Dialect 是 MLIR 的核心基础方言,定义了所有其他方言共享的基础数据类型、结构和操作。它类似于编程语言中的“标准库”,是 IR 的底层基石。

1. Builtin Dialect 的定位

1.1 核心作用

  • 提供基础设施:定义所有方言共用的基础类型(如整数、浮点数)、属性(如字符串、数组)和操作(如模块、函数)。
  • 容器功能:作为顶级容器(Module、FuncOp)的承载者。
  • IR 结构基础:定义 Block、Region、Operation 等核心结构的表示方式。

1.2 特点

  • 隐式加载:无需显式声明,所有 MLIR 文件自动可用。
  • 不可扩展:用户不能添加新的 Builtin 类型或操作(与自定义方言不同)。

2. 关键组成部分

2.1 基础类型(Types)

类型语法示例描述
整数i32i64有符号整数(si32)、无符号(ui8
浮点数f32f64IEEE 浮点
索引类型index循环索引/内存偏移(平台相关)
张量tensor<2x3xf32>静态形状多维数组
MemRefmemref<4x?xi32>带有内存布局的可变缓冲区
函数类型(i32, f32) -> i64输入输出类型签名

2.2 属性(Attributes)

属性语法示例描述
整数42 : i32带类型的常量
浮点数3.14 : f32
字符串"hello"
数组[1, 2, 3]同类型元素列表
字典{key = value}键值对集合
符号引用@func_name全局符号(如函数名)

2.3 核心操作(Ops)

(1) 模块操作(ModuleOp)

  • 作用:IR 的顶级容器,包含所有全局定义(如函数、全局变量)。

  • 示例

    module {
      func.func @main() -> i32 {
        %c1 = arith.constant 1 : i32
        return %c1 : i32
      }
    }
    

(2) 函数操作(FuncOp)

  • 作用:定义可调用的函数单元,包含参数、返回值和函数体。

  • 示例

    func.func @add(%a: i32, %b: i32) -> i32 {
      %sum = arith.addi %a, %b : i32
      return %sum : i32
    }
    

(3) 基本控制流操作

操作示例描述
returnreturn %value : i32函数返回
call%r = call @add(%a, %b)调用函数

3. Builtin 的底层结构

3.1 Operation 的通用表示

所有 MLIR 操作(包括自定义方言)都隐式使用 Builtin 的结构:

// 通用操作语法
%result = "dialect.op_name"(%operand1, %operand2) {
  attr1 = value1,
  attr2 = value2
} : (input_type1, input_type2) -> result_type

3.2 Block 和 Region

  • Block:由一组顺序执行的 Operation 组成,以终止符(如 return)结尾。

    ^bb0(%arg: i32):  // 带参数的Block
      %1 = arith.addi %arg, %arg : i32
      "some_dialect.some_op"(%1) : (i32) -> ()
      return
    
  • Region:包含一组 Block 的子图,用于结构化控制流(如 scf.if 的 then/else 分支)。

Builtin Dialect 是理解 MLIR 设计哲学的起点,掌握它后能更轻松地学习其他方言(如 arithfuncscf)。

MLIR与LLVM IR的区别

MLIR 和 LLVM IR 都是编译器中间表示(IR),但设计目标和应用场景有显著区别。以下是它们的核心对比:

1. 设计哲学

维度LLVM IRMLIR
定位低级静态单赋值(SSA)IR,面向通用硬件代码生成多级混合IR,支持从计算图到硬件的全栈抽象
核心目标生成高效的机器码统一异构编译(深度学习/HPC/DSL)
扩展性固定指令集(不可扩展)可自定义方言(Dialect)

2. 关键特性对比

2.1 抽象级别

  • LLVM IR

    • 接近机器码的底层IR(类似RISC指令集)
    • 强制要求显式内存管理、指针运算、基本块控制流
    ; LLVM IR示例(计算 %a + %b)
    %result = add i32 %a, %b
    
  • MLIR

    • 支持多级抽象,可同时表示高层计算图和低层硬件操作
    • 允许携带领域特定信息(如张量形状、量化参数)
    // MLIR示例(高层张量操作 + 低层循环)
    %result = "tensor.add"(%A, %B) : (tensor<1024xf32>, tensor<1024xf32>) -> tensor<1024xf32>
    "scf.for"(%i = 0 to 1024) {
      %elem = "memref.load"(%A, %i) : (memref<1024xf32>, index) -> f32
      ...
    }
    

2.2 类型系统

特性LLVM IRMLIR
基础类型仅支持机器兼容类型(i32, f64, ptr)扩展类型系统(张量、索引、符号引用等)
类型安全弱类型(指针可任意转换)强类型 + 自定义类型约束
动态形状不支持支持(如 tensor

2.3 控制流表示

  • LLVM IR

    • 基于基本块(BasicBlock)和分支指令(br/switch
    • 所有控制流必须显式展开
    ; LLVM IR条件分支
    %cond = icmp eq i32 %a, 0
    br i1 %cond, label %true_block, label %false_block
    
  • MLIR

    • 支持结构化控制流(如 scf.ifscf.for
    • 允许嵌套区域(Region)表示抽象控制流
    // MLIR结构化循环
    scf.for %i = 0 to 100 step 1 {
      scf.if %i > 50 {
        "some_op"(%i) : (index) -> ()
      }
    }
    

3. 典型应用场景

场景LLVM IRMLIR
传统编译器C/C++/Rust等语言的代码生成主要用于领域特定编译器(如AI/HPC)
硬件支持CPU/GPU通用指令生成异构计算(CPU+GPU+TPU+自定义加速器)
优化灵活性优化集中在Mid-End全栈优化(从计算图到硬件指令)
动态性支持静态编译为主支持动态形状/JIT编译(如TensorFlow)

总结图示

高层DSL/框架 → MLIR(多级优化) → LLVM IR(机器码生成) → 硬件

MLIR 填补了高层领域抽象与低层硬件之间的空白,而 LLVM IR 仍是生成可靠机器码的最终阶段。

误差表示的方法

除了rtol、atol之外,还有ULP,全称是unit in last place,简单来说就是找到浮点表示值的最小有效位,用差的绝对值除以这个最小有效位。