前言

你写一行 torch.matmul(x, y),背后发生了什么?

从 PyTorch 的一次 torch.matmul,到昇腾 NPU 真正执行矩阵乘法,中间经过了 ops-nn、ops-blas、catlass 三层仓库的接力。这篇文章用费曼科普的方式,把这条调用链拆干净,让你能说清楚每层在干什么。

从一次 torch.matmul 说起

Python 代码到 NPU 执行

import torch

# 这行代码触发的调用链:
x = torch.randn(16, 512)
y = torch.randn(512, 2048)

# 方案1:直接 matmul
z1 = torch.matmul(x, y)

# 方案2:通过 torch_npu(昇腾适配)
z2 = torch_npu.matmul(x, y)

无论哪种写法,最终都要落到昇腾 NPU 的硬件上执行。

调用链的五层

整体架构图

┌─────────────────────────────────────────────┐
│  PyTorch / torch_npu                        │  ← 第一层:框架适配
└────────────────────┬──────────────────────┘
                     │
┌────────────────────▼──────────────────────┐
│  AscendCL(aclnnMatMul)                  │  ← 第二层:C API
└────────────────────┬──────────────────────┘
                     │
┌────────────────────▼──────────────────────┐
│  ops-nn(MatMul + 融合)                  │  ← 第三层:算子库
└────────────────────┬──────────────────────┘
                     │
┌────────────────────▼──────────────────────┐
│  ops-blas(GEMM 分块)                    │  ← 第四层:BLAS 库
└────────────────────┬──────────────────────┘
                     │
┌────────────────────▼──────────────────────┐
│  catlass(GEMM 模板)                    │  ← 第五层:硬件模板
└────────────────────┬──────────────────────┘
                     │
┌────────────────────▼──────────────────────┐
│  昇腾达芬奇 NPU(CUBE 单元执行)          │  ← 硬件层
└─────────────────────────────────────────────┘

每层干什么

层级 仓库 职责 关键产物
框架适配 torch_npu PyTorch → AscendCL 算子注册
C API AscendCL 统一接口,参数校验 aclnnMatMul
算子库 ops-nn 融合、融合策略 MatMul+Bias+Act
BLAS 库 ops-blas 分块调度、缓存优化 GEMM
硬件模板 catlass Cube 指令生成 汇编
硬件 达芬奇 NPU 矩阵乘法 矩阵结果

第三层:ops-nn 的 MatMul

ops-nn 里的 MatMul 是什么

ops-nn 是"神经网络层算子库",它的 MatMul 比 ops-blas 的 GEMM 多了一些东西:

ops-nn MatMul = GEMM + BiasAdd + Activation
# ops-nn MatMul 的融合模式
# 原始调用(3次 NPU 调用):
# 1. MatMul
# 2. BiasAdd
# 3. Activation(GELU/SiLU/ReLU)

# ops-nn 融合后(1次 NPU 调用):
# 一个 kernel 跑完 MatMul + Bias + GELU

为什么 ops-nn 要融合

调用方式 Launch 开销 数据搬运 总耗时
分离调用 3×1ms 3×HBM 5ms
融合调用 1×1ms 1×HBM 2ms

融合后,数据只搬一次,kernel 只 launch 一次。

ops-nn MatMul 的融合配置

import cann

# 创建融合 MatMul(MatMul + BiasAdd + GELU)
matmul_op = cann.ops.nn.MatMul(
    transpose_a=False,
    transpose_b=False,
    activation="GELU",  # 激活函数
    output_dtype="float16"
)

# 调用
output = matmul_op(input_tensor, weight_tensor, bias_tensor)

第四层:ops-blas 的 GEMM

ops-blas 是什么

ops-blas 是"基础线性代数算子库",专注矩阵乘法(GEMM = GEneral Matrix Multiply)。

# ops-blas GEMM 的核心参数
def gemm(a, b, c=None,
         alpha=1.0, beta=0.0,
         transpose_a=False, transpose_b=False,
         m=None, n=None, k=None):
    """
    c = alpha * op(A) @ op(B) + beta * c
    
    A: (m, k) 或 (k, m) 如果转置
    B: (k, n) 或 (n, k) 如果转置
    C: (m, n) 输出
    """

