CV算子关键技术

训练营简介

2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖

报名链接:https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro


思维导图

mindmap
  root((CV算子关键技术))
    课程定位
      系统化方法论
      四大技术模块
      优化决策框架
    关键优化技术
      向量化优化
        消除循环
        批量处理
        10-100倍提升
        数据对齐
      内存访问优化
        连续访问模式
        转置操作
        缓存利用率
        2-10倍提升
      矩阵化计算
        转换为矩阵乘法
        Cube单元利用
        50-500倍提升
        稀疏性优化
      分块策略
        多核并行
        滑动窗口
        L2 Cache优化
        核心数倍提升
    性能优化层次
      算法层面
        降低计算复杂度
        数学变换
        预计算
        ROI最高
      实现层面
        向量化
        矩阵化
        计算单元选择
      内存层面
        连续访问
        缓存优化
        数据预取
      硬件层面
        流水线设计
        指令级并行
        计算访存重叠
    优化决策框架
      数据规模
        小尺寸::向量化
        大尺寸::矩阵化
        测试确定平衡点
      计算模式
        矩阵运算::Cube
        向量运算::Vector
        控制流::Scalar
      硬件资源
        Local Memory
        L2 Cache
        多核特性
      应用场景
        端侧推理::低功耗
        云端训练::高性能
        精度要求
    开发实践
      理解算法原理
      分析数据访问
      性能测试分析
      迭代优化策略
      多版本实现
    课程总结
      优化技术优先级
      黄金法则
      学习roadmap

一、课程定位与学习目标

1.1 为什么要学习这门课?

老师在开课时说:“前面几节课我们学了具体的算子实现,但很多同学反馈:知识点太多,不知道如何系统化。这节课就是帮大家提炼出关键技术点,形成完整的优化方法论。”

我的理解:
这节课是"武功秘籍",把分散的招式整合成套路,让我们能够举一反三。

1.2 课程核心内容

老师列出了四大关键技术模块:

┌───────────────────────────────┐
│   CV算子关键技术体系图         │
├───────────────────────────────┤
│ 1. 关键优化技术解析           │
│    ├─ 向量化优化              │
│    ├─ 内存访问优化            │
│    ├─ 矩阵化计算              │
│    └─ 分块策略                │
├───────────────────────────────┤
│ 2. 性能优化层次论             │
│    ├─ 算法层面                │
│    ├─ 实现层面                │
│    ├─ 内存层面                │
│    └─ 硬件层面                │
├───────────────────────────────┤
│ 3. 优化决策框架               │
│    ├─ 数据规模判断            │
│    ├─ 计算模式分析            │
│    ├─ 硬件资源评估            │
│    └─ 场景需求匹配            │
├───────────────────────────────┤
│ 4. 硬件特性深度利用           │
│    └─ Scalar/Vector/Cube协同  │
└───────────────────────────────┘

二、关键优化技术深度解析

2.1 向量化优化——性能提升的第一步

(1)向量化的本质

老师说:“向量化不是简单地用Vector指令替换Scalar,而是一种思维方式的转变——从单个数据到批量数据。”

核心思想:

传统思维:一个一个处理
向量化思维:一批一批处理

就像超市收银:
- 传统:顾客排队,一个个结账
- 向量化:开多个收银台,同时服务
(2)向量化的典型场景

老师详细讲解了插值算子的向量化过程:

问题: 传统实现中的冗余计算

// 传统实现
for (int b = 0; b < batch; b++) {
    for (int c = 0; c < channels; c++) {  // 这里有重复!
        for (int h = 0; h < height; h++) {
            for (int w = 0; w < width; w++) {
                // 计算坐标和权重
                float src_x = calculate_x(h, w);
                float weight = calculate_weight(src_x);
                
                // 加权求和
                output[b][c][h][w] = interpolate(...);
            }
        }
    }
}

问题分析:

  • 每个channel都重复计算相同的坐标映射
  • 每个channel都重复计算相同的权重
  • 256个channel就重复256次!

