ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI芯片软硬件协同:有效算力与存储墙的瓶颈解析

AI芯片软硬件协同:有效算力与存储墙的瓶颈解析 提到AI芯片很多人第一反应是去看规格表上的TOPS数字。我最初也是这么干的但真正把模型从通用平台搬到AI加速卡上之后才意识到一个残酷事实纸面算力只是理论峰值实际跑出来的性能可能连一半都不到有时候甚至跑不过原来的通用处理器。问题多数不在芯片本身而在于软硬件之间没有对齐。这篇是AI芯片软硬件设计系列的开篇我先把两个最基础也最关键的概念——有效算力和存储墙——讲透同时把软件侧需要配合的编译、调度、量化这些问题放进同一张图里讲。适合刚接触芯片设计、正在做推理加速或边缘部署的人参考也适合从应用层往底层走的工程师建立整体认知。1. 单卡算力都去哪了从峰值算力到有效算力1.1 算力规格书为什么经常“虚标”我在跟同行交流时经常听到一句话“这块卡标称算力那么高怎么换上之后反而变慢了”这里有个容易忽略的细节规格书上的算力是峰值算力也就是所有计算单元同时跑到最高频率、数据通路完全无冲突的理论上限。真实场景里数据要一层一层搬运、指令要排队、缓存会写满、某些算子形状根本不匹配硬件布局这些都会让实际吞吐量打折扣。有效算力的公式大致是有效算力 峰值算力 × 实际利用率而实际利用率受三件事影响数据搬运是否顺畅、计算单元是否被喂饱、算子映射是否贴合硬件结构。这三件事恰好对应了软硬件协同设计的主要战场。你光看算力表完全看不到存储带宽是多少、片上缓存多大、编译器能把循环优化成什么样自然也就无法判断这块芯片在具体模型上能发挥几成功力。1.2 真正吃掉时间的三个环节取数、计算、存回用一个很基础的例子卷积操作。假设输入特征图是256×256卷积核是3×3输出也是256×256。每个输出点需要9次乘法累加整体大约60万次乘加操作。听起来不大但如果每次乘加都要从外部内存取数据那就有几十万次内存访问。访问延迟是片上缓存的几十倍功耗更是高出好几个量级整个流水线就被卡在取数上计算单元大部分时间在空转。这就是所谓的“存储墙”不是算不快而是数据喂不进来。我常把AI芯片比喻成一家餐厅后厨的锅灶能力再强如果食材只能靠一辆小推车从仓库一点点运那翻台率必然上不去。软硬件设计的核心任务之一就是把这条“运货通道”加宽、缩短并且让食材在被运进来之后尽量多被用几次。2. AI芯片的计算引擎从MAC单元到脉动阵列2.1 神经网络的计算核心为什么是MAC神经网络里最常见的操作是矩阵乘法加偏置加激活。矩阵乘法最底层的动作就是乘累加运算MAC把一个输入值与一个权重值相乘再把结果累加。业界统计算力时用的TOPS其实就是每秒万亿次MAC操作。如果用通用处理器去执行MAC开销不只是乘法本身。处理器要取指令、译码、读寄存器、写寄存器每一层都有额外功耗和延迟。后果就是大量算力和能量浪费在“指挥”而不是“干活”上。专用AI芯片的思路很直接把成千上万个MAC单元做成固定阵列让控制逻辑尽量简化把能量集中花在乘累加这个动作本身。2.2 脉动阵列的核心思想数据像流水一样流动讲脉动阵列之前先看一个生活场景汽车装配线上每个工位只做一种动作车架沿轨道依次流过各个工位零件从旁边不断送到工位附近。这样整个生产线可以连续出车而不是每辆车都绕回仓库取件再回来装。脉动阵列的电路结构与此类似。内部有大量处理单元PE相邻PE之间直接相连。输入特征图数据和权重数据各自沿着规定的方向流动在每个PE里做一次乘累加再把中间结果传给下一个PE。这样从外部存储取进来的每一个数据都会被多个计算单元重复使用而不是只算一次就丢掉。我在实际对比过这种设计与普通ALU阵列的区别两条路都能算卷积但脉动阵列的数据复用率更高对外部访存的压力更小。代价是灵活性受限某些不规则的网络结构会映射得很吃力这时候就需要编译器去“改写”计算顺序来适配硬件。2.3 硬件架构反过来决定软件怎么写很多软件工程师习惯把硬件当成一个黑盒子觉得上层代码只要符合接口规范就行。但在AI芯片领域这条假设不成立。如果底层是脉动阵列数据布局必须是分块的、对齐的循环的顺序可能要从数据流方向反向推导如果是多核并行的设计还得考虑怎么把不同的算子分配到不同核心上保证负载均衡。换句话说芯片的设计者不能只交付一块裸芯片还要交付配套的算子库、编译器、运行时系统。这也是为什么很多AI芯片团队里做编译器的人比做电路的人还多。软件不是硬件的下游而是同一条流水线上不可分割的一部分。3. 存储墙数据搬运比计算本身更贵3.1 取一次数比算一次数贵两个数量级有组经典数据我一直拿来讲给新同事听一次32位整数乘累加运算消耗的能量大约在0.2到1pJ皮焦耳之间访问一次片上SRAM缓存大概需要几pJ访问一次外部DRAM却要上百pJ。也就是说算一次的成本还没把数据从内存里取一次的成本零头高。这个数字背后藏着一个反直觉的结论AI芯片的性能优化很多时候不是优化“怎么算”而是优化“怎么不搬数据”。只要能让一个数据被反复复用减少从DRAM到计算单元的搬运次数功耗和延迟都会显著下降。这也是为什么所有现代AI芯片都在拼命堆片上SRAM——不是为了显摆容量而是为了把更多数据留在离计算单元近的地方反复使用。3.2 量化不是玄学是一笔带宽账量化为什么有效很多人只记住“INT8比FP32快”但没真正理解原因。FP32是32位INT8只有8位同样一个权重数据体积缩小到四分之一。假如芯片的存储带宽是有限的每秒钟能搬运的总位数就那么多那么改用INT8之后相同带宽下每秒能搬运的权重数量是FP32的四倍。换一个直观的说法量化不是让计算单元算得更快而是让同样宽度的数据通道能喂进来更多的数据同时减少了对缓存的占用。对访存受限的模型来说这种效果是整体吞吐量的成倍提升。当然量化也会牺牲一定精度所以现在大家喜欢混合精度关键层保留FP16或FP32非关键层用INT8甚至INT4。这是典型的软硬件协同取舍不只是模型侧的事。3.3 算子融合与内存布局让中间数据少“落地”如果把一次推理过程完整展开你会发现有大量中间张量生成后被立刻用作下一个算子的输入。按照普通流程算子A的结果要写回DRAM算子B再从DRAM里读出来。这一写一读浪费的带宽和能耗非常大。算子融合的做法是把A、B、C这些原来是独立函数的算子合成一个大算子中间结果只留在片上寄存器或SRAM里直到最后一个阶段才写回DRAM。比如原地融合卷积、批归一化和ReLU以前是三个内核现在变成一个。我在实际项目中做过统计仅这一项优化端侧推理的整体访存时间就下降了大约三成。内存布局同样影响显性。通常我们习惯把图像数据排成NCHW或者NHWC不同硬件对布局有不同偏好。布局选错编译器要做大量重排数据重排本身就是一次大规模搬运。所以写算子实现之前先查清楚目标芯片对张量维度的排布要求能省掉很多看不见的拷贝开销。4. 编译器才是AI芯片的半条命从图优化到指令调度4.1 一块没有编译器的AI芯片只是砖头很多小白认为芯片只要做出来放上驱动就能跑做编译器纯属多余。实际上神经网络模型是用深度学习框架写的框架只认识通用的计算图而芯片底层只认特定的指令和数据流。中间这层翻译就是编译器的活。编译器做的事大致分几层第一层是图优化融合算子、消除无用计算第二层是把算子映射到硬件指令这步骤要根据芯片的MAC阵列尺寸、SRAM容量、访存通道数去做循环变换第三层是内存分配和指令调度决定哪些数据常驻片上、哪些变量该复用、哪些流水线阶段该错开执行。每一层选错了性能都能相差数倍。4.2 Roofline模型快速判断瓶颈在哪调试AI算子性能时我喜欢先用Roofline模型把问题定性。这个模型很简单横轴是计算强度表示每搬运一字节数据对应多少次运算纵轴是实际性能。画出一条斜线斜线的斜率表示存储带宽横过来的一条水平线是峰值算力。落在斜线上的点说明性能被访存限制住了已经顶到屋顶的点说明被算力限制住了。拿计算强度区分的两类算子卷积类型的算子相对偏算力密集型可以多压缩数据搬运、利用量化来减轻带宽压力而类似某些归一化、维度变换类的算子则偏访存密集型主要优化目标是减少读次数、增加数据复用。先用这个模型看指标再决定优化方向比盲目调参数高效太多。不同优化手段的侧重点和典型收益我整理了一个表格优化层级核心动作针对瓶颈类型典型收益图优化算子融合、死代码消除访存密集型减少中间数据往返收益一般很可观数据布局调整张量维度排布访存密集型消除重排拷贝降低显存占用循环变换分块、展开、交换访存/算力密集型提升数据复用率逼近峰值性能量化压缩降低权重和激活精度访存密集型带宽压力降至原来的四分之一指令调度流水线重叠、多核并行算力密集型隐藏延迟提高利用率4.3 一个循环分块的调度实例用一个矩阵乘法作为例子假设A矩阵是M×KB矩阵是K×N。朴素写法是三循环内层反复做乘累加每次都从内存取数据。如果M、N、K都很大数据量远超SRAM容量整个计算就在反复搬运数据。分块优化的思路是把大循环切小比如按tile大小把A和B分别切成小块让一个块里的数据能完整塞进SRAM然后把这个块做完整。伪代码如下for (int i 0; i M; i TILE_M) for (int j 0; j N; j TILE_N) for (int k 0; k K; j TILE_K) // 将A[i:iTILE_M, k:kTILE_K] 和 B[k:kTILE_K, j:jTILE_N] 载入SRAM // 在SRAM内完成TILE_M * TILE_K * TILE_N 次乘累加这样每个数据从DRAM取进来之后能在SRAM内被多次复用访存次数大幅下降。这个循环变换本身不改变结果但性能优化空间往往能达到数倍。在做这块优化时一个好的profile工具会显示每个循环层级上的缓存命中率和总访存字节数盯着这两项指标调tile尺寸是最有效的做法。5. 软硬件协同的入门路径从跑通到吃透5.1 用仿真工具代替流片先看清瓶颈再动手对于刚接触软硬件协同设计的人来说最忌讳一上来就想着写RTL代码去流片。成本高、周期长而且大部分问题其实在架构设计阶段就已经能暴露出来。我建议的路径是先搭一套仿真环境用开源全系统模拟器跑一段基准模型观察缓存命中率、带宽利用率和计算单元占用率这些指标先把“瓶颈到底在哪”这个问题回答清楚。如果目标是做芯片设计接下来可以用开源硬件仿真框架去编写一个简单的硬件加速模块在仿真阶段就跑通整个软件栈。这一步虽然不会真的制造芯片但能让你直观看到编译器生成的指令是如何驱动硬件单元的软硬件接口到底长什么样。5.2 入门项目怎么选怎么设计选项目时不要贪大选一个最简单的算子比如卷积或矩阵乘法结合一个小模型拍照识别之类的任务搭一个完整的端到端流程。这里有一个我常用的虚拟案例某开发者的图像分类加速器。硬件侧只有一个小的MAC阵列模块128个PE每个PE支持8次乘累加整体是简化版脉动阵列软件侧用编译框架把模型的计算图切分成子图再映射到这块硬件上。设计步骤大致是先用Roofline模型分析模型确认算子是访存受限还是算力受限。再确定任务需要的最小SRAM容量。计算峰值带宽和流水线延迟目标不要盲目堆面积。在模拟器上跑一个具体模型统计各操作耗时。调整tile大小和循环展开系数观察有效利用率变化。最后把验证好的模块综合到FPGA开发板上比较实测与仿真数据的差异。这个过程走通一遍你对软硬件协同的理解会比读十篇论文都深。5.3 几个容易踩的坑第一只看理论峰值去选型不拿真实模型跑benchmark。一套推理框架在不同芯片上的表现差异极大别省这一步。第二忽略编译器。有些开发者在写算子实现时完全不考虑编译器会怎么做循环变换结果手工优化和编译器优化互相对冲性能反而更差。第三忽视片上存储容量。有人设计芯片时把算力堆得很高却把SRAM做得特别小结果数据来不及复用算力全部浪费在空等上。第四仿真环境与真实环境差异过大会误导设计。我自己第一次做加速器时就在tile尺寸选择上卡了很久。总觉得tile越大复用越多结果反而占满了SRAM导致缓存频繁溢出性能比小tile还差。后来才意识到tile尺寸要跟SRAM容量、数据宽度、流水线深度这些硬件参数联合调不能只看一个维度。调参的过程很枯燥但每找到一组合适参数性能都能跨上一个台阶。现在很多芯片设计已经走向专用化、异构化。模型结构在变硬件架构也在变但软硬件协同的核心逻辑——减少数据搬运、提升数据复用、让编译器了解硬件结构——一直没有变。最后再分享一个小体会真正理解AI芯片软硬件设计最快的方式不是看你跑通了多牛的模型而是把一个很小的算子从图优化、循环分块、内存分配、指令调度到硬件仿真的整个链路完整走一遍。走完以后你再回来看任何一张AI芯片的规格表都能分辨出哪些参数是真实有效的哪些只是营销数据。下一篇我会拆解一个具体的端侧推理加速器案例从编译器调度一路看到硬件的流水线握手信号把这一篇里提到的思路落到一个能跑的项目里。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进