CV算子关键技术
1. 算法层面优化 (最高ROI)└→ 改进算法 > 优化实现2. 矩阵化优化 (巨大提升)└→ 能矩阵化一定要矩阵化3. 向量化优化 (必做基础)└→ 大数据量场景必须向量化4. 内存访问优化 (隐藏杀手)└→ 连续访问 + Cache优化5. 分块策略 (多核利用)└→ 合理分块 + 负载均衡6. 流水线优化 (锦上添花)└→ 计算访存重叠法则1:测量先于优化└→ 没有数据支撑的优化都是瞎搞法
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 我的个人收获
思维模式的转变:
-
系统化思维
- 不再是零散的技巧
- 形成了完整的方法论
- 能够举一反三
-
硬件意识
- 写代码时会思考硬件如何执行
- 理解性能瓶颈的本质
- 知道如何充分利用硬件
-
权衡思维
- 没有绝对的最优方案
- 要根据场景权衡
- 开发效率 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. 一次性任务
参考资料
- CANN性能优化指南:https://www.hiascend.com/document
- Roofline性能模型:理解计算和访存的平衡
- 算子优化案例集:昇腾社区优秀实践
课堂笔记整理人: [你的名字]
课程日期: 2025年X月X日
讲师: CANN优化团队
学习感悟:这节课把之前学的零散知识点串联起来了,形成了完整的知识体系。特别是优化层次论和决策框架,让我知道了在什么情况下该用什么技术。最大的收获是建立了"系统化优化"的思维模式!💡
更多推荐



所有评论(0)