1
DCU技术达人
海光 DCU 利用率上不去,别再盲调 Batch:用 Roofline 定位计算、带宽与通信瓶颈
2026-08-19 17:50:39
60
> **一段话看懂本文:** DCU 利用率低只是现象,根因可能在 CPU 供数、显存带宽、小 kernel 启动、同步或多卡通信。本文用 Roofline(屋顶线)思维提供一张诊断地图:先确定时间花在哪里,再判断计算受限、带宽受限还是根本没有进入稳态。 “利用率不高,把 batch 调大一点。” 这可能是性能调优中传播最广、也最容易被误用的一句话。batch 增大后,有时吞吐确实会上升;但首 token 时延可能恶化,显存可能 OOM,而真正的瓶颈——数据预处理、访存或通信——依然没有解决。 更有效的做法不是先改参数,而是先回答三个问题:时间到底花在哪里?每搬运一个字节做了多少有效计算?设备是在计算、搬数据,还是等待?这正是 Roofline 思维最适合解决的问题。 ## 一、“利用率低”不是根因,只是现象 设备利用率像汽车仪表盘上的转速:它能告诉你设备是否忙碌,却不能单独解释为什么速度慢。 同样是利用率不高,原因可能完全不同: - CPU 预处理或数据读取太慢,DCU 在等输入; - kernel 数量很多但每个都很短,启动开销占比过高; - 算子需要大量搬运数据,受显存带宽限制; - 寄存器或片上存储使用过多,活跃 wave 数不足; - kernel 之间存在频繁同步; - 多卡任务卡在通信,而不是计算; - batch 太小,硬件并行度没有铺满。 如果不先分类,调参就像在黑屋里拧旋钮。Roofline 模型的价值,是先回答一个最基础的问题:**这个算子的上限更可能由计算能力决定,还是由内存带宽决定?** ## 二、Roofline 的两个核心量 第一个量是算术强度(Arithmetic Intensity,AI): ```text 算术强度 = 浮点运算量 / 从主存搬运的字节数 ``` 第二个量是可达到的性能上限: ```text 性能上限 = min(峰值计算性能, 算术强度 × 内存带宽) ``` 当算术强度很低时,增加计算单元也没有用,因为大部分时间在搬数据,这类算子属于带宽受限。算术强度足够高后,性能才可能碰到计算屋顶,进入计算受限区。 例如逐元素加法:读取两个数组、写回一个数组,每个元素只做一次加法,搬运很多字节却只做很少计算,通常偏带宽受限。大矩阵乘可以让同一块数据在片上被重复使用,算术强度高,更可能靠近计算屋顶。 这里有一个关键误区:算法的理论 FLOPs/Byte 不等于真实算术强度。如果同一数据因为布局不佳被反复从主存读取,实际搬运量会远高于理想值。 ## 三、第一步不是优化,而是建立可信基线 性能分析前应先固定五类变量: 1. 输入 shape、dtype 和数据分布; 2. 软件栈、编译参数和模型版本; 3. 设备编号、功耗/频率状态及其他进程; 4. 预热次数、正式重复次数和同步位置; 5. 正确性阈值。 异步执行会让计时很容易出错。下面这个模式比直接用两个时间戳可靠: ```python import time, statistics, torch def benchmark(fn, warmup=20, repeat=100): for _ in range(warmup): fn() torch.cuda.synchronize() times = [] for _ in range(repeat): torch.cuda.synchronize() t0 = time.perf_counter() fn() torch.cuda.synchronize() times.append((time.perf_counter() - t0) * 1000) times.sort() return { "median_ms": statistics.median(times), "p90_ms": times[int(len(times) * 0.90)], "min_ms": times[0], } ``` 同步会引入额外成本,所以更精确的 kernel 计时可使用设备事件或 profiler;但无论采用哪种工具,都必须写清计时边界。只报“平均快了多少”而没有预热、重复和分位数,数据很难复核。 ## 四、第二步:先看时间花在哪里 性能剖析应分三层: ### 1. 端到端层 把一次请求拆成数据读取、CPU 预处理、主机到设备传输、设备计算、设备到主机传输和后处理。若设备计算只占总时长的 30%,即使 kernel 快一倍,端到端最多也只能改善有限部分。 ### 2. API/时间线层 观察 kernel 是否连续执行、中间是否有长空洞、内存拷贝能否与计算重叠、是否存在不必要的同步。时间线上的空白通常比一个孤立的“利用率”数字更能说明问题。 ### 3. kernel 层 按总耗时排序,优先分析头部热点。一个 kernel 即使单次很慢,如果只调用一次,也可能不如被调用一万次的小 kernel 值得优化。 光合开发者社区的 DCU 编程资料提供了性能分析工具相关章节,DTK 页面也把性能分析、调试和优化列为开发工具链的重要组成。工具名称和参数可能随版本变化,正式采集应以所用 DTK 版本文档为准。 ## 五、带宽受限:重点不是少算,而是少搬 带宽受限算子的典型特征是:计算指令并不饱和,数据搬运占主要成本。可按以下顺序思考: ### 1. 合并访问 相邻线程尽量访问连续地址,避免跨步读取把一次理想访问拆成多次事务。张量转置、非连续 view 和特殊 layout 常让访问效率下降。 ### 2. 提高数据复用 把反复使用的数据放入寄存器或片上共享存储,减少回到显存读取。但片上资源不是越多越好:单个工作组占用过多,会降低同时驻留的工作组数量。 ### 3. 融合算子 如果 A 算子把结果写回显存,B 算子立刻再读,融合后可以让中间值留在片上。例如 bias、激活、残差和归一化经常存在融合机会。 融合的收益来自减少中间张量和启动次数,不是“融合”二字本身。融合后若寄存器压力大幅上升,反而可能变慢。 ### 4. 缩小数据表示 在正确性允许且软件栈支持的前提下,FP16/BF16/FP8/INT8 等低精度既可能减少搬运,也可能提高计算吞吐。但必须重新验证数值精度,并确认关键路径确实使用了对应高效算子。 ## 六、计算受限:让计算单元做有效工作 计算受限并不自动等于“已经优化好了”。还要检查: - 是否使用适合目标 DCU 的矩阵/向量指令; - tile 大小能否兼顾数据复用和并行度; - 是否有大量分支导致同一 wave 内执行路径分化; - 寄存器占用是否限制活跃 wave; - shape 是否与高效 kernel 的约束匹配; - 是否存在本可消除的重复计算。 矩阵乘优化本质上是在三个目标之间找平衡:更大的 tile 提高复用,却消耗更多寄存器和片上存储;更小的 tile 提高并发,却可能增加全局访存。最优参数通常与 M/N/K、dtype 和硬件代际相关,不能把一组参数当成所有 shape 的答案。 ## 七、利用率低但既不缺算力也不缺带宽:检查并发和开销 Roofline 主要描述稳态 kernel,但真实应用还会被“没进入稳态”拖慢。 ### 小 kernel 启动开销 大量微小算子每个只执行几微秒,主机提交和调度成本会变得突出。可考虑算子融合、图执行或批处理,但要先从时间线确认问题确实是碎片化提交。 ### batch 太小 单请求延迟优先的业务常无法用大 batch。此时应考虑连续批处理、请求合并或并发流,而不是无条件把 batch 拉大。吞吐和首 token 时延必须分别衡量。 ### 隐式同步 读取设备 tensor 到 CPU、打印 tensor、某些内存操作或错误的计时方式,都可能触发同步。一次无意同步会切断原本可重叠的流水线。 ### CPU 供数不足 数据解码、tokenization、Python 调度或磁盘读取慢,会让设备时间线上出现空洞。此时优化 kernel 几乎不影响端到端性能,应先做并行预处理、缓存、固定内存或流水线化。 ## 八、多卡场景还要加一条“通信屋顶” 单卡 Roofline 只考虑计算和显存带宽,多卡训练/推理还受互联限制。总时间可粗略拆成: ```text 总时间 = 计算 + 通信 + 等待 - 可成功重叠的部分 ``` 优化多卡性能时应分别记录计算 kernel、collective 通信和空闲等待。若卡间负载不均,最快的卡也要等待最慢的卡;若消息很小且频繁,延迟可能比带宽更关键;若消息很大,拓扑和有效带宽更重要。 “通信计算重叠”也不能只看配置项是否打开,而应从时间线证明通信区间确实被计算覆盖,并检查重叠是否挤占了计算所需资源。 ## 九、把每次优化写成可证伪假设 高质量优化记录不是“把 block size 从 256 改成 512,快了”,而应包含: ```text 观察:热点 kernel 占端到端 38%,主存读吞吐高,计算单元未饱和。 假设:该 kernel 受带宽限制,且中间张量往返显存是主要成本。 改动:融合相邻逐元素算子,减少一次读和一次写。 预测:kernel 时长下降;主存字节数下降;寄存器压力可能上升。 结果:正确性通过;median/p90 变化;资源占用变化。 结论:接受、回滚,或需要下一轮验证。 ``` 这种记录方式的好处是,即使优化失败也会产生知识:假设不成立,下一步就不必继续在同一方向盲调。 ## 十、一张诊断决策表 | 观察 | 优先验证 | 常见方向 | | -------------------- | ---------------------- | -------------------------- | | 设备时间线有长空洞 | CPU、I/O、同步 | 流水线、预取、消除同步 | | 单个热点算子读写量大 | 算术强度、访问合并 | 融合、布局、复用、低精度 | | 计算指令忙但性能不高 | 指令选择、tile、占用率 | 库调用、专用指令、参数分桶 | | 大量微小 kernel | 启动次数和总占比 | 融合、批处理、图执行 | | 单卡快、多卡扩展差 | 通信占比、负载均衡 | 拓扑、并行策略、重叠 | | 平均值好但 p90 抖动 | 频率、争用、动态 shape | 隔离环境、分桶、固定基线 | ## 结语 性能优化最昂贵的不是改代码,而是在没有证据时改错方向。Roofline 思维并不要求一开始就画出完美的屋顶线图;只要持续追问“每字节做了多少有效计算”“时间花在计算、搬运、等待还是启动”,优化就会从猜参数变成验证假设。 对海光 DCU 应用而言,一条可靠路径是:先保证正确性,再固定基线;先看端到端,再看时间线;先抓总耗时热点,再判断计算或带宽受限;最后用同一环境做 A/B 对比。这样得到的结论才可复现、可迁移,也更经得起技术审核。 **讨论题:** 你看到“DCU 利用率不高”时,第一反应会检查什么?欢迎留下具体工作负载和现象:是时间线有空洞、热点 kernel 很慢,还是多卡扩展效率差?不同答案对应的优化方向完全不同。 ## 参考资料 - 光合开发者社区 DTK:软件栈、性能分析与 CUDA 应用移植教程入口,https://developer.sourcefind.cn/dtk - 光合开发者社区《DCU 编程实践》性能分析工具相关章节,https://developer.sourcefind.cn/gitbook//dcu_developer/DeveloperGuide/dcu_programming/DCU_programming_chapter3_7.html - Samuel Williams 等:Roofline: An Insightful Visual Performance Model for Multicore Architectures,https://doi.org/10.1145/1498765.1498785
全部评论(1)
只看楼主
暂无数据
--
楼主
Level.2
0 帖子
楼主热帖
暂无数据