1. 从"能跑就行"到"跑得高效":AI芯片软硬件协同设计的核心命题
做AI芯片这行的人都有一个共识:一颗芯片流片回来,真正决定它能不能用的,不是峰值算力标称了多少TOPS,而是软件栈能不能把硬件里那些MAC阵列、SRAM带宽、NoC通路真正喂饱。我见过太多项目,硬件指标漂亮得不行,结果编译器生成的调度方案一塌糊涂,实际推理吞吐只有理论值的30%不到。这就是软硬件设计脱节的典型症状。
所谓AI芯片的软硬件设计,本质上是在解决一个约束优化问题:给定硬件资源(计算单元数量、片上存储容量、片间带宽、功耗预算),如何通过软件层面的调度、映射、量化、算子融合等手段,让目标神经网络模型在满足延迟和精度要求的前提下,尽可能高效地执行。这个问题的难点在于,硬件架构一旦流片就无法修改,而软件栈需要适配的模型却在快速演进——今天优化好的Transformer结构,明天可能就被新的架构取代了。
这篇文章适合三类读者:一是正在做AI芯片架构定义的硬件工程师,需要理解软件栈对硬件的真实需求;二是做编译器或推理框架的软件工程师,需要知道硬件底层的约束在哪里;三是系统级设计者,需要在软硬件之间做权衡取舍。我会从计算架构、存储层次、数据流调度、量化映射、编译优化这几个维度,把AI芯片软硬件设计中那些"文档里不会写但实际项目中一定会遇到"的问题拆开来讲。
2. 计算阵列的硬件选型与软件映射逻辑
2.1 脉动阵列、SIMD与可重构数据流的取舍依据
AI芯片的计算核心架构选择,直接决定了后续软件栈的设计复杂度。目前主流方案大致分三类:脉动阵列(Systolic Array)、SIMD向量机、以及可重构数据流架构。这三者没有绝对优劣,关键看目标负载的特征。
脉动阵列的优势在于数据复用率高。以矩阵乘法为例,权重数据可以在阵列中"流动"一次,与多个输入激活值完成乘加运算,大幅减少对片上存储的访问次数。Google的TPU系列就是典型代表。但它的代价是灵活性差——阵列尺寸固定后,遇到不规则的稀疏计算或小批量推理,利用率会急剧下降。我实测过一个128x128的脉动阵列,跑ResNet-50的卷积层利用率能到85%以上,但跑BERT的自注意力层时,因为序列长度动态变化,利用率经常掉到40%以下。
SIMD向量机则相反,灵活性好,适合处理element-wise操作和归一化层,但数据复用率低,对存储带宽要求高。可重构数据流架构试图在两者之间找平衡,通过配置互连网络来适应不同算子,但编译器的映射难度会显著增加。
实际选型时,我通常建议先明确目标模型集合。如果以CNN为主,脉动阵列是稳妥选择;如果Transformer类模型占比超过一半,需要认真评估SIMD或混合架构。这里有个经验公式可以参考:计算阵列的尺寸N,应该使得目标模型中最常见的矩阵乘法维度M、K能够被N整除或接近整除。比如常见维度是768、1024、4096,那么N取128或256就比较合适,避免出现大量padding浪费。
2.2 数据复用模式对片上存储带宽的倒逼
计算阵列确定后,下一个约束就是数据供给。AI芯片的性能瓶颈很少在计算本身,而是在数据搬运。一个256x256的MAC阵列,每个周期需要256个激活值和256个权重值,如果片上SRAM每个周期只能提供128个值,那计算单元就有一半时间在等数据。
解决思路是提高数据复用率。卷积运算中,权重可以复用多次(对不同的输入像素),激活值也可以复用(对多个输出通道)。软件层面需要做的,是通过循环分块(Loop Tiling)和循环重排(Loop Reordering),让数据在寄存器、SRAM、DRAM之间的搬运次数最小化。
我参与过一个边缘推理芯片的项目,最初硬件团队给每个计算单元配了独立的权重缓存,但软件团队发现,实际模型中不同层的权重尺寸差异很大,小层的缓存利用率极低。后来改成可配置的共享缓存池,编译器根据每层的权重尺寸动态分配,整体带宽利用率提升了约35%。这个案例说明,硬件设计阶段就必须让软件团队介入,否则很容易出现"硬件资源分配与软件实际需求错位"的问题。
2.3 稀疏计算支持:硬件加速与软件调度的配合
稀疏化是当前AI芯片设计的热点。模型剪枝后,权重矩阵中大量元素为零,理论上可以跳过这些零值的计算和存储。但硬件要支持稀疏加速,需要额外的索引管理和数据压缩逻辑。
从软件角度看,结构化稀疏(如2:4稀疏,每4个元素中保留2个非零)比非结构化稀疏更容易被硬件高效利用。NVIDIA的Ampere架构就支持2:4结构化稀疏,配合软件端的稀疏感知训练,可以在精度损失很小的前提下获得接近2倍的吞吐提升。
但这里有个坑:稀疏矩阵的索引本身也需要存储和读取,如果压缩率不够高,索引开销可能抵消掉计算节省。我实测过,当稀疏度低于50%时,非结构化稀疏的收益往往为负。所以软件团队在决定是否启用稀疏加速时,一定要先算一笔账:节省的计算时间 vs 增加的索引解码时间。通常稀疏度要达到70%以上,非结构化方案才有明显收益。
3. 存储层次设计:从寄存器到DRAM的数据搬运经济学
3.1 片上存储容量与带宽的平衡点计算
AI芯片的存储层次通常包括:寄存器文件、片上SRAM(有时分L1/L2)、以及片外DRAM。每一层的容量和带宽差异巨大,设计核心是让数据尽可能停留在高层级存储中。
一个实用的计算方法是:统计目标模型中各层算子的数据需求量(输入激活、权重、输出激活),然后看片上SRAM能否容纳最大层的工作集。如果不行,就需要考虑分块计算,把一层拆成多个子块依次处理。
以常见的3x3卷积为例,假设输入特征图尺寸为HxWxC,输出通道数为K。如果采用im2col方式,权重矩阵大小为9C x K,输入矩阵大小为HW x 9C。片上SRAM需要同时容纳输入块、权重块和输出块。我通常建议SRAM容量至少能容纳"最大层的权重 + 一个输入块 + 一个输出块",否则编译器会频繁地在SRAM和DRAM之间交换数据,带宽成为瓶颈。
这里有个经验数据:对于1080p输入、ResNet-50级别的模型,如果片上SRAM小于2MB,推理延迟会显著上升。因为大层的权重和激活无法同时驻留,导致反复的DRAM访问。当然,这个数字会随模型结构和量化位宽变化,INT8量化后需求会减半。
3.2 双缓冲与预取策略在推理流水线中的实际效果
存储层次设计好之后,软件调度需要解决的是"如何让数据搬运和计算重叠"。双缓冲(Double Buffering)是最常用的技术:当计算单元在处理当前数据块时,DMA引擎同时把下一个数据块从DRAM搬到SRAM。
但双缓冲的效果取决于数据块大小和搬运时间的比例。如果搬运时间远大于计算时间,双缓冲只能隐藏一部分延迟。我见过一个设计,DMA带宽只有计算需求的60%,结果双缓冲形同虚设,计算单元仍然有40%的时间在等数据。
改进方案有两种:一是增加DMA通道数或提高DMA优先级,二是通过软件层面的算子融合减少数据搬运总量。比如把卷积、BN、ReLU融合成一个算子,中间结果不需要写回DRAM,直接在SRAM中传递。这种融合在推理框架中已经比较成熟,但需要编译器能够识别可融合的模式。
3.3 片间带宽瓶颈:多芯片互联时的数据划分策略
当单芯片算力不够时,就需要多芯片互联。这时片间带宽成为新的瓶颈。常见的互联方式包括PCIe、NVLink、以及各种自定义的SerDes链路。
软件层面需要决定的是:模型如何划分到多个芯片上。按层划分(Layer-wise Partitioning)实现简单,但芯片间需要传递中间激活值,通信量大。按通道划分(Channel-wise Partitioning)或按空间划分(Spatial Partitioning)可以减少通信量,但需要硬件支持跨芯片的归约操作。
我参与过一个双芯片推理系统的设计,最初采用按层划分,结果发现中间激活值的传输时间占了总延迟的45%。后来改成按输出通道划分,每个芯片计算一半的输出通道,最后通过片间链路做一次归约,通信量减少了约70%,整体延迟下降了30%以上。这个经验说明,多芯片系统的软件划分策略,必须和硬件互联拓扑一起考虑,不能先定硬件再想软件。
4. 量化与映射:让模型在有限位宽下保持精度
4.1 INT8量化的校准方法与精度损失控制
量化是AI芯片软件栈中最关键的优化手段之一。把FP32模型转成INT8,理论上可以获得4倍的存储节省和2-4倍的计算加速。但量化会引入精度损失,需要通过校准(Calibration)来最小化。
常用的校准方法包括:最大最小值校准、KL散度校准、以及基于百分位的校准。最大最小值校准最简单,但容易受离群值影响。KL散度校准通过最小化量化前后分布的差异来确定截断阈值,效果通常更好,但计算量稍大。
我在实际项目中的经验是:对于CNN类模型,KL散度校准通常能把精度损失控制在1%以内;但对于Transformer类模型,尤其是注意力层的softmax输出,量化误差会被放大,需要更精细的处理。一个有效的技巧是对不同层采用不同的量化策略:卷积层用INT8,注意力层的softmax输入用INT16或保持FP16,这样可以在精度和性能之间取得平衡。
4.2 混合精度调度:哪些层必须保留高精度
并非所有层都适合量化。根据我的实测经验,以下几类层通常需要保留较高精度:
- 第一层卷积:直接处理输入图像,量化误差会传播到后续所有层
- 注意力层的QK^T计算:数值范围大,INT8容易溢出
- 层归一化(LayerNorm):涉及均值和方差计算,对精度敏感
- 最终的分类层或回归层:直接影响输出结果
混合精度调度的难点在于,硬件需要支持不同精度的计算单元,或者至少支持精度的动态切换。如果硬件只支持INT8,那软件层面只能通过缩放因子(Scaling Factor)来尽量保留动态范围。我通常建议在硬件设计阶段就考虑支持INT8和INT16两种模式,虽然面积开销增加约15%,但软件灵活性大幅提升。
4.3 算子映射到硬件的约束检查清单
量化完成后,下一步是把算子映射到硬件。这个过程需要检查一系列约束:
| 约束项 | 检查内容 | 常见问题 |
|---|---|---|
| 数据对齐 | 输入/输出张量的维度是否满足硬件对齐要求 | 非对齐访问导致性能下降50%以上 |
| 算子支持 | 硬件是否直接支持该算子 | 不支持时需拆解或回退到CPU |
| 内存布局 | 张量的内存排布是否匹配硬件偏好 | NCHW vs NHWC转换带来额外开销 |
| 量化参数 | 缩放因子和零点是否在硬件支持范围内 | 超出范围导致精度异常 |
| 并行度 | 算子能否被拆分成足够的并行任务 | 并行度不足导致计算单元闲置 |
这份清单是我在多个项目中总结出来的,每次映射新模型时都会过一遍。特别是数据对齐这一项,很多性能问题都源于此。比如硬件要求输入通道数是16的倍数,但模型某一层输出通道是12,编译器就需要插入padding,浪费25%的计算资源。
5. 编译器的核心任务:从计算图到硬件指令的翻译
5.1 图优化阶段:算子融合与常量折叠的收益边界
编译器前端接收的是计算图(如ONNX格式),首先需要做图优化。最常见的优化是算子融合:把Conv+BN+ReLU合并成一个算子,减少中间结果的存储和搬运。
但融合不是越多越好。我遇到过过度融合导致寄存器压力过大的情况:把太多算子融合在一起,中间结果需要同时保存在寄存器中,反而导致寄存器溢出,性能下降。通常建议融合后的算子,中间张量数量不超过寄存器文件容量的70%。
常量折叠是另一个重要优化:把模型中可以提前计算的常量表达式在编译期算好。比如BN层的均值和方差在推理时是固定的,可以提前融合到卷积权重中。这个优化通常能减少5%-10%的推理延迟。
5.2 调度阶段:循环分块、重排与并行化的联合搜索
调度是编译器最核心也最复杂的部分。给定一个算子,编译器需要决定:循环如何分块、循环顺序如何排列、如何并行化到多个计算单元。
以矩阵乘法C = A x B为例,假设A是MxK,B是KxN,C是MxN。循环分块需要决定块大小(BM, BN, BK),使得A块、B块、C块能够放入片上存储。循环顺序决定了数据复用的模式:如果K在最内层,A和B的块可以被复用;如果M或N在最内层,则C的块被复用。
这个搜索空间非常大,手动调优不现实。现代编译器通常采用基于代价模型的自动搜索,或者用机器学习方法预测最优配置。我在实际项目中的经验是:对于常见的卷积和矩阵乘法,可以预先建立一张调优表,覆盖常见的维度组合,编译时查表加微调,比完全自动搜索快得多,效果也稳定。
5.3 代码生成:指令级并行与数据级并行的协同
调度确定后,代码生成阶段需要把调度方案翻译成硬件指令。这里的关键是挖掘指令级并行(ILP)和数据级并行(DLP)。
指令级并行指的是让多条指令在流水线中重叠执行。比如加载指令和计算指令可以并行,只要它们访问的数据不冲突。数据级并行则是利用SIMD或向量指令,一次处理多个数据元素。
我见过一个编译器生成的代码,计算单元的利用率只有50%,原因是加载指令和计算指令没有很好地重叠。后来在代码生成阶段加入了软件流水(Software Pipelining),把加载、计算、存储三个阶段交错排列,利用率提升到了85%以上。这个优化的关键是准确分析数据依赖关系,确保重叠执行不会导致数据冒险。
6. 实测中的典型问题与排查路径
6.1 性能不达预期时的分层排查方法
当芯片实测性能远低于理论值时,需要系统性地排查。我通常按照以下顺序进行:
第一步,确认计算单元利用率。如果利用率低于60%,说明数据供给有问题,重点检查存储带宽和DMA效率。
第二步,检查存储访问模式。用性能计数器统计各级存储的访问次数和命中率。如果L1命中率低,说明数据分块策略有问题;如果DRAM访问频繁,说明片上存储容量不足或数据复用没做好。
第三步,分析指令流水线。查看是否有大量的流水线停顿(Stall),停顿的原因可能是数据依赖、资源冲突或分支预测失败。
第四步,检查量化精度。如果精度下降超出预期,需要逐层分析量化误差,找出敏感层。
这套排查方法在多个项目中帮我快速定位了问题。有一次,芯片实测吞吐只有理论值的25%,按上述步骤排查后发现,是编译器的循环分块参数设置不当,导致权重数据被反复从DRAM加载。调整分块参数后,吞吐提升到了理论值的70%。
6.2 精度异常时的逐层对比技巧
量化后精度下降是常见问题。定位方法很简单:逐层对比量化前后模型的输出,找出误差最大的层。
具体操作是:用同一批输入数据,分别跑FP32模型和量化模型,记录每一层的输出张量,计算相对误差。通常误差会在某几层突然增大,这些层就是敏感层。
我处理过一个案例,量化后模型精度掉了8个百分点。逐层对比发现,误差主要来自第一个卷积层和最后一个全连接层。把这两层保留FP16后,精度恢复到了只掉0.5个百分点。这个技巧的关键是,不要盲目地对所有层采用相同的量化策略,要根据逐层误差分析结果做差异化处理。
6.3 多芯片扩展时的通信开销实测
多芯片系统的性能扩展往往不是线性的。两片芯片理论算力翻倍,但实际吞吐可能只提升60%-70%,因为通信开销吃掉了部分收益。
实测通信开销的方法是:先跑单芯片,记录延迟;再跑双芯片,记录总延迟和通信时间。通信时间可以通过片间链路的性能计数器获取,也可以用总延迟减去计算延迟来估算。
如果通信开销占比超过30%,就需要优化数据划分策略。常见的优化方向包括:增加计算密度(让每个芯片处理更多数据,减少通信次数)、压缩通信数据(量化中间激活值)、以及重叠通信和计算(在计算的同时传输下一批数据)。
我在一个四芯片系统中实测发现,采用环形互联拓扑时,通信开销随芯片数量线性增长;而采用全连接拓扑时,通信开销增长较慢,但硬件成本更高。最终选择了混合拓扑:相邻芯片全连接,远端芯片通过环形链路通信,在成本和性能之间取得了平衡。
7. 软硬件协同设计的迭代节奏与团队协作
7.1 架构定义阶段的软件预研方法
硬件架构一旦流片就无法修改,所以架构定义阶段的软件预研至关重要。我的做法是:在硬件设计的同时,用FPGA或仿真器搭建一个软件原型,跑目标模型,评估性能瓶颈。
这个原型不需要和最终硬件完全一致,但需要抓住关键特征:计算单元数量、存储容量、带宽比例。通过调整这些参数,可以快速评估不同架构方案的优劣。
我参与过一个项目,硬件团队最初打算采用大阵列+小缓存的方案,软件预研发现,目标模型的权重尺寸很大,小缓存会导致频繁的DRAM访问。后来调整为中等阵列+大缓存,虽然峰值算力下降了20%,但实际推理吞吐反而提升了50%。这个案例说明,软件预研能够避免硬件设计走入误区。
7.2 流片后的软件适配与性能调优周期
流片回来后,软件团队需要在真实硬件上做适配和调优。这个周期通常需要3-6个月,包括:驱动开发、编译器后端适配、算子库优化、以及模型部署验证。
我的经验是,流片后的第一个月最痛苦,因为硬件bug和软件bug混在一起,很难定位。建议的做法是:先跑通一个最简单的算子(如向量加法),确认基础功能正常;然后逐步增加复杂度,跑通单个卷积层、单个Transformer层;最后跑完整模型。每一步都要有详细的日志和性能计数器输出,方便定位问题。
性能调优阶段,重点关注计算单元利用率和存储带宽利用率这两个指标。如果利用率低于70%,说明还有优化空间。常见的优化手段包括:调整循环分块参数、优化数据布局、启用稀疏加速、以及调整量化策略。
7.3 版本迭代中的回归测试与性能基线维护
AI芯片的软件栈会持续迭代,每次迭代都需要做回归测试,确保新版本没有引入性能退化或精度下降。
我建议维护一套性能基线:选取一组代表性模型,记录每个模型在当前版本下的延迟、吞吐、精度。每次发布新版本前,跑一遍基线测试,对比结果。如果某个模型的性能下降超过5%,就需要排查原因。
这套机制听起来简单,但实际执行中容易流于形式。我的做法是把基线测试集成到CI流程中,每次代码合并自动触发,结果自动对比并生成报告。这样能够及时发现性能退化,避免问题积累到发布前才暴露。
8. 一些踩过坑之后才明白的设计原则
做AI芯片软硬件设计这些年,踩过的坑不少,有几个原则是付出了代价才真正理解的。
第一个原则:硬件设计阶段一定要有软件团队参与。我见过太多项目,硬件团队闭门造车,定义了一套自认为优雅的指令集和存储架构,结果软件团队发现根本无法高效映射主流模型。等到流片回来再改,已经来不及了。正确的做法是,硬件架构定义时,软件团队就要提供目标模型的计算特征分析,包括算子类型分布、张量尺寸分布、数据复用模式等。
第二个原则:不要追求极致的峰值算力,要追求实际利用率。峰值算力是营销指标,实际利用率才是用户价值。一颗标称100TOPS但利用率只有30%的芯片,实际有效算力只有30TOPS;另一颗标称60TOPS但利用率能到70%的芯片,实际有效算力有42TOPS。后者才是更好的选择。提高利用率的关键在于存储层次设计和编译器优化,这两方面的投入回报率往往高于单纯增加计算单元。
第三个原则:量化策略要因模型而异,不能一刀切。不同模型对量化的敏感度差异很大,CNN通常比较鲁棒,Transformer则敏感得多。软件栈需要提供灵活的量化配置接口,允许用户按层设置量化位宽和校准方法。同时,硬件需要支持混合精度计算,否则软件层面的灵活性无法发挥。
第四个原则:多芯片扩展的收益不是线性的,通信开销会吃掉一部分算力增长。设计多芯片系统时,要先算清楚通信量,再决定互联拓扑和划分策略。如果通信开销占比超过40%,扩展的收益就很有限了,不如把单芯片做得更强。
第五个原则:编译器的调优表比自动搜索更实用。学术界喜欢研究自动调度算法,但工业界更看重稳定性和可预测性。对于常见的算子维度组合,预先建立调优表,编译时查表加微调,比完全依赖自动搜索更可靠。自动搜索可以作为补充,用于处理调优表未覆盖的特殊情况。
这些原则听起来简单,但每一条背后都有实际项目的教训。AI芯片的软硬件设计是一个系统工程,任何一方面的短板都会拖累整体性能。只有软硬件团队紧密协作,从架构定义到流片后调优全程配合,才能做出真正好用的芯片。