这期聊聊AI芯片的软硬件设计,确切地说,是软硬件“协同”设计这件事。前两篇我们把AI芯片的大格局和基础概念都过了一遍,这次进入最核心、也最让工程师头疼的环节:一块AI芯片从定义到落地,软件和硬件到底是怎么互相拉扯、互相成就的。如果你正在做芯片架构、嵌入式开发、模型部署,或者只是在选型阶段想知道“为什么有的芯片工具链好用,有的难用得要命”,这篇都值得你花十分钟看完。
我先抛个结论:AI芯片的难点从来不只在硬件本身,而在于你设计硬件的时候,必须脑子里同时跑着软件栈;写软件栈的时候,又必须时刻想着硬件能给你什么、不能给你什么。两侧团队如果没有共同语言,那这个项目大概率会在流片回来的第一天就陷入混乱。
1. 软硬件协同设计:为什么AI芯片必须“软硬一起想”
1.1 通用计算时代的老办法,在AI负载上行不通了
过去的CPU设计思路,本质上是一种“硬件先定,软件适应”的模式。芯片公司把指令集架构定好,把微架构做好,然后扔给编译器团队和操作系统团队去适配。软件这边能做的优化,基本都局限在“如何更好地利用现有硬件资源”的层面,比如指令重排、缓存优化、分支预测调整等等。硬件和软件的交互,被一个相对稳定的指令集边界切开了,大家各管各的,日子过得还算和平。
但AI负载把这个平衡打破了。神经网络的计算模式非常集中,密集的矩阵乘加、大规模的数据搬移、规则或不规则的访存pattern,再加上从卷积到注意力再到各种归一化算子的多样化组合,如果你还指望用一个通用指令集去硬扛,再牛的编译器也救不回来。我见过太多团队最开始用通用CPU去跑Transformer推理,优化来优化去,延迟还是降不下来,瓶颈全在数据搬运和指令开销上。这个时候你就明白了:不是你的软件写得不好,是硬件根本没有为这种负载形态提供合适的“地形”。
1.2 AI负载的三个特征,决定了设计思路
先看访存密集。神经网络的计算密度和访存密度往往是不平衡的,很多算子算力根本跑不满,数据从内存搬来搬去的时间反而占了大头。我记得有人统计过,某些模型在GPU上推理时,真正用于计算的周期可能连30%都不到,剩下的时间全在等数据。这意味着,架构设计时必须把“数据供给”当成头等大事来考虑。
再看算子多样化。早期很多AI芯片押注“把所有东西都做成矩阵乘”,确实卷积可以拆成矩阵乘,全连接也是矩阵乘,但后来的Transformer带来了大量非矩阵类算子,比如Softmax、LayerNorm、各类Elementwise操作,甚至还有动态形状的算子。芯片设计如果只围着一种计算形态转,遇到这些就抓瞎,只能靠编译器软磨硬泡地拆解,性能和效率都很难看。
最后是动态性。模型的输入shape会变,推理过程中的张量维度会变,有的模型还会根据条件跳过一部分计算。硬件如果把一切都写死,软件就没法灵活调度;但硬件如果太灵活,面积和功耗又受不了。这个“灵活度的灰度”怎么把握,就是软硬件协同设计的核心命题。
1.3 协同到底在“协同”什么
说得直白一点,软硬件协同就是双方商定一个“分工契约”,然后各自在自己的领域把对方的需求前置考虑。硬件侧需要知道:未来要跑什么模型、这些模型的算子频率分布是什么、哪些算子值得做成硬逻辑、哪些留在软件里更划算。软件侧需要知道:硬件有几级存储、每个计算单元的吞吐是多少、数据在芯片内部怎么流动、哪些操作开销巨大必须绕开。
具体落到四个层面:指令集层面,要不要为AI算子定制专用指令,比如一条指令完成矩阵乘加;存储层面,片上SRAM的容量和层级怎么设计,编译器才能在把数据切块的时候有足够的“操作空间”;并行层面,多核之间怎么调度、怎么同步,runtime才不至于被任务切分折磨死;精度层面,硬件支持INT8还是INT4,量化算法就得跟着走,精度损失的账要怎么算。
这四条线没有一条可以独立决策。我看到过不少失败案例,本质都是硬件团队拍脑袋把架构定了,软件团队拿到规格文档才发现这个设计根本没法写编译器。等到芯片回来,一切推倒重来或者疯狂打补丁,才是最痛的。
2. 硬件视角:AI芯片的关键架构决策
2.1 计算单元:从SIMD到脉动阵列,各有各的账
AI芯片的计算单元,主流路线无外乎三大类:第一种是经典的SIMD(单指令多数据)路线,很多GPU的CUDA Core和端侧NPU的向量单元都沿用这个思路,一条指令控制一堆处理单元做同样的运算,灵活性好,适合各类不同形状的算子,但指令开销和调度开销相对较大。第二种是脉动阵列路线,Google的TPU是典型代表,让数据像流水线一样在一个二维阵列里“流”过,每个处理单元把计算完的结果直接传给下一个邻居,大大减少了数据搬运,但它的形状适配能力有限,遇到不规整的计算就有点尴尬。第三种是近两年大热的可重构计算路线,把阵列的互联和计算模式变成可配置的,芯片上电后软件可以根据模型结构去“画”一条适合当前计算的数据通路。
选哪条路线,本质上是在灵活性和效率之间做取舍。我做过一次很直观的测试,同一个卷积层,在脉动阵列上跑得非常快,但换成一个动态shape的卷积,流水线根本没法建立起来,性能瞬间掉到和通用处理器差不多的水平。所以当你看到隔壁团队宣称他们的AI芯片算力有多高时,一定要问一句:在什么shape、什么模型上测出来的。抛开模型谈算力,基本都是在耍牛忙。
2.2 存储体系:SRAM、HBM与“数据搬运才是大头”
芯片设计界流传一句话:算力是拿来算的,但能耗和时延是花在搬数据上的。一个MAC操作可能只需要0.2皮焦,但从DRAM搬一个数据,能耗可能是它的上百倍。所以AI芯片的存储体系设计,目标就一条:让最常被用到的数据尽量留在离计算单元最近的地方,也就是片上SRAM。
片上SRAM有多大,直接决定了软件编译器能把模型切成多大的块来做数据复用。举个例子,假设你要在端侧跑一个7B参数的LLM,光权重就是约7GB,哪怕量化到INT8,还是要约7GB。如果芯片只有几MB的片上SRAM,那就意味着每一轮推理都要把权重从DRAM里搬进来搬出去,带宽立刻变成天花板。这也是为什么新的AI芯片都开始上HBM或超大带宽LPDDR的原因——不是算力不够,是喂不饱。
但SRAM不是越大越好,它会占面积、增加功耗,还会拉长物理布局的走线。一个64MB的SRAM块和计算阵列之间的布线距离,就可能让时钟频率上不去。所以你会看到很多芯片的设计选择“多级存储”:L1给每个计算核私有,L2做多核共享,再往下挂DDR或者HBM。软件要做的,就是把数据按热度层层放好,让最热的权重留在最内层。
2.3 NoC、启动和外围:容易被低估的“水电煤”
计算单元和存储是舞台上的主角,但真正决定一块芯片好不好用的,往往是那些“水电煤”性质的部分。片上网络(NoC)承担了数据搬运的任务,设计不好,多核之间一通信就堵死。缓存一致性也是大坑,CPU和NPU共享内存时,如果一致性协议处理得粗糙,你会发现CPU改了数据,NPU读到的还是旧值,排查起来极其痛苦。
另外两块容易被外行忽略的是启动流程和供电/时钟稳定。SoC从上电到NPU能跑模型,中间要经过Boot ROM、启动固件、驱动加载、时钟使能等一系列环节。很多硬件工程师把看门狗当个摆设,等芯片偶发跑飞了才发现,没有看门狗拉一把,系统就死那儿了。电源芯片的选型也一样,我曾经遇到过NPU跑满负载时电压跌落超过5%,导致计算单元偶发出错,表现就是模型推理结果时不时跳变,排查了三天才定位到电源动态响应不够快的问题。设计看门狗、电源、时钟这些“外围”,和设计核心计算阵列一样需要投入,甚至更需要。
3. 软件视角:把模型“翻译”给硬件听
3.1 一条完整的软件栈,每一层都是“翻译官”
AI芯片的软件栈,本质上是一条流水线,负责把高层级框架里的模型变成硬件能执行的指令。最上面是前端工具:读入PyTorch导出的ONNX,或者直接对接框架的IR(中间表示);然后是图优化层,把计算图做融合、消除冗余、重排算子顺序;再往下是算子调度与指令生成,把优化后的图映射到芯片的计算单元上,切块、排流水、分配存储;再往下是Runtime和驱动,负责运行时资源管理、内存管理、任务提交;最底下才是真正的硬件执行。
每一层之间都有大量的“翻译损耗”,如果任何一层做得粗糙,前面所有层的努力都会打水漂。我见过最典型的例子,某芯片公司号称支持Pytorch模型直接部署,但实际转换流程极其繁琐,一个模型转下来要手动改十几处不支持的操作,遇到不支持的算子就直接报错。这个体验跟写代码时编译器报错不一样——编译器报错你知道怎么修,算子不支持你只能改模型结构,这是伤筋动骨的事。
3.2 算子融合:不只是少几次函数调用
很多人以为算子融合就是把几个操作合并到一起,省几次函数调用开销,实际上收益要大得多。拿最经典的Conv+BN+ReLU来说,不融合的时候,每一层都会把中间结果写回DRAM,下一层再读出来,这中间的读写成本远高于计算本身。融合之后,中间结果留在片上寄存器和SRAM里,直接完成下一层操作,可能一个算子链的整体耗时能砍掉一半甚至更多。
但融合不是随便合的,它要求你对硬件的数据生命周期有精准的理解。比如融合点的选择要依赖硬件片上存储能容纳多大的中间结果,如果中间结果太大,硬塞进SRAM里反而会把后续计算的数据挤出去,得不偿失。我习惯的排查方式是,先在profile工具里看每一层的“数据写出/读入”量,把访存开销最大的几个层挑出来优先融合,效果立竿见影。
3.3 量化:把模型压到硬件能跑的状态
AI芯片对INT8的偏爱,本质是赌“低精度带来的效率增益能覆盖精度损失”。INT8的乘加器面积大概是FP16的四分之一,能耗也更低,同一块芯片塞下的计算单元数量可以翻倍。但量化不是直接把float数据截断成int就完事了,至少你要做对称或非对称量化,考虑zero-point和scale的选择,还要决定是per-tensor还是per-channel的粒度。
我做过一个分类模型,训练后直接做PTQ(训练后量化),准确率掉了2.4%,看起来不多,但部署后发现在某些特定类别上错误率飙升。后来改用校准数据集重新跑量化,调整了scale的分布,准确率追回到只掉0.6%。这个经验告诉我,量化校准数据的选取比想象中重要,它必须覆盖模型在真实场景里会遇到的分布,不能用一堆同类图片去校准。另外,如果精度实在压不回来,就得考虑QAT(量化感知训练),让模型在训练阶段就“适应”低精度的噪声,工程量和效果通常都是可控的。
4. 从设计到流片:一个AI芯片项目的真实推进路径
4.1 架构探索:性能模型和RTL仿真怎么配合
芯片项目立项后,第一步是做架构探索,确定各种微架构参数。这里的常识是,先不要急着写RTL,而是建一个performance model(性能模型),用高抽象的C++或Python描述芯片的关键参数:有几个核心、每个核心几个MAC、片上SRAM多大、内存带宽多少、NoC拓扑什么样。然后拿目标模型在这个模型上跑trace,估算吞吐率和瓶颈。
性能模型的好处是迭代快,改一个参数几秒钟就能出结果,方便做设计空间探索。但它模拟的精度有限,一般只能做到“趋势正确”,精确到周期开销就得靠RTL仿真。靠谱的做法是两者配合:先用性能模型筛掉明显不行的方案,再把候选方案落到RTL做cycle级精确仿真,验证时钟、握手、存储冲突这些细节。我见过不少团队跳过性能模型直接写RTL,结果参数一改就要改一大片代码,等发现架构性缺陷时已经是几百万行代码的返工,这种教训真的非常值得记下。
4.2 芯片回来之前,怎么验证软件栈
很多软件工程师有个心理误区,觉得要等芯片流片回来了才能开始写和调软件,这完全错了。软硬件协同开发的精髓在于,软件栈的开发和硬件验证是并行的,靠的是FPGA原型平台、虚拟原型和指令集仿真器。
FPGA原型是把RTL综合后跑到FPGA上,频率可能只有真实芯片的十分之一,但能跑真实的模型、真实的工具链。虚拟原型更灵活,本质是一个用软件模拟硬件行为的模型,schema脚本配好之后,能在几分钟内启动一个虚拟SoC,让驱动和runtime在上面跑起来。指令集仿真器则是纯粹逐条模拟指令的软件,最慢但最容易调试。
我实际项目中,最常用的组合是:工具链和编译器的早期开发跑在指令集仿真器上,驱动和系统软件跑在虚拟原型上,然后FPGA原型验证整个软硬件栈的集成。曾经有一次,编译器在虚拟原型上跑一个模型是好的,到了FPGA上就开始出奇怪的问题,最后定位到是NoC上某个路由器的仲裁逻辑在特定访问模式下出现了死锁。如果没有FPGA这层验证,这种问题就要等真芯片回来才暴露,损失会大得多。
4.3 芯片回来后:Bring-up流程的第一天要做什么
芯片回片后的Bring-up,整个团队的紧张程度堪比发射卫星。我建议的流程是:先验证最基本的电源和时钟,不要一上来就跑复杂程序;然后通过JTAG或串口访问内部寄存器,确认CPU核能正常读写;接着启动Boot ROM,加载固件,点亮一个LED或者打印一行串口日志就算阶段性胜利;再之后才是让NPU跑第一个“冒烟测试”——通常是一个简单的向量加法或者单层卷积。
冒烟测试的设计原则是“最最小化”,不要跑任何复杂模型,因为它一旦失败,你根本不知道是硬件问题、驱动问题还是工具链问题。等冒烟测试通过后,再逐步增加复杂度,跑小型网络、中等网络、真实模型。这个阶段最容易暴露的是缓存一致性问题、内存映射错误、以及中断处理逻辑的缺陷。系统性测试矩阵也要在这时候建立起来,从模块级到系统级层层递进,记录每一次测试的环境、输入、输出和结论。
5. 实操场记:在一块带NPU的开发板上部署模型
5.1 选板子、搭环境:前期准备决定后期体验
理论讲完,来点实际的。这几年带NPU的开发板越来越多,选型的时候我有一个三原则:一看工具链社区活跃度,二看NPU算力和带宽是否匹配你的模型规模,三看开发文档里有没有“踩坑指南”。
选好板子之后,先把官方提供的Hello World跑通,不要急着部署自己的模型。这一步的意义是验证环境本身没问题,把交叉编译工具链、驱动、runtime、连接板子的流程走一遍。很多新人一上来就转换大模型,失败了一堆报错,结果最后发现只是驱动没加载好,太浪费情绪了。
5.2 模型转换和量化:从PyTorch到芯片指令
典型的部署流程大概是四步:导出(从PyTorch导出ONNX)→ 转换(用厂商提供的工具链转成芯片的模型格式)→ 量化(用校准集做精度换算)→ 验证(在板子上跑测试样例,对比输出和原始模型的误差)。
转换这一步最容易出问题的是算子映射。官方工具链通常提供一张“支持算子清单”,你看到清单的长度就知道这个芯片的软件生态有多成熟。遇到不支持的算子,我的建议是优先绕开,比如把一个复杂算子拆成几个简单算子的组合,或者把某些计算放到CPU上执行,NPU只跑重计算部分。用量化校准的时候,务必用真实业务数据,不要用一张通用图片集走天下,不同业务场景的分布差异巨大,用错校准集测试时精度挺好看,上线就翻车。
5.3 性能调优:我常用的五个旋钮
模型跑通之后,就该调性能了。第一个旋钮是多核调度,看看runtime有没有把所有NPU核心都用上,有些工具链默认只跑单核,配置一个环境变量就能满血。第二个旋钮是内存复用,模型中间张量如果能在SRAM里复用缓冲,减少在DDR上的分配和释放,速度会明显提升。第三个旋钮是算子替换,把慢的算子换成等价但更快的实现,比如某些激活函数可以融合到前面或后面的算子中。第四个旋钮是动态形状处理,很多推理框架会把动态shape当成静态shape来处理,遇到多batch或者变长输入就会浪费算力。第五个旋钮是功耗模式,很多开发板默认跑保守功耗档位,改成高性能模式后NPU频率能明显提升,但要注意散热。
调优做完,我习惯做一个“性能基线记录表”,把模型名、输入尺寸、芯片频率、单次推理耗时、内存占用、功耗全部记下来。这个表不仅便于自己复盘,也是跟其他团队协作时最有说服力的沟通工具。
6. 常见问题排查与避坑速查
6.1 算子不支持、转换报错
这是部署时最高频的问题,报错通常长这样:“Unsupported operator: aten::group_norm”。遇到它,第一反应不是补算子实现,而是先查工具的samples和issues库,大概率已经有人踩过坑。如果确实需要自己补,可以分两步:先看这个算子能不能拆解成多个支持算子的组合,不能的话再看runtime支不支持插桩自定义算子。我建议尽量避免自研算子实现,一旦你维护自己的算子库,后续工具链升级很可能就会把你的补丁冲掉,升级信心直接归零。
6.2 精度下降
精度问题排查有个固定套路。第一步,把量化关掉,用FP32跑一遍,看误差是否还存在;如果存在,说明问题在算子映射或图优化阶段,比如某个融合逻辑算错了。如果FP32正常,第二步就是在量化参数上找问题,检查scale和zero-point的数值是否异常、校准集是否覆盖真实分布。我遇到过最诡异的一次,是模型里某个clip操作在低比特下把极小值截断成了0,导致后续计算全部偏移,最后是换了一个非线性激活函数才解决。
6.3 性能不达标
性能不达标通常有两个解释:要么瓶颈在带宽,要么瓶颈在算子本身。你先看一眼profile报告,如果NPU利用率很低,而内存吞吐已经贴着理论峰值,那就是数据搬移的问题,优化方向是多做数据复用和融合。如果带宽没跑满但算力也没跑满,那就看看是不是算子被切得太碎,调度开销太大,或者有什么同步等待在拖后腿。
6.4 系统不稳定、随机死机
随机死机最怕的是“偶发”,一旦出现,先不要怀疑软件逻辑,先看硬件层面的基础指标。排查顺序我建议是:电源电压是否稳定、时钟信号是否有毛刺、看门狗是否被正确地喂狗、DDR训练参数是否正确、NoC有没有死锁。把这些硬件层面的因素逐一排除后,再回头看软件当中是否有越界访问或者驱动竞态。多用日志和计数器记录现场,别靠猜。
下面这张表是我在多个项目里沉淀下来的排查速查,直接照抄就行:
| 现象 | 可能原因 | 排查手段 | 推荐解法 |
|---|---|---|---|
| 转换报错 | 算子不支持 | 查看工具链支持算子清单 | 拆解算子或换等价实现 |
| 精度下降 | 量化校准不当 | 关闭量化对比FP32 | 重新采集校准集或改用QAT |
| NPU利用率低 | 数据搬运瓶颈 | 查看profile带宽数据 | 算子融合、增加数据复用 |
| 推理结果偶发错误 | 电源不稳/时序违规 | 示波器量电源纹波 | 优化电源供电、升压补偿 |
| 系统随机死机 | 看门狗失效/驱动竞态 | 开看门狗日志 | 修正喂狗机制或驱动锁 |
7. AI芯片软件生态的新变量:AI Agent与AI辅助开发
7.1 端侧AI Agent给芯片出了新难题
这两年AI Agent的热度飙升,它对芯片软硬件设计的影响,正在从“可选项”变成“必答题”。端侧AI Agent的特点是:输入不再是单张图片或一段短文本,而可能是多模态的、长上下文、且需要连续多轮交互。这意味着模型推理会从“单次短任务”变成“长时间多轮任务”,对芯片的内存容量、带宽上限、功耗控制和多任务的并发调度都提出了新要求。
多AI协作也是一个很有意思的方向。以后端侧场景可能会同时跑好几个模型——一个用于语音识别、一个用于视觉、一个负责决策和工具调用——这些模型同时存在、分时或并行执行。这对硬件的多核调度能力、模型间的内存隔离、以及runtime的优先级管理都是很大的考验。做软硬件设计的时候,如果还只盯着跑单个模型去优化,将来在真实的Agent场景里很可能就会“水土不服”。
7.2 用AI辅助芯片设计本身:提效但要留个心眼
最后聊个行业内正在发生的趋势:用AI来辅助芯片设计和软件开发。现在有团队在尝试用大模型生成Verilog代码辅助RTL设计,用AI写编译器和runtime的单元测试,用AI去读spec生成寄存器描述等。从我试用下来的感受看,AI在代码生成和文档梳理上的效率提升是实打实的,特别是那些“模式化”的胶水代码和测试用例,AI写得又快又整齐。
但我也要泼一盆冷水:AI生成的代码,务必当它是一个“需要严格review的结对编程对象”,而不是直接信任的输出。芯片设计和底层软件的特点是容错率极低,一个位错误可能让整个系统崩溃,一个时序问题可能在特定工作负载下才暴露。AI生成的代码可能在语法上完美无瑕,逻辑上却蕴含深层的理解偏差。我给自己定的规矩是:AI生成的代码,必须配合形式化验证或者架构级测试用例,才能在项目里使用。辅助归辅助,责任始终在人。
如果顺着这个思路往下走,AI芯片的软硬件设计会越来越依赖“人+AI”的协作模式:硬件架构师用AI探索设计空间,软件工程师用AI生成和验证工具链,而最终的关键决策和风险判断,仍然需要人类工程师来拍板。这也是我个人觉得这个行业未来几年最有意思的部分——工具在变强,但人的判断力反而更加值钱。