昇腾AI芯片与算子开发
3.3 数据搬运与内存管理:队列与多缓冲
深入 Ascend C 的数据搬运机制——DataCopy、UB 内存申请/释放、队列(EnQue/DeQue)同步,以及双缓冲/多缓冲流水线优化,掌握昇腾算子性能优化的核心手段
Ascend C 数据搬运 DataCopy 队列 双缓冲
3.2 里我们「草率地」忽略了同步问题。真实情况是:DataCopy 是异步的,搬数据和处理数据如果不同步,结果就是错的;而把它们正确重叠起来,性能就起飞。这一章把昇腾数据搬运与内存管理的「硬核细节」补齐。
📑 目录
- 1. 异步搬运:正确性的第一道坎
- 2. UB 内存管理:申请与释放
- 3. 队列模型:EnQue / DeQue 同步
- 4. 为什么同步至关重要
- 5. 双缓冲与多缓冲:让搬运和计算重叠
- 6. 数据搬运的性能准则
- 总结
- 延伸阅读
1. 异步搬运:正确性的第一道坎
在 3.2 的 Add 示例里,我们写了:
DataCopy(aUb, src1Global[offset], curLen); // 发起搬运(异步)
DataCopy(bUb, src2Global[offset], curLen);
Add(cUb, aUb, bUb, curLen); // 马上计算?!
如果 DataCopy 还没把数据搬进 UB,Add 拿到的就是旧数据/垃圾数据。
昇腾的处理思路是「队列化 + 显式同步」:
- 每个 UB 缓冲区对应一个队列(Queue);
- 搬运完成后,数据「入队(EnQue)」;
- 计算前,先从队列「出队(DeQue)」取数据;
- 出队成功即代表「数据已就位」。
用队列把「搬运完成」和「开始计算」这两个时刻显式连接起来,就消除了异步导致的竞态。
2. UB 内存管理:申请与释放
UB 是 AI Core 内的共享资源,必须显式管理:
// 在 UB 上申请一块缓冲区(指定类型与元素个数)
LocalTensor<float> aUb = tb.AllocTensor<float>(UB_LEN);
LocalTensor<float> bUb = tb.AllocTensor<float>(UB_LEN);
LocalTensor<float> cUb = tb.AllocTensor<float>(UB_LEN);
// ... 使用完毕后释放,归还 UB 空间
tb.FreeTensor(aUb);
tb.FreeTensor(bUb);
tb.FreeTensor(cUb);
要点:
- 申请时机要短:UB 总量有限,长期占用会挤占其他缓冲区。
- 与 Tiling 联动:申请大小由 Tiling 块大小决定,块越大 UB 占用越高。
- 申请/释放是「软件层面的登记」,真实物理分配由编译期静态规划——所以不要试图「偷偷超申请」,编译器会拒绝。
3. 队列模型:EnQue / DeQue 同步
昇腾用队列(Queue)管理数据在 GM/UB 之间的流转。简化模型如下:
搬运引擎(DataCopy 异步执行)
│
▼
[ 数据搬运完成 → EnQue(入队)]
│
▼
计算侧 → DeQue(出队,拿到数据)
│
▼
执行计算 → 计算完成 → EnQue 通知下游
// 搬运侧:数据就位后入队
DataCopy(aUb, src1Global[offset], curLen);
EnQue(aUb);
// 计算侧:出队,确认数据就位
DeQue(aUb);
Add(cUb, aUb, bUb, curLen);
理解重点:EnQue/DeQue 不是性能优化,而是正确性保证——它让异步流水线的各个阶段按照「数据依赖」正确衔接。没有它,异步搬运就是埋雷。
4. 为什么同步至关重要
用一个时间线图说明:
无同步(错误):
搬运引擎: [搬数据块0].............[搬数据块1]
计算单元: [算块0 ← 可能还没搬完!]
有同步(正确,但串行):
搬运引擎: [搬块0] [搬块1] [搬块2]
计算单元: [算块0] [算块1] [算块2]
同步 + 双缓冲(高效):
搬运引擎: [搬块0] [搬块1] [搬块2] [搬块3]
计算单元: [算块0] [算块1] [算块2]
第三种模式就是下一节的双缓冲——正确性的问题解决之后,性能的问题才浮出水面。
5. 双缓冲与多缓冲:让搬运和计算重叠
**双缓冲(Double Buffering)**是昇腾算子性能优化的核心技巧:
- 申请两块输入缓冲(A/B);
- 计算块 i 的同时,搬运块 i+1 到另一块缓冲;
- 搬运和计算在时间上重叠,隐藏搬运延迟。
UB: [缓冲0] [缓冲1] [缓冲0] [缓冲1] ...
搬入块0 搬入块1 搬入块2 搬入块3
计算块0 计算块1 计算块2 ...
进一步可以上多缓冲(3 块及以上),覆盖更长延迟;块数与 UB 容量是 trade-off,块太多 UB 不够,块太少重叠不充分。
判断收益:用 msprof 看「计算忙」与「搬运忙」的时间占比。搬运占比高 → 双缓冲收益大;计算本来就满 → 优先优化计算本身。
6. 数据搬运的性能准则
- 一次搬大块:单次 DataCopy 搬运的数据越多,带宽利用率越高。
- 对齐:GM 地址与 UB 地址对齐时搬运效率最高(未对齐会有额外开销或走低效路径)。
- 减少搬运次数:能复用片上的数据就复用,别反复上下搬运(算子融合的思路)。
- 重叠:用双缓冲/多缓冲让搬运与计算并行。
- 搬最少的:尽量在计算侧「少搬」——比如 softmax 可以先算局部 max 再搬结果,避免搬整个中间张量。
总结
DataCopy是异步的,EnQue/DeQue 队列同步是正确性保证,绝不能省。- UB 内存显式申请/释放,与 Tiling 块大小联动。
- 双缓冲/多缓冲让「搬运」与「计算」重叠,是昇腾算子性能优化的核心手法。
- 搬运性能准则:大块、对齐、少搬、重叠——与 CUDA 访存优化同构。