做了这么多年芯片相关的工作,我越来越觉得"AI芯片的软硬件设计"这句话被说滥了。很多人以为设计AI芯片就是把一堆矩阵乘法单元堆上去,然后配个编译器就完事——真不是这样。我做过的项目里,真正的精力消耗往往不在芯片本身,而在于让软件栈能把硬件的能力榨干。一个很扎心的事实是:硬件能跑100 TOPS的算力,如果编译器调度不合理、数据搬运路径绕远路,实际端到端性能可能连40 TOPS都到不了。这正是软硬件协同设计要解决的根本问题。
这篇文章我想从一个系统工程师的角度,把AI芯片软硬件设计这件事拆开讲清楚。目标读者不是只看新闻的围观者,而是真正要入行做芯片、做编译器、做算子库、或者做AI应用性能优化的工程师。这块领域交叉性极强,纯硬件背景的人容易忽略软件栈的复杂度,纯软件背景的人又容易把硬件细节想得太简单。所以这篇文章的核心命题就是一句话:为什么AI芯片的性能天花板是由软硬件一起决定的,以及你在实际项目里该怎么入手去设计这个协同体系。
1. 算力只是标称值:AI芯片真正的三个性能瓶颈
先记住一个概念:任何AI芯片的标称算力(比如XX TOPS)都只是理论上限,实际能跑到多少取决于你能否绕过三个瓶颈。这三个瓶颈是软硬件协同设计必须直面的一线战场。
第一个是计算瓶颈(Compute Bound)。矩阵乘、卷积这类算子,理论上乘法器越多越好。但你要知道,一旦算力上去了,数据供给就成了问题——这就是第二个瓶颈,访存瓶颈(Memory Bound)。AI计算的核心规律是"一次读取的数据要尽可能多地参与计算",否则时间全浪费在数据搬运上。业界常提的"算术强度"(Arithmetic Intensity,即计算量除以访存量,单位是FLOPs/Byte)就是在量化这件事。算法上算术强度越高,越容易逼近计算瓶颈的极限;反之,如果数据复用性差,就算算力再高也白搭,因为数据从DRAM搬进片上缓存的带宽就那么大。
第三个是延迟瓶颈(Latency Bound)。小模型、边缘场景或者交互式推理,不仅是吞吐问题,单个请求的延迟也很关键。这种场景下,算力过剩没用,能不能快速把数据流式地灌进计算阵列反而更重要。软硬件设计里就得考虑流水线部署、多级缓冲、局部性调度这些细活。
这里还要引出一个关键概念:Roofline模型。它把可达到的性能画成一条屋顶曲线:左边是内存受限区,斜率为带宽;右边是计算受限区,水平线为算力峰值。软硬件协同优化的本质,就是让一个个算子尽量从"屋顶的左边"移动到"屋顶的右边"。
我常跟团队说一个生活化的类比:把芯片想象成一个中央厨房。算力是灶台数量,访存是食材运输通道,编译器就是厨师长。你在厨房里堆再多灶台,如果食材通道只有一扇小门,厨师再快也只能等着食材到场。AI芯片设计里,灶台(算力)容易堆,但食材通道(片内总线、缓存、HBM接口)的硬件面积和功耗都是巨大开销,怎么分配资源,是软硬件协同设计的核心权衡。
2. 从神经网络到芯片指令:算法如何映射成硬件行为
软硬件协同设计的第一步,不是写RTL代码,也不是写Python模型,而是想清楚一个问题:你支持的神经网络算子,该怎么变成硬件上的具体行为?
2.1 算子拆解与硬件原语对齐
主流的神经网络无非是卷积、矩阵乘、激活、归一化、池化、各种elementwise算子。硬件层面上,你需要定义一套"硬件原语",也就是芯片能高效执行的最小操作单元。通常是一个矩阵乘块(比如一个Cycle算完一个16x16x16的乘累加)、一个向量处理单元(Vector Unit)用来跑激活和elementwise,再加上数据搬运引擎(DMA)和片内缓冲。
我见过不少团队一上来就想着做通用可编程架构,结果硬件原语复杂到软件根本驾驭不住,最后只能用手写算子,维护成本高得惊人。正确的做法是反过来——从算法负载的特征出发,定义少量且高频的原语。当时我们做目标检测模型的时候,最高频的操作就是3x3卷积和1x1卷积,那硬件原语就围绕这两种形态来优化,其他不常见的例如空洞卷积,直接分解成多个3x3卷积的组合在软件层解决,而不是为它在硬件上加特殊通道。这个原则叫"让90%的算子跑得极快,10%的算子靠组合变通"。
2.2 一个具体例子:GeLU激活函数的硬件处理
很多人觉得激活函数有什么好设计的?但在软硬件协同里,这种不起眼的算子往往是性能黑洞。比如Transformer里的GeLU激活函数,数学定义是 x * Φ(x),里面带着一个误差函数。如果完全用精准数学去算,要么上多项式展开,要么查表,硬件逻辑会变得很大。
我们的做法是软硬件协同设计这个算子——精度上允许误差,硬件上不直接算GeLU,而是先算一个相对简单的近似:在硬件里内置一个近似单元,利用分段线性插值或者低阶多项式去拟合。编译器在编译计算图的时候,识别到GeLU节点,自动替换成硬件近似实现。这样就在不显著影响模型精度的前提下,省掉了一大块硬件面积。这个流程需要回流验证——把优化后的算子再放回整个PyTorch模型里跑一遍精度对比,误差在允许范围内才能落地。这就是软硬件协同在日常项目里的一个缩影:硬件提供能力选项,软件负责在精度和效率之间找到平衡点。
2.3 卷积如何映射到矩阵乘单元上
卷积在硬件上真正的执行方式,往往不是直观的滑窗扫描,而是转化为矩阵乘。两种经典映射方案:im2col(把卷积窗口展开成矩阵)和隐式GEMM(直接用矩阵乘逻辑去映射卷积的滑动窗口)。两者在硬件实现里都为了同一个目的——让卷积复用在为矩阵乘设计的脉动阵列或者乘累加阵列上。
这里面有个关键决策:数据在片上的布局。你是用NHWC还是NCHW?权重是存储成亲和计算阵列的形状,还是亲和连续访存的形状?别小看这个选择,它直接影响编译器后续做数据切片和张量布局优化的空间。我们的经验是:硬件上定义好逻辑的数据布局约束,编译器在这个约束下自由优化,模型跑出来之前不要过早固定布局方案。先设计空间大一点,保留编译器优化的自由度,到后期发现瓶颈再收敛约束,这个序别搞反。
3. 编译器是芯片灵魂:中间表示、调度与内存分配的实战逻辑
如果只做硬件不做配套的编译器,AI芯片几乎没有实用价值。业界常说"编译器是芯片灵魂",这话一点不夸张。因为AI算法迭代太快,你不能指望每一个算子都靠手写汇编或者手写RTL适配模型。
3.1 工具链的分层结构
一个典型的AI芯片软件栈从下往上大致是:硬件抽象层(HAL)→ 指令集或宏指令 → 编译器(对应TVM、XLA、llvm等)→ 图优化层(前端)→ 算子库/运行时 → 推理框架接口。不同公司对这个分层的命名不同,但逻辑大致如此。
这里我特别想强调前端图优化层的重要性。这个层跟你用什么后端实现关系较小,它负责的是从计算图层面做算子融合、布局转换、消除冗余计算。比如把"Convolution + BatchNorm + ReLU"三个节点融合成一个算子,推理时BatchNorm在训练之外是可以折叠进卷积权重的,这一步融合在纯框架层面就能做,但是只有当编译器告诉它"我底层硬件支持融合算子形态"时,它才敢合得彻底。所以软硬件协同的一个细节就是:图优化层要早定义一套可扩展的算子语义,硬件原语的边界要跟这个语义对齐。
3.2 调度与内存分配里的真功夫
编译器真正体现价值的地方在调度和内存规划。一个模型跑在芯片上,每一层的数据从哪里来?算完放哪去?中间结果要多大空间?多条流水线之间怎么重叠?这些号称"调度问题"的内容,是纯软件可解的难点中最硬的一块。
一个很常见的优化叫算子融合(Operator Fusion)。两个连续的elementwise算子可以进行"算子融合",内存里数据就不用来回搬运。举个量化场景的例子:推理的时候"Conv -> ReLU -> Quantize"三个算子,以前是三个算子做三次读写,融合后,卷积单元算完一个像素块,数据直接留在片内buffer,过ReLU和Quantize,再直接写回输出。对HBM带宽的节省可以达到几十个GB/s的级别。
另一个实战中容易踩的坑是循环分块(Loop Tiling)。你以为把维度拆开就完事了?分块的大小必须和片上SRAM大小、DMA对齐粒度、算子的算术强度配合起来。我们当时调一个3x3卷积的性能,死活上不去,最后发现问题出在分块大小选的跟DMA突发传输长度不匹配——传输的每一步都带了一点无效数据,带宽利用率掉了近一半。这种问题,光在软件层面看是看不出来的,你必须理解硬件的突发长度与对齐要求。这就是软硬件协同设计最真实的状态:两边都要懂,两边都要妥协。
| 优化手段 | 核心收益 | 主要代价 | 适用场景 |
|---|---|---|---|
| 算子融合 | 降低访存次数 | 编译器复杂度上升 | w1. 内存带宽受限的模型 |
| 循环分块 | 提升数据复用率 | 需要硬件参数配合 | 超大feature map卷积 |
| 布局转换 | 改善内存访问连续性 | 引入额外transpose算子 | NHWC/NCHW混用场景 |
| 内存复用 | 降低片上buffer占用 | 可能有生命周期分析问题 | 大模型推理,端侧部署 |
3.3 性能模型与预估工具
软硬件协同设计不可能每次优化都等到芯片流片回来看结果,那样周期太长,根本不能接受。真正的做法是开发一套性能模型(Performance Model),在芯片还在设计阶段就能估算出每条指令的执行周期、访存冲突情况、流水线stall情况。
性能模型的价值在于可迭代:硬件改一个参数(比如SRAM容量从1MB加到2MB),软件侧模拟就能立刻看到卷积算子的延迟变化;编译器改了调度策略,也能通过模型验证是否真实改进了性能。我们团队曾通过性能模型发现,把两个DMA通道从"轮流搬运"改成"乒乓交替搬运",整体推理吞吐提升了约23%。这个改动如果等到芯片回来再验证,成本会高太多。
4. 精度、数值格式与模型量化:软硬件协同里的隐蔽战场
AI芯片不只是算得快,还要算得对。这里的"对"不是指数学意义上的绝对准确,而是最终模型精度可接受。软硬件协同里最隐蔽、最烧脑的一块就是对数值精度和量化的处理。
4.1 为什么不能全部用FP32
道理很简单:成本和效率。FP32的乘法器和存储单元面积大约是INT8的几倍,功耗更高,访存量还大。在边缘端跑大模型,全FP32根本不现实。所以业界普遍用混合精度策略——对精度敏感的层用FP16/FP32,对精度不敏感的层用INT8甚至INT4。但问题是:你怎么知道哪一层敏感?
这就要靠量化感知训练(QAT,Quantization-Aware Training)来做。那我的建议是:软硬件设计时先确保硬件支持多种精度模式的快速切换,而把"哪层用多少bit"的权力交给编译器。编译器结合硬件后端的性能和实测精度来决定每一个算子的实际精度配置。我们内部称为"精度搜索",思路跟神经架构搜索有点像,只是搜索空间变成了每个算子的数值格式。
4.2 不凑整数的数据宽度
最近这些年,几个大厂开始推进非整数倍bit宽度的浮点格式,比如MXFP4、MXFP6这种。动机很直白:在模型精度只损失一点点的情况下,访存和运算开销又降了一个档次。硬件上支持这些花式格式,会给编译器带来额外负担——所有数据都需要做格式转换。这个转换的代价也是同步在设计时需要算进去的:格式转换单元放在DMA路径上还是计算阵列前,直接影响流水线会不会出现气泡。
我给一个体感参考:如果只是一般业务模型,FP16和混合精度其实够用了;如果做超大模型,那么低位宽格式是绕不开的,而且最好是软硬件一起设计好这个新格式,而不是等软件栈先出一版格式、硬件后面再去适配,那样一定会出现数据布局对不上的兼容性噩梦。我们的经验是先定好数值格式语义,再分别做硬件和软件侧的实现,最终用一套统一的抽象描述格式来串联——这样两边不会出现理解偏差。
4.3 数值误差的追踪手段
还有一个在实践里容易翻车的细节:浮点累加的顺序会影响结果。你在性能模型里模拟FP16累加和在真实硬件上跑的结果会有一点点差异,但在评估大模型最终输出时误差可能被放大。我们是主张从设计初期就建立bit级精确的行为仿真器,在硬件还没回来之前,软件就已经知道"某种组合的数值计算会产生什么误差"。同时配合一套自动化测试,每次模型精度有异常就自动下钻到具体哪一层算子的误差贡献大。没有这套工具,AI芯片项目做深了迟早会被精度问题搞得焦头烂额。
5. 验证与调试的真实世界:从仿真、FPGA到硅后调试
任何芯片项目都会经历从仿真验证到硅后调试的漫长链路,AI芯片因为软硬件的深度耦合,这条链路会更加纠缠。
5.1 仿真阶段就先跑真实模型
其实一个非常值得推广的做法是:在RTL仿真阶段就接入真实模型。当时我们是把编译器的输出变成一组指令序列,然后在RTL仿真器里跑起来,比对结果与CPU参考实现的差异。在仿真阶段跑全量ResNet50或者BERT,确实非常慢,但好处惊人——基本两层以内的错误都能被逮到。等芯片回来,软件栈的初始版本大概率是能跑通的,能省掉无数调试的夜晚。
这个阶段我们能得到什么数据?每一条指令的周期数,每个模块的空闲率,DMA的带宽利用率,SRAM的拥塞情况。这些数据对后续优化硬件调度策略极其有用。其实芯片设计不是一个纯脑力活,很多时候是靠仿真数据来引导的,仿真阶段把硬件行为吃透,软硬件接口就已经成功了一大半。
5.2 FPGA原型验证与性能预估的差距
FPGA原型平台也是绕不开的验证手段。不过必须说清楚:FPGA跑AI模型,目标是验证功能,而不是看性能。FPGA的频率和片上网络跟真实ASIC的差异非常大,所以你看到FPGA上的性能数据,往往远低于真实芯片。但FPGA的价值在于做软硬件集成的预演——编译器、runtime、驱动、推理框架,全部跑一遍,把接口问题都暴露干净。
我们踩过的坑包括:
- DMA地址对齐与burst length不匹配,导致传输效率骤降;
- 中断处理路径过长,导致推理流水线停顿;
- 权重布局没有按硬件SRAM bank对齐,出现bank冲突,读取效率减半。
这些问题里,前两个其实是比较经典的软硬件接口问题,不是芯片逻辑错了,而是软件调用方式跟硬件设计预期不一致。到了FPGA阶段才暴露,虽然有点晚,但好在不是在硅片上暴露。
5.3 硅后调试的新挑战
芯片真正回来后,调试的难度会再次上升。你看不到内部节点的真实状态,只能通过扫描链、观察寄存器、以及精心设计的硬件计数器来反推。这里最需要的东西,是硬件侧埋足够的性能计数器。
我们是强烈建议AI芯片设计早期就规划好性能监控单元(PMU,Performance Monitoring Unit),至少按sub-system粒度去统计:计算单元利用率、DMA传输总量、片上buffer命中率、总线stall周期数、流水线空泡数。没有这些数据,性能调优的路基本走到头了。在那些头部团队的分享中,很多难度极高的性能问题是靠性能计数器一层一层跟踪定位出来的,而不是靠猜。所以,在RTL设计阶段就规划PMU,我认为是AI芯片软硬件协同设计里性价比最高的行为之一。
6. 生态、工具链与商业落地的现实博弈
最后回到一个更现实的话题:芯片做出来不难,难的是有人愿意用你的工具链,并且性能真的达标。这里面有大量生态层面的软硬件博弈。
6.1 好用 vs 可控
在项目管理上,你通常会面临一个持续不断的选择:编译器是尽量自动完成调度,还是把调度权开放给开发者手工调优?
自动调度的好处是上手门槛低,PyTorch模型扔进去,改改配置就能跑起来;坏处是通用路径往往拿不到极致性能。高水平的性能工程师会希望手写关键算子,或者至少能手动干预调度策略,比如指定循环分块的大小、指定数据放在哪一级Buffer、指定DMA预取时机。
我的实践经验是:两套机制都要有。默认情况下编译器自动调度,提供一系列分析报告,告诉用户"哪几个算子最耗时、为什么耗时、建议怎么优化";高阶能力则通过一种"意图式编程接口"向用户开放,让懂行的人直接干预调度。这两个层面缺一个,都会在实际落地时遇到重大阻碍。只开放自动编译,顶尖客户会觉得芯片太弱;只强调手工调优,普通客户根本用不起来,OS出包就没了竞争力。
6.2 与主流框架的适配节奏
另一个核心问题是:AI芯片的软件栈必须快速拥抱主流的训练框架(如PyTorch)和推理运行时(如ONNX Runtime),而且这两者的版本迭代极快。芯片软件团队必须时刻跟进框架的新特性。我们会把"跟框架社区周期同步"作为一条工作原则:每个季度做一次主干合并,每次发布新版本都要完整回归一遍相关算子和端到端模型清单。
这里有个具体的软硬件协同点:框架侧对算子的语义定义,会直接影响编译器中算子的语义映射实现的一致性。比如PyTorch新出了一个融合的注意力算子,编译器要判断它能否额外地做一次折叠、融合进之前的矩阵乘,这就涉及硬件片上缓存容量的问题——如果缓存放不下整个中间结果,就得分块做,性能又变了。所以这些协同设计,往往不是一次性的,而是一条持续拉通的长线任务。
6.3 路标决策:通用与专用之争
设计AI芯片时还有一个宏观决策:走更通用的GPGPU路线,还是走面向特定模型的专用ASIC路线。通俗一点说,这其实是"铺高速公路"和"修地铁专线"的区别。高速公路能让各种车都跑,但跑特定班次的高速专列不一定最划算;专线很高效,但一旦出行需求变了,专线可能会废掉。
我们当年的判断标准很简单:考虑清楚产品生命周期内,你主打的模型family是什么。如果你是先锚定CNN做优化,那么Transformer大火之后,硬件能不能灵活应对?如果硬件里计算单元高度专用于卷积的im2col逻辑,处理attention矩阵乘可能就很吃力。所以现代AI芯片设计普遍强调在计算阵列可重构的前提下,对一些固定pattern做专用硬化。软硬件协同设计在这里收敛形成一个结论:计算要通用,访存要丰富,专用算子要用软件融合去实现。
7. 我的几条个人经验总结
如果非要用几句话总结AI芯片软硬件设计的核心心得,我会说以下几点:
第一,硬件设计从第一天起就要想着软件。很多芯片团队在流片回来之后才召集软件团队开会,这是最大的错误。我见过太多这样的事。编译器开发者需要的是和RTL开发者坐同一张桌子,提前讨论指令集语义、缓存替换策略、DMA对齐方式这些细节。这种"软件早于硬件介入"的方式,是协同设计能否落地的根源保障。
第二,性能模型的投入永远是划算的。在芯片设计周期中,性能模型越早建立、越贴近真实实现,后面调节点的时候越有底气。纯靠估算、靠经验拍脑袋,很多问题直到硅后才暴露,那就来不及了。
第三,准备好一套从模型到芯片的可复现调试链路。从PyTorch算子到高层IR,到底层指令,再到仿真器和FPGA,再到真实芯片,每一步的状态都要能追踪、能回放、能对比。这个工程基建听起来不性感,但没有它,软硬件协同设计就是纸上谈兵。
最后分享一个操作层面的小技巧:在做算子映射的初期,先把性能模型和指令集手册整理成一份机器可读的"硬件能力描述文件",然后让编译器直接读这个文件,做资源调度和可行性检查。这样能避免编译器里硬编码大量硬件假设,后续硬件调整时,只要更新描述文件,编译器就能自动适配。这个小事对我们的开发效率提升极大,强烈建议做AI芯片软硬件协同的团队都考虑这个方向。
AI芯片的软硬件协同设计是一条少有人走完的路,但它的价值正体现在每多走一步,你对"如何让计算更快、更省、更准"这件事的理解就更深一层。希望这篇偏实战的梳理,能帮你在这条路上少走几步弯路。