AI 芯片的软硬件设计:从算子映射到系统调优的完整链路
做AI芯片这几年,我见过最打击人的场景不是流片失败,而是芯片造出来、峰值算力标得漂漂亮亮,算法团队一跑模型,性能连一半都摸不到。明明INT8算力到了上百TOPS,实际跑个Transformer却卡在访存上,GPU能跑到几十毫秒的延迟,自家芯片连人家一半都不到。问题往往不在芯片,而在软硬件设计这条链路上的某个环节断了。这篇是“AI芯片软硬件设计”系列的第三篇,我会把算法到硬件这条映射链路拆开讲——算子在芯片上怎么落地、编译器在其中扮演什么角色、多核互联如何影响系统性能,以及我在真实项目中踩过的坑和排雷经验。适合正在做AI芯片工具链、算子库、部署优化的工程师,也适合想搞懂“芯片为什么跑不快”的算法同学。
1. 同样一块AI芯片,为什么有人能榨出80%算力,有人连30%都摸不到
很多团队评估芯片只看一个指标:峰值算力。但这个数字在真实业务里几乎没有参考价值。同样的芯片,有人能做到优异的端到端性能,有人做出来的模型延迟惨不忍睹,差异不在于芯片本身,而在于软硬件设计的协同深度。
1.1 一个让我印象深刻的性能压测案例
之前有一次帮客户调试一款自研AI加速芯片。同一块芯片、同一个ResNet-50模型,客户自己的团队调了一周,batch size 32下的吞吐是每秒钟处理850张图。我们接手后没改任何算法,只是在编译策略和算子实现上做了调整,同样的精度、同样的硬件,吞吐翻到了每分钟超过32000张——折算下来是每秒530多张,整体提升了接近两倍多。我说这个例子不是为了秀操作,而是想说明一个事实:芯片的物理能力是固定的,能让它发挥多少取决于软件栈。
那位客户的团队其实很认真,算子库写了,指令也调了,但有一个根本问题:他们一直把“芯片”当成一个独立的硬件来看,而没有把“算法—编译器—硬件”当成一个整体来设计。软硬件协同设计,说的就是这件事——从算法表达的第一天起,硬件架构、数据流方式、存储层级就在影响结果,而不是等芯片回来再适配。
1.2 软硬件协同设计解决的不只是“加速”,而是“利用率”
芯片设计里有个概念叫算术利用率(MAC Utilization),指的是单位时间内实际参与计算的MAC单元比例。峰值算力是“所有MAC全部塞满”的理论值,而利用率能到70%就相当不错了。为什么到不了100%?因为数据喂不过来。
我举个例子你们就明白了。假设一个AI芯片有128x128的MAC阵列,运行在1GHz,那么每个周期理论上能完成16384次乘加运算,每秒就是16.4 TMACs,如果是INT8,算力就是16.4 TOPS附近。看起来没问题。但要让这16384个MAC同时干活,每个周期至少要喂进去对应卷积窗口的输入激活和权重数据。如果其中一部分MAC在等待数据,利用率就掉下来了。数据在哪等?不是在片外DRAM,就是在片上SRAM和计算阵列之间的搬运路上。访存越远,等待越久,利用率越低。
所以软硬件协同设计的第一个目标是让数据尽量在片上自洽循环,减少对外部存储的依赖。第二个目标是让计算模式和存储匹配,把“算法结构”映射成“数据结构”,再把“数据结构”映射成“硬件执行结构”。这三层映射任何一层错位,性能都会打折扣。
1.3 拆开AI落地链路:算法表达、算子实现、指令映射、硬件执行
一个神经网络要跑在AI芯片上,至少经过四层:
- 算法表达层:PyTorch、TensorFlow里的Conv、MatMul、LayerNorm,是高度抽象的数学描述
- 算子实现层:把张量运算拆成循环、矩阵分块、地址偏移等代码逻辑
- 指令映射层:通过编译器和手写汇编把算子代码变成芯片能执行的指令序列
- 硬件执行层:指令驱动MAC阵列、访存单元、片上网络真正跑起来
每层之间都有翻译损耗。算法层一个BatchNorm折叠优化,能在指令层减少几十上百次内存访问;算子层一个tile size选错,能让后端指令多造很多次没必要的SRAM reload;指令层一个依赖调度没排好,执行层就只能空转等数据。所以你不要问“为什么芯片算力高却跑不快”,而是问“这四层之间有没有做过协同优化”。
提示:评估AI芯片前期,先拿一个真实模型跑通全链路再谈选型。只看RTL仿真和峰值算力做决策,等拿板子的时候很容易被现实打脸。
2. 算子落到芯片上的关键一公里:数据流、存储层级与计算阵列的配合
现在我们把镜头拉近到芯片内部,看单个算子——以卷积为例——到底怎么在硬件上落地。这一层是软硬件设计最硬核的部分,也是最容易被软件背景的人忽略的部分。
2.1 MAC阵列的三种主流数据流风格:权重固定、激活固定、输出固定
卷积本质是乘累加,但同一个卷积在硬件上可以用不同方式调度数据。学术界和工业界总结下来主要有三种数据流风格,区分标准是“哪个数据被固定在计算阵列里不动”。
权重固定(Weight Stationary):权重矩阵加载到MAC阵列的寄存器里,然后不断换输入激活、累加输出。DianNao、PaddlePaddle很多加速器用这个风格。好处是卷积的每个输出只算一次、权重复用率极高,适合卷积权重相对固定、逐层加载的场景。坏处是对激活值的广播带宽有要求,每一层都要重新装载权重,权重切换开销也存在。
激活固定(Input Stationary):输入激活被固定在片上,多个输出并行计算,配不同的权重。这种风格适合深度可分离卷积这类激活复用多的算子,但在普通卷积里激活窗口没有那么大,收益不明显。
输出固定(Output Stationary):每个PE负责计算某个输出像素的部分和,多个PE之间传递权重和激活来做数据复用。英伟达Tensor Core其实更接近这种思路——一个warp的矩阵乘法被切分到不同工位上做累加。输出固定风格的优势是它能保存部分和而不必频繁回写DRAM,对于全连接、MatMul这类算子特别友好。
你不要问我哪种最好——这取决于你要跑什么模型。这也是软硬件协同设计里最难的部分:架构定了、数据流风格定了,就等于器件期权的上限定了,之后算法只能去适配它。所以架构选型阶段就要把目标负载的算子和统计特性拉出来。
2.2 片上带宽才是真正的稀缺资源,比算力宝贵得多
回到刚才那个128x128 MAC阵列的例子,继续算数据账。每个周期16384个MAC完成一次乘累加,如果每个数据是INT8,每个MAC需要一个权重字节加一个激活字节,那就意味着每个周期阵列至少要消耗32KB数据。1GHz下就是32TB/s的带宽。这个数字在当下芯片里有效现实吗?
片上SRAM通常能做到每周期提供几百byte到几KB级别的带宽,注意这是总额。要支撑32TB/s,需要超大位宽的片上总线,面积和功耗都会爆炸。所以硬件设计者才想尽办法做数据复用:卷积里,一个权重要被很多输入用,一个输入像素也要和很多权重做乘加,只要把数据搬一次、用多次,等效带宽就大幅下降。
这里有一个基本公式:性能天花板取决于算力除以算术强度(Arithmetic Intensity)。算术强度=计算量/访存量,单位是FLOPs/Byte。如果一个算子的算术强度低于Roofline模型里的拐点,那它一定是带宽受限的,再快的MAC阵列也只能等着。我在实际项目里见过太多团队盯着峰值算力优化算子,最后发现瓶颈全在DRAM带宽。先算清楚你的目标算子是计算受限还是访存受限,再决定优化方向,能省一半力气。
2.3 算子重写:硬件适配软件,软件也要适配硬件
那么算法层程序员能做什么?答案是算子重写(Operator Rewriting)。这个是软硬件协同里最接地气的一环,门槛不高但收益直观。
举个例子,很多人直接拿PyTorch里的nn.Conv2d跑自研芯片,发现慢得离谱。问题往往不在卷积本身,而在于数据布局。PyTorch默认的NCHW布局下,芯片每次想要取出一个通道的连续数据,地址很可能正好卡在DRAM burst对齐的边界上,导致有效带宽砍半。你要是把数据重排成NHWC,或者干脆用通道优先布局,访存一下子就顺了。
另外还有几个常见操作:
- 降低内存粒度:计算过程中尽量用tile分块访问SRAM,不要动不动全图走DRAM
- 地址对齐:把张量的行宽pad成芯片访存单元对齐的字节数
- 算子融合:把前面的ReLU融合到卷积里,省掉一次中间结果的写回和读取
- 精度包装:INT8比FP16在同样的带宽下能多传一倍数据,能量粒度也更细
这些都不是很深的技巧,但每一条都要求你理解硬件是怎么访存和计算的。这就是软硬件协同设计对工程师直接的要求。
3. 编译器与工具链:软硬件协同的翻译官,决定芯片好不好用
算子写好了,还要有工具把它部署到芯片上。很多芯片团队早期觉得“硬件做好了,编译器随便配一个就行”,结果产品发布后被吐槽工具链稀烂,成了最大的短板。编译器在AI芯片里的角色不是简单的“翻译官”,更像一个优化调度器,它决定了哪些算子融合在一起、数据在哪个层级停留、计算单元怎么流水。
3.1 从计算图到机器码需要经历哪些阶段
一个深度学习模型经过工具链时,通常要经历这几个阶段,我简化一下:
- 前端解析:把ONNX或自定义格式的计算图读进来,做算子的合法性检查与张量形状推断
- 图优化:算子融合、常量折叠、公共子表达式消除、删除无用节点
- 物理化/布局转换:把逻辑张量映射到具体的存储布局(NHWC/NCHW/NC1HWCO等)
- 指令生成:把算子循环展开成目标芯片支持的指令序列,包括DMA搬运、矩阵计算、SIMD向量、同步等待
- 调度与寄存器分配:安排指令在硬件流水线上的先后顺序和并行粒度
每一步都可能让性能发生数量级变化。比如图优化里的算子融合,把Conv+BN+ReLU合成一个算子,中间结果不用写回DRAM,直接以寄存器/片上SRAM的形式传递,这能省掉的访存次数经常是惊人的。实测中,ResNet-50里融合几个关键算子之后端到端延迟能提升40%到60%——这在纯硬件层面是不可能出现的提升幅度。
3.2 算子融合和内存布局:编译器最有价值的两个优化
算子融合的原理一句话:减少中间张量的物理物化(materialization)。但融合不是无脑做。有些融合能稳赚,有些反而亏。
比如Conv+ReLU的融合几乎总是赚的,因为ReLU是逐元素操作,不需要改变数据布局。但Conv+LayerNorm就要小心:LayerNorm需要统计整体均值和方差,如果直接把两层融合,要么提前缓存全部卷积输出(省了访存但占了片上资源),要么分块计算但需要多次扫描(增加了开销)。编译器的取舍实际是工程权衡,每类芯片的资源模型不同,融合决策也不同。
内存布局就更关键了。对卷积来说,数据是NCHW还是NHWC,或者像寒武纪用过的NC1HWCO这种带子通道的变体,直接决定卷积那个循环怎么展开。布局选得好,一个卷积可以是用向量指令硬算;选不好,大量时间花在数据重排(transpose和copy)上。我见过有的芯片跑一个1x1卷积,实际计算时间占比不到15%,剩下全在搬数据。出现这种情况,八成是布局和硬件访存模型不匹配。
注意:编译器里如何布局往往不是自动得出的,很多时候需要手写“layout transform”或者干脆写一个专用卷积的实现,把重排和计算融为一体。
3.3 自动调度并非万能,手写算子库的价值在哪里
现在开源社区非常推崇自动调度,TVM、MLIR、Triton这些工具能通过搜索找到性能好的tiling和循环顺序。但我的实际经验是:自动调度在“芯片能力比较规整”的GPU上表现很好,在一个新架构的自研AI芯片上,它经常找不到最优解,因为搜索空间和硬件建模不够细。
差距的根源在于“代价模型”。编译器判断一条指令好不好,需要预测它在硬件上的执行时间。如果硬件行为很复杂——比如有bank冲突、指令发射延迟、DMA搬移重合等——代价模型建模不准确,那搜索出来的调度就是纸上谈兵。
所以在真实项目里,好的做法是两者并行的双轨策略:
- 框架自动调度兜底,保证95%的模型能跑起来且性能不差
- 核心算子用手写汇编或C intrinsics硬优化,把每个周期省下来
哪些算子值得手写?通常是高频、耗时的那二三十个:3x3卷积、大尺寸MatMul、Attention里的QK^T和Softmax、各种LayerNorm。这些算子的共同特点是占用端到端时间比例很高,且内部有清晰的并行结构可以手工优化。我甚至会在编译器接受不了的场景下直接绕过编译器“下降”到手工加载权重、手工DMA的方式写推理。这个做法看着粗暴,但每轮手写优化都能稳定提升。
4. 从单芯片到多核互联:系统级软硬件设计不容忽略的环节
芯片不止一个核。今天的AI芯片动辄几百个计算核心,甚至像Cerebras那种晶圆级芯片。多核系统下,性能瓶颈又从“单核有效利用率”变成“多核协同、数据同步、片间通信”,这一层软硬件协同设计往往被低估。
4.1 多核AI芯片绕不开的带宽墙
假设你有64个核心,每个核心每秒需要10GB数据,那片内总需求就是640GB/s,从L2/系统缓存到核的带宽要满足这个量级。如果每个核标配一个局部SRAM,那问题就变成:数据怎么分布到各个核上,以及核间怎么共享中间结果。
最经典的操作是“模型并行”或“张量并行”——把一个大层切成多份,每个核存一份权重、算一份输出。但切分就会产生通信开销。比如全连接层,如果输出要跨核做concat,就需要一个NoC上的多路广播或收集操作,代价随核数增长。这就是带宽墙:核多了,计算确实快了,但每次通信的体积也成倍增长,最后时间反而被通信填满。
4.2 Ring、Mesh与NoC:互联拓扑的取舍
互联拓扑是系统软硬件设计里成本最高的部分之一。简单说三种常见方案吧。
Ring互联:所有核连成一圈,数据接力传。优点是布线简单、成本低;缺点是远距离通信的延迟随跳数线性增长,不适合数据密集的全局操作。适合低期望值下的简单场景。
Mesh互联:每个核和上下左右邻居相连,2D格子网络。这是半导体工艺友好的选择,扩展性好。英伟达的NVSwitch和许多多核AI芯片都用类似设计。Mesh的问题在于要避免“热点”——如果某个核频繁和其他核通信,它所在的格子会成为拥挤点。
NoC(片上网络):更一般化的交换/路由网络,类似微型的互联网路由器。它能把通信调度的复杂度放到运行时,但面积功耗都不低。软硬件协同的视角下,NoC更好做负载均衡。
实际项目的选择逻辑是:如果目标模型精度较高、层间张量大,建议Mesh或NoC,因为可以支持乱序和动态路由;如果目标场景是端侧小模型、权重本身就小,Ring就能满足还省成本。
4.3 端到端延迟与吞吐指标怎么选
做多核系统评估时,我常看到一个错误:只看总体吞吐(throughput),忽略单次延迟(latency)。推理场景往往对延迟敏感——比如视频里每一帧都不能等太久。多核并行通常能拉高吞吐,但如果你每层输出都要跨核同步,延迟反而会恶化。
正确做法是建立系统级性能模型。假设一个操作切到N个核上,计算时间减少到T/N,但通信时间加上同步开销C,那端到端延迟=T/N+C。当N太大,C可能比T/N还大。所以存在一个最优核数,不是越多越好。这块用Roofline或者更细的执行时间模型都能算,重点是把通信的成本“进模型”。
经验:不要在测试时只比较算力利用率,要直接跑端到端的batch 1延迟和不同batch下的吞吐,并记录通信时间的占比。如果通信占比超过30%,你的互联设计或数据布局大概率有问题。
5. 真实项目中软硬件协同设计的排雷清单
最后这部分,我结合这些年踩过的坑,整理一个排雷清单。每一项都是我实际在项目里遇到过,并且花了不少时间才解决的问题。
5.1 评估芯片前先评估工具链,别被峰值指标迷惑
选了芯片之后发现算子支持不全、编译器质量糟糕,是迟到的代价。芯片流片前就要确认工具链的支持范围:它支持哪些算子?支持动态shape吗?量化工具可不可用?有没有在线编译能力?这些能提前从文档和SDK里查出来,别拖到板子到手。
如果团队可以给工具链提定制需求,优先做三件事:
- 保证主流模型的计算图能无错编译过,即使性能不是最优
- 提供算子的回退(fallback)路径,防止编译器死掉整个流程
- 内置基础性能测试工具,方便开发者定位瓶颈
5.2 量化精度与性能的权衡:没有免费的午餐
INT8量化几乎是推理芯片的标配了,但量化失败的项目我见了不少。常见问题:用per-tensor的scale量化敏感模型导致精度大跌,然后团队互相甩锅。其实问题不难解,关键在软硬件协同的两个层面:
- 算法层:做量化感知训练(QAT),把量化的误差模拟进训练过程
- 硬件层:支持per-channel量化,或在矩阵乘法部分用混合精度,比如用INT8算主体、FP16做累积和残差连接
性能收益和精度损失要提前量化。我一般会在项目初期跑一个量化精度测试表:同样的模型用FP16、INT8、混合量化跑一遍,记录各种指标对比,再决定优化方向。
5.3 Roofline模型是软硬件协同的通用语言
我做评估和优化时最常用的工具不是特别高深的框架,而是一张Roofline模型图。横轴是算术强度(FLOPs/Byte),纵轴是性能,硬件的能力体现为一条(或多条)峰值性能线,软件的实际性能在图上就是一个点。
- 点落在横线以下,说明是带宽受限,优化方向是改布局、压缩、提高复用
- 点贴到横线上,说明是计算受限,优化方向是增强向量化、改进流水
几乎每个算子优化问题都能用Roofline定出方向,再落到细节。尤其在做软硬件协同设计时,架构师、编译器工程师、算法工程师可以用这张图对齐语言:你说你算子慢,慢在哪一轴?一画便知。
拿我们那次ResNet调优来说,一开始analyze出来Conv层的点都在带宽线下面——数据搬运瓶颈明显。我们改了数据布局和融合策略,点往上移动,最后几个算子贴到了计算线上,利用率才算正常。没有Roofline引导,我不会定位得那么准。
5.4 最后一件事:性能回归测试要自动化
软硬件团队分工总会导致一个问题:算法团队改模型结构、编译器团队升级代码、硬件团队微调频率,各自都觉得自己的改动“没影响性能”。结果集成到一起,整机性能降了20%,谁都不认账。
性能回归测试一定要自动化,并且纳入CI。每次工具链、算子库或驱动更新后,同一组模型必须跑一遍基准吞吐和延迟,任何一个关键指标掉了5%以上就自动告警。没有这个机制,性能劣化会在几周内悄悄累积到你很难定位。这个工作不复杂,但能治软硬件协同里的最大顽疾——信息断层。
从算子映射到系统级互联再到工具链,AI芯片的软硬件设计从来不是某一个角色单独的事。我越来越觉得,好的芯片产品是架构师、编译团队、算法团队和系统团队一起“吵”出来的:架构师听算法团队说负载需要什么,编译器团队告诉架构师哪些微架构特性不现实,系统团队在最后集成时兜住所有的性能风险。这条链路很长,但每一步都有迹可循、有模型可算、有捷径可走。希望这篇文章能帮你在自己的项目里少走几段弯路。