1. 从算力焦虑说起为什么第八篇还要聊软硬件协同做AI芯片这行的人最近几年都有一个共同的体感单看纸面参数芯片的峰值算力年年翻倍但真正把模型部署上去能跑出来的有效吞吐往往只有标称值的三四成。这个落差不是某一颗芯片的问题而是整个软硬件设计链条上每一环都在漏。我把它叫做算力焦虑——不是算力不够而是算力用不出来。这个系列写到第八篇前面七篇陆陆续续聊过架构选型、数据流、量化、编译栈这些话题。到了这一篇我想把视角收一收专门讲一个最容易被忽视、但决定成败的东西软硬件的协同设计边界。也就是说哪些事情应该交给硬件去做哪些事情必须留给软件去兜这条线画在哪里直接决定了你这颗芯片是能用还是好用。如果你正在做AI加速器的架构定义、在写算子库、在搭编译工具链或者只是想把一个模型高效地跑到某块板子上这篇内容都值得你花时间看完。我不会给你一堆空洞的方法论而是把我在实际项目里踩过的坑、算过的账、做过的取舍一条条摊开来讲。核心关键词就三个软硬件协同、数据流设计、算子映射。这三个词贯穿全文也是我认为AI芯片设计里最值钱的三块硬骨头。先说一个反直觉的结论AI芯片的性能瓶颈八成不在计算单元本身而在数据搬运和软硬件接口的错配上。你堆再多的MAC阵列如果数据喂不进去、结果搬不出来、算子映射对不齐那这些计算单元就是在空转烧电。所以这一篇的主线是围绕数据怎么流、算子怎么落、软硬件怎么分工来展开的。2. 数据流设计AI芯片真正的性能命门2.1 为什么说搬运比计算更贵先算一笔账这笔账我在很多次架构评审里都拿出来讲过。假设你有一颗芯片片上SRAM带宽是每周期读取256字节主频1GHz那么理论片上带宽就是256GB/s。听起来不小。但如果你要跑一个典型的卷积层权重加激活的数据量动辄几MB到几十MB而片上SRAM可能只有几MB。这意味着数据必须反复在片外DRAM和片上缓存之间倒腾。片外DRAM的带宽通常只有几十GB/s延迟还是片上访问的几十倍甚至上百倍。于是问题就来了计算单元一个周期能算几百次乘加但喂给它一个操作数可能要等几十个周期。这就是所谓的内存墙。我见过太多项目架构图上画着漂亮的PE阵列理论算力标到几百TOPS结果实测跑ResNet只有个位数TOPS的有效性能根子全在这里。所以数据流设计的第一个原则尽量让数据在片上多待一会儿让每一次搬运都物尽其用。这就引出了数据复用这个概念。卷积运算里同一个权重会被很多个输出像素复用同一份激活也会被多个卷积核复用。好的数据流设计就是把这些复用机会榨干。2.2 三种主流数据流的取舍逻辑业内常提的数据流大致分三类权重固定Weight Stationary、输出固定Output Stationary、行固定Row Stationary。名字听着抽象我用生活化的方式解释一下。权重固定好比你把菜谱贴在灶台边上厨师计算单元反复看同一份菜谱做不同的菜。它的好处是权重只加载一次适合权重复用度高的场景比如大卷积核。缺点是激活要不停换激活带宽压力大。输出固定好比厨师守着一口锅把需要的食材一样样往里加。它适合输出复用度高的场景比如全连接层。缺点是权重和激活都得频繁搬。行固定是前两者的折中试图同时复用权重和激活在卷积场景下往往能拿到更好的综合能效。但它的控制逻辑复杂对编译器和调度器的要求高得多。我个人的经验是没有绝对最优的数据流只有和你的目标模型、目标工艺最匹配的数据流。如果你主打的是Transformer类模型注意力机制里的矩阵乘对权重和激活的复用模式和CNN完全不同照搬CNN那套数据流往往事倍功半。这时候你可能需要重新设计片上缓存的层次和搬运策略。2.3 双缓冲与流水线把等待藏起来数据流设计里有一个几乎必用的技巧双缓冲Double Buffering。原理很简单准备两块缓冲区一块在算的时候另一块在搬。等算完了两块角色互换。这样计算和搬运就能重叠起来把搬运的延迟藏到计算背后。但这里有个坑很多人第一次做会踩双缓冲的深度不是越多越好。缓冲区开太多片上SRAM被占满留给权重和中间结果的空间就没了反而逼着你去访问片外。我一般建议从两级缓冲起步根据实测的搬运延迟和计算时间比例再决定要不要加到三级。判断标准很直接如果搬运时间已经小于计算时间再加缓冲就是浪费面积。还有一个细节是流水线的粒度。粗粒度流水比如按整个层来切控制简单但灵活性差细粒度流水按tile切灵活但调度开销大。实测下来按tile切、tile大小取到能填满计算阵列的1.5到2倍是个比较稳的甜点区。3. 算子映射编译器与硬件的翻译博弈3.1 算子映射到底在解决什么问题硬件只会做它被设计出来的那几件事比如矩阵乘、卷积、激活。而一个真实模型里有几百种算子还有各种奇怪的shape和边界情况。算子映射要做的就是把模型里的算子翻译成硬件能执行的指令序列同时尽量让硬件跑满。这个翻译过程本质上是编译器的工作。但AI芯片的特殊之处在于硬件往往有很强的专用性——比如某个数据排布格式、某个特殊的累加器结构。编译器如果不懂这些硬件细节生成的代码就会很低效。这就是为什么AI芯片的编译器不能是通用的必须和硬件深度绑定。我见过一个典型的失败案例某团队用了一个通用编译器框架硬件设计得也不错但编译器不知道硬件的片上缓存分bank、不知道bank冲突的规避规则结果生成的代码在访问缓存时频繁冲突性能直接砍半。后来他们重写了调度部分把bank信息喂给编译器性能才回来。这个教训很贵。3.2 算子融合省的不只是时间算子融合是算子映射里性价比最高的一招。把相邻的几个算子合并成一个kernel执行中间结果不落回片外直接在片上传递。比如Conv BN ReLU这个经典组合如果不融合你要写三次片外、读三次片外融合之后中间结果全程在片上片外访问直接省掉一大半。但融合不是无脑合。有几个约束你得盯着数据依赖只有真正前后依赖、中间结果不被别处引用的算子才能融合。片上容量融合后的中间结果得放得下放不下就得切分切分又可能引入额外开销。并行度融合后的大kernel可能并行度不够填不满计算阵列反而降低利用率。我的经验是融合的收益在访存密集型的算子上最明显在纯计算密集的算子上收益有限。所以做融合策略时优先盯那些读多写多但计算少的算子比如各种element-wise操作、归一化层。3.3 动态shape编译器最头疼的对手真实业务里输入shape经常是动态的——今天batch是8明天是16序列长度还变来变去。这对算子映射是巨大的挑战因为很多硬件的高效执行路径是编译期确定的shape一变之前优化好的调度就废了。应对动态shape业内大致两条路。一条是运行时编译shape变了就现场重新编译灵活但编译开销大延迟敏感的场景受不了。另一条是多版本预编译把常见的几档shape提前编译好运行时按需选。后者更实用但你要能预测出业务里shape的分布。我倾向于混合策略对高频shape预编译对低频shape走运行时编译兜底。同时硬件层面留一些shape无关的通用执行路径虽然效率低一点但保证任何shape都能跑起来不至于直接报错。4. 软硬件分工哪些交给硬件哪些留给软件4.1 划分边界的三个判断维度软硬件分工是AI芯片设计里最考验判断力的地方。划错了要么硬件做了一堆用不上的功能浪费面积要么软件被迫用低效的方式模拟硬件该做的事。我一般用三个维度来判断第一频率。高频执行的操作值得做成硬件。低频的、一次性的交给软件。比如激活函数里ReLU几乎每个模型都用、每个像素都过做成硬件天经地义但某个冷门激活函数做成软件查表就够了。第二灵活性需求。如果这个操作未来可能变、算法还在演进就别焊死在硬件里。比如注意力机制的各种变体层出不穷硬件最好只提供基础的矩阵乘和softmax原语具体怎么组合交给软件。第三能效敏感度。对能效极度敏感的操作硬件实现通常比软件快几个数量级。比如量化里的定点乘加硬件一个周期搞定软件模拟要几十个周期。把这三个维度画成一个决策矩阵大部分操作该放哪边就清楚了。4.2 指令集设计给软件留够抓手硬件给软件留的接口核心就是指令集。AI芯片的指令集设计和CPU很不一样它往往需要一些粗粒度的指令比如执行一个tile的矩阵乘这种而不是一条条细碎的load/store。但粗粒度指令有个问题软件对硬件的控制力变弱了。如果硬件内部调度不透明软件就没法针对性地优化。所以我的建议是指令集要粗中有细——高层用粗粒度指令表达意图同时暴露一些细粒度的控制位让编译器在需要时能干预硬件行为比如指定缓存策略、指定数据排布。这里有个实操心得指令集的文档一定要和编译器团队一起写。我见过硬件团队自己定义了一套指令文档写得漂亮但编译器团队拿到手发现根本没法生成高效代码因为指令的语义和编译器的中间表示对不上。这种返工非常痛苦。4.3 运行时系统别让调度成为瓶颈硬件和编译器都搞定了还有一层容易被忽略运行时系统。它负责在芯片上调度任务、管理内存、处理多模型并发。这一层做不好前面的优化全白搭。我踩过的一个坑是内存管理。运行时如果频繁做动态内存分配和释放碎片化会很严重跑久了性能就往下掉。后来我们改成预分配内存池按模型的生命周期管理稳定性立刻上来了。这个经验很朴素但真的管用。另一个坑是任务调度粒度。调度太细开销大调度太粗负载不均。实测下来按一个算子的一个tile作为调度单元配合一个轻量的任务队列是个比较平衡的选择。5. 实测中的那些意外从纸面到硅片的落差5.1 仿真跑得欢上板就拉胯几乎每个AI芯片项目都会经历这个阶段仿真环境里性能曲线漂亮得不行一上FPGA或者流片回来性能直接打骨折。原因通常有几个。一是仿真模型过于理想。仿真里假设内存访问零延迟、带宽无限真实硬件里这些假设全不成立。二是时钟和功耗的墙。仿真跑在理想频率下真实芯片受限于功耗和散热频率上不去。三是外设和接口的瓶颈。仿真里往往忽略了DDR控制器、总线仲裁这些环节的开销。我的应对办法是尽早做带时序的仿真尽早把真实的内存模型接进来。哪怕仿真慢一点也比上板后才发现问题强。另外FPGA原型验证这一步千万别省它能暴露大量仿真发现不了的问题。5.2 精度与性能的跷跷板量化是AI芯片提性能的利器但精度和性能之间的平衡非常微妙。INT8通常能带来数倍的性能提升但有些模型对量化极其敏感掉点严重。我的经验是量化要分层做不要一刀切。对精度敏感的层比如第一层、最后一层、某些归一化层保留高精度对中间的大量卷积层大胆用低精度。同时配合量化感知训练让模型在训练阶段就适应低精度比训练后直接量化效果好得多。还有一个细节硬件对量化的支持要留有余地。比如你设计的是INT8乘加但最好也支持INT4甚至二值化的路径因为算法演进很快说不定明年主流就变了。5.3 多模型并发下的资源争抢真实业务里芯片上往往不是跑一个模型而是多个模型并发。这时候计算单元、片上缓存、片外带宽都成了争抢对象。如果调度不当会出现一个模型把资源占满其他模型饿死的情况。解决思路是给资源做配额和优先级。计算单元按时间片轮转缓存按模型划分区域带宽按优先级分配。听起来像操作系统那套确实就是那个思路。但AI芯片的实时性要求更高调度决策要做得更快所以硬件层面最好有一些辅助机制比如硬件队列、硬件信号量别全压在软件调度器上。6. 给正在做AI芯片的你几条掏心窝的建议6.1 先想清楚目标模型再定架构我见过太多团队先埋头把硬件架构设计得很通用结果发现目标模型根本用不上那些通用性反而在关键算子上效率低下。架构设计一定要从目标模型出发。你主打CNN还是Transformer主打训练还是推理主打云端还是端侧这些问题的答案会彻底改变你的架构选择。具体做法是先拿几个代表性模型做profiling看清楚算力、带宽、存储的真实需求分布再据此定架构参数。别拍脑袋。6.2 编译器团队要早介入别等硬件定型硬件和编译器是共生关系。硬件定型后再让编译器团队接手往往已经晚了因为很多硬件设计决策会直接影响编译器的优化空间。理想状态是架构定义阶段编译器团队就参与进来一起评估这个设计编译器能不能生成高效代码。6.3 留足验证和调优的时间芯片项目的时间表里验证和调优往往被压缩得最狠但这两块恰恰是最耗时的。我的建议是验证和调优的时间至少占总周期的三分之一。流片一次成本极高宁可前期多花时间验证也别指望流片后靠软件补。6.4 建立自己的性能模型不要依赖厂商给的峰值算力数字那玩意儿参考价值有限。建立自己的性能模型把计算、访存、调度开销都算进去用它来指导架构决策和软件优化。这个模型一开始可能不准但随着你积累的实测数据越来越多它会越来越靠谱成为团队最宝贵的资产。说到底AI芯片的软硬件设计是一门平衡的手艺。硬件给你下限软件决定上限而协同设计决定了你能走多高。这个系列写到第八篇我越来越觉得真正拉开团队差距的不是谁堆的算力多而是谁把软硬件这条缝补得更严实。希望这些从实战里抠出来的经验能帮你在自己的项目里少走点弯路。