
1. AI芯片软硬件协同设计的核心逻辑1.1 为什么软硬件必须一起设计做AI芯片这行的人都有一个共识硬件决定性能上限软件决定实际能跑出多少。我见过太多团队花两年流片结果编译器跟不上实际推理效率只有理论峰值的30%不到。这不是硬件的问题是软硬件脱节造成的。传统CPU的设计思路是硬件通用、软件适配但AI芯片不一样。AI负载有极强的规律性——大量矩阵乘加、固定的数据流模式、可预测的访存行为。这意味着硬件可以针对这些规律做深度定制但代价是软件必须精确匹配硬件的数据流和存储层次。举个具体的例子。一个典型的卷积层输入特征图是56×56×256卷积核3×3×256输出56×56×512。在GPU上这个计算会被拆成多个线程块并行执行靠的是大量的寄存器堆和共享内存做缓存。但在专用AI加速器上同样的计算会被映射到脉动阵列上数据以特定的节拍流入和流出。如果编译器不知道阵列的尺寸和流水线深度就无法生成正确的调度方案硬件就会空转。所以软硬件协同设计的本质是在芯片架构定义阶段就把编译器的约束、算子的映射方式、数据流的调度策略一起考虑进去。这不是锦上添花是生死攸关的事。1.2 从算法到硅片一条完整的链路一个AI芯片项目从启动到量产大致要经过这么几个阶段算法分析统计目标模型的计算特征包括算子类型分布、张量形状范围、稀疏度、量化敏感度等架构探索确定PE阵列规模、片上缓存大小、数据位宽、互联拓扑指令集与编译器设计定义硬件能理解的指令设计从高层框架到硬件的编译路径RTL实现与验证把架构变成可综合的硬件描述流片与回片测试实际跑模型看性能是否达标软件栈迭代根据实测数据优化编译器和运行时这条链路上架构探索和编译器设计是最容易脱节的两个环节。架构师画框图的时候觉得数据流很清晰但编译器工程师拿到指令集后发现很多操作没法表达。我参与过一个项目架构定义了非常灵活的PE互联结果编译器根本生成不了高效的调度最后只能退化成简单的行广播模式灵活性完全浪费了。1.3 当前主流的技术路线对比目前AI芯片的硬件架构大致分三派架构类型代表方案优势劣势脉动阵列TPU风格数据复用率高能效比好灵活性差稀疏支持难通用向量矩阵扩展GPU风格编程模型成熟生态好面积效率低功耗高数据流架构粗粒度可重构灵活性和效率折中编译复杂度极高脉动阵列之所以成为很多专用加速器的首选核心原因是数据复用。在一个N×N的阵列里每个权重可以被复用N次每个输入也可以被复用N次这样访存量就降到了原来的1/N。对于计算密集型的矩阵乘这直接把访存瓶颈缓解了一个数量级。但脉动阵列的问题也很明显它假设计算是稠密的、规则的。一旦遇到稀疏矩阵或者不规则的计算模式阵列的利用率就会断崖式下降。这就是为什么后来出现了结构化稀疏的支持——在保持阵列规则性的前提下利用稀疏性跳过零值计算。2. 脉动阵列的硬核原理与设计取舍2.1 脉动阵列到底是怎么“脉动”的脉动阵列的核心思想可以用一句话概括让数据像心跳一样有节奏地流过计算单元每个单元只做最简单的乘加但整体形成高效的流水线。想象一个工厂流水线。每个工位上有一个工人PE处理单元他手里拿着一个固定的工具权重。传送带上的零件输入数据依次经过每个工位每个工人对零件做一次加工乘加然后把半成品传给下一个工位。零件在传送带上移动的节奏就是“脉动”。具体到矩阵乘法C A × B假设A是M×KB是K×N。在脉动阵列中通常让A的行从左侧流入B的列从上方流入PE阵列是M×N的。每个PE(i,j)负责计算A的第i行和B的第j列的点积。数据流动的过程是这样的第0个周期A[0,0]进入PE(0,0)B[0,0]进入PE(0,0)第1个周期A[0,1]进入PE(0,1)同时A[0,0]从PE(0,0)传到PE(0,1)B[1,0]进入PE(1,0)B[0,0]从PE(0,0)传到PE(1,0)以此类推每个PE在每个周期接收来自左边和上边的数据做一次乘加把结果累加在本地关键点在于每个PE只需要和相邻的PE通信不需要全局互联。这大大降低了布线复杂度和功耗。而且权重是预先加载到PE里的在整个计算过程中保持不变这就是权重静止Weight Stationary数据流。2.2 权重静止、输出静止还是行静止脉动阵列的数据流设计有三种基本模式选择哪种直接决定了芯片的适用场景权重静止Weight Stationary, WS权重固定在PE中输入和部分和流动。适合权重可复用的场景比如卷积层。TPU第一代用的就是这种。优点是权重加载一次可以用很久访存少。缺点是如果权重矩阵太大放不下就得换换权重的开销大。输出静止Output Stationary, OS部分和固定在PE中输入和权重流动。适合输出通道多的场景。优点是部分和不需要频繁搬移省带宽。缺点是权重和输入都需要流动对互联要求高。行静止Row Stationary, RS这是Eyeriss提出的方案折中了前两者的特点。它在行方向上做权重静止在列方向上做输出静止通过巧妙的调度减少访存。实际测试下来RS在卷积层上的能效比WS和OS都要好但控制逻辑复杂不少。我在实际项目中的体会是没有万能的数据流只有匹配目标负载的数据流。如果你的模型以1×1卷积和全连接为主WS就很合适如果以3×3甚至更大的卷积为主RS的优势更明显。很多芯片支持可配置的数据流但代价是面积和功耗增加需要权衡。2.3 阵列尺寸怎么定一个实际的推算过程假设我们要设计一个用于推理的AI加速器目标模型是ResNet-50峰值算力需求大约4 TOPSINT8。工作频率定在1GHz那么每个周期需要完成4000次乘加运算。如果PE阵列是N×N每个PE每周期做一次乘加那么N² 4000N约等于64。所以一个64×64的阵列理论上能满足算力需求。但实际中要考虑几个折损因素阵列利用率由于卷积层的形状多样不是所有层都能完美映射到64×64。实测利用率通常在60%-80%之间流水线填充数据流入流出需要时间对于小尺寸的层填充开销占比很高访存瓶颈如果数据供给跟不上阵列再大也没用所以实际设计中我会把阵列做到64×64但同时在片上缓存和DMA上留足余量。如果面积允许做到128×64行×列不等也是常见做法因为卷积的输入通道和输出通道往往不对称。注意阵列尺寸不是越大越好。我见过一个项目为了追求峰值算力把阵列做到256×256结果大部分层只能用到一小部分PE实际性能反而不如128×128的版本因为大阵列的布线延迟和功耗都上去了。2.4 脉动阵列的局限与应对脉动阵列最大的软肋是灵活性。它擅长的是规则的稠密矩阵乘但现代模型里有很多不规则的算子深度可分离卷积、注意力机制里的softmax、各种归一化层。这些算子如果硬塞到脉动阵列上效率会非常低。常见的应对策略有三种第一种是异构设计。在脉动阵列旁边放一个通用的向量处理单元专门处理那些不规则的算子。比如Google的TPU里就有专门的激活函数单元和归一化单元。这样脉动阵列专注做矩阵乘其他算子交给向量单元各司其职。第二种是算子融合。把多个算子合并成一个大的矩阵乘减少中间结果的搬移。比如把卷积、批归一化、激活函数融合在一起在脉动阵列的输出端直接做后续处理。这需要编译器和硬件紧密配合。第三种是动态重构。让脉动阵列的互联方式可配置支持不同的数据流模式。但这会显著增加硬件复杂度而且编译器要能自动选择最优模式难度很大。3. 结构化稀疏2:4稀疏的硬件实现细节3.1 为什么是2:4而不是随机稀疏稀疏化是降低计算量的直接手段。一个90%稀疏的矩阵理论上只需要10%的计算量。但随机稀疏在硬件上极难高效实现因为非零元素的位置是随机的索引开销可能比计算本身还大。2:4稀疏也叫50%结构化稀疏的定义是在每4个连续元素中最多保留2个非零值。这个约束看起来简单但它带来了几个关键好处索引开销固定每4个元素只需要2位来编码哪两个位置非零总共6种可能C(4,2)6加上全零的情况3位就够了硬件实现规整PE可以设计成每周期处理4个元素中的2个控制逻辑简单压缩率可预测存储和带宽需求直接减半不需要复杂的压缩解压逻辑NVIDIA的Ampere架构就是2:4稀疏的典型代表。在A100上稀疏矩阵乘的吞吐是稠密的两倍因为硬件可以跳过零值对应的乘加操作。3.2 稀疏脉动阵列的PE设计在标准脉动阵列中每个PE每周期做一次乘加。要支持2:4稀疏PE需要做几个改动输入选择器每个PE需要从4个输入中选择2个非零值。这需要一个4选2的多路选择器以及对应的控制信号。控制信号来自稀疏编码每4个元素3位。权重选择器权重也需要对应的选择。如果权重是稀疏的那么PE需要根据权重的稀疏模式选择输入如果输入是稀疏的则反过来。实际中通常只对权重做稀疏因为权重是预先知道的可以在编译时确定稀疏模式。累加器旁路当某个位置是零值时对应的乘加操作直接跳过累加器保持不变。这需要时钟门控来省功耗。一个具体的PE结构大概是这样的// 简化的2:4稀疏PE伪代码 module sparse_pe ( input clk, input [3:0] in_data, // 4个输入元素 input [2:0] sparse_ctrl, // 稀疏控制信号 input [3:0] weight_data, // 4个权重 output reg [31:0] acc // 累加器 ); wire [1:0] sel_in; wire [1:0] sel_w; // 根据稀疏控制信号选择非零位置 assign sel_in decode_sparse(sparse_ctrl); // 选择对应的输入和权重 wire [7:0] a in_data[sel_in[0]] ; wire [7:0] b weight_data[sel_in[0]]; wire [7:0] c in_data[sel_in[1]]; wire [7:0] d weight_data[sel_in[1]]; always (posedge clk) begin acc acc a*b c*d; end endmodule实际实现中还要考虑流水线、位宽、符号处理等细节但核心逻辑就是这样。关键是选择器的延迟要控制好否则会成为关键路径限制工作频率。3.3 稀疏编译从模型到硬件的映射硬件支持2:4稀疏只是第一步更难的是让编译器自动生成满足2:4约束的稀疏权重。这涉及到训练阶段的稀疏化。目前主流的做法是训练时稀疏Train-time Sparsity在训练过程中逐步引入2:4约束让模型在保持精度的同时适应稀疏模式。具体步骤正常训练先按稠密模型训练到收敛稀疏化微调对每个4元素组保留绝对值最大的2个其余置零然后继续训练几个epoch掩码固定确定最终的稀疏模式生成稀疏编码导出部署把稀疏权重和编码一起导出到编译器这个过程听起来简单但实际操作中有几个坑稀疏模式的选择是按权重绝对值选还是按对输出的贡献度选实测下来按绝对值选在大多数情况下够用但对某些层比如第一层和最后一层可能需要特殊处理精度损失2:4稀疏通常会导致0.5%-2%的精度下降需要通过更长的微调或者知识蒸馏来补偿层间差异不是所有层都适合2:4稀疏。深度可分离卷积的权重本来就少再稀疏化可能得不偿失实操心得我通常会对模型做逐层的稀疏敏感度分析。具体做法是每次只稀疏一层看精度下降多少。敏感度低的层优先稀疏敏感度高的层保持稠密。这样可以在整体稀疏率不变的情况下把精度损失降到最低。3.4 稀疏带来的实际收益与代价从实测数据来看2:4稀疏在支持该特性的硬件上矩阵乘的吞吐确实能提升接近2倍。但端到端的收益要打折扣因为非矩阵乘算子不受益softmax、LayerNorm这些算子的耗时占比可能达到20%-30%数据搬移开销虽然权重存储减半了但输入和输出的搬移没有减少编译开销稀疏编码的生成和验证增加了编译时间综合下来端到端的推理加速通常在1.3倍到1.6倍之间。这个收益是否值得取决于你的应用场景。如果是云端推理吞吐就是金钱1.5倍很可观如果是边缘设备可能更看重功耗的降低。4. 软硬件协同的实操流程与避坑指南4.1 从模型到芯片的完整映射流程一个典型的部署流程是这样的第一步模型分析与算子提取。用工具比如TVM的Relay或者ONNX把模型解析成计算图统计每个算子的类型、形状、调用次数。这一步的输出是一张算子清单标注了哪些是计算密集型的适合脉动阵列哪些是访存密集型的需要特殊处理。第二步算子映射与调度。对每个算子决定它在硬件上怎么执行。矩阵乘类的算子映射到脉动阵列需要确定分块大小、数据流方向、缓存策略。这一步通常需要搜索空间探索因为不同的分块方案性能差异很大。第三步内存分配与数据搬移。确定每个张量在片上缓存和外部内存之间的搬移时机和方式。这里的关键是双缓冲在当前块计算的同时预取下一块数据隐藏访存延迟。第四步指令生成与代码发射。把调度方案翻译成硬件指令序列包括DMA配置、PE阵列配置、同步信号等。第五步性能验证与迭代。在仿真器或FPGA原型上跑实际模型对比理论性能和实测性能找出瓶颈并优化。这个流程里第二步和第三步是最耗时的通常占整个编译时间的70%以上。而且很多决策是相互耦合的——分块大小影响缓存需求缓存需求影响数据搬移策略数据搬移策略又反过来影响阵列利用率。4.2 双缓冲与流水线隐藏访存延迟的关键脉动阵列的计算速度很快但数据供给往往跟不上。假设阵列每周期需要256字节的数据而外部内存的带宽只有64字节/周期那么阵列75%的时间都在等数据。解决办法是双缓冲加流水线。具体做法把片上缓存分成两块或更多块一块用于当前计算一块用于预取DMA引擎在后台把下一块数据从外部内存搬到预取缓冲区当前块计算完成后交换两块的角色计算单元无缝切换到新数据这样只要预取时间不超过计算时间访存延迟就被完全隐藏了。实际中预取时间通常是计算时间的1.5到2倍所以需要多级缓冲或者更激进的预取策略。注意双缓冲的深度不是越多越好。每增加一级缓冲就增加一份片上存储面积和功耗。我通常的做法是先用2级缓冲如果实测发现访存仍然是瓶颈再考虑加到3级。大多数情况下2级就够了。4.3 常见问题速查表问题现象可能原因排查方法解决方案阵列利用率低于50%分块大小不匹配打印每层的分块配置和实际阵列占用调整分块策略优先保证大层的高利用率实际算力远低于峰值访存瓶颈用性能计数器统计DMA等待周期增加缓冲深度优化数据复用稀疏加速比不达预期非矩阵乘算子占比高逐算子统计耗时对非矩阵乘算子做融合或专用硬件加速编译时间过长搜索空间太大分析各阶段耗时限制搜索空间用启发式替代穷举精度下降超过预期稀疏化过于激进逐层做敏感度分析对敏感层保持稠密调整稀疏策略4.4 几个我踩过的坑坑一忽视编译器团队的早期介入。我参与过一个项目架构定义完了才让编译器团队介入结果发现硬件指令集缺少几个关键操作比如带偏移的累加、条件写回等。这些操作在算法层面很常见但架构师没考虑到。最后只能改指令集推迟了两个月。坑二过度追求峰值算力。峰值算力是给投资人看的实际性能才是用户关心的。我见过太多芯片标称128 TOPS实际跑ResNet只有20 TOPS的有效算力。与其堆阵列规模不如把数据通路和缓存做好。坑三稀疏化的精度补偿不足。2:4稀疏听起来只丢一半权重但实际精度损失可能比预期大。我的经验是稀疏化后至少要做10个epoch的微调学习率降到原来的1/10。如果精度还是不够可以考虑混合稀疏——部分层稠密部分层稀疏。坑四忽略散热和功耗。脉动阵列的功耗密度很高尤其是全速运行时。如果散热设计不到位芯片会降频实际性能打折扣。我在一个边缘设备项目上就遇到过这个问题最后只能限制阵列的工作频率。5. 从当前方案出发的扩展思路5.1 动态稀疏下一步的方向2:4稀疏是静态的稀疏模式在编译时确定。更激进的方向是动态稀疏即稀疏模式在运行时根据输入数据动态变化。这需要硬件支持动态索引和负载均衡复杂度高很多但潜力也更大。目前学术界有一些探索比如用bitmap编码动态稀疏模式PE根据bitmap实时选择非零元素。但动态稀疏的负载不均衡问题很难解决——有的PE可能有很多非零值有的很少导致阵列利用率下降。5.2 混合精度与稀疏的结合另一个方向是把稀疏和混合精度结合起来。比如对非零值用INT8对零值直接跳过或者对重要的权重用高精度对次要的用低精度。这需要硬件支持可变位宽的乘加以及更复杂的编译调度。从实测来看稀疏INT8的组合在大多数视觉模型上能保持精度但在NLP模型上可能需要INT16或者混合精度。这取决于模型对量化误差的敏感度。5.3 软硬件协同的自动化目前软硬件协同设计很大程度上依赖人工经验自动化程度不高。未来的方向是用机器学习来辅助架构探索和编译优化。比如用强化学习来搜索最优的分块策略用神经网络来预测不同架构配置下的性能。但这需要大量的训练数据而芯片设计的迭代周期很长数据积累慢。所以短期内人工经验仍然是主导自动化工具只是辅助。我个人在实际操作中的体会是软硬件协同设计没有银弹核心是迭代。先做一个能跑的版本实测性能找到瓶颈然后针对性优化。不要试图一次就设计出完美的方案那是不可能的。快速迭代、持续优化才是正道。