1. 先从产品视角看:AI芯片的软硬件设计到底在解决什么问题
做芯片方向好几年了,每次有新同事入职,问的第一个问题基本都是"AI芯片和普通芯片到底差在哪"。这个问题看着基础,其实特别值得展开聊。AI芯片的软硬件设计,说白了就是围绕一个核心矛盾在打转:算法模型更新太快,芯片流片周期太长。你花两年把架构定下来、流片回来,结果最新的模型结构已经变了,两年前的硬件设计还能不能高效跑起来?这就是软硬件协同设计存在的根本原因。
1.1 "专用"二字究竟专在什么地方
传统CPU走的是通用指令集,GPU虽然是并行架构,但底层还是为图形渲染那一套设计的。AI芯片则完全不同,它把神经网络里最核心的计算模式直接硬编码到电路里。比如卷积里的乘加累积(MAC),普通CPU要好几条指令才能完成一次乘加,AI芯片里则是成百上千个MAC单元排列成阵列,一条指令下去,整片数据同时算完。
拿我做过的一个端侧NPU项目举例,芯片里设计了512个MAC组成的阵列,配合专门的行缓冲(line buffer)和片上SRAM,跑一个3x3卷积时,输入数据能沿着数据通路流动,每个cycle都在做有效计算。这种专用性带来了数量级上的能效比提升,但代价就是灵活性差:你想在上面跑个非神经网络算法,比如排序或者字符串匹配,那基本是灾难。
现在热点方向又变了。大家挂在嘴边的AI大模型、AI Agent、多AI协作,对芯片提出了完全不同于传统CNN的负载需求。Transformer结构的注意力机制、KV Cache的反复读写、多个Agent模型并发调度,这些都需要芯片在架构层面做一些新的取舍。我做过的第六版软硬件设计文档,正好就是在处理这类新负载与旧架构之间的矛盾。
1.2 为什么软硬件必须一起设计,而不是硬件做完再配软件
很多团队容易犯一个错误:先把芯片架构定死,功能仿真通过,流片回来以后才开始写编译器和算子库。这种做法在五年前可能还行,现在几乎行不通了。
原因很简单。AI芯片的性能上限不是由硬件单独决定的,而是由"硬件+编译器+运行时"这个组合决定的。你硬件设计的MAC阵列利用率再高,如果编译器生成指令的时候调度不合理,数据搬运和计算没有重叠,实际跑起来可能只有峰值算力的三成。反过来,如果软件团队在开发算子库的时候发现硬件缺少某个关键的数据通路,想改架构已经来不及了,只能靠软件绕路,性能大打折扣。
所以真正靠谱的做法,是在指令集定义阶段就让编译器工程师参与进来。我记得有一个项目,架构师最开始设计指令集的时候,把权重加载和计算执行设计成串行依赖关系,编译器工程师一看就摇头:这种设计下每层卷积之间都要等待权重加载完成,流水线完全打不起来。后来改成DMA加载和计算并行执行,编译器只要做双缓冲调度,整体性能直接提升了一倍多。这就是典型的软硬件协同设计案例,硬件架构的一个小改动,给软件省了极大的功夫。
2. 硬件侧的几个关键设计取舍,每一条都在为软件铺路
AI芯片的硬件设计看着是电路问题,其实每个关键决策背后都是为软件工具链服务的。我梳理了几个在项目里反复撕扯的取舍点,这些决策直接决定了后续软件好不好写、性能能不能到预期。
2.1 算力指标不能只看TOPS,MAC阵列与存储墙的博弈
先说最表面的算力。业界常用TOPS表示芯片算力,公式很简单:TOPS = MAC数量 x 2 x 工作频率。假设你设计了一个512 MAC的阵列,跑1GHz,峰值算力就是1 TOPS。听起来不错,但实际跑模型的时候,几乎不可能达到这个数字。
问题出在数据搬运上。AI计算是典型的访存密集型任务,卷积一层接一层,每层都要读输入特征图、读权重、写输出。如果数据都在片外DDR里,每次访存都要经过片外总线,延迟是片上SRAM的上百倍。计算单元在那里干等数据,MAC阵列再快也白搭。这就是所谓的"存储墙"。
我在项目里吃过这个亏。第一版硬件设计时,片上SRAM只留了256KB,觉得够用了,结果跑一个稍微大一点的检测模型,中间特征图根本塞不下,每层都频繁地往DDR写读,实际MAC利用率只有28%。后来重新做floorplan,把SRAM扩到2MB,代价是芯片面积大了将近三成,但实测利用率提升到了65%以上。这个取舍值不值,取决于产品定位:如果是追求极致能效比的端侧芯片,多花面积换SRAM是值得的;如果是追求成本最小化的低端芯片,那就得反过来优化算法,用更多访存换更小的存储。
2.2 片上存储与片外带宽:最容易低估的系统性瓶颈
SRAM和带宽这两个东西,账要分开算。
片上SRAM解决的是单核或者单层计算的局部数据复用问题。但整个神经网络模型跑起来,各层之间的数据流动是全局性的。比如一个1024x1024的特征图,要是片上放不下,就必须切分成tile一块块处理。切多大?切小了增加边界冗余计算,切大了放不下。这个tile size参数,硬件设计的时候就要在数据通路里留好可配置寄存器,让软件能动态调整。我见过有些芯片把这个参数硬编码死了,结果软件想优化的时候发现改不了,只能重新流片,代价极其惨痛。
片外带宽就更关键了。DDR的带宽是有限的,频率拉高、位宽加宽都能提高带宽,但代价是功耗和pin脚数量剧增。一个很现实的计算题:假设你的模型运行时有70%的数据需要从DDR读取,目标带宽需求是25.6GB/s,那么选LPDDR4x 3200Mbps,位宽需要64bit才能覆盖。这时候硬件设计就要考虑,64bit的数据总线会占用大量pin,对封装形式、走线布局都有直接影响。这些参数在项目早期就得算清楚,不能说等流片回来发现带宽不足了才想办法。
2.3 指令集边界:软硬件分工的接口所在
指令集是软硬件之间的契约,这个边界画在哪,直接决定你的编译器好不好写、硬件灵活度高不高。通用CPU的指令集是细粒度的,每条指令做一件微小的事;而对AI芯片来说,指令粒度一般是"任务级"的。
举个例子。我们设计的NPU指令集里,一条"CONV_LAYER"指令就代表执行一整层卷积,参数包括输入地址、输出地址、权重地址、tile尺寸、步长等。硬件收到这条指令后,内部微控制器自动完成整层的计算调度。这种设计的好处是编译器不用关心底层每个cycle在干什么,坏处是灵活性差,万一模型里出现了指令集没覆盖的计算模式,编译器就得把它分解成基础指令慢慢拼,性能断崖式下降。
这个边界要在设计早期就定死,并且要拿真实模型做backtest。我通常的做法是收集当前主流模型的算子列表,统计每个算子的出现频次和计算量占比,然后保证覆盖率在95%以上。剩下的5%跑得慢可以接受,但绝不能让某个热门算子完全跑不起来。
3. 软件侧工具链:决定芯片能否真正"用起来"的隐形战场
硬件做得再漂亮,没有好用的软件工具链,芯片就是一块废硅。这个道理几乎所有芯片团队都懂,但真正愿意投入人力做工具链的并不多。我个人的强烈建议是:工具链团队的规模至少要是硬件设计团队的一半,否则项目后期一定会被软件问题拖死。
3.1 编译器和算子库的各司其职
编译器负责把神经网络的计算图转换成芯片可执行的指令序列。它做的事情包括图优化、算子融合、内存规划、指令调度。算子库则是底层的微内核(micro kernel),针对具体芯片的MAC阵列布局、数据通路宽度,写出最高效的汇编级实现。
两者分工明确,但边界也容易模糊。我见过有些团队的编译器架构师什么都想干,把底层算子库的逻辑也塞进编译优化里,结果中间表示层(IR)设计得一塌糊涂,改一个算子要动全局。正确的方式应该是:编译器只做图级别的优化,比如算子融合、死代码消除、内存复用分析;具体某个算子怎么算到极致,那是算子库工程师的事。
算子融合是一个收益极大的优化。以最常见的Conv+BN+ReLU为例,三段独立的计算如果依次执行,每层都要把中间结果写回内存再读出来。融合以后,卷积的结果直接流进激活函数和BN的参数运算,一个pass搞定,既省了访存又省了指令数。我实测过一个MobileNet模型,光做这种基础融合,端到端延迟就降了40%。
3.2 从PyTorch模型到芯片指令的完整落地流程
AI芯片如果不谈PyTorch,那就是空中楼阁。现在几乎所有算法工程师都在PyTorch/TensorFlow里做训练,所以工具链第一件事就是把训练好的模型转换到芯片能跑的形式。流程大致是:PyTorch模型导出成ONNX,然后做量化、图优化、算子映射、指令生成,最后交给运行时库加载执行。
每一步都有坑。量化这一步最容易掉精度。很多人以为量化就是把fp32的权重cast成int8,其实不是。你需要先准备校准数据集,在芯片的量化仿真器上跑一遍,观察每一层的数值分布,然后选择合适的量化尺度。我踩过一个坑:用了一组和实际部署场景差异很大的校准图片,结果量化后精度掉了3个百分点,换了跟场景匹配的校准数据就恢复正常了。
算子映射则是考验工具链覆盖率的地方。模型里只要出现一个工具链不认识的算子,整个图就要在中间表示层做"图切分",把不支持的算子切出去,用CPU回退执行。CPU回退的成本极高,因为数据要从NPU的内存搬运回CPU侧,跑到完再搬回来,一来一回可能比在纯CPU上跑还慢。所以设计工具链的时候,优先支持的算子一定要跟实际模型清单对齐,不能只按照开发人员的热情来决定支持什么。
3.3 调试与性能分析:没有profiling工具就寸步难行
给AI芯片写软件,最痛苦的就是不知道性能瓶颈在哪。算法工程师说模型跑得慢,芯片架构师说指令已经是最优了,两个人都觉得自己没错。这时候就需要一个可靠的性能剖析(profiling)工具,把每层算子实际耗时、MAC利用率、访存次数、DDR带宽占用率这些指标量化出来。
我在项目里坚持把profiling功能提前到硬件仿真阶段就开始开发。也就是说,在RTL仿真环境里跑指令序列的时候,就通过插入性能计数器(performance counter)的方式采集数据。这个习惯帮了大忙,因为仿真环境下可以观测到每个信号,比流片后靠寄存器读回容易太多了。流水线哪里气泡多,总线哪里拥塞,一眼就能看出来。
顺便提一句,现在也可以用AI辅助分析性能日志了。写脚本批量扫出异常数据点,再让AI帮忙归类根因,效率比人肉翻日志高很多。不过这属于锦上添花,核心还是profiling工具本身的数据完整性和准确度。
4. 软硬件协同设计的具体流程,手把手带着过一遍
理论说了一堆,落到项目里到底是什么节奏?我把这几年跑完的几个项目流程抽出来,整理成一套可复用的方法论,按阶段拆开讲清楚每个环节的产出和注意点。
4.1 从需求分析到架构映射:先收集负载特征,再谈硬件结构
第一步绝不是画架构图,而是搞清楚你的芯片要跑什么负载。我的习惯是收集一批目标场景的真实模型,比如做端侧安防,就收集YOLO系列的检测模型、ResNet的分类模型;做语音助手,就收集Conformer、流式Transformer。然后把每个模型的层类型、张量形状、数据量统计成表,算出各类算子的计算量占比和访存量占比。
这份负载特征报告是整个硬件架构设计的输入。比如你发现模型里3x3卷积占比超过60%,那MAC阵列就设计成有利于3x3的排布;如果发现全连接层或者Transformer的GEMM占比高,就要考虑MAC阵列在高计算密度下能不能同时保持高带宽利用率。没有这一步,架构设计就是拍脑袋,后面性能肯定不及预期。
架构映射完成以后,下一步就是指令集定义。指令集不要求一上来就大而全,反而推荐采用"核心指令集+扩展指令集"的层次:先定义一套最小的可跑通CNN的指令,保证工具链能在早期开始开发;然后根据后续模型演进的需求,逐步扩展新的指令。这样硬件和软件可以并行迭代,不用等全部设计完才能干活。
4.2 算力评估的正确姿势:目标利用率不是拍脑袋拍出来的
很多架构师算芯片需要的算力时,直接拿模型的乘法次数除以目标帧率,得出TOPS需求。68G次MAC / 30fps = 2.27 TOPS,那就设计2.5 TOPS。这个算法没错,但忽略了利用率这个关键系数。真实场景下,芯片不可能100%时间都在做乘法,数据搬移、指令解码、同步等待都会吃掉大量cycle。
我建议的算法是:先假设一个合理的初始利用率,比如50%,算出需要的硬件MAC数量;然后搭一个cycle级的性能仿真器,把目标模型编译成指令序列,在仿真器上跑,得到更精确的利用率;再根据仿真结果调整MAC数量。这个过程至少要迭代两轮。第一轮用粗估的参数快速攒一个仿真器原型,第二轮等硬件设计更细节了再校准仿真精度。
我手头一个项目里,刚开始估算目标利用率55%,仿真跑完只有31%。原因是有个关键的数据依赖导致每层卷积之间都要停顿。后来通过双缓冲设计,把停顿消掉,利用率拉到49%。这个过程中随时要跟硬件设计团队沟通调整架构,而不是死守着最初估算的参数不动。
4.3 大模型时代的新磨合:GEMM和GEMV的效率之战
大模型给软硬件协同设计带来的新挑战,我这里可以展开细讲。
一个大语言模型跑起来,整个生命周期其实分为prefill和解码两个阶段。prefill阶段是用户输入大量token并行算,计算模式是标准的GEMM,也就是矩阵乘,MAC阵列吃满,算力利用率高没问题。但解码阶段是一个token一个token地生成,计算模式变成了GEMV,本质是矩阵乘向量,计算量小了一大半,内存访问时间反而更长。如果你用同一套调度策略,解码阶段的算力利用率会低到惨不忍睹。
针对这种情况,我们做的软硬件协同优化是:把MAC阵列的工作模式设置成可配置的,GEMM模式下阵列分组做矩阵乘;GEMV模式下聚合成单一向量计算单元,减少空转。同时在工具链里给prefill和解码分别生成不同的调度序列,prefill阶段优先堆算力,解码阶段优先控访存调度。这套改动在性能仿真器里模拟,解码阶段的有效吞吐量提高了将近3倍。
还有多AI协作的场景。多个Agent模型同时运行,不是简单地排队执行,而是需要动态分配算力。我们尝试在运行时层做了优先级抢占机制:低优先级的任务遇到高优先级的请求,可以主动暂停自己的调度,释放算力给高优先级任务。这个机制对硬件的要求是每个核的状态必须能快速保存和恢复,硬件设计的时候就必须预留足够的寄存器组和上下文存储空间。
5. 项目实操中的高频坑,每一条都是真金白银换来的
这个章节是踩坑实录,全是我在真实项目中遇到过的问题。列出来给大家参考,能避一个是一个。
5.1 编译器版本和硬件版本不对齐
AI芯片迭代快,经常出现硬件改版了,工具链没同步更新的情况。常见场景:硬件团队为了修一个时序违例,把某个指令的编码方式改了,但是工具链里还是旧编码。编译生成的二进制跑在新芯片上,行为完全错误,排查两天后发现是版本对齐问题。
我的建议是建立严格的版本对齐机制。硬件定义的寄存器描述文件要作为单一数据源,用一份标准格式的CSR描述文件管起来,然后通过代码生成器自动生成硬件侧的头文件、软件侧的寄存器访问代码、编译器侧的指令编码表。这样三边永远从同一份文件生成,从源头上杜绝版本漂移。这个办法我们实践了两年,效果很好,虽然初期搭建这套自动生成流程花了些时间,但后面省下的调试时间远远超过投入。
5.2 多核并行时的内存访问冲突
多核NPU并行执行,听起来能线性扩展性能,实际上一涉及共享内存访问,性能就可能腰斩。我们遇到过一个问题:两个核同时算不同层的卷积,但它们在片外DDR里的buffer地址恰好落在同一个bank上,DDR控制器只能串行处理,两个核互相等,总体耗时反而比单核串行还慢。
排查方法是用性能计数器看DDR带宽利用率和bank冲突次数。发现异常后,解决方案是在内存规划阶段就考虑bank对齐,让不同核的buffer基地址错开,或者采用伪随机的地址交织方式,让访问模式均匀分布在各个bank上。这类问题没办法完全避免,但通过软硬件协同设计可以大幅降低概率。
5.3 散热和供电对性能稳定性的影响
AI芯片高速运行时的功耗很高,这点经常被软件团队忽略。芯片在高负载下温度飙升,超过阈值后就会触发降频保护,性能出现周期性波动。如果你的软件里有严格的实时性要求,这种波动会直接导致任务超时。
我经历过一次很奇怪的现象:同样的模型跑推理,每次跑到第五分钟,延迟突然增加40%,再过两分钟又恢复了。排查到最后才发现是散热设计余量不足,芯片温度到了85度触发了降频,降到临界点后温度回落,又恢复全频。后来我们在软件里加了温度感知调度:温度高的时候主动把低优先级任务摊开,避免高功耗集中在同一时刻;同时把看门狗芯片的作用发挥起来,监控核心电压和温度,一旦异常就触发系统级的安全对策。这类问题在设计阶段就要留好温度传感器接口和软件策略位,别等流片回来再想怎么办。
6. 最后说几句关于工具链和团队协作的心里话
项目做了这么久,我最深的体会是:AI芯片项目能不能按时交付,往往不取决于硬件设计水平,而取决于工具链与硬件的对齐速度。很多团队把精力全花在架构创新上,软件团队到后期才开始介入,结果硬件流片了工具链还差一大截,芯片只能在实验室里吃灰。如果让我给一个最实用的建议,那就是在设计流程的Day 1就把编译器工程师拉进架构讨论,哪怕他们一开始只能提意见,也能帮你避开大量后期返工的坑。
再分享一个小技巧:版本管理上,硬件寄存器定义、指令编码、编译器后端代码建议放到同一个代码仓库里做原子提交。每一次硬件微调,工具链都同步跟进,代码评审的时候两边的人都会看到改动,自然就形成了强制的沟通机制。这个做法比什么流程文档都管用。
后续这个项目方向还可以继续延伸的方向也有很多。比如多芯片互联成为常态之后,软硬件协同的粒度会从单芯片内部的算子调度,扩展到跨芯片的任务编排;再比如芯片量产后,用真实场景数据持续做回灌校准,反过来指导下一代架构定义。这些内容到时候有机会再单独开一篇聊。