
从入行做芯片验证到现在带软硬件协同的项目我前后经历过三代加速器架构的迭代有一个感触越来越深AI芯片能不能打软件栈至少占一半功劳很多时候甚至更多。所谓“AI 芯片的软硬件设计”与其说是在设计一颗芯片不如说是在同时设计一套硬件机制和与之咬合的软件契约。硬件负责提供可能性软件负责把可能性变成实际吞吐和能效。这几年业内讨论的算子编译器、tiling策略、存储调度、性能建模本质上都是在回答同一个问题交给芯片的模型能不能跑出硬件手册上的理论峰值。这篇文章我想把这块的经验做个系统整理重点放在软硬件接口上编译器如何映射算子、存储系统如何影响能效、性能验证怎么骗人又怎么识破以及我踩过的一些坑。适合刚接触AI芯片设计、或者已经被神经网络编译器折磨到怀疑人生的朋友参考。1. AI芯片软硬件设计的核心矛盾当算力不再是唯一答案1.1 为什么算法和硬件的“代差”成了常态过去几年算法侧的迭代速度快得惊人。新算子、新注意力机制、新的激活函数层出不穷很多模型结构从提出到大规模落地往往只有几个月。而硬件侧的开发节奏完全是另一个量级架构定义、前端设计、验证、流片、封装测试一套流程走下来以年为单位。一个项目组刚把斗殴型卷积加速器做到极致业界可能已经转向大模型推理和小batch场景了。这种节奏差导致了一个非常尴尬的局面芯片里固化的硬核加速单元往往追不上算法演进的速度。于是业界慢慢达成了一种共识与其堆专用单元去赌算法方向不如把通用性和可编程性做在更底层的位置。所谓“软硬件协同设计”本质就是在架构里找一个平衡点局部关键计算用专用数据路径控制流和灵活组合用可编程逻辑。我见过一个典型的失败案例。某团队把一款模型里的卷积层全部硬化成系数固定的乘法累加阵列测试精度和速度都很好。结果算法团队把卷积核尺寸从3x3换成了5x5并且引入空洞卷积。只是激活时间不重要重点是硬件团队被迫推翻重做。这个教训让我明白一件事AI芯片的设计不能只盯着当前模型而要为未来三个版本的算子留出缓冲带这个缓冲带恰恰是软件栈最该发挥价值的地方。1.2 定指标之前先学会给性能预期“算总账”很多项目启动时都会布置一张目标表格TOPS算力多少、存储带宽多少、功耗限制多少。但真正有经验的团队一定还会额外布置两个软硬件黏合度很高的指标单算子在不同输入形状下的利用率下限、端到端pipeline之间数据搬运的占比上限。为什么这两个指标关键因为AI芯片的纸面算力很好看真实利用率通常很难看。一个大矩阵乘如果tiling没有处理好片内利用率可能只有60%如果数据切分太碎DMA和计算单元互相等待利用率会跌到30%以下。算总账的思路是这样的先定一个目标模型的端到端延迟反推每层网络分配到的预算再检查每个算子的计算量、访存量、片上存储容量能否在预算内完成。这个反推过程如果发现带宽缺口大得离谱要么改架构要么改tiling策略要么参考实际batch size做软流水折中。我经常用一组很朴素的数字来快速判断项目方案的合理性。假设一个加速器的算力是A TOPS内存带宽是B GB/s某个精简网络的计算量是C FLOPs权重加特征图的总访存量是D GB。理想状态下计算耗时约C/A访存耗时约D/B。只有当访存时间压得住计算时间芯片才能“喂饱”。如果D/B远大于C/A那这芯片基本被内存墙卡死了——后续所有优化都会在搬运数据这件事上绕圈。这个简单的估算帮我在项目初期筛掉过好几版不靠谱方案。2. 从算子映射到数据流编译器在芯片眼里看到的“世界”2.1 一个卷积算子在软件栈里的完整旅程很多人以为神经网络编译器就是把模型翻译成汇编听起来无比朴素。真正深入进去才发现从模型的宏观结构到芯片上的微指令中间会经过好多层重组。我习惯把这条链路分成四段每一步都可能成为性能瓶颈。第一段是图优化。编译器把模型里的算子做归一化比如把卷积和Add归一化成带偏置的卷积把连续几个矩阵乘法做融合或者公共表达式消除。这个阶段在CPU上跑的收益通常不明显但NPU上很关键因为NPU访存代价高算子越多一次循环次数越多数据被搬进搬出的次数增加端到端性能眼看就崩了。第二段是算子下沉与算子选择。编译器会根据target的算子库把每个图节点映射到算法原语上。这里有个常见误区看起来等价的算子实现在特定硬件上性能可以差十倍。比如卷积可以直接跑脉动阵列也可以转成矩阵乘还可以用Winograd思路减少乘法次数。编译器要做的事是根据输入形状、对齐约束、活动计划综合打分选出最适合当前硬件配置的实现。第三段是tiling与调度。片上存储永远装不下一整层网络的特征图编译器必须把算子的循环拆成一系列“地板砖”大小的小块交错执行。这一步复杂度极高因为涉及循环顺序重排、内存复用、跨核心通信模式。第四段是代码生成把调度方案落到同步原语、DMA搬运和计算指令上。我印象最深的一次调试是一个很简单的3x3卷积编译器自动tiling后利用率只有28%。后来手工把tile大小从128调成96并且把通道维度拆成两轮做利用率抬到了70%以上。原因就在于数据复用模式改变了更小的tile让同一块输入自然留在片上少走了好几轮DRAM读。编译器用众多规则做这种事调参也是开发者的必修课。2.2 数据布局与共享存储分配才是性能的真战场很多做AI芯片设计的人把精力花在算好MAC阵列的排布上这当然重要。但等我实际调过几个项目后我的感受是存储分配和布局转换才是决定跑分能不能上去的最大变量。举个例子。很多框架默认的特征图布局是NHWC因为对CPU访存友好而很多AI加速器的数据路径希望按NCHW那样把所有通道拆开配合矩阵乘的K维度。编译器如果不做一层布局转换硬件就只能等到计算时临时做重叠搬运结果每次计算前都要多等一次结转带宽瞬间翻倍消耗。又比如矩阵乘里的B矩阵通常要按K轴主序切片。编译器表现出来的是一个很不起眼的“transpose”动作却可能主导整个tiling策略的选择。共享存储上还有一个经典坑bank冲突。硬件把片上SRAM分成多个bank每个周期支持同时访问多个地址。如果编译器生成的地址模式总是让每个线程访问同一个bank冲突率就会剧增实际带宽直接砍半甚至更多。排查的方法是查看访存trace看同一周期有多少请求命中同一个bank然后靠调整数据padding来错开。这个过程我写过非常多的automated脚本核心逻辑就是把数组的stride改成质数节拍的倍数虽然听着是雕虫小技实际拯救了太多性能测试。所以我经常跟同事说软件栈在某种意义上才是完整架构书里最“细节”的篇章因为一个小小布局字段没对齐硬件所有加速宏都会变成装饰品。3. 能效账本为什么数据搬运比计算更贵3.1 每一笔FLOPS背后的“隐形账单”聊AI芯片不可能绕开能效。但很多人只盯着MAC阵列的功耗有个更隐蔽的事实是一次片内乘加运算消耗的能量通常远低于把操作数从远处存储搬过来的能量。这个问题行业内说法很统一——内存墙与功耗墙是绑定在一起的。单纯做一次MAC消耗在能量尺度上可能只是很小的单位而从动态随机存储器读一个数据进来能量开销能高出一到两个数量级。也就是说芯片真正烧掉的电大部分花在了“搬数据”而不是“算数据”上。这就解释了为什么很多加速器论文都在谈近存计算、数据复用、片上融合本质上就是想改变数据流动方式把同一份数据尽量在片内多算几次而不是搬一次算一次。我做过一个粗略估算。假设某个加速器算力较高片内计算单元密集同时它每秒要从外部存储读数据支撑计算。只看功耗计算单元那部分的功耗可能只占系统功耗的三分之一其余三分之二消耗在互联、存储控制器、外部读通路和时钟网络上。这正好印证了物理设计领域那句老话功耗问题的答案往往不在单元级而在数据路径级。只要数据路径上做一次多余搬运整块芯片的能效就明显打折。3.2 我最常用的一组能效估算数字与方法虽然不同制程、不同工艺下的数字会有差异但我会在项目早期用一组经验数量级快速搭建软硬件权衡的账本。这组数大致是片内一次低精度乘加约消耗相对较低的几个飞焦至皮焦量级能量读一次片上缓存能量大约是它的数倍而访问一次外部动态存储器能量开销又比片上缓存高一个数量级以上。基于这个表我常用一个方法评估某次优化是否值得假设一个算子需要把一份权重反复从外部存储读入如果改成“先把权重搬进片上缓存原地完成n次复用”就能省下(n-1)次外部访问。把每次访问的能量乘以省下的次数就能得出一个优化收益值。如果这个收益远大于改tiling带来的额外索引开销那就值得做。实际项目中很多tiling策略能带来的能效提升就藏在这种反复计算里。我还习惯同时看“有效算力利用率”和“有效存储带宽利用率”。前者是实际FLOPS除以理论峰值后者是实际吞吐量除以理论带宽。两个率通常互相制约为了提升算力利用率常常得加大tile把缓存填满为了提升带宽利用率又希望流水线尽量不空等。最终选择往往是折中——目标是把两个率做成同一梯队而不是一个跑到90%、另一个只有20%。那种畸形指标组合的产品实际端到端体验多半很难看。4. 软硬件协同验证让性能模型与硬件实测对得上4.1 性能模拟与实测结果为什么总对不上做软硬件协同设计最痛苦的一环就是验证环境仿真器说性能提升35%上板实测只有12%这差距从哪来的我这么多年踩坑下来基本锁定几个常见的元凶。第一性能模型太“乐观”忽略了程序启动的固定开销。很多模拟器算的是稳定流水段的吞吐却没有考虑任务调度、上下文切换、片内同步栅栏的等待时间。真实芯片是模块化流水线每一步都可能因为资源竞争导致周期损失。第二就是软件栈本身的成本被低估了指令下发本身可能占据数百个周期驱动层的中断和内存分配也有耗损。如果模型没有把驱动路径建模进去高估几乎必然。第三种非常常见且隐蔽模拟器假定了缓存总是命中但真实场景里多核共享存储的竞争会引入随机性抖动。最简单的修复方法是建立分层验证环境。第一层是做cycle级功能模拟重点验证tiling和指令执行逻辑的正确性第二层是在后端仿真平台上加实际时序约束把芯片内部存储的有效带宽、bank冲突、时钟门控都建模进去第三层才是上板实测。每一层之间用同一套基准程序和性能计数器对齐一旦发现某层趋势偏离立刻缩小范围排查是哪一级的建模失真。这个过程听起来繁琐但价值非常大。我有一次在模拟器里看到的加速比很高上板测试却死活复现不出来。最后定位到是编译器在target上启用了一个模拟器根本不支持的浮点近似模式导致模拟器结果是“理想模式”真实模式根本没开。这种问题没有分层确认靠肉眼几乎无法发现。4.2 一套可复现的性能基准流程做AI芯片性能评估另一个常见的坑是“换模型、换环境、换配置导致结论不成立”。我一般会固定一套流程保证横向比较是公平的。首先明确基准集和输入形状。固定一批有代表性的模型固定batch size和输入分辨率。不要总是跑同一个经过调优的模型否则编译器会不知不觉为它做特判。其次规定编译器配置开哪些优化级别、有没有用算子库的特定变体全部写进测试记录。我吃过一个大亏某个版本默认不开算子融合某个版本默认开结果两版成绩差了非常多后来才发现比较环境不统一。最后是统计方法。在跑benchmark的时候取多次运行的中位数而不是最好成绩或平均成绩。更关键的是同时记录能耗如果芯片为了跑快而把功耗拉满那种性能数字意义有限。我也习惯让软件团队和硬件团队对同一份性能报表负责硬件给出理论可用资源和约束软件给出关键路径上的资源利用率报告。两边对不上就开会对账。这个过程很笨但很多项目就是靠这个把性能目标一点一点逼出来的。5. 下一阶段的三个趋势与其对软硬件设计的影响5.1 稀疏性不再只是论文里的加分项未来AI芯片的软硬件设计很大概率是围绕“稀疏计算”展开的。结构化和非结构化剪枝、激活稀疏、MoE专家路由各种玩法已经开始常态化。硬件的挑战在于跳过零值意味着数据路径不再均匀需要一套索引机制去动态标记有效数据。这看起来十分不愉快因为传统SIMD风格的计算单元非常怕不规则数据流。从软件侧看稀疏化带来的收益依赖编译器对剪枝模式的感知能力如果编译器不知道某个tensor是稀疏的它依然会按稠密布局做tiling那么硬件再会跳过零值效果也很有限。所以未来的编译器必须和训练端打通——把剪枝模式直接编码进模型导出格式里让推理栈从一开始就能看到哪些数据需要跳过。这个打通一旦形成稀疏计算才真正可能带来数量级的吞吐提升否则就是空有理论而不落地。5.2 可重构架构让编译器变得更“紧”另一条路线是让硬件变得可重构。现在很多加速器已经不是固定脉动阵列了而是带可编程网络、可配置运算单元、可变数据通路的形态。好处很明显兼容未来的算子形态。代价也很明显——编译器面对的搜索空间暴涨功能映射从“查表”变成“求解”。我见过一个尝试把同一个注意力机制算子在两种结构上实现一个是固定FMA阵列一个是可重构PE。前者编译器只需要几行映射规则后者需要搜索数据流矩阵才能确定每个PE的互连配置。这种硬件的软件栈开发周期往往会是固定的2-3倍。因此做可重构架构前要慎重评估编译器团队有没有能力接住这个复杂度。我个人的建议是如果硬件团队没有在架构定型前就让软件团队介入做可行性实验这个项目大概率会在软件栈上翻车。5.3 从跑单侧峰值到全链路效能的观念转换最后整个行业的评价标准也在变化。早年AI芯片发布会喜欢喊“多少TOPS”现在已经越来越多人问“跑某个大模型首token延迟多少吞吐多少功耗多少”。这个变化推动软硬件设计的范围扩大不止是单个算子的优化更要覆盖整条推理pipeline包括prefill并行化、decode阶段的小batch、KV cache的搬运策略等等。从设计方法论上讲这要求团队把“系统级视角”从项目第一天就带入架构选型。一套擅长超大矩阵运算但小shape效率低下的架构虽然跑benchmark阅兵很好看但实际应用被聊天场景拖垮这个教训已经在行业里重复出现好多遍了。软硬件协调设计的终极目标是让芯片在真实负载下展现出稳定的“能用”和“好用”。做了这么多年我最大的体会是AI芯片的软硬件设计真正难的地方永远不在某一个magic单元而在所有组件之间的接缝处。编译器要知道硬件的物理限制门控逻辑要理解算法的数据流验证要覆盖从模型到硅片的每一层翻译。没有哪个环节能独善其身。刚入行的朋友如果问我怎么成长最快我的建议是别只盯着自己的模块试着把一条真实模型从完整软件栈一路追踪到硬件指令搞清楚每一步在哪停顿、为什么停顿。这个过程做过一遍你对“软硬件协同”的理解会比读十本教材都更扎实。