GEMM 的分块策略

为什么要分块?因为 Cube 单元的寄存器有限,一次算不完整个矩阵。

原始矩阵乘法:

A (m×k) × B (k×n) = C (m×n)

┌────────┐     ┌────────┐
│        │     │        │
│   A    │  ×  │   B    │
│        │     │        │
└────────┘     └────────┘

分块后:

A 被切成 m/M 个 tile(每块 M×k)
B 被切成 k/K 个 tile(每块 k×n)
C = Σ (A_tile_i × B_tile_i)

每个 tile 刚好放得进 Cube 单元的缓存

ops-blas 的缓存优化

# ops-blas GEMM 的 L1 Cache 优化策略

class GEMMTiler:
    """
    把大矩阵切成小 tile,充分利用 L1 Cache
    
    L1 Cache 大小:昇腾 910B 是 64KB
    """
    
    def __init__(self, m, n, k, l1_size=64*1024):
        # 计算最优分块大小
        # 每个 tile 需要存 A 的 M×K 和 B 的 K×N
        # 2 × M × K × sizeof(half) ≤ 64KB
        
        self.block_m = min(512, m)    # 输出 tile 大小
        self.block_k = min(256, k)    # 中间维度
        self.block_n = min(512, n)
    
    def tile(self, A, B, C):
        """分块执行"""
        for m_start in range(0, A.shape[0], self.block_m):
            for n_start in range(0, B.shape[1], self.block_n):
                # 加载当前 tile 到 L1
                A_tile = A[m_start : m_start+self.block_m, :]
                B_tile = B[:, n_start : n_start+self.block_n]
                
                # Cube 计算
                C_tile = self.cube_matmul(A_tile, B_tile)
                
                # 累加到 C
                C[m_start : m_start+self.block_m,
                  n_start : n_start+self.block_n] += C_tile

第五层:catlass 模板

catlass 是什么

catlass 是"昇腾算子模板库",它给 ops-blas 提供 GEMM 的底层实现。

# catlass GEMM 模板的参数化接口
# 来自 catlass/gemm/template.h

class GemmTemplate {
    // 矩阵形状
    uint32_t m;  // A 的行数,C 的行数
    uint32_t n;  // B 的列数,C 的列数
    uint32_t k;  // A 的列数,B 的行数
    
    // 数据类型
    DataType dtype_a;  // A 的数据类型
    DataType dtype_b;  // B 的数据类型
    DataType dtype_c;  // C 的数据类型
    
    // 分块参数
    uint32_t block_m;  // M 方向分块
    uint32_t block_n;  // N 方向分块
    uint32_t block_k;  // K 方向分块
    
    // 硬件配置
    CubeUnit cube_unit;
    VectorUnit vector_unit;
};

catlass 生成的是什么

catlass 模板实例化后,生成的是昇腾达芬奇 NPU 的汇编代码

┌──────────────────────────────────────┐
│  catlass 模板                        │
│  ↓ 实例化                            │
│  生成的汇编(Vector 指令 + Cube 指令)│
│  ↓ 编译                              │
│  NPU 可执行 kernel                   │
└──────────────────────────────────────┘

完整调用链代码

# 完整调用链示例
import torch
import torch_npu

# 1. PyTorch 调用(框架层)
x = torch.randn(16, 512).npu()      # 移到 NPU
w = torch.randn(2048, 512).npu()   # 移到 NPU

# 2. torch_npu 拦截,调用 AscendCL(API 层)
# torch_npu 内部:aclnnMatMul(x, w, &output)

# 3. AscendCL 调用 ops-nn MatMul(算子层)
# ops-nn 内部:matmul = ops.blas.gemm()
# ops-nn 内部:output = matmul(x, w)

# 4. ops-nn 调用 ops-blas GEMM(BLAS 层)
# ops-blas 内部:tiler = GEMMTiler(m=16, k=512, n=2048)
# ops-blas 内部:result = tiler.tile()

# 5. ops-blas 调用 catlass 模板(模板层)
# catlass 内部:生成汇编,提交 Cube 指令

# 6. 达芬奇 NPU 执行(CUBE 单元)
# 最终计算在硬件上完成

output = torch_npu.matmul(x, w.t())  # 最终输出

