AI芯片软硬件协同设计:从编译器到数据搬运的实战拆解

发布时间:2026/10/12 1:16:08
AI芯片软硬件协同设计:从编译器到数据搬运的实战拆解
前几年做那颗边缘侧AI芯片的时候我感触最深的一件事就是芯片设计团队和编译器团队在一个会议上吵了整整半天最后发现两边说的其实是一回事只是用了完全不同的术语。硬件工程师口中的“带宽”“利用率”软件团队嘴里的“调度”“寄存器分配”落到同一个神经网络模型上本质都是在问同一句话——数据到底怎么流动才最快从那时候起我就坚信AI芯片的设计如果不把软硬件当成一个整体来考虑后面必定会有一方疯狂返工。这篇就把我的真实经验和拆解过程整理出来给同样在搞AI芯片软硬件设计的朋友一个参考。1. 为什么AI芯片必须走软硬件协同设计这条路AI芯片不像传统ASIC功能模块一画完就结束了。它的核心价值是“跑算法跑得快”而算法是在持续演变中的。你今天对着一个卷积网络调的架构明天客户拿来了大模型或者深度可分离卷积硬件能不能适应软件能不能把它跑出理论性能这两者是死死绑在一起的。1.1 单纯堆硬件算力解决不了实际部署问题不少团队评估AI芯片上来就看总算力比如多少个TOPS、多少TFLOPs。但真实项目里我看过太多“算力虚高”的案例。有一个很典型的场景一个4核AI加速器理论上能做到800 GOPS但跑一个轻量级检测网络时软件团队调了一个月实际帧率只有理论值的40%左右。查到最后发现数据从DRAM搬到片上SRAM的时间占了整个推理延时的六成。算力阵列在白白等数据这不是硬件不行而是软硬件没有一起设计导致的。实际部署性能的公式可以简化成有效性能 理论算力 × 数据供给充足率 × 算子映射效率这三个乘数任何一个小于1最后的有效性能都会大打折扣。硬件设计者如果只在理论上追求峰值算力而不考虑编译器能否将循环切得足够规则、DMA能否提前把下一块数据搬上来那这颗芯片到了客户手上只能是一块“看起来很厉害”的硅片。我后来在定义新架构的时候必做的一件事就是把编译器团队拉进架构评审会让他们对着还没有定稿的存储层次结构先口头模拟一下典型算子的调度方案。这一步成本极低却能在硬件还没冻结之前就暴露大量问题。1.2 编译器与架构天生是“一枚硬币的两面”传统CPU的编译器已经高度成熟但AI芯片的编译器其实还在快速发展期。原因在于AI芯片的微架构高度定制化没有统一标准。不同芯片的片上存储大小、数据通路宽度、算力阵列排布方式、指令发射机制都不同编译器必须针对每一种架构单独做调度和优化。这意味着硬件架构的每一个特殊设计都会直接变成软件编译器的负担。你在硬件上做了一个“很聪明”的加速器比如支持某种非对齐的张量搬运但如果编译器根本无法充分利用它甚至要为这个特性额外处理各种边界情况那这个“聪明设计”可能反而拖慢整体开发节奏。反过来也一样。你写一个编译器特性比如自动算子融合如果硬件侧没有预留相应的寄存器配置接口和状态同步机制那么这个融合就无法落地。软硬件协同设计的本质就是在硬件还没定型的时候先定义好“软硬件接口契约”包括指令格式、内存模型、同步机制、状态寄存器布局这些细节。只有把这些接口当作芯片设计的一部分来管理而不是软件mount上来强行适配这颗芯片才有可能真正好用。2. 硬件侧核心设计拆解算力、存储与数据搬运做AI芯片的硬件设计真正决定成败的往往是那些“不起眼”的系统级设计决策。算力阵列怎么排、片上SRAM放多大、总线带宽留多少这几个决策一环扣一环任何一个单独优化都是在给系统埋雷。2.1 算力单元的真实利用率AI芯片的算力单元主流还是MAC乘累加阵列。以常见的脉动阵列结构为例它要求数据以规则流动的方式进入阵列每个处理单元PE只需要做一次乘加操作然后把部分和传给下一个PE。这种结构在矩阵乘法上效率极高但遇到不规则的算子比如池化、切片、动态shape的算子阵列利用率会断崖式下降。这里有一个非常容易踩的坑单纯按峰值算力选型MAC阵列规模。比如客户要跑一个1 GOPS的网络有人就配了足够大的阵列结果因为数据带宽喂不饱阵列有一半时间在空转。算力阵列的规模应该由“数据供给能力”约束而不是由“峰值算力”约束。推荐的评估方式是先画出目标模型的访存足迹估计每层计算需要的数据量然后和硬件数据通道的带宽做对比。一个简单的判断标准是“算访比”每1 TOPS的算力至少要配上每秒1~2 TB级别的片上数据带宽具体取决于数据复用度和数据类型。如果算访比失衡堆再多算力都是白费。还有一点MAC阵列的位宽设计也很关键。INT8和FP16混跑看起来是大趋势但真正实施时要注意累加器的精度。累加器的位宽不够是导致推理结果漂移的经典元凶。ARM风格的架构通常用32位累加器这在大多数场景够用但如果你支持大通道数的卷积累加建议预留40位甚至更宽的累加器数据通路否则后期软件团队会被精度问题折腾到崩溃。2.2 片上存储容量与带宽的取舍AI芯片的片上存储一般做分层设计最靠近PE的是寄存器文件和tiny buffer中间是一级SRAM再往外可能是二级SRAM或者直接接DDR。存储容量越大数据复用率越高越不用频繁搬数据但代价是面积暴涨、频率下降、编译器的调度复杂度也直线上升。实际上我见过很多团队在早期架构阶段拍脑袋定了一个SRAM容量比如“就做2MB吧”。这个数字如果没有经过真实模型的算子循环切分验证后期几乎一定会出问题。比较务实的做法是挑三到五个代表网络把每一个算子的循环层拆开按不同块大小tile size计算需要多大的片上缓冲然后把所有算子的需求分布画出来取一个覆盖度比较高的折中值。这里有个经验值可以分享对于边缘侧AI芯片几TOPS级别片上SRAM容量通常做到几百KB到几MB之间宽容度比较好。对于数据中心级别的大芯片L2 SRAM做到几十MB的量级也很常见。关键不是这个数字本身而是软件能不能把它用好。带宽方面片上SRAM通常采用多bank交错的结构以匹配PE阵列的并行访问需求。如果你的PE阵列一行有32个PE那么SRAM最好按32字甚至64字去bank划分保证一次读操作能把一整行数据塞给所有PE。如果软件访问的连续性差带宽利用率也会下降这时候很有可能不是硬件的问题而是数据布局和编译器调度的问题。2.3 互联总线与DMA设计是隐形瓶颈很多第一次做AI芯片的人容易把注意力放在计算阵列上忽视DMA直接内存访问控制器和总线设计。实际上AI推理过程中最耗时的往往是数据搬运而不是计算本身。DMA的作用就是把数据从DDR搬到SRAM把结果从SRAM搬回DDR这个过程最好和PE阵列的计算完全重叠否则就是排队等待。我记得一个项目里我们在FPGA原型上验证一个语义分割模型发现推理时间比预期慢了1.6倍。排查到最后问题出在DMA的通道优先级配置连续搬运的读请求总是被写回请求打断导致带宽在读写切换时大量浪费。解决办法倒是简单把读写通道做分离并且给读请求更高的优先级性能一下就恢复正常了。总线这块常用的做法是AXI总线互连。设计时要注意两点一是读写的并发性二是总线位宽和频率的乘积是否能匹配DMA的需求。举个例子你希望DMA以320MB/s的速度搬数据但总线实际最高只能提供256MB/s那无论软件怎么调度吞吐都上不去。硬件设计阶段我建议把“各模块带宽预算表”做细每一个模块的峰值带宽、典型带宽、期望并发压力都列出来再统一核算总线的承载能力。DMA的设计还要考虑数据对齐和descriptor链。AI推理过程中数据的起始地址经常是不对齐的会带来额外的边界处理开销。如果硬件不支持非对齐搬运软件就要在driver层多拷贝一次性能损失很可观。建议DMA的descriptor设计要足够灵活至少支持二维搬运、数据步长的一些常用组合这样编译器在排调度的时候自由度才够大。3. 软件工具链如何与硬件咬合映射、量化与指令集硬件再强没有一套克制、高效的软件工具链也是一堆硅片。AI芯片的软件栈通常分三层最上层是深度学习框架接入比如ONNX前端中间是算子库和编译器最底层是驱动和运行时。绝大多数性能问题都发生在中间层。这一章我重点说三个环节算子映射、量化策略、以及指令集后端。3.1 算子映射决定硬件利用率所谓算子映射就是把一个深度学习算子卷积、全连接、归一化等翻译成目标硬件上的具体指令序列。这个过程里最重要的一个概念是“分块”。比如一个卷积输入是HxWxC输出是HoxWoxCo硬件不可能一次性把整张图塞进片上存储必须切成一个个tile分块计算。分块大小直接决定了两件事一是数据复用率二是PE利用率。如果tile太大片上存储放不下会频繁发生溢出如果tile太小数据复用差搬运的时间占比过高。理想的tile大小应该尽量贴合片上SRAM的容量和PE阵列的尺寸最好能做到“一次搬入多次复用”。我在实际项目里习惯用循环变换的思路来指导映射优化。典型的优化手段包括循环拆解、循环交换、循环融合、寄存器缓存等。这里面最有效的通常是循环融合把两个相邻算子的循环合并成一个中间的数据直接在片上流转不落DDR。比如卷积之后接一个激活函数如果不做融合卷积结果要全部写回DDR再读出来给激活算子跑一遍白花一倍带宽做了融合激活操作就在数据从PE搬到SRAM的过程中顺手做掉了省时省力。还有一个关键点算子库的静态预优化编译器的动态调度相结合。固定尺寸的卷积可以提前在算子库里手动调优好选择最优的数据布局和tiling参数而动态shape的算子只能依赖编译器实时生成核心代码。架构设计上要尽量保证“形状不同但数据路径一致”这样编译器才能用一套后端框架覆盖尽量多的场景。3.2 量化与精度折中软硬件协同的第一道考题量化是AI芯片里“软硬件必须同时考虑”的典型环节。很多团队在硬件设计时信誓旦旦支持INT8结果真跑模型的时候客户发现精度掉了2个点最后要么靠量化感知训练QAT把精度拉回来要么偷偷把某些层切回FP16。这个锅很多时候不是算法的错而是硬件的量化设计做了“假支持”。INT8量化真正要关注的不是换算单元能不能处理INT8乘法而是三个软硬件一起决定的细节量化粒度、量化对称性、以及反量化的位置。量化粒度方面Per-tensor整张特征图用同一个scale最简单但精度损失大Per-channel每个输出通道一个scale精度好但对硬件数据通路的要求更高——反量化要发生在每个通道上需要额外的scale查找和乘法操作。好的硬件设计是同时支持这两种模式的由编译器在编译阶段根据精度需求去选择。反量化位置又是一个大坑。INT8矩阵乘法的结果本质上是INT32累加值需要乘上scale系数才能回到FP16或FP32继续往后算。如果你在硬件上没设计好这个“反量化”单元那么软件只能把这个操作拆到通用ALU上用指令模拟性能一落千丈。正确的做法是在MAC阵列的输出端直接内置scale乘法器和clamp电路让下溢、溢出在这个环节一次处理干净。我的建议是芯片设计阶段就把量化模型跑一遍确定几个关键参数——哪些层用INT8、哪些层必须FP16、累加器需要多少位、激活函数放在哪个精度域做。这些结论直接反馈给硬件架构组让他们把对应的数据通路、指令扩展都预留好。3.3 指令集与编译器后端的取舍AI芯片的指令集设计通常有两种极端路线一种是极简的定制指令只覆盖少数固定算子用起来高效但灵活性差另一种是通用VLIW指令集编译器来做大量并行调度灵活性强但指令密度和编译难度都不小。项目实践中大部分实用的AI芯片是走中间的混合路线有少量标量控制指令、向量计算指令、以及特殊的DMA搬移指令。混合指令集的好处是既能让编译器对计算密集型算子做深度优化也能为控制流复杂、shape不固定的算子预留灵活性。但它的代价是编译器后端需要同时处理两种代码生成路径一条是面向固定模板的算子库一条是面向动态调度的VLIW编译器后端。这两条路径在工程实现上要小心别做成两套自说自话的框架不然维护成本会非常痛苦。指令集设计上我最想提醒的是“同步”问题。AI加速器里的计算单元和DMA单元是异步工作的一般靠状态寄存器、中断和事件同步机制来协调。如果指令集里缺少“等待DMA完成”这类原生指令编译器就得退化成用轮询寄存器来同步白白浪费几十个周期的CPU等待时间。更聪明的做法是支持“事件链”机制让DMA搬运、阵列计算、写回这几个阶段可以按流水线方式重叠起来。软件编译器生成代码时只要把搬数据和计算排成一张流水线图硬件负责按事件链递进执行就可以了。还有一点调试功能。AI芯片的指令级调试远没有CPU的GDB那么好用需要底层支持专门的trace buffer和断点指令。如果你从头设计指令集一定要预留调试指令的编码空间不然后期找bug会想哭。4. 实操闭环从算法原型到芯片点亮很多项目做软硬件协同只在文档里谈等到芯片回来才开始联调这是效率极低的方式。下面这套流程是我在多个项目里沉淀下来的实操闭环覆盖从算法到硬件的整个链路每一步都有目的的、可落到代码或文档的产出。4.1 在硬件冻结前先跑通“端到端性能仿真”真正有效的软硬件协同在RTL寄存器传输级写第一行代码之前就应该启动了。当时我们的做法是先把目标网络在PC上用参考框架跑通得到每个算子的数据量、计算量、内存访问模式。然后把它丢进一个“性能仿真器”——这个仿真器可以是基于脚本的周期级模拟器甚至可以简单到用Python写一个“带宽账本”记录每个算子在给定硬件参数下会花多少周期。这步看似粗糙却能在硬件成型之前就回答一个重要问题架构参数是否合理。举个例子我们曾经在仿真中发现当片上SRAM只有512KB时一个语义分割网络的访存开销占总耗时的75%。于是把SRAM翻倍到1MB总时间直接下降了三成。这个改动如果在流片之后才做代价是重来一版而现在只需要改一个参数重新评估。建议参考模型准备一组“标准跑分网络集”不要只挑对自家架构有利的网络比如某些网络可以写成对访存特别友好的形式。跑分集至少包含一个标准卷积网络、一个带shortcut的残差网络、一个分割网络高频访存、一个检测网络有后处理。这样可以在早期暴露系统级的瓶颈。4.2 用Roofline模型校准带宽与算力Roofline模型是一种可视化分析方法横轴是运算强度每字节数据能换来多少FLOPs纵轴是可达性能。它最大的作用是帮你一眼看出当前设计是“算力受限”还是“带宽受限”。实操时我用它做架构微调先给每个关键算子点算出它在当前架构下的运算强度画在Roofline图上。落在斜线区域带宽受限的算子优化方向是增加数据复用、加大SRAM tile落在平台区算力受限的算子优化方向才是调整PE阵列或频率。有一次项目碰到一个很奇怪的现象整体网络性能比预估低了20%但每个算子单独看都表现正常。后来用Roofline一画才发现算子衔接处的中间张量写回DDR产生了隐藏的带宽消耗。我们把两个相邻算子做了融合之后平台的斜线区域右移整体性能一下子上来了。这就是“系统行为”和“局部行为”的差异只有用整体模型才能看清楚。4.3 芯片回片后的bring-up联调步骤芯片从代工厂回来第一步通常不是跑AI模型而是做最基本的寄存器读写、时钟复位、SRAM自检。这些基础测试过了才进入AI相关的联调。我的个人建议是按这个顺序对AI系统功能进行逐步点亮写一个最简单的MAC阵列自测程序向PE注入已知数据直接验证乘累加结果的正确性。测试DMA的搬运功能用descriptor链跑一次DDR到SRAM的二维搬运检查地址映射和中断状态。联合测试DMA搬入一块数据MAC计算一次再把结果DMA搬出。这个闭环打通了说明最核心的硬件链路是通的。加载一个小型网络比如一个只有十几层的分类网络跑完整推理。如果结果和PC端参考一致说明整套硬件和基础软件栈的对接没问题。最后才上目标大网络开始跑性能优化。这中间最容易出问题的是时序收敛的余量不足导致样片在某个工艺角下工作不正常。我的一般处理办法是在测试代码里预留一个“降频测试”的入口一旦出现不确定的复位或者数据错误立刻能切到低频模式排除时序问题。5. 常见问题与排查技巧实录软硬件协同里没有太多“灵光一现”的时刻大多数时候是在一个接一个的问题里找出最小的那个卡点。下面几条是我在多个项目里验证过的排查思路按优先级排好了直接照着做就能省大量时间。5.1 性能不达标先查数据搬运再查计算优化如果你发现某个网络的实际推理时间远大于仿真预期不要急着去优化算子实现。我的经验是至少有一半的AI芯片性能问题都出在“数据供给”环节。排查顺序建议如下先用 profiling 工具硬件自带的计数器或者软件插桩统计每个算子的计算时间和等待时间。如果等待时间占比超过20%优先查DMA scheduling 和 buffer大小如果在计算时间内利用率低再去查tile循环切分是否合理。有个真实案例我们在跑一个3x3卷积时发现PE利用率只有25%一开始以为是数据排布问题后来仔细查才发现代码生成器在边缘处理时做了大量的“补零判断”导致每个循环多了几十次空转。解决方式是在硬件上让卷积边缘的填充逻辑直接由专用硬件电路处理软件不再逐像素判断利用率直接回到了80%以上。这就是一个典型的“硬件特性没被软件充分利用”的案例。5.2 功能正确但结果不对先锁定精度是数据层还是计算层AI推理结果和参考值对不上十次里有七次是量化精度问题两次是数据布局问题一次是真正的硬件bug。排查要讲究分层定位先在通用CPU上模拟量化后的算子输出确认量化这一步的数值没问题再用单层测试在AI芯片上跑同一个算子对比输出如果单层输出不对那就进入硬件级debug。我踩过最深的一个坑是长短期记忆网络里的“时间步”方向量化。整网都是INT8只有时间步之间传递的隐藏状态向量因为累计误差太大导致输出发散。解决的办法是在硬件上为这个特殊向量单独保留FP16精度域编译器识别到这类tensor就自动走高精度路径。这个调整不到十行代码却让最终模型的精度从“不可用”变成了“完全匹配”。5.3 工具链“断头路”框架兼容性问题AI芯片的工具链最怕的是上游框架某个算子无法解析。经常出现的情况是ONNX导入的时候某个新算子或者某个属性不被支持编译器直接报错退出。这种“断头路”问题解决起来并不难但需要团队里有人非常熟悉框架和模型的血缘关系。我的处理思路是建立一个“算子兼容性清单”把已经支持的算子、以及每个算子的属性变体记录下来。每当上游软件版本更新立刻跑一遍回归测试看看有没有新算子出现。如果出现评估它的计算模式是否能映射到现有硬件能映射的加到编译器前端不能映射的用“算子回退”机制分解成多个基础算子。这个回退机制比如把group convolution拆成普通卷积虽然性能差一些但至少能保证模型跑得通不至于客户拿到手一片空白。6. 给同样在这条路上的人一些建议软硬件协同设计这件事做到最后拼的基础工程能力。硬件侧的人要读懂软件编译器的调度逻辑软件侧的人要看懂RTL里数据通路的边界在哪。回到这篇文章最初的题目一颗AI芯片之所以能被称为“好芯片”从来不是因为某一个模块设计得特别惊艳而是因为从编译器到下层的每一个细节都匹配得严丝合缝。如果只让我选一条最值得投入的经验那就是“早验证、多仿真、尽量把软硬件的联调动作前移到架构阶段”。你可以少修一堆后期无法修改的bug少经历一些“原来当初架构评审时提一句就不会这样”的痛苦。我见过太多团队把软硬件协同设计当成口号等到流片回来才发现架构的根本性问题那种代价真的不是几个月时间的问题而是整个项目方向的崩塌。后面如果有机会我会把编译器调度算法、量化精度评估这些更细的环节单独展开分享。这个系列的第一篇就先停在这里。