向量化解决方案:

// 向量化实现
for (int h = 0; h < height; h++) {
    for (int w = 0; w < width; w++) {
        // ⭐ 权重只计算一次!
        float src_x = calculate_x(h, w);
        float weight = calculate_weight(src_x);
        
        // ⭐ 向量化处理所有channel
        __vec256 all_channels = vec_load(&input[0][h][w]);  // 加载所有channel
        __vec256 result = vec_mul(all_channels, weight);    // 一次计算所有channel
        vec_store(&output[0][h][w], result);
    }
}

性能对比:

老师给出的测试数据(256个channel):

实现方式 耗时 加速比
标量实现 850ms 1x
向量化 10ms 85x

我的震惊: 85倍的性能提升!这就是向量化的威力!

(3)向量化的代价与权衡

老师诚实地指出向量化也有代价:

代价1:开发复杂度增加

// Scalar代码:直观易懂
output = input * weight;

// Vector代码:需要考虑对齐、边界等
vec_input = vec_aligned_load(input, offset);
vec_weight = vec_broadcast(weight);
vec_output = vec_mul(vec_input, vec_weight);
vec_aligned_store(output, vec_output, offset);

代价2:数据对齐要求

未对齐数据 → 性能下降50%
需要padding → 内存浪费
边界处理   → 额外逻辑

老师的建议:
“对于大channel数(>64),向量化收益远大于代价,必须做!对于小channel数(<16),可能不值得。”

2.2 内存访问优化——隐藏的性能杀手

(1)内存墙问题

老师用一个生动的例子说明内存访问的重要性:

比喻:

计算单元 = 跑车(时速300km/h)
内存带宽 = 乡村小路(限速30km/h)

再好的跑车,在小路上也跑不快!

实测数据:

Cube算力:10 TFLOPS
DDR带宽:50 GB/s

如果每个数据只用一次:
理论性能 = 50GB/s / 4bytes × 2 = 25 GFLOPS
算力利用率 = 25/10000 = 0.25%

😱 99.75%的算力浪费了!
(2)连续访问 vs 跳跃访问

老师做了一个对比实验:

实验设置: 读取1GB数据

方案A:连续访问

for (int i = 0; i < n; i++) {
    data = input[i];  // 连续访问
}
时间:5ms
带宽利用率:95%

方案B:跳跃访问(stride=64)

for (int i = 0; i < n; i++) {
    data = input[i * 64];  // 每64个元素取一个
}
时间:50ms
带宽利用率:10%

性能差距:10倍!

原因分析:

老师画了缓存行的示意图(我用文字描述):

一个Cache Line = 64字节 = 16个float

连续访问:
[0][1][2][3]...[15]  ← 一次加载,用16个 ✓
利用率:100%

跳跃访问(stride=64):
[0][64][128][192]... ← 一次加载,只用1个 ✗
利用率:6.25%
(3)转置优化的实践

问题场景: NCHW格式下的channel维度处理

数据布局(NCHW):
Channel 0: [0, 1, 2, 3, ..., W*H-1]
Channel 1: [W*H, W*H+1, ..., 2*W*H-1]
...

访问同一位置的所有channel → 跳跃访问!

解决方案:转置为NHWC

转置后:
位置(0,0): [C0, C1, C2, ..., Cn]  ← 连续!
位置(0,1): [C0, C1, C2, ..., Cn]
...

老师的测试数据:

方案 访存时间 计算时间 总时间
不转置 50ms 10ms 60ms
转置优化 3ms+2ms(转置) 10ms 15ms

收益:4倍提升!

但是要注意:

小数据:转置开销 > 收益 → 不转置
大数据:转置开销 < 收益 → 转置

老师给的经验值:

  • 数据量 < 1MB:一般不转置
  • 数据量 > 10MB:必须转置
  • 1MB ~ 10MB:测试决定

2.3 矩阵化计算——性能优化的核武器

(1)矩阵化的思想转变

