1. 从"能跑就行"到"跑得省电":AI芯片软硬件协同设计的核心命题
做AI芯片这行的人都有一个共识:一颗芯片流片回来,能点亮、能跑通ResNet-50,这只是万里长征的第一步。真正决定这颗芯片能不能被市场接受的,是它在跑真实模型时的能效比——每瓦能出多少TOPS,每块钱能买到多少有效算力。我在实际项目中见过太多"实验室里跑分漂亮、客户现场一跑就拉胯"的案例,根子往往不在某一个单点技术上,而在软硬件设计之间的那道裂缝。
这个系列写到第7篇,我想把话题收拢到一个最容易被忽视、却最影响最终产品竞争力的环节:软硬件协同设计中的能效优化。具体来说,就是当你的芯片架构已经定了、指令集已经冻结了、编译器后端也搭起来了,怎么通过软硬件两侧的联合调优,把实际推理能效再往上提一个台阶。这不是讲某个具体算法怎么加速,而是讲一套"从模型到硅片"的全局优化方法论。
适合读这篇的人,我大致分三类:一是正在做AI加速器架构设计、需要理解软件侧约束的硬件工程师;二是做编译器或推理框架、需要知道硬件到底在乎什么的软件工程师;三是做系统级产品定义、需要在功耗、面积、成本之间做取舍的技术负责人。如果你只关心某个算子怎么写CUDA kernel,这篇可能不太对胃口;但如果你关心的是"为什么同样的MAC阵列规模,别人家的芯片能效是我的两倍",那接下来的内容应该能给你一些可落地的思路。
先给一个我个人的核心判断:AI芯片的能效瓶颈,80%不在计算单元本身,而在数据搬运。这个结论在近几年的多个实际项目中反复被验证。下面我会从数据流、存储层次、指令调度、精度策略、软硬件接口五个维度,把这件事拆开讲透。
2. 数据搬运才是真正的电老虎:从功耗构成反推设计优先级
2.1 一次MAC运算和一次DRAM访问的能耗差距有多大
很多刚入行的朋友对功耗的直觉是"算得越多越费电",所以第一反应是减少计算量。但在AI推理场景里,这个直觉是反的。我拿一组业界广泛引用的量级数据来说明(不同工艺节点会有差异,但数量级关系稳定):
| 操作类型 | 相对能耗(归一化) | 说明 |
|---|---|---|
| 32位整数加法 | 1 | 基准 |
| 32位浮点乘法 | 约 3~5 | 计算单元内部 |
| 32位寄存器文件访问 | 约 1~2 | 片上最近的一级 |
| 32位SRAM访问 | 约 10~20 | 片上缓存 |
| 32位DRAM访问 | 约 200~400 | 片外主存 |
这张表最扎眼的是最后一行:一次DRAM访问的能耗,是一次算术运算的几百倍。这意味着什么?意味着如果你的设计让数据在片外和片内之间反复倒腾,那么无论你的MAC阵列做得多宽、频率拉得多高,能效都会被数据搬运吃掉。
我在一个边缘推理项目里做过实测:同一颗芯片,同一个模型,仅仅通过调整数据复用策略,把DRAM访问次数降低40%,整体推理功耗直接下降了约35%。计算单元的工作量几乎没变,省下来的全是搬运的功耗。这个案例后来成了我给团队新人讲"数据流优先"的经典教材。
2.2 为什么"算力堆料"路线在能效上走不通
理解了上面的能耗阶梯,就能明白为什么单纯堆MAC数量是一条死路。假设你把MAC阵列从1024个扩到4096个,理论峰值算力翻了4倍,但如果你的存储带宽和片上缓存没有同步扩展,那么:
- 计算单元会频繁等待数据,实际利用率(MAC utilization)可能从70%掉到30%;
- 为了喂饱更多计算单元,你必须提高数据供给速率,DRAM访问频率上升,功耗非线性增长;
- 最终结果是:峰值算力4倍,实际有效算力可能只有1.5倍,而功耗涨了2倍以上,能效比反而恶化。
这就是典型的"内存墙"(Memory Wall)问题。我在评审架构方案时,看到一个设计如果只报峰值TOPS而不报"在目标模型上的实测能效",基本可以判断这个方案还没想清楚。
提示:评估一颗AI芯片时,不要只看峰值算力,一定要问三个问题——目标模型是什么?在该模型上的MAC利用率是多少?端到端能效(TOPS/W)在什么工作点测得?这三个问题答不上来,峰值数字就没有意义。
2.3 从"计算中心"转向"数据中心的架构思维
传统CPU/GPU的架构思维是"计算中心":有一堆通用计算单元,数据需要的时候从存储里取。这个思路在AI推理上效率很低,因为AI计算的本质是规整的、可预测的大规模数据流,完全可以在架构层面为数据流动做专门设计。
我倾向于把AI加速器的设计思维总结为"数据中心":先想清楚数据从哪里来、经过哪些层次、被复用几次、最终到哪里去,然后围绕这条数据通路去安排计算单元的位置和数量。计算单元是"跟着数据走"的,而不是数据"追着计算单元跑"。
这个思维转变听起来抽象,但落到具体设计上非常实在。比如:
- 权重数据是静态的、可预取的,那就应该设计专门的权重缓存和预取机制,让权重尽量留在片上;
- 激活值是动态的、逐层变化的,那就需要设计高效的激活值复用路径,减少层间搬运;
- 如果某一层的输出正好是下一层的输入,那就在硬件上把这两层"焊"在一起,中间结果根本不落片外。
这些思路在后面的章节会展开。核心就一句话:先画数据流图,再画计算阵列图。
3. 存储层次的设计取舍:片上缓存到底该做多大
3.1 缓存容量的边际收益递减规律
片上SRAM是AI芯片里最贵的资源之一,面积占比经常超过50%。做架构设计时,最常被问到的问题就是:"缓存做多大合适?"这个问题没有标准答案,但有一条经验规律:缓存容量的边际收益是递减的,而且递减得很快。
我做过一组仿真实验,针对一个典型的卷积网络,逐步增加片上权重缓存容量,观察DRAM访问次数的变化:
| 权重缓存容量 | DRAM访问次数(相对值) | 边际改善 |
|---|---|---|
| 64 KB | 100% | 基准 |
| 128 KB | 72% | 显著 |
| 256 KB | 51% | 显著 |
| 512 KB | 40% | 中等 |
| 1 MB | 34% | 放缓 |
| 2 MB | 31% | 很小 |
| 4 MB | 30% | 几乎无 |
可以看到,从64KB到512KB,DRAM访问降了一半多;但从1MB到4MB,只降了不到5%。而面积呢?4MB的SRAM面积可能是512KB的七八倍。这就是典型的边际收益递减。
所以我的建议是:找到那条"拐点",把缓存做在拐点附近,而不是盲目做大。拐点位置取决于你的目标模型和batch size,需要针对具体场景做仿真确定。
3.2 权重缓存与激活缓存的差异化设计
很多设计把权重和激活值放在同一个缓存里统一管理,简单是简单,但效率往往不高。原因是这两类数据的访问模式完全不同:
- 权重:只读、可预取、跨batch复用、访问模式规整;
- 激活:读写混合、生命周期短、batch内变化、访问模式随层变化。
我倾向于把两者分开设计。权重缓存可以做得"深"一些,配合预取逻辑,让权重在计算开始前就位;激活缓存则要做得"灵活"一些,支持乒乓缓冲(ping-pong buffer)和行缓冲(line buffer)等模式,适应不同层的需求。
在一个实际项目里,我们把权重和激活缓存分开后,权重缓存的命中率从约85%提升到98%以上,激活缓存的冲突率下降了约30%。代价是控制逻辑复杂了一些,但能效收益很值。
3.3 片上网络(NoC)的带宽分配策略
当计算单元数量上去之后,片上网络就成了新的瓶颈。数据要在多个计算簇之间流动,如果NoC带宽分配不合理,会出现"有的簇饿死、有的簇堵死"的情况。
我的经验是,NoC设计要遵循"就近原则"和"分层原则":
- 就近原则:让数据尽量在产生它的地方被消费,减少跨簇传输。比如卷积的输入特征图和输出特征图,如果能在同一个簇内完成计算,就不要绕到别的簇去。
- 分层原则:把NoC分成几层,层内高带宽、低延迟,层间带宽可以低一些但要有足够的缓冲。这样既保证了局部通信效率,又控制了全局布线复杂度。
具体到带宽分配,我通常会用"最坏情况分析 + 动态仲裁"的组合:静态分配保证每个簇的最低带宽需求,动态仲裁则让空闲簇的带宽可以临时借给繁忙簇。这个策略在多个项目里都验证有效,能提升约15%~25%的有效带宽利用率。
4. 指令调度与流水线:让计算单元别闲着
4.1 为什么MAC利用率总是上不去
前面说了数据搬运是功耗大头,但还有一个同样致命的问题:计算单元的空转。一颗芯片标称4096个MAC,实际跑模型时可能只有1000个在有效工作,其余的在等数据、等指令、等同步。这种空转不产生有效算力,却照样耗电(时钟树、漏电),是能效的隐形杀手。
MAC利用率上不去,通常有三个原因:
- 数据依赖:后一个计算依赖前一个计算的结果,流水线被迫停顿;
- 指令开销:取指、译码、发射这些控制开销占用了周期;
- 同步等待:多核或多簇之间需要同步,同步点前后有大量空闲。
针对这三点,软硬件两侧都有优化空间。
4.2 软件侧的指令级优化:循环展开与软件流水
在编译器层面,最有效的两个手段是循环展开(loop unrolling)和软件流水(software pipelining)。
循环展开是把循环体复制多份,减少循环控制开销,同时给硬件更多指令级并行(ILP)的机会。比如一个内层循环展开4次,取指和分支判断的开销就摊薄到4次计算上。
软件流水则是把不同迭代的计算重叠起来:当前迭代在做计算时,下一次迭代的数据已经在预取,再下一次迭代的地址已经在计算。这样流水线就不会因为数据没到位而停顿。
我在一个卷积算子的实现里,把内层循环展开8次并配合软件流水,MAC利用率从约45%提升到78%。代码量增加了,但编译器后端做这些变换是自动的,前提是你的指令集和硬件支持足够的寄存器资源和预取机制。
注意:循环展开不是越多越好。展开次数过多会导致寄存器压力过大,反而引起寄存器溢出(spill),把数据挤到栈上,得不偿失。一般展开4~8次是比较稳妥的区间,具体要看寄存器文件大小。
4.3 硬件侧的支撑:多发射、乱序与预取
软件优化要生效,硬件得配合。三个关键的硬件特性:
- 多发射:一个周期发射多条指令,让计算、访存、控制指令并行执行。AI计算里计算和访存的比例相对固定,多发射能有效填充流水线空隙。
- 有限乱序:完全乱序太复杂,但有限度的乱序(比如允许访存指令越过计算指令)能显著减少停顿。我见过一些设计用简单的记分牌(scoreboard)机制就实现了不错的乱序效果。
- 硬件预取:根据访问模式自动预取下一批数据。AI计算的访问模式很规整,硬件预取器很容易做准,命中率经常能到90%以上。
这三者里,我认为预取的性价比最高。它不需要复杂的乱序逻辑,只需要一个能识别规整访问模式的预取器,就能把访存延迟隐藏掉大半。
4.4 双缓冲与乒乓操作的实际收益
双缓冲(double buffering)是AI加速器里最经典也最有效的技术之一。原理很简单:准备两块缓冲区,一块在计算时,另一块在加载数据,计算完切换。这样计算和数据加载完全重叠,理论上可以把数据加载时间完全隐藏。
实际收益取决于计算时间和加载时间的比例。如果计算时间是加载时间的3倍,那双缓冲能隐藏掉大部分加载开销;如果两者相当,那双缓冲只能隐藏一半。我在项目里的一般做法是:先估算每层的计算/加载时间比,对比例接近1的层重点优化数据复用,对比例大于3的层用双缓冲就够了。
乒乓操作是双缓冲的推广,用在多级流水上。比如"加载→计算→写回"三级流水,用三块缓冲轮转,可以让三级完全并行。这个技术在卷积、矩阵乘等规整计算上效果极好。
5. 精度策略:不是所有计算都需要高精度
5.1 从FP32到INT8:能效提升的量化账
精度是AI芯片能效优化的一个大杠杆。把数据位宽从32位降到8位,理论上:
- 计算单元面积可以降到约1/4(乘法器面积与位宽平方相关);
- 存储容量需求降到1/4;
- 数据搬运量降到1/4,对应的搬运功耗也大幅下降;
- 计算延迟降低,同样时间内能处理更多数据。
综合下来,INT8相比FP32,能效提升经常能达到5~10倍。这就是为什么现在主流推理芯片都主打INT8甚至INT4。
但精度降低是有代价的:模型精度会下降。关键在于找到精度和能效的平衡点。我的经验是,对于大多数视觉模型,INT8量化后精度损失可以控制在1%以内;对于部分NLP模型,可能需要混合精度策略。
5.2 混合精度:不同层用不同位宽
不是所有层对精度都同样敏感。通常:
- 第一层和最后一层对精度最敏感,建议保留较高精度(如FP16或INT16);
- 中间的卷积层和全连接层对精度相对不敏感,可以用INT8甚至INT4;
- 归一化层和激活函数需要根据动态范围决定,有时需要较高精度。
混合精度策略需要软硬件协同:硬件要支持多种位宽的计算单元和存储格式,软件要能根据每层的敏感度自动分配精度。我在一个项目里实现了逐层精度分配,整体能效比全INT8还高了约20%,因为把一些不敏感的层降到了INT4,而敏感层保住了精度。
5.3 量化感知训练与训练后量化的选择
量化有两种主要路径:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 训练后量化(PTQ) | 简单、无需重新训练 | 精度损失可能较大 | 模型已定型、快速部署 |
| 量化感知训练(QAT) | 精度损失小 | 需要重新训练、成本高 | 精度要求高、有训练资源 |
我的建议是:先用PTQ试,如果精度达标就用PTQ;如果不达标,再考虑QAT。PTQ现在做得越来越好了,很多模型PTQ后精度损失很小。QAT虽然效果好,但训练成本高,而且需要训练框架和推理框架的配合,工程复杂度不低。
提示:做PTQ时,校准数据集的选择很关键。不要随便拿几张图就校准,要用有代表性的数据,覆盖各种场景。校准数据分布和实际推理数据分布不一致,是PTQ精度掉点最常见的原因。
5.4 稀疏化:另一条被低估的能效路径
除了降精度,稀疏化是另一条提升能效的路径。模型剪枝后,大量权重为零,如果硬件能跳过这些零,就能省下计算和搬运。
结构化稀疏(比如整通道剪掉)对硬件最友好,因为可以整块跳过;非结构化稀疏虽然压缩率高,但需要专门的稀疏计算单元,硬件复杂度高。我倾向于在硬件上支持结构化稀疏,软件上做结构化剪枝,这样软硬件配合最顺畅。
实测下来,50%的结构化稀疏配合INT8,能效可以再提升约1.5~2倍。这个收益相当可观,而且精度损失通常可控。
6. 软硬件接口:编译器与硬件的"握手"细节
6.1 指令集设计要为编译器留余地
指令集是软硬件的契约。设计指令集时,一个常见的错误是"为某个特定算子定制指令",结果编译器很难通用地生成代码。我的经验是:指令集要提供足够的原语(primitive),让编译器能组合出各种算子,而不是为每个算子做一条指令。
比如,与其做一条"卷积指令",不如提供"向量乘加""数据搬移""地址计算"这些基础原语,让编译器去组合。这样灵活性高,新算子出来不用改硬件。
当然,纯原语方式可能效率不够高,所以实践中往往是"基础原语 + 少量高频算子专用指令"的组合。关键是专用指令要选那些真正高频、通用的,而不是为某个模型特化的。
6.2 编译器后端的调度与硬件资源的映射
编译器后端要把计算图映射到硬件资源上,这个映射质量直接决定能效。几个关键点:
- 算子融合:把多个算子合并成一个,减少中间结果的搬运。比如"卷积+BN+ReLU"融合成一个,中间结果不落片外。
- 内存分配:合理分配片上缓存,让数据复用最大化。这本质上是一个图着色或线性规划问题。
- 流水线编排:决定哪些计算并行、哪些串行,以及数据加载和计算的 overlap 方式。
我在项目里发现,算子融合的收益往往最大。一个典型的卷积网络,融合后中间结果的搬运量能减少60%以上,能效提升非常明显。
6.3 运行时调度:动态负载均衡
静态编译时的调度再好,也难应对运行时的动态变化(比如多任务并发、输入尺寸变化)。所以运行时调度也很重要。
我的做法是:编译时做粗粒度调度,运行时做细粒度调整。编译时确定大致的执行顺序和资源分配,运行时根据实际负载动态调整任务分配,让各计算簇尽量均衡。
这里有个坑:运行时调度会引入额外开销(调度决策、任务迁移),如果开销大于收益,就得不偿失。所以运行时调度要做得轻量,决策逻辑要简单快速。
6.4 一个真实的软硬件协同调优案例
说一个我参与过的项目。芯片流片回来,跑目标模型,能效比预期低了约30%。排查过程大致如下:
- 先用性能计数器看MAC利用率,发现只有约50%,远低于预期的75%;
- 进一步看,发现是某些层的激活值缓存冲突严重,导致频繁的缓存换入换出;
- 软件侧调整了内存分配策略,把冲突严重的层单独分配缓存区域,MAC利用率提升到约65%;
- 硬件侧发现预取器的预取深度不够,调整了预取参数,MAC利用率进一步提升到约72%;
- 最后软件侧做了算子融合,减少了中间结果搬运,能效达到了预期水平。
这个案例说明,软硬件协同调优是一个迭代过程,单靠一侧很难达到最优。而且调优要有数据支撑,性能计数器、功耗分析工具这些基础设施必须到位。
7. 一些踩过的坑和实操心得
7.1 不要过早优化,但也不要太晚
软硬件协同优化有个时机问题。太早优化,架构还没定型,优化方向可能白费;太晚优化,架构已经冻结,能改的空间很小。
我的经验是:架构定义阶段就要做能效建模,用粗略的模型估算不同方案的能效上限;详细设计阶段做关键路径的优化;流片后做实测调优。三个阶段各有侧重,不能颠倒。
7.2 仿真和实测的差距要心里有数
仿真工具(不管是架构仿真还是功耗仿真)和实测总有差距,有时差距还不小。我见过仿真能效比实测高50%的情况,原因是仿真没考虑某些实际开销(比如时钟树功耗、电源管理开销)。
所以我的做法是:仿真结果打个折再用。具体打几折,取决于仿真工具的精度和团队的历史经验。一般架构级仿真打7~8折,RTL级仿真打8~9折,比较稳妥。
7.3 功耗分析工具要早用、常用
功耗分析不是流片后才做的事,设计阶段就要持续做。我建议把功耗分析集成到日常设计流程里,每次架构调整、每次RTL修改都跑一下功耗估算,及时发现功耗异常。
常用的功耗分析手段包括:架构级功耗建模、RTL级功耗仿真、门级功耗分析。精度递增,速度递减,根据设计阶段选择。
7.4 和软件团队坐在一起
最后一条,也是最重要的一条:硬件团队和软件团队要坐在一起。我见过太多项目,硬件团队闷头做架构,软件团队闷头写编译器,两边接口对不上,最后互相甩锅。
有效的做法是:架构定义阶段双方一起参与,硬件团队理解软件的需求和约束,软件团队理解硬件的特性和限制;设计过程中定期同步,发现接口问题及时调整;流片后联合调优,软硬件一起看性能计数器,一起找瓶颈。
这个"坐在一起"不是形式上的开会,而是真正的工作协同。我在一个项目里,硬件和软件工程师的工位就挨着,有问题随时讨论,效率比走流程高得多。那个项目的能效指标最终超出了预期,我认为协同方式是关键因素之一。
7.5 关于能效优化的优先级排序
如果资源有限,只能做几件事,我的优先级排序是:
- 数据复用和搬运优化(收益最大,往往能提升30%以上能效);
- 精度降低(INT8/INT4,收益大,但需要精度验证);
- 算子融合(减少中间结果搬运,收益中等偏大);
- 指令调度优化(提升MAC利用率,收益中等);
- 稀疏化(收益中等,但硬件复杂度高)。
这个排序不是绝对的,具体项目要根据实际情况调整。但大方向是:先解决数据搬运,再解决计算效率。因为搬运的能耗基数大,优化的绝对收益也大。
7.6 一个容易被忽视的点:电源管理策略
最后提一个容易被忽视的点:电源管理策略对能效的影响。动态电压频率调节(DVFS)、时钟门控、电源门控这些技术,如果用得好,能省下可观的功耗。
关键是要根据工作负载动态调整。AI推理的负载波动很大,有的层计算密集,有的层访存密集,统一的工作点往往不是最优的。我见过一个设计,通过细粒度的DVFS,在不同层用不同的电压频率,整体能效提升了约15%。
当然,DVFS会引入切换开销和复杂度,要权衡。但对于功耗敏感的场景(比如边缘设备),这个收益值得争取。
写到这里,关于AI芯片软硬件协同设计中的能效优化,我把自己这些年踩过的坑、验证过的方法、以及一些还在探索的思路都摊开了。这个领域没有银弹,每个项目的情况都不一样,但底层逻辑是相通的:理解数据流、尊重能耗阶梯、软硬件一起设计。希望这些内容能给正在做类似工作的朋友一些参考。如果非要我留一句话,那就是:下次评审架构方案时,先问数据怎么流,再问算力有多大。