Linalg Dialect
Linalg (Linear Algebra) Dialect 是 MLIR 中用于表示线性代数操作的高级方言。它提供了一种结构化的方式来表示张量操作,是连接高级机器学习框架和低级优化的重要桥梁。
🎯 核心概念
什么是 Linalg
Linalg Dialect 专注于表示结构化的线性代数操作,它的设计目标是:
- 可组合性: 操作可以轻松组合和嵌套
- 可优化性: 提供丰富的优化机会
- 可降级性: 能够逐步降级到更低级的表示
- 硬件无关性: 不绑定特定的硬件实现
核心特性
graph LR A["Linalg Dialect"] --> B["结构化操作"] A --> C["张量抽象"] A --> D["迭代器语义"] A --> E["仿射映射"] B --> B1["矩阵乘法"] B --> B2["卷积操作"] B --> B3["元素级操作"] C --> C1["输入张量"] C --> C2["输出张量"] C --> C3["形状推导"] D --> D1["并行迭代"] D --> D2["归约迭代"] D --> D3["窗口迭代"] E --> E1["索引映射"] E --> E2["访问模式"] E --> E3["数据布局"] classDef core fill:#e3f2fd,stroke:#1976d2,stroke-width:2px classDef feature fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px class A core class B,C,D,E,B1,B2,B3,C1,C2,C3,D1,D2,D3,E1,E2,E3 feature
🔧 核心操作
1. linalg.generic
最通用的 Linalg 操作,可以表示任意的结构化计算:
%result = linalg.generic {
indexing_maps = [
affine_map<(d0, d1) -> (d0, d1)>, // 输入映射
affine_map<(d0, d1) -> (d0, d1)> // 输出映射
],
iterator_types = ["parallel", "parallel"] // 迭代器类型
} ins(%input : tensor<4x8xf32>)
outs(%output : tensor<4x8xf32>) {
^bb0(%in: f32, %out: f32):
%add = arith.addf %in, %in : f32
linalg.yield %add : f32
} -> tensor<4x8xf32>
2. 命名操作
矩阵乘法 (linalg.matmul)
// C = A * B
%C = linalg.matmul
ins(%A, %B : tensor<4x8xf32>, tensor<8x16xf32>)
outs(%C_init : tensor<4x16xf32>) -> tensor<4x16xf32>
批量矩阵乘法 (linalg.batch_matmul)
// C[b] = A[b] * B[b]
%C = linalg.batch_matmul
ins(%A, %B : tensor<2x4x8xf32>, tensor<2x8x16xf32>)
outs(%C_init : tensor<2x4x16xf32>) -> tensor<2x4x16xf32>
卷积操作 (linalg.conv_2d)
// 2D 卷积: output = conv2d(input, filter)
%output = linalg.conv_2d
ins(%input, %filter : tensor<1x32x32x3xf32>, tensor<3x3x3x64xf32>)
outs(%output_init : tensor<1x30x30x64xf32>) -> tensor<1x30x30x64xf32>
3. 元素级操作
加法 (linalg.add)
%result = linalg.add
ins(%lhs, %rhs : tensor<4x8xf32>, tensor<4x8xf32>)
outs(%output : tensor<4x8xf32>) -> tensor<4x8xf32>
乘法 (linalg.mul)
%result = linalg.mul
ins(%lhs, %rhs : tensor<4x8xf32>, tensor<4x8xf32>)
outs(%output : tensor<4x8xf32>) -> tensor<4x8xf32>
📊 迭代器类型
Linalg 使用迭代器类型来描述计算的语义:
并行迭代器 (parallel)
// 元素级加法 - 所有元素可以并行计算
iterator_types = ["parallel", "parallel"]
归约迭代器 (reduction)
// 矩阵乘法 - k 维度是归约维度
iterator_types = ["parallel", "parallel", "reduction"]
窗口迭代器 (window)
// 卷积操作 - 滑动窗口计算
iterator_types = ["parallel", "parallel", "window", "window"]
🗺️ 仿射映射
仿射映射定义了输入输出张量的访问模式:
基本映射示例
// 矩阵乘法的映射
indexing_maps = [
affine_map<(m, n, k) -> (m, k)>, // A[m, k]
affine_map<(m, n, k) -> (k, n)>, // B[k, n]
affine_map<(m, n, k) -> (m, n)> // C[m, n]
]
广播映射
// 向量加法广播
indexing_maps = [
affine_map<(d0, d1) -> (d0, d1)>, // 矩阵 A
affine_map<(d0, d1) -> (d1)>, // 向量 b (广播)
affine_map<(d0, d1) -> (d0, d1)> // 结果矩阵
]
🔄 转换和优化
1. Tiling (分块)
将大的操作分解为小块,提高缓存局部性:
// 原始矩阵乘法
%C = linalg.matmul ins(%A, %B : tensor<1024x1024xf32>, tensor<1024x1024xf32>)
outs(%C_init : tensor<1024x1024xf32>) -> tensor<1024x1024xf32>
// 分块后 (32x32 块)
scf.for %i = %c0 to %c1024 step %c32 {
scf.for %j = %c0 to %c1024 step %c32 {
scf.for %k = %c0 to %c1024 step %c32 {
%A_slice = tensor.extract_slice %A[%i, %k] [32, 32] [1, 1]
%B_slice = tensor.extract_slice %B[%k, %j] [32, 32] [1, 1]
%C_slice = tensor.extract_slice %C[%i, %j] [32, 32] [1, 1]
%result = linalg.matmul ins(%A_slice, %B_slice : tensor<32x32xf32>, tensor<32x32xf32>)
outs(%C_slice : tensor<32x32xf32>) -> tensor<32x32xf32>
%C = tensor.insert_slice %result into %C[%i, %j] [32, 32] [1, 1]
}
}
}
2. Fusion (融合)
将多个操作融合为一个,减少内存访问:
// 融合前:两个独立操作
%temp = linalg.add ins(%A, %B : tensor<4x8xf32>, tensor<4x8xf32>)
outs(%temp_init : tensor<4x8xf32>) -> tensor<4x8xf32>
%result = linalg.mul ins(%temp, %C : tensor<4x8xf32>, tensor<4x8xf32>)
outs(%result_init : tensor<4x8xf32>) -> tensor<4x8xf32>
// 融合后:单个操作
%result = linalg.generic {
indexing_maps = [
affine_map<(d0, d1) -> (d0, d1)>, // A
affine_map<(d0, d1) -> (d0, d1)>, // B
affine_map<(d0, d1) -> (d0, d1)>, // C
affine_map<(d0, d1) -> (d0, d1)> // result
],
iterator_types = ["parallel", "parallel"]
} ins(%A, %B, %C : tensor<4x8xf32>, tensor<4x8xf32>, tensor<4x8xf32>)
outs(%result_init : tensor<4x8xf32>) {
^bb0(%a: f32, %b: f32, %c: f32, %out: f32):
%add = arith.addf %a, %b : f32
%mul = arith.mulf %add, %c : f32
linalg.yield %mul : f32
} -> tensor<4x8xf32>
3. 向量化 (Vectorization)
将标量操作转换为向量操作:
// 向量化前:标量操作
scf.for %i = %c0 to %c1024 step %c1 {
%val = memref.load %A[%i] : memref<1024xf32>
%result = arith.mulf %val, %val : f32
memref.store %result, %B[%i] : memref<1024xf32>
}
// 向量化后:向量操作
scf.for %i = %c0 to %c1024 step %c8 {
%vec = vector.load %A[%i] : memref<1024xf32>, vector<8xf32>
%result = arith.mulf %vec, %vec : vector<8xf32>
vector.store %result, %B[%i] : memref<1024xf32>, vector<8xf32>
}
🎯 实际应用示例
深度学习中的全连接层
func.func @fully_connected(
%input: tensor<32x784xf32>, // batch_size x input_dim
%weight: tensor<784x128xf32>, // input_dim x output_dim
%bias: tensor<128xf32> // output_dim
) -> tensor<32x128xf32> {
// 初始化输出张量
%c0 = arith.constant 0.0 : f32
%output_init = tensor.empty() : tensor<32x128xf32>
%output_zero = linalg.fill ins(%c0 : f32) outs(%output_init : tensor<32x128xf32>) -> tensor<32x128xf32>
// 矩阵乘法: output = input * weight
%matmul_result = linalg.matmul
ins(%input, %weight : tensor<32x784xf32>, tensor<784x128xf32>)
outs(%output_zero : tensor<32x128xf32>) -> tensor<32x128xf32>
// 添加偏置: output = output + bias (广播)
%final_result = linalg.generic {
indexing_maps = [
affine_map<(d0, d1) -> (d0, d1)>, // matmul_result
affine_map<(d0, d1) -> (d1)>, // bias (广播)
affine_map<(d0, d1) -> (d0, d1)> // result
],
iterator_types = ["parallel", "parallel"]
} ins(%matmul_result, %bias : tensor<32x128xf32>, tensor<128xf32>)
outs(%output_init : tensor<32x128xf32>) {
^bb0(%mm: f32, %b: f32, %out: f32):
%add = arith.addf %mm, %b : f32
linalg.yield %add : f32
} -> tensor<32x128xf32>
return %final_result : tensor<32x128xf32>
}
2D 卷积层实现
func.func @conv2d_layer(
%input: tensor<1x28x28x1xf32>, // NHWC format
%filter: tensor<5x5x1x32xf32>, // HWIO format
%bias: tensor<32xf32>
) -> tensor<1x24x24x32xf32> {
// 初始化输出
%c0 = arith.constant 0.0 : f32
%output_init = tensor.empty() : tensor<1x24x24x32xf32>
%output_zero = linalg.fill ins(%c0 : f32) outs(%output_init : tensor<1x24x24x32xf32>) -> tensor<1x24x24x32xf32>
// 2D 卷积
%conv_result = linalg.conv_2d_nhwc_hwcf
ins(%input, %filter : tensor<1x28x28x1xf32>, tensor<5x5x1x32xf32>)
outs(%output_zero : tensor<1x24x24x32xf32>) -> tensor<1x24x24x32xf32>
// 添加偏置
%final_result = linalg.generic {
indexing_maps = [
affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>, // conv_result
affine_map<(d0, d1, d2, d3) -> (d3)>, // bias
affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)> // result
],
iterator_types = ["parallel", "parallel", "parallel", "parallel"]
} ins(%conv_result, %bias : tensor<1x24x24x32xf32>, tensor<32xf32>)
outs(%output_init : tensor<1x24x24x32xf32>) {
^bb0(%conv: f32, %b: f32, %out: f32):
%add = arith.addf %conv, %b : f32
linalg.yield %add : f32
} -> tensor<1x24x24x32xf32>
return %final_result : tensor<1x24x24x32xf32>
}
📈 性能优化技巧
1. 内存布局优化
// 选择合适的内存布局
// 行主序 (Row-major) vs 列主序 (Column-major)
%A_row_major = tensor.empty() : tensor<1024x1024xf32> // 默认行主序
%A_col_major = tensor.empty() : tensor<1024x1024xf32, #col_major_layout>
2. 数据类型优化
// 使用混合精度
%input_fp16 = tensor.empty() : tensor<1024x1024xf16> // 半精度输入
%weight_fp16 = tensor.empty() : tensor<1024x1024xf16> // 半精度权重
%output_fp32 = tensor.empty() : tensor<1024x1024xf32> // 单精度输出
// 混合精度矩阵乘法
%result = linalg.matmul
ins(%input_fp16, %weight_fp16 : tensor<1024x1024xf16>, tensor<1024x1024xf16>)
outs(%output_fp32 : tensor<1024x1024xf32>) -> tensor<1024x1024xf32>
3. 并行化策略
// 使用 async dialect 进行异步并行
%token1 = async.execute {
%result1 = linalg.matmul ins(%A1, %B1 : tensor<512x512xf32>, tensor<512x512xf32>)
outs(%C1 : tensor<512x512xf32>) -> tensor<512x512xf32>
async.yield %result1 : tensor<512x512xf32>
}
%token2 = async.execute {
%result2 = linalg.matmul ins(%A2, %B2 : tensor<512x512xf32>, tensor<512x512xf32>)
outs(%C2 : tensor<512x512xf32>) -> tensor<512x512xf32>
async.yield %result2 : tensor<512x512xf32>
}
%final1 = async.await %token1 : tensor<512x512xf32>
%final2 = async.await %token2 : tensor<512x512xf32>
🔧 调试和分析
1. 打印中间结果
// 使用 linalg.generic 添加调试输出
%debug_result = linalg.generic {
indexing_maps = [affine_map<(d0, d1) -> (d0, d1)>],
iterator_types = ["parallel", "parallel"]
} ins(%input : tensor<4x4xf32>) outs(%output : tensor<4x4xf32>) {
^bb0(%in: f32, %out: f32):
// 可以在这里添加调试逻辑
linalg.yield %in : f32
} -> tensor<4x4xf32>
2. 性能分析
// 使用 func.call 包装操作进行性能测量
func.func @benchmark_matmul(%A: tensor<1024x1024xf32>, %B: tensor<1024x1024xf32>) -> tensor<1024x1024xf32> {
%start_time = func.call @get_time() : () -> i64
%C_init = tensor.empty() : tensor<1024x1024xf32>
%result = linalg.matmul ins(%A, %B : tensor<1024x1024xf32>, tensor<1024x1024xf32>)
outs(%C_init : tensor<1024x1024xf32>) -> tensor<1024x1024xf32>
%end_time = func.call @get_time() : () -> i64
%duration = arith.subi %end_time, %start_time : i64
func.call @print_duration(%duration) : (i64) -> ()
return %result : tensor<1024x1024xf32>
}
📚 最佳实践
1. 操作选择
- 优先使用命名操作(如
linalg.matmul
)而不是通用操作 - 对于复杂计算,使用
linalg.generic
提供最大灵活性 - 考虑操作融合的可能性
2. 内存管理
- 合理使用
tensor.empty()
初始化输出张量 - 避免不必要的内存拷贝
- 考虑 in-place 操作的可能性
3. 类型系统
- 保持类型一致性
- 合理使用混合精度
- 注意张量形状的兼容性
4. 优化策略
- 从高级优化开始(融合、分块)
- 逐步降级到低级优化(向量化、并行化)
- 根据目标硬件调整优化策略