经常有朋友问我AI芯片到底难在哪。很多人觉得算法是核心搞几个大模型跑通就完事了也有人以为堆算力就行晶体管多了自然快。但真正做过AI芯片的人心里都清楚最难的不是单点设计而是软硬件之间那层看不见摸不着、却又时刻在拉扯的“胶水”。一个模型在GPU上跑得飞快换到自研芯片上就变成拖拉机性能只剩理论值的两三成——这种案例在我们这行早就见怪不怪了。AI芯片的软硬件设计本质上是把算法、架构、编译、运行时、算子库、驱动这些完全不同的工程领域硬生生拧成一股绳。今天我想借“软硬件设计”这个主题把这些年踩过的坑、总结的规律和一些实操细节摊开聊一聊。无论你是做算法想要了解硬件约束还是做硬件想理解软件需求这篇内容应该都能帮你少走点弯路。这篇文章适合几类人看刚入行想建立全局观的芯片工程师做模型部署天天跟算子库打交道的算法工程师以及那些正在评估“要不要自研AI芯片”的技术管理者。内容不会只讲概念我会穿插具体的设计条目、参数取舍和性价比分析尽量还原一次真实设计过程中的思考路径。1. 整体设计思路别急着谈算力先定义好你的“边界”很多团队做AI芯片上来就喜欢盯峰值算力INT8能到多少TOPSFP16能到多少TFLOPS。这个数字确实好记也方便跟领导汇报但它恰恰是最容易误导人的一个指标。你真正该问自己的是我要支持的模型在真实运行时的有效算力是多少这个有效算力跟峰值之间的差距背后全是软硬件协同设计的活儿。1.1 先搞清楚你的模型长什么样AI芯片的设计必须从“负载分析”开始而不是从架构开始。我见过不少项目芯片都流片回来了才发现模型里有大量稀疏的注意力计算、动态shape的张量、不规则的循环依赖结果固定流水线根本施展不开性能惨不忍睹。负载分析重点看三件事计算密度高不高模型里卷积、矩阵乘这类高密度算子占多少比例像MLP、卷积网络计算密度高适合做大规模 systolic array 或 matrix core而 Transformer 里的 attention虽然有张量核心能加速QKV投影但后续的softmax、mask、Gather等操作反而是访存和索引密集型的。数据复用性强不强同一个输入特征图会被多个卷积核重复读取同一个权重矩阵会被batch内的多个样本重复使用。数据复用直接影响片上存储的层级设计和数据流方向。动态性高不高模型输入分辨率是否固定序列长度是否变化有无控制流比如循环、条件分支这决定了编译器的工作量和数据类型调度的弹性。这三件事全部定量之后你才能回答一个核心问题我的芯片到底是“计算受限”还是“访存受限”。如果是前者重点堆运算单元如果是后者重点改存储层级和内存带宽。答案不一样设计方向就会完全相反。1.2 软硬件边界划分是最大的取舍软硬件协同设计里最难的不是考虑“硬件能做什么”而是“硬件该做什么”。每多做一个指令硬件面积、功耗、验证复杂度都会上涨每少做一个特性软件还得想办法用基础指令组合去弥补性能可能大幅下降。我习惯把边界划分问题拆成四层来看算子层哪些高频算子需要硬件直接支持哪些用软件组合实现。循环层循环展开、分块、变换是编译器做还是硬件流水线自动做。数据类型层FP16、BF16、INT8到底支持几种混合精度怎么做。图优化层算子融合、内存规划是编译器做主还是硬件提供特殊缓存机制来配合。一个实际经验是硬件不要过度追求“全能”而应该把80%的资源投入到20%的高频场景里。比如你主攻推理芯片那么多路batch、连续调度这些特性就比复杂的二维阵列更值得投入。当年做某代架构时我们反复权衡要不要加入对动态shape的原生支持最后从编译器前端的角度模拟了一下发现加了个简单的“shape推导流水线”就能解决80%的问题于是硬件只加了非常轻量的动态索引支持省下一大块面积。2. 硬件架构解析从算力到数据流的工程落地硬件架构不是一个抽象的框它是由存储层级、计算阵列、互联网络和控制逻辑组成的复杂系统。AI芯片和我之前做过的CPU/GPU设计最大的不同在于它把“数据搬运”和“数据计算”之间的配合关系提到了前所未有的高度。2.1 存储层级是AI芯片真正的命根子芯片内部最贵的是电容最慢的是握手最耗能的是搬数据。对一个AI芯片而言数据从哪里来、往哪里去、在哪个层级待多久几乎决定了一切。一个典型的存储层级如下层级容量带宽用途控制方式寄存器堆KB级极大运算单元的即时操作数编译期静态分配SRAM bufferMB级上百GB/s输入特征图、权重、中间结果显式DMA搬运 / cache片上互连—片上多核间数据流转硬件路由/软件配表DRAMGB级数十GB/s大权重、长序列、备用数据DMA/同步原语这里有个关键概念数据中心级AI芯片通常更依赖大容量的HBM来缓解带宽压力而边缘端芯片则倾向于把更多面积留给SRAM。不要一味追求“大缓存”缓存太大了延迟会上去面积会膨胀你必须根据实际的算子特性反复仿真调整。设计中常踩的坑在于很多人做存储规划时只关注容量忽略了访问冲突。多核同时读同一块bank时如果bank数量不足或者地址映射不够随机带宽会直接打对折。所以在设计地址交叉策略时我会额外考虑哪些数据会被哪些核同时读取甚至在编译器中预留“bank对齐”的接口。2.2 算力阵列的选型没有银弹算力阵列无外乎三类形态脉动阵列、SIMD阵列、向量标量混合。三者各有适用的负载区间。脉动阵列在矩阵乘上效率极高因为数据和权重都以“流水式”方式穿过阵列每个数据被多次复用。但它在处理不规则计算时很僵。SIMD阵列灵活但对数据复用和带宽要求极高需要大量的片上数据搬运。我一直建议团队根据负载分析的结果来做选型如果主攻Transformer推理脉动阵列加上适当的向量单元补充是常见组合如果主攻多模态模型要处理大量的加噪、Pad、Resize等非矩阵操作SIMD向量单元的比例反而得多给一点。关于阵列尺寸业界常有一个误区——阵列越大越好。其实阵列越大利用率反而容易越低。因为一个大矩阵运算虽然能塞满阵列但一旦矩阵维度不够整除边缘的单元就会空转。我一般建议根据目标算子中最常见的shape来决定阵列尺寸。比如目标模型中典型的GeMM是4096x4096你就可以考虑用64x64的阵列每一个tile都规整利用率极高。如果模型里矩阵尺寸五花八门反而用多个小阵列做动态分配更划算。2.3 多核互联和一致性是绕不开的难题多核架构下的互联网络直接影响扩展性。常见做法有总线互联、环形网络、Mesh网络。总线简单但容易瓶颈环形带宽共享但延迟均匀Mesh可扩展性好但路由开销大。做AI芯片和做CPU的最大差别在于AI芯片对一致性要求没那么严格——你不需要保证每个核看到的内存视图完全一致重点反而是“数据在正确的时间出现在正确的位置”。所以大部分加速器会选择“软件控制的分布式存储”而不是硬件Cache一致性。这意味着编译器和runtime要把数据同步的职责扛起来。哪块数据该在哪个阶段同步多核之间什么时候发生屏障都要在编译期算好。这听起来麻烦但其实反而给了软件更大的优化空间。因为同步点是可预测的你可以提前预取、重叠计算和搬运获得接近线性的多核效率。3. 软件栈设计编译器是硬件的灵魂很多人以为软件栈就是编译器加runtime实际上AI芯片的软件系统复杂度远超普通嵌入式系统。它要同时处理高层图优化、底层指令生成、资源分配、内核调度还要向用户提供稳定的编程接口。一套好的软件栈能弥补硬件80%的缺憾一套差的软件栈能让优秀硬件“明珠蒙尘”。3.1 编译器分层和LLVM之间的关系AI编译器大多基于多层中间表示。最上层是计算图IR主要做算子融合、内存规划、shape推断中间层是循环IR主要做循环变换、流水线安排底层才是类似LLVM的机器代码层负责寄存器分配和指令调度。谈到LLVM很多团队会纠结到底要不要用。我的建议是如果你的底层汇编指令很规整寄存器模型跟LLVM的模型接近那就直接基于LLVM做后端省时省力。但AI芯片往往有特殊的存储架构和流水线同步机制LLVM的传统“内存指令”模型并不能很好地表达。这时候需要做一层“伪指令映射”将自研的中间指令翻译成LLVM能理解的语义算是一种折中。实际经验是底层尽量保持简洁因为编译器的复杂度越靠近底层出bug的概率越高而底层bug又最难排查。3.2 算子库的灵活性和确定性算子库是硬件往上层暴露的“最后一公里”。它的好坏直接决定框架能否高效接入。算子库要解决两个问题一是覆盖目标模型所需的算子集合二是每个算子在各种shape和layout下都能达到接近峰值的性能。覆盖度的问题很容易被低估。你以为只要把卷积、矩阵乘、layernorm这些基础算子写好就行但真实模型里还有各种奇怪的组合——搜广推模型里的稀疏embedding和聚合操作生成式模型里的Gather/Scatter和Variadic shape。更常见的是框架层会把某个大算子拆解成若干个基础算子如果算子库没办法高效组合性能就会稀碎。因此我特别强调算子的“原子性”和“可组合性”。每个算子要尽可能独立不要为了单算子峰值而牺牲掉灵活性。业界常犯的毛病是为了某个benchmark把算子改得很特化结果真实模型跑不出同样效果。宁可单算子峰值低个5%也要保证在不同shape下都有稳健效率。3.3 图优化算子融合能省下的不只是时间很多人理解算子融合就是为了省掉中间张量的拷贝。这只是其一。融合更大的价值在于减少kernel launch次数、降低同步开销、让数据尽量留在片上。一个Transformer推理过程里QKV投影、Softmax、Output投影等其实可以融合成几个大的融合算子。例如把LayerNorm融合进前面的GeMM把Residual Add和激活函数融合进后续GeMM把GELU和矩阵乘融合。这些都已经是常识了。但我要提醒一个细节融合不是无脑操作。当你把算子A和B融合后如果B的尾部和A的尾部在存储位置上产生冲突反而会带来额外的bank冲突。有的融合看似减少了kernel数量却破坏了原有的流水线调度窗口导致整体延迟不降反升。所以图优化必须自带“性能代价模型”不能光看计算量的增减。4. 协同设计实践从模型到芯片的闭环流程理论讲了一堆真正动起手来软硬件协同设计更像是一个不断迭代的闭环。我这里给出一套我反复使用的流程每一步都有明确的输入输出和评审标准。4.1 第一步建立性能模型从模型到芯片的闭环建模型不是做个Excel表而要做一张能估算端到端延迟的性能模型。这张表至少得包含每层算子的计算时间根据目标阵列大小、数据复用率来估算。访存时间根据数据形状、存储层次、带宽以及搬运重叠情况来估算。同步和launch开销多核之间是否需要屏障kernel切换是否要flush缓存这些都不能忽略。性能模型要能针对同一个模型快速对比不同硬件配置下的表现。比如把SRAM容量从2MB提到4MB性能提升多少把阵列从32x32扩到64x64瓶颈会不会转移如果没有这个工具架构团队只能靠拍脑袋验证团队只能靠流片后救火。4.2 第二步把真实模型转成硬件友好的中间表示这里有一个“先有鸡还是先有蛋”的问题。模型的原始结构通常不是硬件友好的你需要一个模型改写工具做layout转换、算子替换、常量折叠、循环分块等。这个工具本身也可以用来评估硬件架构的变化。我通常把这一步叫做“模型适配”。典型操作包括将PyTorch模型里的连续transpose/permute操作合并减少layout转换。将动态shape的模型节点根据batch和sequence length做静态化处理。把多个小GeMM合并成一个大GeMM迎合阵列形状。将部分计算从浮点降到整数模拟INT8推理的精度损失。模型适配做得好不仅能直接提高真机性能还能给硬件设计提供“反推需求”的素材。比如你在适配过程中发现大量“矩阵维度过小导致阵列利用率低”的情况这就说明硬件应该加入“小算子加速模式”。4.3 第三步指令集和微架构的联合调优这是名副其实的“协同”环节。指令集不是硬件设计好了再让编译器去适配而是要先写一套模拟器哪怕是纯功能的模型然后让编译器生成指令跑一遍cycle-level仿真观察每条指令的开销和利用率。一个比较典型的迭代案例某个项目初期设计了一个“二维DMA搬运指令”理论上一条指令能搬一块数据块。但模拟器跑下来发现这种指令对编译器非常不友好因为编译器很难在程序里天然地把数据排成一整块总是需要额外的padding。于是我们改成“一维DMA指令循环展开”面积少了功耗低了编译器也好多了。这才是软硬件协同的意义。4.4 第四步裸机性能调优 — 同构问题真正流片之后你会发现所有仿真都不完美。裸机调优阶段重点做两件事一是用微基准测试验证芯片的存储带宽、延迟和阵列峰值二是在芯片上跑真实模型定位瓶颈并尝试修改映射方式。这个阶段常遇到的问题是模拟器上完美的调度在真机上因为启动延迟而性能崩塌。解决办法是软件层面做“指令预取”和“多核任务双缓冲”。比如在一组核心还在计算的时候提前把下一组权重搬到片上。类似这样的改动不需要动硬件却能挽回20%到30%的性能损失。5. 工具链和生态建设芯片能不能用起来靠的是“外围”我一直跟团队强调AI芯片不光是芯片本身更是一个完整的开发环境。如果一个芯片的计算能力是100但开发效率只有30用户实际感受到的就是30。整个工具链的价值往往被低估。5.1 仿真器的层次选择仿真器分几层层次速度精度用途功能仿真极快与周期无关验证算法和指令正确性Cycle仿真中等可测延迟架构探索和编译器调优RTL仿真极慢周期性精确验证硬件正确性FPGA原型较快接近真机软件预开发、性能估算建议三个不同层次的仿真器都要有。架构探索阶段用Cycle级验证阶段用RTL级软件开发和benchmark预研则用FPGA原型或者功能级。FPGA原型是一个非常关键的节点。不要等流片之后才写软件栈FPGA阶段就能把编译器、runtime、算子库全部跑起来。哪怕FPGA的主频只有真机的十分之一运行速度慢一点但是正确性和流程能提前验证大半年。这个时间差在AI芯片这个行业就是生死线。5.2 调试手段和性能分析工具芯片开发过程中最怕的就是“性能瓶颈不知道在哪”。所以一个良好的profiler工具是软件栈里不可或缺的一部分。我们开发过一种“硬件计数器”机制在芯片里记录访存次数、阵列利用率、流水线气泡周期等。软件层把这些数据定期dump出来再通过一个可视化界面展示到每一层算子、每个核上。有了这个能力性能问题就能快速定位。比如某个模型跑得慢profiler显示某层算子的加载利用率只有15%说明内存搬运和计算严重不重叠那就去优化该层的存储调度。如果没有profiler只能靠猜和改参数乱试效率差太多。5.3 生态兼容的稳妥路线任何新芯片要考虑生态问题。最稳妥的做法是尽量兼容行业主流的编程框架对外提供基于常见框架的前端接口而把自研的编译器和runtime留在底层。例如在训练侧常见做法是提供类似GPU的抽象接口用户写PyTorch代码底层自动分发算子到自研芯片。在推理侧则导出标准模型格式再用自研runtime解析执行。这种兼容路线最大的好处是用户迁移成本低但也意味着你要保证前端的“语义一致”特别是在自动混合精度级别上要做到行为对齐其实很费功夫。6. 常见问题与排查技巧实录我在这几年的项目里踩过不少坑有些问题长得很隐蔽但一旦遇到就会耗费几周时间。这里挑几个典型的连同排查思路一并分享。6.1 性能模型预测偏高30%以上这个问题出现的原因很多样最常见的一个是“忽略了流水线启动和排空效应”。理论上当一个kernel的循环流水线填满后每个cycle都能输出结果但实际在kernel启动和结束时流水线有大概几十甚至上百周期的empty和drain。如果kernel本身很小启动开销占比就非常高。排查方法很简单用周期级仿真统计小算子和大算子的启动开销差异然后在性能模型里加入“固定开销项”。另一个容易被忽略的因素是片上存储体冲突建议用带bank冲突统计的模拟器提前跑一轮而不是到真机才暴露。6.2 多核同步导致的计算停顿多核并行时经常出现某个核跑得快在等待其他核的情况。这个问题的根源在于任务划分不均衡。尤其当模型中有依赖关系时如果编译器只是按层分配给不同核很容易出现“拥塞”和“空转”并存。我通常的做法是引入“多级任务队列”每个核心不再静态绑定某层算子而是动态从队列中取任务。处理器核心数量不大的时候这种动态调度可以显著提高整体利用率。但注意动态调度会增加额外的管理开销对于极小的任务反而得不偿失所以需要设置一个阈值阈值只有任务大到超过若干周期才启用动态调度。6.3 精度异常仿真正常真机不正常这是我见过最玄学的问题之一。仿真和真机结果不一致通常不是计算错误而是“非确定性”造成的。比如多核之间的累加顺序不同导致浮点结果的舍入误差不同又比如硬件把INT8乘法的累加部分分成了多段每段消除顺序不一样。这类问题看似小但会在多层累加之后被放大。排查思路先用确定性模式跑模型锁核、锁指令调度顺序确认结果一致。如果仍然不一致就要检查是否有未初始化的片上存储。有一次我们排查了很久发现是某块DMA配置错误导致一小段数据没被搬运而那个位置恰好存的是上上一次推理留下的旧值。这个教训告诉我们软件栈里的每一处内存初始化都不能省略。6.4 工具链“新人容易上手老手难以调优”这听起来像矛盾但确实真实存在。新手需要的是封装良好的高层接口能快速运行模型老手需要的是裸暴露硬件细节的底层接口好进行性能调优。解决方案是双轨制软件栈一套面向框架用户稳定高级API另一套面向性能工程师提供硬件操作原语和脚本接口。两者之间通过内部中间表示连接高层API底层自动映射到底层原语。这样既保证了易用性又能让专家精准调优。7. 实操中的几点体会说了这么多最后分享几个我在实际项目中总结的小经验不一定全会写在文档里但往往决定项目成败。7.1 性能模型一定要“可以调参”性能模型不是一次性的而是要和硬件演进同步更新的资产。有些团队把性能模型写成硬编码的脚本换一个架构就得重写。我习惯在模型里把所有关键参数带宽、延迟、阵列大小、缓存容量、核数、同步开销全部做成配置文件甚至用简单的Python表达式来描述这样就能快速试验各种硬件配置把这个工具变成架构探索时的“计算器”。7.2 编译器日志要能还原到算子级调试AI芯片软件栈时最痛苦的是看到一个底层的“矩阵乘指令”出错了却不知道它对应的是模型里的哪一层。如果编译器在生成指令时同时输出一层“映射日志”把每个指令和源算子ID关联起来排查时间能缩短一半。这个看似不起眼的设计实际会救无数工程师于水火。7.3 FPGA太慢也可以用“硬件模拟器”如果FPGA资源不足或者验证环境受限可以退而求其次用一个多线程的软件模拟器来模拟多核并行。虽然慢但能提前验证软件栈的并行逻辑。我经常把软件模拟器当成“调整同步策略”的工作台在上面先跑通再移植到真机。7.4 别迷信“编译器的完美自动优化”编译器能自动完成很多事情但完全不需要人工干预的“全自动优化”目前还不存在。实际项目中我们还是需要手工为每一类模型做模板化的映射方案比如“标准编码器模板”、“大规模GeMM模板”、“稀疏检索模板”。编译器负责在模板里进行参数搜索而模板本身是人工沉淀的经验结晶。这条路看起来不够“智能”但却是工程中最稳妥、最可控的路径。7.5 预留一部分可编程性硬件的功能不能做得太死。哪怕你觉得某个模块未来不会改最好也留下一两个可配置的寄存器或者一小块可编程控制器。因为模型迭代太快你会不断接到新需求。几次之后你就会发现当初预留的这部分可编程性价值远超那点面积和功耗。这个原则在AI芯片设计里永远适用。AI芯片的软硬件设计从来不是一条笔直的路它更像一场持续求索的磨合。你设计硬件时心里得装着软件写软件时脑海里得有硬件。建议你从第一个模型适配用例开始一边搭建软硬件协同流程一边打磨性能模型和工具链让设计和验证并行跑起来这样才能在AI这个快速变化的领域里稳住阵脚。希望这些从实践中攒下来的经验能给你带来一些真正的启发。