老师说这是最难理解但也最重要的优化技术。

核心思想:

不是所有运算都长得像矩阵乘法
但很多运算可以"变成"矩阵乘法!

老师的比喻:
“就像武功高手’化腐朽为神奇’,把普通招式转化为绝世武功!”

(2)经典案例:求和运算的矩阵化

老师用一个简单例子讲解矩阵化思想:

原始问题: 计算向量的和

// 普通方法
float sum = 0;
for (int i = 0; i < n; i++) {
    sum += data[i];
}

矩阵化思路:

把求和看作矩阵乘法:

data = [a₀, a₁, a₂, ..., aₙ]  (1×N矩阵)
ones = [1, 1, 1, ..., 1]ᵀ     (N×1全1矩阵)

sum = data @ ones = [a₀+a₁+a₂+...+aₙ]  (1×1矩阵)

我恍然大悟! 原来求和可以用矩阵乘法表示!

(3)插值算子的矩阵化

老师重点讲解了插值的矩阵化(这是最经典的案例):

双线性插值的传统计算:

对每个输出点(h, w):
1. 找到4个输入点
2. 计算4个权重
3. 加权求和

伪代码:
for h in output_h:
    for w in output_w:
        p = w0*Q00 + w1*Q01 + w2*Q10 + w3*Q11

矩阵化表示:

老师在黑板上推导(我记录如下):

观察1:所有输出点可以组成矩阵
Output = [p₀₀, p₀₁, ..., p_{H,W}]

观察2:横向插值可以写成矩阵形式
H_interp = Input @ H_weight
其中H_weight是横向权重矩阵

观察3:纵向插值也可以写成矩阵形式  
Output = V_weight @ H_interp

结论:完整插值 = 两次矩阵乘法
Output = V_weight @ Input @ H_weight

性能飞跃:

老师展示的性能对比(256×256 → 512×512):

实现 耗时 加速比
标量实现 320ms 1x
向量化 12ms 27x
矩阵化 0.8ms 400x

我惊呆了! 从向量化到矩阵化又提升了15倍!

(4)权重矩阵的稀疏性优化

老师进一步讲解如何利用稀疏性:

上采样场景(10×10 → 100×100):

权重矩阵形状:100×10

矩阵特点:
- 非常稀疏(90%以上是0)
- 非零元素集中在对角线附近

可视化(用字符表示):
[■ □ □ □ □ □ □ □ □ □]
[■ ■ □ □ □ □ □ □ □ □]
[□ ■ □ □ □ □ □ □ □ □]
[□ ■ ■ □ □ □ □ □ □ □]
...(主要在对角线)

■ = 非零,□ = 零

优化策略:滑动窗口

// 不优化:计算整个矩阵(包括很多零)
result = matmul(input, weight);  // 浪费90%计算

// 优化:只计算非零块
for (window in sliding_windows) {
    if (has_non_zero(window)) {
        result[window] = matmul(input[window], weight[window]);
    }
}
// 节省90%计算!

老师的实测:

  • 稀疏矩阵乘法(不优化):5ms
  • 滑动窗口优化后:0.5ms
  • 又提升了10倍!

2.4 分块策略——让多核火力全开

(1)为什么需要分块?

老师列举了三个原因:

原因1:硬件内存限制

Local Memory容量:512KB
大矩阵:1024×1024×4B = 4MB

4MB > 512KB → 放不下!
必须分块处理

原因2:多核并行需求

Atlas 310: 32个AI Core

整块数据 → 只用1个核心 → 浪费31个
分成32块 → 32个核心同时工作 → 性能提升32倍

原因3:缓存优化

L2 Cache: 2MB

工作集 < 2MB → Cache命中率95%
工作集 > 2MB → Cache命中率<20%

合理分块 → 每块<2MB → 保持高命中率
(2)分块策略设计

老师详细讲解了如何设计分块:

步骤1:确定单块大小

# 考虑因素
local_memory_size = 512 * 1024  # 512KB
l2_cache_size = 2 * 1024 * 1024  # 2MB