ops-nn MatMul vs ops-blas GEMM:什么时候用哪个

场景 推荐 理由
Transformer 前向推理 ops-nn MatMul 融合 Bias+Activation,省一次数据搬运
BERT / GPT 推理 ops-nn MatMul 多层堆叠,融合节省明显
裸矩阵乘法(无激活) ops-blas GEMM 跳过融合开销,更直接
自定义融合模式 ops-blas + catlass 从底层自己拼
性能 profiling 两层都要看 看 ops-nn 的融合是否生效

附录:ops-nn vs ops-blas vs catlass 的关系图

ops-nn
  ├── MatMul(融合版)
  │     ├── 内部调用 ops-blas
  │     └── 融合 Bias + Activation
  ├── Conv2d
  ├── LayerNorm
  └── Softmax

ops-blas
  ├── GEMM(基础版)
  │     ├── 内部调用 catlass
  │     └── 分块 + 缓存优化
  ├── Batch GEMM
  └── Strided GEMM

catlass
  ├── GemmTemplate
  ├── ConvTemplate
  └── AttentionTemplate

选择建议

  • 只需要矩阵乘 + 激活 → ops-nn

  • 需要裸 GEMM → ops-blas

  • 需要深度定制 → catlass

常见问题 FAQ

Q1: ops-nn 的融合会自动开启吗?
是的,默认开启。但只有 MatMul+Bias+Activation 连续调用时才会融合。

Q2: 融合后精度掉了?
检查是否开启了错误的融合模式。用精度对比工具验证。

Q3: 为什么有时候 ops-blas 比 ops-nn 更快?
融合有 overhead。如果只有 MatMul 没有后续操作,ops-nn 的融合反而浪费。

性能调优实践

profiling 工具

import cann.profiler as profiler

# profiling MatMul 调用
with profiler.Profile("matmul_profile.pb") as p:
    for _ in range(1000):
        result = matmul_op(x, w)

report = p.get_report()
# 输出:
# - MatMul kernel 耗时
# - BiasAdd kernel 耗时
# - Fusion 融合效果
# - L1/L2 Cache 命中率

# 如果 Cache 命中率 < 80%,ops-blas 分块参数需要调

分块参数调优

# catlass 手动分块(高级用法)
tiler = cann.ops.blas.GEMMTiler(
    m=512, k=512, n=2048,
    block_m=64,   # M 方向分块
    block_k=128,  # K 方向分块
    block_n=64    # N 方向分块
)

# L1 Cache 命中率测试
for bm in [32, 64, 128, 256]:
    for bk in [64, 128, 256]:
        for bn in [32, 64, 128]:
            tiler.block_m = bm
            tiler.block_k = bk
            tiler.block_n = bn
            
            # 测试性能
            elapsed = benchmark(tiler)
            cache_hit = tiler.get_l1_hit_rate()
            
            if cache_hit > 0.85 and elapsed < best_time:
                best = (bm, bk, bn)

精度 vs 性能权衡

# 融合后精度对比
def check_fusion_accuracy():
    # FP32 基准
    result_fp32 = model_fp32(input)
    
    # FP16 + 融合
    result_fp16 = model_fp16_fused(input)
    
    # 计算相对误差
    diff = torch.abs(result_fp16 - result_fp32) / (torch.abs(result_fp32) + 1e-8)
    max_rel_err = diff.max()
    
    print(f"Max relative error: {max_rel_err:.6f}")
    
    # 误差阈值:< 1% 通常可接受
    if max_rel_err > 0.01:
        print("⚠️  Fusion accuracy degradation detected")
    else:
        print("✅  Fusion accuracy OK")

总结

torch.matmul 到 NPU 执行:

  1. PyTorch → torch_npu 拦截
  2. AscendCL → 参数校验、API 分发
  3. ops-nn → 融合 MatMul+Bias+Act
  4. ops-blas → GEMM 分块、缓存优化
  5. catlass → 生成 Cube 指令
  6. 达芬奇 NPU → 硬件执行

理解调用链的价值:遇到性能问题,你知道该优化哪一层。融合不够去 ops-nn,缓存不命中去 ops-blas,指令生成有瓶颈去 catlass。

仓库地址:https://atomgit.com/cann/ops-nn

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