
这些年我带过不少AI芯片项目最常见的现象不是“芯片做不出来”而是“芯片做出来了但性能对不上账”。有一回我们内部同时评估两款标称算力几乎一样的边缘AI芯片跑同一个视觉模型实测吞吐量却差出一倍多。硬件规格高的那颗反而输了。问题不出在电路出在软硬件之间的衔接层编译器不会用硬件算子库覆盖不了模型运行时调度拖垮了多核并行。AI芯片的软硬件设计本质上不是“硬件设计软件适配”而是从架构定义阶段就必须一起想清楚的事。这篇是这个系列笔记的第六篇想重点把计算引擎选型、存储层次、编译器映射和一次真实调试过程串起来聊适合正在做AI芯片架构、工具链或者算子库的同学做参考。1. 同样的算力为什么实测性能差出一倍多1.1 AI芯片是专用架构但算法一直在变AI芯片属于领域专用架构DSA它和通用CPU不一样通用CPU面对的是“什么都能跑”的不确定性任务AI芯片面对的是一个相对确定的算子集合卷积、矩阵乘法、归一化、激活、注意力机制这些。所以设计者可以针对这些算子的数据流特征做定制加速把片上资源堆到访存和数据复用上而不是堆到分支预测、乱序执行这些通用能力上。但这里有个核心矛盾硬件一落地就固定了算法却一直在变。前几年边缘侧还是卷积网络的天下这两年Transformer类的模型大量下放到端侧把Attention算子里面的矩阵乘、缩放、Mask、Softmax统统变成了一条新的计算流水。如果硬件当初只顾着把卷积的脉动阵列做得又大又宽面对新的模型结构时很可能计算利用率迅速下降大量MAC单元在空转。我用一个生活化的类比DSA芯片像潮汕牛肉火锅店里的定制灶台火力猛、涮肉快但是菜单固定通用CPU是家里厨房什么菜都能做就是熟得慢。软硬件协同设计难点在于你要决定灶台固定哪几道菜、能不能临时加菜、加菜的时候要改造多少烟道和水管。1.2 纸面算力、峰值算力与有效算力是三个概念芯片手册上写的“8 TOPS INT8”只是峰值算力它的含义是所有MAC单元满负荷运行、每个时钟周期都输入有效数据时一秒钟能完成的整数乘加次数。比如一个设计成512个MAC单元的加速核主频1GHz每个MAC一次完成一次乘加算两个操作那峰值就是512×1e9×2 1024 GOPS约1 TOPS。要做到8 TOPS要么MAC数量翻倍要么主频往上拉要么加更多的并行计算簇。峰值算力是上限不是实际值。实际跑一个模型时数据要从片外DDR搬到片上SRAM从SRAM再搬到计算阵列计算完结果又得写回去。搬运过程一旦来不及供数MAC单元就在干等。这个“干等”的比例直接决定了有效算力能到峰值的几成。很多项目的性能差距就是从这里拉开的硬件设计时没给软件留足够多的缓存空间和DMA通道软件编译时没做好tiling和双缓冲导致搬运和计算串行。结果同等峰值算力的芯片一个有效算力能到七成另一个只有三成。1.3 软硬件协同设计要解决的不只是“把电路做快”芯片设计里流片成本高、迭代周期长一次大的架构调整可能要花一年以上。如果等芯片回来了才发现算子库怎么写性能都上不去、编译器怎么调度都没法利用片上互联带宽那基本很难回头改硬件了。所以现在主流做法是硬件还在RTL阶段时软件团队就用周期级模拟器做预研编译器、算子库和硬件验证环境同步开发架构师给指令集定义的时候编译器和驱动的人就坐在旁边逐个指令讨论“这条指令编译出来长相是否合理、寻址模式会不会让循环展开很难做”。这才是“软硬件协同设计”的真正含义它不是两个团队各干各的然后在系统联调阶段相遇而是从微架构定义、存储层次划分、指令集设计到编译优化和数据流调度全程用一个性能模型对齐。后面的几个章节我会按硬件侧、软件侧和联调实战分别展开。2. 硬件侧的关键决策计算引擎、存储层次与互联拓扑2.1 计算引擎选型脉动阵列、SIMD还是近存计算硬件侧第一个要拍板的问题是计算引擎的形态。同一个卷积算子可以用不同硬件结构实现但效率差别很大。脉动阵列Systolic Array把许多MAC单元排成二维网格数据像在流水线上一样从阵列边缘流入每个MAC只和相邻MAC通信。它的优势是数据复用极其高效权重可以待在本地寄存器里输入激活沿着一行行“流动”每个数据被用很多次。对于卷积、矩阵乘法这种规则运算脉动阵列的能效比很高。代价是灵活性差遇到稀疏计算、动态图结构、非规则shape时阵列里会有大量空闲单元。SIMD向量单元则更像一个“小号的通用处理器”灵活度高能处理各种不规则的计算但能效不如脉动阵列。不少AI芯片走的是混合路线先把卷积映射到脉动阵列把剩下的归约、逐点运算、非线性激活丢给向量单元。近存计算和存内计算则是另一个方向把计算逻辑放到存储单元附近甚至存储单元内部减少数据搬运距离。这种方案对带宽瓶颈很有效但工艺、可靠性和灵活性问题相对多端侧商业产品里真正大规模用的还不算多。我给一张对比表方便做架构选型时参考计算引擎擅长计算灵活性能效主要风险脉动阵列卷积、矩阵乘、规则逐单元运算低固定数据流模板高规则以外场景利用率下降SIMD向量单元逐点算子、不规则归约、非线性高中控制开销和寄存器读口受限可重构数据流阵列多数据流模式可切换中中高编译器调度复杂度高近存/存内计算大带宽访存的算子低理论极高工艺成熟度、量化精度、可生产性选型时的核心判断点是你目标负载里规则算子占多少不规则算子占多少。如果一个芯片主要做视觉CNN脉动阵列比例可以高一些如果还要跑各种动态shape模型就得给SIMD和灵活互联多留资源。2.2 存储层次访存带宽为何比算力更早成为瓶颈很多芯片堆了很多MAC单元一算峰值算力很漂亮但实际跑起来片上SRAM被频繁写满数据根本供不上。问题出在设计时没算清楚“单位算力需要多少存储带宽”。举个例子一个8 TOPS INT8的芯片每秒需要4 T次乘加。每次乘加至少需要读一个权重和一个输入激活也就是至少8字节两个INT8的数据供应。如果全部依赖片外DDR那DDR带宽需要32 TB/s以上这在端侧根本做不到。实际必须靠数据复用让同一份数据留在片上反复使用。所以存储层次设计要回答几个问题片上SRAM总量多大、分成几个bank、每个bank带宽多少、DMA能从片外连续搬多大数据块、计算阵列和SRAM之间有几条数据通路。我的经验是在架构定义阶段就要跑一个简单的访存带宽模型把目标模型的计算量、参数量、激活尺寸列出来按不同数据流倾向估算片外和片上流量。比如权重固定模式权重只加载一次激活不断流进来输入固定模式一份输入激活尽量喂给多个输出通道。哪种模式匹配你的目标模型硬件就多配哪条数据通路。很多团队在规划SRAM大小时只想着“容量越大越好”其实更重要的是每个bank能支持的并发读写端口和带宽。容量再大如果只有一个口数据进出还是得排队MAC阵列照样饿肚子。2.3 片上互联与多核扩展别让资源内耗单核算力总有上限主频做不高、面积受限制时就要上多核。多核之间怎么通信就成了另一个决定实际性能的因素。总线互联在核心数少时简单好用但多个AI核心同时搬数据时总线很容易成为瓶颈。真正大规模并行通常选择片上网络NoC常见的拓扑包括环形、网格和交叉开关。环形实现简单、扩展性尚可但远距离通信延迟高网格可以支持并发通信延迟更可控面积开销也更大交叉开关带宽极高但到几十个端口以上复杂度爆炸。这块我的建议很直接在设计NoC之前先让软件侧给出任务并行和通信模式的假设。比如模型并行时每个计算核心需要和邻居交换多少数据交换频率多高是同步通信还是异步流水。如果软件侧的并行策略都没定NoC拓扑做出来就是拍脑袋后面要么带宽闲置要么延迟大到没法用。内存一致性也是容易忽略的问题。多核共享SRAM时谁负责维护数据一致是硬件自动处理还是软件显式管理会直接影响运行时的调度难度。端侧AI芯片一般倾向显式管理由编译器在代码里安排好数据搬运和同步硬件做简单的屏障指令。这个选择能省很多硬件复杂度代价是编译器必须足够聪明。3. 软件侧的关键链条图优化、算子融合与编译器映射3.1 从模型到指令完整软件栈的分层职责硬件再好最终要靠软件栈跑起来。AI芯片的软件栈大致分成这么几层上层是深度学习框架负责模型训练和导出中间层是图编译和优化模块把模型的计算图做精简、融合、布局转换再往下是算子实现层每个算子要展开成针对具体硬件的数据搬运、分块计算、写回的指令序列最底层是驱动和运行时负责管理上下文、申请内存、提交计算任务、同步多核状态。很多团队在写加速器时只顾着做算子库忽略图优化层结果图上一个卷积后面跟着一个BatchNorm、再接一个ReLU生成了三个独立算子中间结果反复写到DDR再读回来带宽被白白浪费掉。实际上这三者完全可以融合成一个算子中间结果只停留在寄存器或片上SRAM性能可能提升一倍以上还不止。软件栈每一层都会吃掉一部分性能最后真正能跑出来的有效算力是所有层扣完损耗之后的结果。你可以把每一层的开销当成一个乘法系数CPU调度有开销、算子没有充分利用MAC、数据搬运等待、量化转换损失乘起来可能只剩三成。3.2 算子融合为什么能“免费”提速算子融合是图优化里最简单直接也最有效的手段。它的核心逻辑是减少片外访存。谁都知道DDR很慢但很多软件工程师写算子时还是习惯一个算子一进一出结果激活张量在DDR和SRAM之间来回搬运。举个具体例子卷积 BatchNorm ReLU融合。传统写法是先算卷积把结果写到DDR再读回来做BatchNorm写到DDR再读回来做ReLU输出。一次推理中间结果被写了两遍、读了两遍。融合之后卷积计算完成的数据在SRAM里直接做缩放、加偏置、再过ReLU写一次最终结果就行。别小看这“少写少读几次”对于带宽受限的端侧芯片节省的可能是大部分运行时间。类似的融合还有残差相加、权重预打包、激活的量化反量化点融合。编译器做这类优化的前提是掌握硬件的存储层次信息什么地方能放中间结果、多大容量、什么形状访问效率最高。这也是为什么编译器团队和微架构团队必须紧密配合。3.3 编译器的活干不干净直接决定硬件性能兑现率即便算子已经融合好了编译器还要负责把每个算子变成硬件能高效执行的指令序列。这包括循环分块tiling、循环重排、向量化、软件流水、DMA搬运指令和计算指令的重叠调度。以卷积算子为例编译器的目标很简单在有限的片上SRAM里让每个数据尽可能被复用让DMA搬运和MAC计算尽量并行。一个常见的映射思路是先做输入通道方向的split把一个大卷积拆成多个小块每个小块放进SRAM后连续计算多个输出像素然后换下一块。下面是一个简化到只剩核心流程的伪代码风格偏C语言方便理解tiling和双缓冲的思路for (int batch 0; batch N; batch) { for (int tile_oh 0; tile_oh num_tiles_h; tile_oh) { // 双缓冲提前搬运下一块输入 dma_load(input[next_tile], smem_buffer[1], tile_size); dma_wait(prev_dma_done); for (int tile_ow 0; tile_ow num_tiles_w; tile_ow) { // 计算当前块 compute_conv_tile(smem_buffer[cur], weight_tile, output_tile); // 同时搬运下一个块 dma_load(input[next_w], smem_buffer[next], tile_size); } dma_store(output_tile, ddr_out); } }这段代码里最关键的调度在于DMA搬运和compute之间不能互相等。硬件设计时如果提供了足够深度的DMA队列和独立的搬运通道这个双缓冲就能真正重叠起来否则编译器再怎么排指令计算单元也得等数据。编译器做得好不好直接决定了MAC利用率能到多少。我见过一个团队硬件峰值算力不错但因为编译器生成的指令里循环边界判断和地址计算指令太多占据了大量发射槽位MAC阵列只能零星干活。后来把内层循环展开四倍、把地址计算改成增量式后性能提升了将近一倍。3.4 自动调优搜索空间tiling、流水线、并行度怎么找手写算子性能好但人力有限。现代AI芯片编译器会引入自动调优把tiling大小、循环顺序、向量宽度、DMA流水级数、多核并行拆分方式这些参数组合起来搜索。搜索空间非常大比如一个矩阵乘法算子的tiling尺寸可能有几万种组合全部试一遍不现实。实践中会先用代价模型做剪枝搞清硬件的访存带宽、寄存器数量、SRAM大小估算哪些组合不会破编译资源上限再从中采样出来做实际执行时间测量。自动调优最大的坑在测量环节不稳定。芯片运行温度、相邻核抢占带宽、DDR刷新周期都会影响实验数据造成搜索方向走偏。我建议在自动调优阶段固定主频、固定核数、固定数据布局同时多次重复取最小执行时间减少噪声。还有一个很多人忽略的点编译搜索出来的最优参数往往依赖具体的shape。模型部署后如果用户输入图像尺寸变了最优tiling可能就变了。这就要求运行时保留多套方案并根据实际shape动态选择或者在一开始就约束输入尺寸做静态专项优化。4. 一次真实调试从仿真器到实芯片的软硬件联调4.1 流片前的性能仿真为什么不能全信我参与过的一个端侧AI加速项目内部代号就叫“模拟项目X”。硬件团队在RTL仿真阶段报的性能其实很乐观利用周期级模拟器跑基准算子MAC利用率能到70%以上。但流片回来后跑一个公开的视觉分类网络整网有效算力只有峰值的30%左右。为什么仿真结果和实测差这么多一个原因是RTL仿真时大家都挑“好跑”的输入shape比如正方形分辨率、批量大小固定为1数据对齐情况比较理想。另一个原因是整网推理时算子是一个接一个的前一个算子的输出作为后一个算子的输入中间搬运、同步、调度开销都叠加起来了单算子测得的效率根本不能代表整网。所以现在的项目里我会要求软件团队在流片前用周期级模拟器跑完整网络而不是单个算子并且把每个算子的耗时、每条DMA搬运的字节数、每个核的空闲周期都记录下来。这些数据可以提前暴露很多问题。4.2 实测性能只剩三成的排查过程和优化步骤我们当时的第一步是定位瓶颈在计算还是搬运。加速核上有一组性能计数器能看到MAC阵列忙周期数、等待数据周期数、片外访问次数。跑完分类网络后发现MAC忙占比只有28%剩下的时间大多在等数据说明访存瓶颈非常严重。于是优化分成三步走。第一步修改算子内的tiling顺序。原始实现里计算一行输出就会搬一次输入权重反复从DDR读取。改成把整个输出通道维度打散成“先沿输入通道累加再做输出通道展开”之后权重读取次数降了不少MAC利用率从28%升到41%。第二步做DMA双缓冲。硬件本身支持异步搬运但算子实现里没有把搬运和计算重叠起来每次计算前都要等数据到位。花了大概三天时间改造搬运流程让DMA提前加载下一块输入当前块计算和下一块搬运同时进行MAC利用率到了58%。第三步是编译器层面的循环展开和软件流水。把内层计算循环展开后减少了循环控制指令占比同时重新安排了寄存器的读写顺序尽量让长依赖链路的等待时间被其他计算填充。经过这三步这个算子在实测芯片上的有效算力达到了峰值的75%左右。不要小看这个数字对很多商用AI芯片来说整网有效算力能到六成以上已经算调度得很好了。4.3 量化精度与功耗墙部署阶段的两个硬约束算力终于上去了精度问题又冒了出来。为了压功耗芯片全链路用INT8计算但校准时用的是简单粗暴的均匀量化结果在一个目标检测模型上精度掉得比较明显检测率下降了好几个百分点。后来改用逐通道量化和分阶段校准在校准数据集上统计每层激活的分布为不同层单独搜索合适的缩放系数。这里有个容易踩的坑校准数据必须覆盖实际部署场景中的典型输入不能随便挑几十张背景单一的图片否则分布估计会偏模型上线的第一天就可能出现大量误检。功耗墙也很有意思。实测发现把主频从800MHz降到600MHz峰值算力下降了25%但整板功耗下降了45%以上。原因是存储系统的功耗占比很高降频直接降低了DDR和SRAM的动态功耗而很多端侧场景对功耗的敏感度高于对算力的敏感度。做性能优化时不能只看算力指标还要看“每瓦有效帧率”这个综合值。5. 项目里最容易翻车的三个环节以及团队协作建议5.1 把FPGA原型机的数据当成最终性能很多项目在芯片流片前会用FPGA做原型验证。FPGA的好处是可修改、可调试但它和真正ASIC在布线资源、存储接口、时钟网络上差异巨大跑出来的性能数据基本不能直接外推。有一回我们在FPGA上跑某个算子频率只有芯片目标频率的十分之一DDR带宽也完全不同软件团队却拿FPGA的数据来优化tiling结论自然没什么参考价值。FPGA适合做的是功能联调、寄存器读写验证、指令执行正确性检查不适合做性能调优。如果要性能调优还是优先用周期级模拟器它的每一级流水、排队延迟和带宽限制都是建模过的。5.2 接口冻结不畅导致软件反复返工AI芯片项目另一个常见的坑寄存器接口、中断号、地址映射、DMA描述符格式一直在变软件团队每次都得跟着改驱动和算子库。反反复复改几轮之后双方很容易互相不信任。我们的经验是设一个“接口冻结点”某个里程碑之后所有对外的寄存器、指令编码、地址布局都进入版本管理要改动必须走变更评审评估对编译器、驱动、算子库、测试用例的连锁影响。不要小看这个流程它能让软硬件团队省掉大量返工时间。5.3 只优化静态shape业务侧一换尺寸就崩还有一个很现实的坑模型部署时shape不是固定的。同一个目标检测模型输入分辨率可能是从320到1280之间任意值。如果编译器和算子库只针对几个特定尺寸做了深度优化遇到不支持的尺寸就只能回退到慢速通用实现性能可能掉好几倍。所以架构定义阶段最好就给算子实现定一个“shape灵活度”要求哪些维度的变化必须保持高性能哪些可以用通用路径兜底。比如通常卷积的通道数不会随便变但输入宽高会变那么tiling策略就围绕通道维做优化宽高维走通用循环。5.4 软硬件协作的几条实践最后分享几条我在多个项目里验证过的协作方法跨团队统一性能指标别软件报“算力利用率”硬件报“MAC占用率”双方对着不同的数字做判断。定义一套共用指标比如有效TOPS、DDR吞吐、核间同步开销所有评审都用同一组数据。用基准模型集当契约在项目启动时确定一组有代表性的模型和期望性能预算之后架构改动、编译器优化都以这组模型为基准做回归测试。联调例会尽早开别等流片前才启动。架构阶段就可以让编译器团队把指令集模拟器跑起来每两周对一次性能数字问题越早发现越好改。驱动和运行时要有可观测性芯片里多放几个性能计数器上位工具能读出来每核的忙闲和访存等待这套观测系统能救整个项目。从我个人的实际体会来说AI芯片的软硬件设计最迷人的地方在于它不像纯软件那样可以无限patch也不像纯硬件那样做完就交付。芯片一旦流片硬件就死在那里了所有性能问题只能靠软件补而软件能补的程度早在架构定义时就已经被锁死。所以我现在做项目都会要求架构师先回答一个问题如果编译器团队现在就开始写代码你给的指令集能不能让他们自然地写出高性能算子如果答案是否定的那这个架构大概率要改。这块内容展开讲还有很多细节等下一篇再继续聊。