# 单块大小应满足:
block_size <= local_memory_size / 2  # 留一半给其他数据

# 对于矩阵(M×N)
max_M = sqrt(block_size / element_size)
max_N = max_M

步骤2:计算分块数量

num_blocks_h = ceil(height / block_h)
num_blocks_w = ceil(width / block_w)
total_blocks = num_blocks_h * num_blocks_w

步骤3:分配到核心

num_cores = 32
blocks_per_core = ceil(total_blocks / num_cores)

# 负载均衡很重要!
# 理想情况:每个核心工作量相同
(3)滑动窗口分块实战

老师用插值算子举例:

场景: 256×256 上采样到 512×512

分块方案:

输出:512×512
块大小:64×64(适应Local Memory)
分块数:8×8 = 64个块
核心数:32
每核心:2个块

输入对应关系:
输出块[64×64] ← 输入窗口[32×32 + padding]

滑动窗口示意:

输出块: [0,0]-[64,64]
     ↓
输入窗口: [0,0]-[34,34]  (多2个边界)

输出块: [64,0]-[128,64]  
     ↓
输入窗口: [30,0]-[66,34]  (窗口滑动)

性能测试:

方案 耗时 核心利用率
不分块(单核) 25ms 3%
分块但负载不均 5ms 50%
优化分块 0.8ms 95%

三、性能优化的四个层次

3.1 层次划分

老师提出了一个优化层次模型:

┌─────────────────────────────┐
│  算法层面(最高层)          │  效果:数量级提升
│  - 降低计算复杂度            │  难度:⭐⭐⭐⭐⭐
├─────────────────────────────┤
│  实现层面(代码优化)        │  效果:几十倍提升
│  - 向量化/矩阵化             │  难度:⭐⭐⭐⭐
├─────────────────────────────┤
│  内存层面(访存优化)        │  效果:几倍提升
│  - 连续访问/缓存优化         │  难度:⭐⭐⭐
├─────────────────────────────┤
│  硬件层面(底层调优)        │  效果:倍数提升
│  - 流水线/指令级并行         │  难度:⭐⭐⭐⭐⭐
└─────────────────────────────┘

3.2 算法层面优化

老师说:“这是投入产出比最高的优化!”

案例1:快速傅里叶变换(FFT)
问题:计算N点的DFT

朴素算法:O(N²)
  N=1024 → 1,048,576次运算

FFT算法:O(N log N)  
  N=1024 → 10,240次运算

加速比:100倍!

老师强调: 算法改进是从根本上减少计算量,不受硬件限制!

案例2:插值算法改进
传统双线性插值:
- 每个输出点访问4个输入点
- 计算4个权重
- 4次乘法 + 3次加法

优化思路:预计算权重表
- 离线计算所有可能的权重
- 存储在查找表中
- 运行时直接查表

时间换空间:
- 运行时计算 → 10ms
- 查表方案   → 2ms
案例3:利用数学性质

老师讲了一个巧妙的例子:

// 计算 1/(1 + exp(-x))  (sigmoid函数)

// 朴素方法:
float sigmoid(float x) {
    return 1.0f / (1.0f + exp(-x));
    // 包含:exp + 加法 + 除法
}

// 优化:利用对称性
float sigmoid_fast(float x) {
    if (x >= 0) {
        float z = exp(-x);
        return 1.0f / (1.0f + z);
    } else {
        float z = exp(x);
        return z / (1.0f + z);  // 避免exp溢出
    }
}

// 再优化:查找表近似
float sigmoid_lut(float x) {
    int index = quantize(x);  // 量化到离散值
    return lookup_table[index];  // 查表
}

3.3 实现层面优化

这一层就是我们前面学的向量化、矩阵化等技术。

老师总结的关键点:

优化技术对比:

技术 提升倍数 适用场景 难度
向量化 10-100x 向量运算 ⭐⭐⭐
矩阵化 50-500x 矩阵运算 ⭐⭐⭐⭐
循环展开 1.2-2x 小循环 ⭐⭐
多线程 核心数x 并行任务 ⭐⭐⭐⭐

3.4 内存层面优化

老师说:“算力过剩的时代,内存才是瓶颈!”

优化技巧清单:

(1)数据复用
// ❌ 重复访存
for (int i = 0; i < n; i++) {
    result += array[i] * array[i];  // array[i]访问2次
}

// ✓ 复用数据
for (int i = 0; i < n; i++) {
    float val = array[i];  // 只访问1次
    result += val * val;
}
(2)数据预取
// 提前加载下一轮需要的数据
for (int i = 0; i < n; i++) {
    prefetch(&data[i + 64]);  // 提前64个元素预取
    process(data[i]);
}
(3)缓存分块
// 大矩阵乘法:分块计算
for (int ii = 0; ii < M; ii += BLOCK) {
    for (int jj = 0; jj < N; jj += BLOCK) {
        // 这个小块能放入Cache
        matmul_block(C[ii][jj], A[ii], B[jj], BLOCK);
    }
}

3.5 硬件层面优化

老师说这是最难但也最精细的优化。

(1)流水线设计
时间 →
  
Core 0: [读数据][计算][写数据][读数据][计算][写数据]
Core 1:    [读数据][计算][写数据][读数据][计算][写数据]

优化为流水线:

Core 0: [读1][算1][写1][读2][算2][写2]
Core 1:    [读1][算1][写1][读2][算2][写2]

读写和计算重叠 → 提升30%吞吐量
(2)指令级并行
// 串行指令
a = b + c;  // 等待
d = e * f;  // 等待
g = h - i;  // 等待

// 并行发射(编译器优化或手动调度)
a = b + c;  ┐
d = e * f;  ├─ 同时执行(无依赖)
g = h - i;

四、优化决策框架

4.1 数据规模决策

老师给出了一个决策表:

小数据(<1KB)
建议方案:
✓ 简单直接的实现
✓ Scalar或简单Vector
✗ 避免复杂的优化(开销大于收益)
✗ 不要分块
✗ 不要转置

理由: 优化的固定开销会抵消收益

中等数据(1KB ~ 1MB)
建议方案:
✓ 向量化必须做
✓ 考虑简单的矩阵化
? 转置需要测试决定
✓ 适度分块(利用多核)

理由: 优化收益开始显现,但要权衡

大数据(>1MB)
建议方案:
✓ 向量化必做
✓ 矩阵化必做(如果适用)
✓ 转置必做(如果有访存问题)
✓ 精细分块设计
✓ 流水线优化

理由: 优化收益远大于开销,值得投入

4.2 计算模式决策

老师的决策树:

计算类型?
  │
  ├─ 矩阵乘法
  │   └→ 必用Cube单元(加速数百倍)
  │
  ├─ 向量运算
  │   ├─ 数据量大?
  │   │   ├─ 是 → Vector单元
  │   │   └─ 否 → Scalar/简单Vector
  │   │
  │   └─ 能转矩阵吗?
  │       ├─ 能 → 考虑矩阵化
  │       └─ 不能 → 向量化
  │
  └─ 复杂控制流
      └→ Scalar单元(无可替代)

4.3 硬件资源评估

评估清单:

□ Local Memory容量:_____ KB
  └→ 决定单块大小上限

□ L2 Cache容量:_____ MB
  └→ 决定工作集大小

□ AI Core数量:_____ 个
  └→ 决定并行度

□ 内存带宽:_____ GB/s
  └→ 判断是否访存受限

□ 计算峰值:_____ TFLOPS
  └→ 评估算力利用率

老师的经验公式:

# 判断是否访存受限
compute_intensity = FLOPs / Bytes_accessed

if compute_intensity < (Peak_FLOPS / Memory_BW):
    print("访存受限!优先优化内存访问")
else:
    print("计算受限!优先优化计算")

4.4 应用场景匹配

