1. 从“算力焦虑”说起:为什么软硬件协同设计成了AI芯片的生死线
做AI芯片这行的人,最近几年都有一个共同的感受:单纯堆算力的时代已经过去了。前几年大家比的是TOPS数字,谁的峰值算力高谁就牛,但真正到了部署环节,你会发现一个很尴尬的现实——标称256TOPS的芯片,跑一个真实的大模型推理任务,实际利用率可能连30%都不到。这不是某一家的个别问题,而是整个行业的普遍困境。
我接触过不少做AI芯片的团队,有做云端训练的,也有做边缘推理的,大家踩的坑出奇地相似:硬件团队把算力做上去了,软件团队却接不住;或者软件栈勉强跑通了,但性能调优的空间极其有限,因为硬件架构在设计阶段就没有给软件留出足够的灵活性。这个问题的根源,在于软硬件设计之间的割裂。
AI芯片和传统通用CPU最大的区别在于,它的价值高度依赖于特定的计算模式——矩阵乘法、卷积、注意力机制这些操作。如果硬件架构师只盯着PE阵列的利用率和带宽指标,而不去理解上层框架(比如PyTorch、TensorFlow)实际生成的算子长什么样、调度器怎么分配任务、内存访问模式有什么特征,那设计出来的芯片大概率是“理论上的巨人,实战中的矮子”。
这一篇要聊的,就是AI芯片软硬件设计中那些真正决定成败的协同问题。我会从计算架构的选择、编译器的角色、内存层次的设计、以及实际部署中的调优经验几个维度展开,尽量把“为什么这么做”讲透,而不是只给一堆结论。适合正在做AI芯片架构设计、编译器开发、或者推理部署的同行参考,也适合想了解这个领域全貌的技术管理者。
2. 计算架构的取舍: systolic array、SIMD还是可重构数据流
2.1 三种主流架构的本质差异
AI芯片的计算核心架构,目前主流的选择大致分三类:脉动阵列(systolic array)、SIMD/向量处理器、以及可重构数据流架构。这三种架构没有绝对的优劣,关键在于它们各自适合什么样的计算模式和工作负载。
脉动阵列的核心思想是让数据在PE(处理单元)之间“流动”,每个PE只做简单的乘加运算,数据从内存加载一次后可以在多个PE之间复用。这种设计对矩阵乘法极其友好,因为矩阵乘法的本质就是大量的乘加操作,而且权重和激活值都有很高的复用潜力。Google的TPU就是典型的脉动阵列架构,它的优势在于能效比高,因为数据搬运的次数被压到了最低。
但脉动阵列的问题也很明显:它的灵活性差。一旦遇到非矩阵乘法的操作,比如element-wise的激活函数、softmax、layer normalization,脉动阵列的PE阵列就处于半闲置状态。而且脉动阵列的尺寸是固定的,遇到小矩阵或者不规则的计算图,利用率会急剧下降。
SIMD/向量处理器则是另一条路。它的思路是让一条指令同时处理多个数据元素,适合处理规则的数据并行任务。GPU就是最典型的SIMD架构,它的优势在于通用性强,能适应各种计算模式,但代价是功耗高、面积大。对于边缘端的AI芯片来说,GPU的功耗往往是不可接受的。
可重构数据流架构是近几年比较热的方向。它的核心思想是根据计算图动态配置PE之间的连接关系和数据流向,让硬件结构能“变形”去适应不同的算子。这种架构在灵活性和能效之间找到了一个折中点,但代价是编译器的复杂度急剧上升——你需要一个足够聪明的编译器来把计算图映射到可重构的硬件资源上。
2.2 选择架构时真正该问的问题
很多团队在选架构的时候,第一反应是看同行用什么、看论文里什么架构跑分高。但真正该问的问题其实是:我的目标工作负载是什么?我的软件栈能支撑到什么程度?
如果你主要做的是云端训练,那矩阵乘法的占比极高,脉动阵列或者类似的矩阵加速架构是合理的选择。但如果你做的是边缘推理,模型可能是各种轻量级网络(MobileNet、EfficientNet),里面包含大量的深度可分离卷积、注意力机制,那纯粹的脉动阵列就不一定合适了,可能需要考虑更灵活的架构。
还有一个经常被忽略的问题是:你的编译器团队能不能跟上?可重构数据流架构的理论峰值性能可能很高,但如果编译器无法有效地把计算图映射到硬件上,实际性能可能还不如一个简单的SIMD架构。我见过不少团队在架构选型时被“理论峰值”迷惑,结果流片回来发现软件栈根本跑不出性能,这就是典型的软硬件脱节。
2.3 一个实际的架构评估框架
在实际项目中,我一般会用下面这个框架来评估架构选择:
| 评估维度 | 关键问题 | 权重参考 |
|---|---|---|
| 目标负载匹配度 | 主要算子的计算模式是什么?矩阵乘法占比多少? | 高 |
| 编译器可行性 | 现有编译器能否有效映射?需要多少定制开发? | 高 |
| 能效比 | 在目标功耗预算下,实际利用率能到多少? | 高 |
| 灵活性 | 未来模型结构变化时,架构能否适应? | 中 |
| 面积成本 | 在目标工艺节点下,面积是否可接受? | 中 |
| 验证复杂度 | 功能验证和性能验证的难度如何? | 中 |
这个框架的核心逻辑是:不要只看峰值算力,要看“可达到的有效算力”。而有效算力取决于编译器能否把计算图高效地映射到硬件上,这又取决于架构本身的可编程性。
3. 编译器:AI芯片软件栈里最容易被低估的环节
3.1 编译器到底在做什么
很多人对编译器的理解还停留在“把Python代码翻译成机器码”这个层面,但在AI芯片的语境下,编译器的角色远不止于此。它实际上承担了三个核心任务:计算图的优化、算子到硬件的映射、以及内存分配和调度。
计算图优化包括算子融合、常量折叠、死代码消除等。算子融合尤其重要,比如把Conv+BN+ReLU融合成一个算子,可以减少内存访问次数,提升性能。但这个优化不是随便做的,它需要编译器理解硬件的内存层次和计算单元的特性。
算子到硬件的映射是编译器最核心的工作。以矩阵乘法为例,编译器需要决定:把矩阵分成多大的块?每个块映射到哪些PE上?数据怎么在PE之间流动?这些决策直接影响硬件的利用率。一个好的编译器能根据硬件的PE阵列大小、寄存器数量、内存带宽等参数,自动搜索出最优的映射方案。
内存分配和调度则是决定数据在什么时间、放在什么位置。AI芯片通常有多个内存层次:片上SRAM、寄存器文件、以及外部DRAM。编译器的任务是把数据在正确的时间放到正确的位置,避免频繁的DRAM访问,因为DRAM访问的能耗可能是片上SRAM的几十倍甚至上百倍。
3.2 为什么编译器这么难做
编译器难做,根本原因在于搜索空间太大。一个计算图有无数种映射方式,每种方式对应不同的性能和功耗。编译器需要在合理的时间内找到一个足够好的方案,而不是穷举所有可能性。
另一个难点是硬件的不规则性。通用CPU的编译器可以假设硬件是相对规则的,但AI芯片的硬件往往有很多特殊的设计,比如某些PE支持特定的稀疏计算模式,某些内存bank有特殊的访问规则。编译器需要对这些细节有深入的理解,才能生成高效的代码。
还有一个经常被忽视的问题是:编译器的开发需要和硬件设计同步进行。如果硬件已经流片了,编译器才发现某些设计决策导致无法高效映射,那就来不及了。所以成熟的团队会让编译器团队在架构设计阶段就深度参与,甚至用编译器的反馈来指导硬件架构的调整。
3.3 编译器开发中的几个实战经验
第一,先做正确性,再做性能。很多团队一开始就追求极致的性能优化,结果编译器bug一堆,连基本的正确性都保证不了。我的建议是先用一个简单的映射策略把功能跑通,然后再逐步加入优化。
第二,建立完善的性能分析工具。编译器生成的代码到底跑得怎么样,需要有一套工具来度量。包括每个算子的执行时间、内存访问次数、PE利用率等。没有这些数据,优化就是盲人摸象。
第三,重视算子库的建设。不是所有的算子都适合用编译器自动生成,有些高频算子(比如卷积、矩阵乘法)手写优化版本可能比编译器生成的快很多。合理的做法是:编译器负责通用算子的生成,手写库负责关键算子的极致优化,两者结合。
第四,编译器的测试用例要覆盖真实模型。不要只用几个简单的算子测试,要拿真实的模型(ResNet、BERT、GPT等)来跑,这样才能发现真实场景下的问题。
4. 内存层次设计:被忽视的性能杀手
4.1 为什么内存比计算更重要
在AI芯片设计中,有一个反直觉的事实:很多时候,性能瓶颈不在计算单元,而在内存访问。一个PE阵列的计算能力可能很强,但如果数据供不上,PE就只能空转。
这个问题在Transformer类模型上尤其突出。Transformer的注意力机制需要频繁地访问和更新中间结果,内存访问模式非常不规则。如果内存层次设计得不好,注意力层的性能可能只有理论峰值的10%到20%。
内存访问的能耗也是一个大问题。在典型的AI芯片中,从DRAM读取一个字节的能耗可能是执行一次乘加运算的100倍以上。所以减少DRAM访问次数,把数据尽量留在片上,是提升能效比的关键。
4.2 片上内存的容量与带宽权衡
片上内存(SRAM)的设计需要在容量和带宽之间做权衡。容量越大,能缓存的数据越多,DRAM访问次数越少;但SRAM的面积开销很大,而且大容量SRAM的访问延迟也会增加。
一个常见的做法是采用多级SRAM结构:一级SRAM靠近计算单元,容量小但带宽高;二级SRAM容量大但带宽稍低。编译器需要根据数据的使用频率和访问模式,决定把数据放在哪一级。
另一个关键决策是SRAM的bank划分。如果所有数据都放在一个bank里,访问冲突会很严重。合理的bank划分可以让多个PE同时访问不同的bank,提升并行度。但bank划分也需要编译器配合,把数据合理地分配到不同的bank上。
4.3 数据复用策略的实际考量
数据复用是减少内存访问的核心手段。在矩阵乘法中,权重和激活值都有复用潜力。权重复用是指同一个权重被多个激活值使用,激活值复用是指同一个激活值被多个权重使用。
实际设计中,需要根据矩阵的维度来决定复用策略。如果权重矩阵很大但激活值矩阵很小,那权重复用的收益有限,应该优先考虑激活值复用。反之亦然。
还有一个容易被忽略的点是:数据复用需要硬件支持。比如,如果PE之间没有数据通路,那数据就无法在PE之间复用,只能通过共享内存来中转。所以在架构设计阶段,就要考虑PE之间的互联结构,以及共享内存的带宽是否足够。
5. 从流片到部署:那些只有踩过才知道的坑
5.1 仿真和实测的差距
在流片之前,团队通常会做大量的仿真来验证性能和功耗。但仿真和实测之间往往有差距,这个差距可能来自多个方面:工艺偏差、电压温度变化、以及仿真模型的不准确。
我的经验是,仿真结果要打一个折扣来看。如果仿真显示利用率能到80%,实测可能只有60%到70%。所以在架构设计时,要留出足够的余量,不要把指标定得太紧。
另一个问题是:仿真往往只覆盖了理想情况,比如数据已经在片上、没有内存冲突。但实际部署时,数据需要从DRAM加载,内存冲突也难以避免。所以仿真时要把这些因素考虑进去,否则结果会过于乐观。
5.2 软件栈的成熟度决定芯片的可用性
一颗AI芯片流片回来,如果软件栈不成熟,那基本上就是一块砖头。我见过太多团队在硬件上投入了大量资源,但软件栈只有几个工程师在维护,结果芯片回来后发现驱动有问题、编译器有bug、算子库不全,根本无法部署。
软件栈的成熟度包括几个方面:驱动的稳定性、编译器的正确性和性能、算子库的覆盖率、以及调试工具的完善程度。这些都需要在流片前就投入足够的资源来建设。
一个实用的建议是:在流片前,用FPGA或者仿真器把整个软件栈跑通,包括真实的模型和数据集。这样可以提前发现大部分问题,避免流片后的被动。
5.3 部署阶段的性能调优
芯片到了部署阶段,性能调优是一个持续的过程。同样的硬件,不同的模型、不同的batch size、不同的精度设置,性能表现可能差异很大。
调优的第一步是建立性能基线。用标准的模型和数据集跑一遍,记录每个算子的执行时间和内存访问情况。然后找出瓶颈算子,针对性地优化。
常见的优化手段包括:调整batch size来提升PE利用率、使用混合精度来减少内存带宽压力、以及调整数据布局来减少内存冲突。这些优化需要编译器和运行时系统的配合,不是单纯改几个参数就能解决的。
6. 软硬件协同设计的组织保障
6.1 团队结构怎么搭
软硬件协同设计不是一句口号,它需要组织结构的支撑。如果硬件团队和软件团队各自为政,那协同就是空谈。
比较有效的做法是:在架构设计阶段,就成立一个跨职能的核心团队,包括硬件架构师、编译器工程师、以及算法工程师。这个团队共同定义芯片的指令集、内存层次、以及编程模型。指令集的设计尤其关键,它决定了软件能表达什么样的计算模式,也决定了编译器的优化空间。
另一个关键是:软件团队要有能力影响硬件决策。如果软件团队只是被动地接受硬件团队的设计,那很多问题要到流片后才会暴露。所以软件团队需要在架构评审中有话语权,能够从软件的角度提出需求和约束。
6.2 开发流程的迭代节奏
AI芯片的开发周期通常比较长,从架构设计到流片再到量产,可能需要一到两年。但AI模型的演进速度很快,半年可能就有新的架构出现。这个时间差是一个很大的风险。
应对这个风险的方法是:在架构设计时留出足够的灵活性,同时建立快速迭代的软件更新机制。硬件流片后,通过软件更新来适应新的模型结构,而不是每次都要重新流片。
另一个做法是:先用FPGA或者可编程加速器来验证架构和软件栈,等软件栈成熟后再流片。这样可以降低流片风险,也能更快地响应市场变化。
6.3 度量与反馈闭环
软硬件协同设计需要建立度量与反馈的闭环。硬件团队需要知道软件的实际性能表现,软件团队需要知道硬件的设计约束。这些信息需要通过定期的评审和共享来流通。
具体的度量指标包括:算子的实际利用率、内存带宽的利用情况、编译器的编译时间和生成代码的质量、以及端到端的模型推理延迟。这些指标要定期跟踪,发现问题及时调整。
反馈闭环的另一个重要方面是:把部署阶段发现的问题反馈到下一代架构设计中。很多问题在流片后才发现,但如果能记录下来并在下一代设计中避免,那就是进步。
7. 一些个人体会
做AI芯片这些年,我最大的感受是:这个领域的门槛不在于单点技术,而在于系统性的协同。你可以找到很好的硬件架构师,也可以找到很好的编译器工程师,但让这些人高效地协作,让硬件和软件真正融为一体,才是最难的。
另一个体会是:不要追求理论上的最优,要追求实际可用的最优。一个理论峰值很高但软件跑不出来的架构,不如一个理论峰值一般但软件能跑满的架构。用户关心的是实际性能,不是纸面参数。
最后,AI芯片的软硬件设计是一个持续迭代的过程。没有一劳永逸的方案,只有不断适应新需求的能力。保持对新技术、新模型的敏感度,保持软硬件团队的紧密协作,才能在这个快速变化的领域里站稳脚跟。