一、算子性能分析基础

1.1 算子执行模型

昇腾上每个算子的执行都会经历:编译时优化运行时调度硬件执行。任何一个环节出问题都会导致性能下降。

┌────────────────────────────────────────┐
│          算子执行流程                  │
├────────────────────────────────────────┤
│                                        │
│  编译时                                │
│  算子融合 → 图优化 → 内存规划 → 代码生成 │
│      ↓                                  │
│  运行时                                │
│  任务提交 → Stream 调度 → 等待依赖     │
│      ↓                                  │
│  硬件执行                              │
│  Cube/Vector/Scalar → 同步结果        │
│                                        │
└────────────────────────────────────────┘

1.2 常见瓶颈类型

瓶颈类型 表现 定位方法
计算瓶颈 算子本身耗时长 Profiling 时间线
内存瓶颈 带宽利用率高、延迟大 内存 Profiling
调度瓶颈 Stream 空闲、等待久 Timeline 分析
同步瓶颈 频繁等待、流水线断流 Timeline 分析

二、Profiling 定位瓶颈

2.1 算子级 Profiling

# 8.1 及之前:基础 Profiling
export ASCEND_PROFILING_ENABLE=1
export ASCEND_PROFILING_OPTIONS="tensor_dump,trace

,output:/workspace/profiling_data"

python train.py

# 8.2 新增:算子级 Profiling
export ASCEND_PROFILING_ENABLE=1
export ASCEND_PROFILING_OPTIONS="op_stats,output:/workspace/op_profiling"

2.2 Timeline 分析

Profiling 报告中的 Timeline 可以直观看出问题:

# 8.2 新增:Timeline 事件标注
import ascend_profiling as ap

profiler = ap.Profiler()

profiler.start()
# ... 训练代码 ...
profiler.stop()

# 分析结果
report = profiler.report()
for event in report.timeline_events:
    if event.duration > 1.0:  # 耗时超过 1ms 的事件
        print(f"{event.name}: {event.duration:.2f}ms")

2.3 算子耗时排序

# 8.2 新增:算子耗时统计
import ascend_profiling as ap

profiler = ap.Profiler()
profiler.start()

# 运行训练
for batch in dataloader:
    output = model(batch)
    loss.backward()

profiler.stop()

# 输出算子级别统计
stats = profiler.operator_stats()
sorted_stats = sorted(stats.items(), key=lambda x: x[1], reverse=True)

print("Top 10 slowest operators:")
for name, duration in sorted_stats[:10]:
    print(f"  {name}: {duration:.2f}ms")

三、算子融合优化

3.1 为什么融合能加速

每执行一个算子都有固定开销(Kernel Launch、数据移动等)。融合多个算子可以减少这些开销,同时让编译器做更好的优化。

融合前 融合后
Conv2d → BN → ReLU(3 次 Kernel Launch) Conv_BN_ReLU(1 次 Kernel Launch)
每次独立显存分配 一次分配,中间结果复用

3.2 常见融合模式

模式 1:Conv + BN + Act 融合

# 融合前:三个独立算子
class UnfusedModel(nn.Module):
    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

# 融合后:ATC 自动识别并融合
# 用户只需确保算子顺序符合融合 pattern
class FusedModel(nn.Module):
    def forward(self, x):
        # CANN 会自动识别 conv+bn+relu 并融合
        x = self.conv_bn_relu(x)
        return x

模式 2:MatMul + Bias + Act 融合

# 融合前
def unfused_attention(x, weight, bias):
    x = torch.matmul(x, weight)  # MatMul
    x = x + bias                  # Add
    x = torch.relu(x)             # ReLU
    return x

# 融合后(编译器自动识别)
# 不需要改代码,保持正确顺序即可
def fused_attention(x, weight, bias):
    return torch.nn.functional.linear(x, weight, bias)  # 编译器融合

3.3 融合规则与例外

算子组合 可融合 说明
Conv2d + BN 训练和推理均可融合
MatMul + Add + Act 激活函数种类决定是否能融合
MatMul + Softmax 编译器识别 pattern
Conv2d + Dropout Dropout 融合收益低
MatMul + Reshape Reshape 打断融合

四、内存优化

4.1 内存复用策略

昇腾的 Unified Buffer 大小有限,需要合理复用:

# 8.2 新增:内存复用配置
import ascend_npu as npu

# 设置全局内存池
npu.set_memory_mode("pool", max_memory_gb=16)

# 单算子内存优化
npu.set_op_memory_reuse("MatMul", enabled=True)
npu.set_op_memory_reuse("Conv2d", enabled=True)

4.2 原地计算(In-place)

# 原地计算可以省显存
class InPlaceModel(nn.Module):
    def forward(self, x):
        # 原地 ReLU,节省一个中间张量
        x = torch.relu_(x)  # _ 表示 in-place
        
        # 原地操作列表
        # torch.relu_(x)
        # torch.sigmoid_(x)
        # torch.tanh_(x)
        
        return x

4.3 Gradient Checkpointing

显存受限时可以用时间换空间:

# 8.2 新增:Gradient Checkpointing
from torch.utils.checkpoint import checkpoint

class CheckpointedModel(nn.Module):
    def forward(self, x):
        # 中间结果不保存,反向时重新计算
        x = checkpoint(self.layer1, x)
        x = checkpoint(self.layer2, x)
        x = checkpoint(self.layer3, x)
        return x

五、数据加载优化

5.1 数据预取

训练中 GPU/NPU 等待数据是常见的瓶颈:

# 8.1 及之前:单线程加载
for batch in dataloader:
    data = batch['image']  # 等待加载完成才开始计算

# 8.2 新增:数据预取
from torch.utils.data import DataLoader

dataloader = DataLoader(
    dataset,
    batch_size=32,
    num_workers=4,           # 多线程加载
    prefetch_factor=2,       # 预取因子
    pin_memory=True           # Pinned memory 加速传输
)

# 配合 NPU 异步执行
for batch in dataloader:
    data = data.npu(non_blocking=True)  # 异步传输
    output = model(data)

5.2 混合精度数据加载

# 数据加载时直接用 FP16
class NPUDataLoader:
    def __init__(self, dataloader):
        self.dataloader = dataloader
    
    def __iter__(self):
        for batch in self.dataloader:
            # 异步传输到 NPU
            batch_npu = batch['data'].npu(non_blocking=True)
            
            # 转 FP16(如果模型用混合精度)
            batch_npu = batch_npu.half()
            
            yield batch_npu

六、常见问题与解决

问题 诊断 解决方案
某算子耗时异常高 Profiling 看 Timeline 检查 shape 是否最优
显存 OOM nvidia-smi / Profiling Gradient Checkpointing
多卡训练慢 通信 Profiling 优化 HCCL 参数
预热后还是慢 检查 Core Type 指定 Cube/Vector
融合未生效 检查算子顺序 确保符合融合 pattern

相关仓库

  • torch_npu - 数据加载优化 https://atomgit.com/cann/ops-nn
  • ASCEND - 算子融合规则 https://atomgit.com/cann/ascend-transformer-boost
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