老师对比了不同场景的优化侧重:

场景 优先指标 优化重点
云端训练 吞吐量 极致性能,可接受高功耗
端侧推理 延迟+功耗 平衡性能和功耗
实时系统 最差延迟 稳定性>峰值性能
批处理 总吞吐量 大batch优化

五、开发实践建议

5.1 深入理解算法原理

老师说:“很多人急于动手写代码,结果走了很多弯路。先理解算法,优化才有方向!”

学习路径:

第1步:数学推导
  - 写出算法的数学公式
  - 分析计算复杂度
  - 找出可优化的点

第2步:数据流分析
  - 画出数据依赖图
  - 找出可并行的部分
  - 识别重复计算

第3步:寻找优化机会
  - 能否转换为矩阵形式?
  - 有没有数学对称性可利用?
  - 能否预计算?

5.2 分析数据访问模式

老师强调:“内存访问是性能杀手,必须重视!”

分析checklist:

□ 是否连续访问?
  - 连续 ✓
  - 跳跃 ✗ → 考虑转置

□ 数据复用率如何?
  - 高复用 ✓ → 利用缓存
  - 低复用 ✗ → 考虑算法改进

□ 访存带宽利用率?
  - >80% ✓ 优秀
  - <50% ✗ 需要优化

□ Cache命中率?
  - >90% ✓ 优秀
  - <70% ✗ 考虑分块优化

5.3 性能测试与分析

老师推荐的测试方法:

(1)建立性能baseline
# 测试框架
def benchmark(func, data, iterations=100):
    # 预热
    for _ in range(10):
        func(data)
    
    # 正式测试
    start = time.time()
    for _ in range(iterations):
        func(data)
    end = time.time()
    
    avg_time = (end - start) / iterations
    throughput = calculate_throughput(data_size, avg_time)
    
    return avg_time, throughput
(2)性能对比表

老师建议维护一个性能对比表:

版本 耗时 吞吐量 算力利用率 备注
v1_baseline 100ms 10 GFLOPS 5% 初始实现
v2_vectorize 15ms 67 GFLOPS 30% 向量化
v3_matmul 2ms 500 GFLOPS 80% 矩阵化
v4_tiling 0.8ms 1250 GFLOPS 95% 分块优化
(3)性能分析工具
# 使用profiler分析
msprof --application="./my_op" --output=profile

# 查看热点函数
msprof --view=timeline profile.data

# 分析内存访问
msprof --view=memory profile.data

5.4 迭代优化策略

老师的迭代流程:

┌─────────────────────┐
│ 1. 实现正确的版本   │
│    (功能优先)       │
└──────────┬──────────┘
           ↓
┌─────────────────────┐
│ 2. 性能测试         │
│    (找出瓶颈)       │
└──────────┬──────────┘
           ↓
┌─────────────────────┐
│ 3. 针对性优化       │
│    (解决最大瓶颈)   │
└──────────┬──────────┘
           ↓
┌─────────────────────┐
│ 4. 验证正确性       │
│    (回归测试)       │
└──────────┬──────────┘
           ↓
      性能达标?
       ↓    ↓
      是    否 → 回到步骤2
       ↓
     完成

老师强调:
“每次只优化一个点,优化后立即测试。这样出了问题容易定位!”


六、课程总结与学习心得

6.1 老师的核心总结

优化技术的优先级排序:

1. 算法层面优化 (最高ROI)
   └→ 改进算法 > 优化实现

2. 矩阵化优化 (巨大提升)
   └→ 能矩阵化一定要矩阵化

3. 向量化优化 (必做基础)
   └→ 大数据量场景必须向量化

4. 内存访问优化 (隐藏杀手)
   └→ 连续访问 + Cache优化

5. 分块策略 (多核利用)
   └→ 合理分块 + 负载均衡

6. 流水线优化 (锦上添花)
   └→ 计算访存重叠

性能优化的黄金法则:

法则1:测量先于优化
  └→ 没有数据支撑的优化都是瞎搞

