2026/10/13 17:16:54

AI芯片软硬件协同设计:从计算架构到编译器调优的工程实践

AI芯片软硬件协同设计:从计算架构到编译器调优的工程实践 做AI芯片这几年我越来越有一个体会很多人一听说“AI芯片”第一反应就是看算力、看制程、看TOPS但真正决定一颗芯片能不能用的往往是软件和硬件之间那层薄薄的“契约”。你硬件堆了再多的MAC阵列编译器映射不上去、算子跑不出效率、内存搬移把时间全吃掉那这颗芯片就只是一块昂贵的砖头。AI芯片的软硬件设计本质上是在做一件事让计算架构和算法表达彼此妥协、彼此成就。这篇文章我会从整体设计思路、硬件微架构、软件栈协同、以及实测调优四个维度把AI芯片从定义到落地过程中真正值得抠的细节讲透内容偏工程实践适合芯片工程师、AI框架开发者、以及准备入行AI基础设施方向的同学参考。1. AI芯片软硬件设计真正要权衡的几件事1.1 动手写Verilog之前先回答三个问题我见过不少团队做AI芯片开局就是看对手方案、抄计算阵列尺寸、拼工艺节点结果做出来的东西要么编译器改到吐血要么在某些模型上性能好看、换个模型就直接“翻车”。如果你问我设计AI芯片第一步该干什么我会说先不要谈核数、不要谈频率先回答三个问题。第一这颗芯片主要服务什么场景云端训推一体、边端低功耗推理、还是自动驾驶这种时延敏感的实时任务不同场景直接决定你计算精度的选择、片上存储的大小、以及数据流架构的方向。第二个问题目标模型家族是什么CNN、Transformer还是混合架构这决定了你计算阵列的形态——比如Transformer的矩阵乘法和CNN的卷积在数据复用模式上差异很大我见过在阵列设计时只按卷积优化、结果跑Attention时MAC利用率掉到三成以下的例子。第三谁在使用这颗芯片是给内部的软件团队还是开放给外部开发者如果是外部开发者你不仅要有完善的指令集文档还要有足够友好的编程接口。这三个问题看起来简单实际上是把“软硬件设计”从概念变成具体约束的过程。计算精度是FP16、BF16还是混合精度激活函数是放硬件还是走软件模拟内存子系统是纯SRAM还是带LPDDR每一条都牵扯到软件栈怎么写。这些问题如果靠拍拍脑袋决定后面大概率要用十倍的时间去填坑。1.2 算力、带宽与能效的三角拉扯很多人会把TOPS当成AI芯片最重要的指标但从系统角度看TOPS只是上限真正决定实际性能的是“算力/带宽”的比值。你的MAC阵列每秒钟能算4096次乘加但你片外内存带宽每秒只够喂512个数那计算阵列会一直处于饥饿状态。这里有一个常用的估算方式假设一个矩阵乘法算子MAC阵列需要从片外读取A矩阵的一个块和B矩阵的一个块为了不浪费计算能力必须保证读取这些数据的时间小于或等于计算时间。举个例子假设你的AI核心以1GHz运行每次周期能完成1024次MAC计算那么每秒就是1024 G MACs即2048 GOPS。要喂满这个阵列假设每次读取一个32KB的数据块用于计算那么你能做到的带宽需求约等于算力除以数据复用倍数。如果是卷积算子输入像素被多个输出通道复用数据复用倍率可能很高而如果是逐元素算子或者Embedding查找这类低复用场景带宽需求会陡然增大。这也是为什么AI芯片必须围绕算子特征来做软硬件协同单纯堆算力没有任何意义。能效则是另一个硬约束。很多端侧AI芯片的功耗预算只有几瓦到十几瓦你提高主频、增加阵列宽度都可能让功耗直接超预算。工程上常用“能效比”TOPS/W来衡量但你要注意这个数字在不同精度、不同稀疏度条件下差异极大。硬件架构师最怕看到“峰值TOPS/W”很高的设计——因为那通常意味着只有在极度理想的情况下才能达到真实负载下能打到五六成就算不错了。1.3 软硬件边界怎么划哪些固话、哪些可编程接着上面说软硬件边界划分是整个项目中最需要克制力的环节。业内普遍的做法是把最成熟、最稳定、最频繁被调用的底层算子交给专用硬件单元比如矩阵乘法单元、量化/反量化单元、激活函数查表单元把这些单元之上的调度、融合、循环变换交给编译器而把上层网络结构解析、算子调度策略交给运行时和框架层。我自己经历过一个项目团队想把Space-to-Depth这类只在少数模型里出现的算子做成硬件指令结果为了支持这个算子消耗了大量验证资源软件侧的使用率却非常低最后只能作为“遗留特性”默默躺在手册里。反过来如果所有算子都走通用可编程的方式比如让编译器把任何计算都拆成通用的向量指令来跑又会牺牲性能和能效。这个平衡点的把握基本靠对目标应用场景的充分调研来决定。我的建议是在项目初期建立一个算子热点表统计目标模型在推理时的耗时占比和调用频次把Top级别的算子作为硬件优化的重点其他算子用可编程指令覆盖。这样软硬件边界就不再是拍脑袋拍出来的而是有量化依据的。这个热点表也会成为后续编译器调度策略和硬件验证用例的重要输入。2. 硬件侧的核心设计细节从指令集到数据通路2.1 计算阵列选型SIMT与脉动阵列的取舍AI计算阵列的形态目前主要有两个方向一类是类似GPU的SIMT风格大量通用ALU配合线程调度器灵活性强适合复杂控制流和多种算子形态另一类是脉动阵列数据像流过血管一样在阵列中沿固定方向流动每个处理单元只做一次乘加并把结果传给邻居数据复用率高、功耗低但灵活性差。两者在编程上差异很大。SIMT风格本质上是在硬件调度器上做线程级并行软件侧只需要把计算映射成线程块即可脉动阵列则要求编译器在编译期就决定数据在阵列中怎么流动、在哪里驻留、何时输出这是一项非常考验编译器设计能力的工作。我见过有些团队拿到脉动阵列IP之后写了满屏的调度伪代码最终性能仍然离理论峰值差两倍以上问题就出在数据流的安排上。实际操作中如果是做边缘端推理芯片且模型相对固定脉动阵列通常是更优的能效选择如果是面向多模型、多种算子形态的通用AI芯片SIMT风格加矩阵运算指令扩展会更容易做软件生态。还有一类混合方案我称之为“矩阵核心向量核标量核”的三级异构矩阵核心负责高密度乘加运算向量核负责归一化、激活、逐元素操作标量核负责控制逻辑和地址计算。这种结构虽然设计复杂度高但能较好地兼顾灵活性和能效也是目前主流AI芯片IP的常见架构。2.2 存储层次与数据流策略怎么定存储层次设计在AI芯片里分量极大。很多团队把精力都花在计算阵列上结果系统上板之后发现性能瓶颈全在数据搬运。这里有一个基本经验在同等工艺下片上SRAM的访问能耗比片外DRAM访问能耗低一到两个数量级因此片上数据复用做得越好整体能效就越高。存储层次通常是两级寄存器或Buffer级、片上SRAM / Cache级、再加片外DDR。设计时要明确每个数据流的驻留位置。以卷积为例主要有三种数据流策略Weight Stationary权重驻留、Input Stationary输入驻留、Output Stationary输出驻留。权重驻留适合权重复用率高的场景输入驻留适合输入特征图复用高的场景输出驻留则以减少输出累加次数为目标。没有绝对的最好只有最适合目标模型组合的策略。这几类数据流策略对软件栈的要求也不一样。比如Weight Stationary需要编译器知道卷积核尺寸并把它切进计算阵列Output Stationary则需要注意累加器的位宽和溢出控制。我实测下来对于大多数卷积网络把输入特征图切块放进片上SRAM、对输出通道做分块累加是一个稳定性很高、也最容易在编译器中落地的方案。因为它的数据流控制逻辑简单编译器只需要维护一个“当前SRAM中驻留了哪些数据块”的状态表就能比较自然地生成搬运指令和计算指令的交错序列。2.3 同步、流水与片上网络的工程实现如果你以为AI芯片的硬件设计只是计算阵列那就大错特错了。真正让硬件团队掉头发的往往是同步机制和片上数据通路。当一个算子被切分成若干个小任务、分发到不同计算核心上执行时核心之间如何同步、数据怎么流动、如何避免死锁和竞争都是非常磨人的工程细节。同步机制通常有硬件同步原语比如栅栏Barrier、原子操作、以及依赖计数器。设计这些机制时要特别注意它们既要保证正确性又不能在正常流程中过度消耗时间。我见过一个设计每个算子执行完都要做一次全芯片栅栏同步结果模型一深、算子一多同步开销占到了总执行时间的百分之十几。片上网络NoC也是设计重点。AI芯片的流量模式跟普通多核CPU不太一样更多是单向的、大块的数据搬运比如从DMA搬入SRAM、从SRAM搬入计算阵列、累加结果搬回SRAM。这个时候传统的复杂路由网络反而没有优势简单的总线或环形拓扑配合专用的DMA通道往往更高效。更关键的是DMA引擎要支持二维地址变换strided copy这可以直接支持对输入特征图子块的高效搬运减少软件侧的地址计算负担。3. 软件侧的实现要点算子映射、编译器与部署3.1 算子映射循环切分到底在解决什么问题硬件流片回来之后、软件栈之前有一层非常关键的抽象叫“算子映射”。它的本质是把神经网络中的张量运算翻译成硬件指令序列算法上对应的就是循环变换和循环切分。以卷积为例原本是个七重循环N、Cin、H、W、Cout、Kh、Kw。你不可能把整个循环完全展开硬件阵列尺寸、SRAM容量都有限。因此编译器需要做循环切分Tiling把大循环切分成适合硬件阵列大小的小块同时安排数据搬运和计算的重叠。这个过程中涉及两个经典概念循环顺序loop order和寄存器分块register blocking。循环顺序决定数据流动的局部性寄存器分块决定数据在寄存器级和阵列级的复用程度。我做过的实用策略是先把输出通道(Cout)分成大块再把输入通道(Cin)进行分块接着在空间维度(H, W)上切分。对于每个计算核心尽量让输入特征图子块和权重子块同时驻留在片上SRAM中然后循环执行乘加运算。这样设计的好处是数据搬运量小每次搬运都能支持足够多的计算MAC利用率能稳定保持在70%以上。另外要提一个细节编译器生成的指令序列一定要考虑“数据搬运与计算重叠”。也就是说当计算单元正在算当前数据块时DMA引擎同时搬运下一块数据。这个流水线重叠如果不做你会发现计算单元利用率非常低。实现层面的关键是设置双缓冲double buffering在SRAM中分配两组Buffer交替承担“计算中”和“搬运中”的角色。3.2 内存分配与数据搬移的调度策略如果软件栈里有一个角色最容易被忽视那就是内存分配器。AI芯片的芯片内SRAM碎片化管理直接决定你能同时驻留多少个算子执行。我建议内存分配采用“区域划分 池化管理”的方式把SRAM按用途划分为输入缓冲区、输出缓冲区、权重缓冲区、临时缓冲区每个区域内部再按算子需求动态分配。有一个很实际的经验临时缓冲区的总大小要按照“最大单个算子”的峰值需求来预留而不是按所有算子需求之和。因为实际运行中算子之间是串行的临时空间可以被复用。若按之和来预留需要更大的SRAM直接推高成本。这个道理听起来很简单但很多团队在早期架构评估时都会算错。搬移调度方面DMA指令应该由编译器生成而不是运行时动态计算地址。编译器知道每一层算子的形状可以在编译期把搬运清单变成一张二维表的偏移量运行时只需要按顺序触发这些DMA。这样可以省掉大量地址计算逻辑。如果你做的是全静态编译方案甚至可以提前规划好整张图的执行顺序和数据生命周期做到“零动态分配”。我强烈建议AI推理芯片一到两年之内的项目都优先走静态方案因为它的验证成本低、性能可预期等生态做大了再考虑动态执行图路线。3.3 量化模型在芯片上的精度与性能调优AI芯片几乎绕不开量化因为整数运算的能效远高于浮点。INT8推理已经是标配BF16/FP16用于训练回传或者大模型推理更激进些的INT4、INT2也在特定场景落地。但量化不是一个简单的“截断位宽”问题它牵涉到数值范围分布、量化参数的选择以及硬件对量化算子的支持程度。在软件侧量化分为训练后量化PTQ和量化感知训练QAT。PTQ算起来简单但对数值分布敏感的模型精度可能掉很多。QAT精度高但需要修改训练代码、重新训练模型周期长。对于端侧推理芯片我通常建议先上PTQ用高精度校准集数据找到合适的scale和zero_point如果精度不达标再对有问题的层做QAT。硬件侧的量化支持则需要关注两点一是“双端量化”的指令支持即支持卷积输入和输出分别使用不同的量化参数这是大多数推理框架的标准行为二是“反量化插入位置”很多时候为了精度计算累加器保持INT32最后的激活层再进行反量化到INT8或FP16。这个序列在硬件上要能简洁地表达为一条融合指令否则编译器就要产生额外的内存搬移。我踩过的一个典型的坑某个模型经过QAT之后精度已经达标准了但在部署到芯片之后跑出来完全不对。排查了很久才发现是硬件在处理残差连接时把上一层的FP16输出和当前层的INT8输入直接相加数值范围没有对齐。后来修复方式是在残差分支也插入量化/反量化节点。因此部署时一定要仔细检查多输入算子的量化一致性尤其是Add、Concat、Reshape这些本身不做计算的算子。4. 从仿真到流片调优实测中必须面对的坑4.1 搭建软硬件协同仿真环境软硬件协同仿真环境是AI芯片项目中期投入产出比最高的部分。它本质上是在硬件RTL模型之上挂一个软件执行环境让编译器生成的指令可以直接在这个模型上跑起来输出逐周期的行为日志和计算结果。这个环境做得好很多软件侧的bug在流片之前就暴露了省下来的调试时间非常可观。我建议把协同仿真环境分成三层第一层是纯功能级仿真只验证指令语义的正确性跑得非常快适合验证编译器生成的指令序列有没有原则性错误第二层是周期级仿真会模拟微架构的流水线、存储冲突、带宽限制适合做性能评估第三层是FPGA原型验证速度最快、最接近真实行为但开发和调试复杂度高。项目节奏上先做第一层尽早把软件栈跑通等微架构稳定之后再做第二层FPGA平台则可以平行推进重点验DDR控制器和系统级集成问题。另一个工程建议是协同仿真环境中必须加入“指令覆盖率”统计。也就是统计编译生成的所有指令中哪些指令类型被覆盖、哪些硬件模块被充分激活。覆盖率数据可以反向指引编译器开发者增加新的优化策略也能用于验证团队评估硬件设计是否存在冗余逻辑。4.2 实测性能与理论性能差在哪从协同仿真到实测最打击团队信心的一个环节就是跑分。明明理论算力标着202 TOPS一跑真实模型居然只有30多TOPS。这里面的差距几乎总在以下四个地方第一内存带宽瓶颈。前面说过数据复用倍数低算力就喂不饱第二算子融合不够。有些团队把多个算子拆成了独立的指令序列每个算子之间都要把中间结果写回SRAM再读出白白浪费了一半带宽第三流水线stall。比如计算单元在等DMA搬运、或者DMA在等总线授权第四动态形状和Padding处理不对称导致某些输入尺寸下计算阵列利用率减半。我自己的排查习惯是先看整张图的每一层算子实际周期数跟理论周期数做个对比表。如果单层算子的performance大于理论值的1.5倍就深入看该层的指令序列借助周期级仿真工具拉出stall因果关系。曾经有一层卷积算子性能异常最终定位到是SRAM bank冲突——两个输入块恰好落在同一个bank访问串行化导致每个周期实际只执行了预期的一半MAC。另外建议把图优化工具做得“透明”一点。编译器优化之后的计算图、指令序列、以及SRAM分配表都以可视化或日志形式吐出来。这样不仅便于算法团队审查也方便硬件团队反向确认某些异常数据到底是由模型引起还是芯片引起。4.3 常见问题与排查思路速查下面这个表算是我在多个AI芯片项目里踩坑经验的压缩版。覆盖了从编译到上板最常见的问题每一条都是真实扔过调试日志的。问题现象可能的根因排查方向算力利用率偏低数据搬移与计算没有重叠检查指令序列中DMA与计算指令的流水间隔确认双缓冲是否生效量化模型输出偏差大多输入算子的量化参数不一致检查残差连接、Concat、Add、以及Reshape后的Scale/ZP值编译后死循环或卡死同步屏障等待条件未满足检查核心间依赖关系查看同步计数器是否溢出某些层计算结果时对时错SRAM数据覆盖被提前触发检查内存分配表中生命周期标记确认数据块是否被提前释放带宽利用率只有预期一半DMA访问模式的bank冲突调整数据块的地址偏移尽量按bank数对齐避免冲突访问同一模型不同输入尺寸性能差异巨大Padding和Tiling未做该尺寸适配查看循环切分尺寸是否为阵列宽度的整数倍必要时做block padding4.4 编译器性能优化的两个突破点算子融合与自动调优前面反复提到算子融合这里具体展开。AI芯片的算子融合核心目标是在保证语义不变的前提下减少中间结果的内存读写。最常见的融合组合包括“卷积批归一化激活”三段式融合和“矩阵乘法残差Add”融合。卷积加BN加ReLU的融合思路是把BN的缩放和偏移参数在编译期折算到卷积的Weight和Bias里这样运行时就完全不需要单独的BN算子直接输出ReLU结果。这个优化几乎对所有CNN模型都有效。自动调优则是编译器里更进阶的玩法。你可以预定义一组切分策略包括循环顺序、分块大小、双缓冲的位置、融合与否然后通过一个性能模型或者直接在硬件上做short-run评测找到每个算子最优配置。这个过程听起来复杂但在AI芯片的编译器里价值巨大。因为算子形态随着模型迭代总会有新的变化而人工调参完全跟不上模型变化的速度。5. 写在最后一点个人体会AI芯片的软硬件设计表面上拼的是架构和算力实际上拼的是工程系统能力。做这个方向这几年我最大的体会是软件栈和硬件架构必须从一开始就一起设计、互相约束而不是等硬件做完再写编译器。硬件设计的每一个灵活性和限制条件最终都会在软件栈里被放大或者被消化。另一个很深的感受是做AI芯片没有捷径所有宣称“一键部署、开箱全速”的方案在实际模型面前都需要做大量调优。真正的核心竞争力来自于对目标场景的深刻理解、对编译器细节的偏执打磨以及对每一次性能异常追查到底的耐心。如果你正在做或者准备做AI芯片我希望这篇文章能帮你少走几个弯路——至少在看到那些好看的理论峰值数字时能多问一句喂得饱吗调得动吗跑得稳吗