法则2:优化最大瓶颈
  └→ 80%时间花在20%代码上

法则3:理解硬件特性
  └→ 顺应硬件,事半功倍

法则4:保持代码正确性
  └→ 快但错的代码毫无价值

法则5:持续迭代改进
  └→ 优化是个渐进过程

6.2 我的个人收获

思维模式的转变:

  1. 系统化思维

    • 不再是零散的技巧
    • 形成了完整的方法论
    • 能够举一反三
  2. 硬件意识

    • 写代码时会思考硬件如何执行
    • 理解性能瓶颈的本质
    • 知道如何充分利用硬件
  3. 权衡思维

    • 没有绝对的最优方案
    • 要根据场景权衡
    • 开发效率 vs 运行效率

技术能力的提升:

优化前我的水平:
- 能写出功能正确的代码
- 不知道如何优化
- 性能差距10-100倍

优化后我能做到:
- 分析性能瓶颈
- 选择合适的优化策略  
- 接近硬件理论性能

6.3 学习roadmap

短期目标(1个月):

  • 实现一个向量化的算子
  • 尝试矩阵化优化
  • 掌握性能测试工具

中期目标(3个月):

  • 优化5个不同类型的算子
  • 算力利用率达到80%以上
  • 形成自己的优化checklist

长期目标(6个月):

  • 贡献高性能算子到社区
  • 能够设计新算子的优化方案
  • 分享优化经验

6.4 重要知识点卡片

卡片1:优化技术对比

向量化: 10-100x, 基础必做
矩阵化: 50-500x, 核心技术
分块: 核心数x, 多核利用
内存优化: 2-10x, 隐藏收益

卡片2:决策关键点

数据量 → 选择优化技术
计算模式 → 选择计算单元
硬件资源 → 设计分块策略
应用场景 → 权衡优化目标

卡片3:常见性能指标

算力利用率 >80%: 优秀
            50-80%: 良好
            <50%: 需要优化

Cache命中率 >90%: 优秀
            70-90%: 良好  
            <70%: 需要优化

访存带宽利用率 >70%: 优秀
                <50%: 有优化空间

七、课后思考题

思考题1:为什么矩阵化比向量化提升更大?

我的理解:

  • Vector单元:128个数据/周期
  • Cube单元:256个数据+专用脉动阵列
  • 矩阵化不仅数据并行度更高,而且硬件专用化程度更高
  • 算法特性(矩阵乘法的密集计算)也更适合硬件

思考题2:如何判断一个算子是访存受限还是计算受限?

我的方法:

# 计算强度
compute_intensity = total_ops / total_bytes

# 硬件平衡点
hardware_balance = peak_flops / memory_bandwidth

if compute_intensity < hardware_balance:
    print("访存受限")
else:
    print("计算受限")

思考题3:优化的投入产出比如何评估?

我的思路:

ROI = 性能提升 / (开发时间 + 维护成本)

优先优化:
1. 提升大、投入小的(如向量化大数据)
2. 影响广的(核心算子)
3. 长期运行的(训练/批处理任务)

谨慎优化:
1. 提升小、投入大的(细节优化)
2. 使用频率低的(冷门算子)
3. 一次性任务

参考资料

  1. CANN性能优化指南:https://www.hiascend.com/document
  2. Roofline性能模型:理解计算和访存的平衡
  3. 算子优化案例集:昇腾社区优秀实践

课堂笔记整理人: [你的名字]
课程日期: 2025年X月X日
讲师: CANN优化团队

学习感悟:这节课把之前学的零散知识点串联起来了,形成了完整的知识体系。特别是优化层次论和决策框架,让我知道了在什么情况下该用什么技术。最大的收获是建立了"系统化优化"的思维模式!💡

Logo

CANN开发者社区旨在汇聚广大开发者,围绕CANN架构重构、算子开发、部署应用优化等核心方向,展开深度交流与思想碰撞,携手共同促进CANN开放生态突破!

更多推荐