1. 从"能跑就行"到"跑得省电":AI芯片软硬件协同设计的核心命题做AI芯片这行的人,大概都经历过这样一个阶段:模型在仿真环境里跑通了,精度也达标了,团队欢呼雀跃,觉得大功告成。结果一上板子,功耗直接爆表,散热压不住,频率被迫降档,实际吞吐量连理论值的一半都不到。这种"仿真猛如虎,实测二百五"的落差,几乎成了每一代AI加速器项目都要交的学费。这个系列写到第五篇,我想把话题收拢到一个最容易被低估、却最能决定产品成败的交叉地带——软硬件协同设计。注意,这里说的"协同"不是一句口号,而是指硬件微架构的每一个取舍,都要在软件栈里有对应的映射策略;反过来,软件调度和编译优化的每一个决策,也要能落到硬件资源的具体分配上。两者脱节,芯片就是一块昂贵的硅片;两者咬合,同样的面积和功耗预算下,有效算力可以差出好几倍。这篇文章适合谁看?如果你正在参与AI加速器的架构定义、RTL设计、编译器开发或者系统级性能调优,尤其是那种"硬件已经流片、软件还在补窟窿"或者"软件需求天天变、硬件改不动"的团队,这里面的思路应该能帮你少走一些弯路。我会尽量把原理讲透,把参数怎么算、方案怎么选、坑在哪里,都摊开来说。需要提前说明的是,文中涉及的具体数值和案例,都是基于行业常见实践做的合理推演,用来把逻辑讲清楚,不代表某个特定产品的真实参数。2. 算力账本要两头算:峰值算力与有效算力的鸿沟从哪来2.1 为什么纸面TOPS经常是"注水肉"几乎每一份AI芯片的规格书首页,都会印着一个醒目的算力数字,比如多少TOPS。这个数字怎么来的?通常是MAC阵列规模乘以频率再乘以2(一次乘加算两次操作)。问题是,这个数字假设了所有MAC单元在每个时钟周期都在做有效计算,且数据供给从不间断。现实中,这个假设几乎从不成立。有效算力上不去,根子往往不在计算单元本身,而在数据搬运。我习惯用一个类比:MAC阵列是厨房里的灶台,数据是食材。灶台再多,如果配菜、传菜的速度跟不上,厨师只能干等着。AI计算里,这个"传菜"就是片上存储带宽和片间/片外访存带宽。一个典型的矩阵乘法,如果数据复用率没设计好,计算单元有七成时间在等数据,那有效算力就只有峰值的30%左右。所以看一颗AI芯片,不能只看TOPS,要同时看三个数:峰值算力、片上缓存带宽、外部内存带宽。这三者之间存在一个"平衡点",业界常用计算强度(Operational Intensity,每字节数据能支撑多少次运算)来衡量。当某个算子的计算强度低于硬件的平衡点时,它就是访存瓶颈;高于平衡点时,才是计算瓶颈。软硬件协同的第一件事,就是让尽可能多的算子落在计算瓶颈区,或者通过数据复用把访存瓶颈区的算子"抬"上去。2.2 用一张表把瓶颈算清楚下面这张表是我在做架构评估时常用的一个简化模型,用来快速判断某类算子在目标硬件上到底卡在哪:算子类型典型计算强度(OP/Byte)硬件平衡点瓶颈判定优化方向大矩阵乘(GEMM)50~200约30计算瓶颈提升MAC利用率、减少流水线气泡深度可分离卷积5~20约30访存瓶颈提高数据复用、算子融合逐元素激活1~3约30严重访存瓶颈与前后算子融合、片上完成归一化层2~8约30访存瓶颈融合进卷积、减少往返访存这张表的价值在于,它把"为什么我的芯片跑ResNet快、跑Transformer慢"这类问题量化了。你会发现,很多轻量级网络里大量存在的是深度可分离卷积和逐元素操作,它们的计算强度远低于硬件平衡点,所以芯片的峰值算力根本发挥不出来。解决办法不是继续堆MAC,而是从软件侧做算子融合,把多个低强度算子合并成一个高强度算子,让数据在片上多待一会儿。2.3 数据复用:协同设计里最值钱的两个字数据复用分三个层次:时间复用(同一份数据在不同时刻被多次使用)、空间复用(同一份数据被多个计算单元同时使用)、层次复用(数据在寄存器、片上缓存、外部内存之间合理驻留)。硬件设计时,片上缓存的容量、bank划分、互联拓扑决定了复用的上限;软件设计时,循环分块(tiling)、数据布局(layout)、调度顺序决定了能否逼近这个上限。举个具体的例子。假设你要在片上做一次64x64的矩阵乘,硬件有16x16的MAC阵列和一块256KB的片上缓存。如果软件把整个矩阵一次性搬进来,缓存装不下,只能反复换入换出,带宽全浪费在搬运上。正确的做法是做分块:把大矩阵切成16x16的小块,每次只加载计算当前输出块所需的数据,让每个数据块在被换出前尽可能多地被复用。这个分块大小怎么定?它受限于片上缓存容量、MAC阵列尺寸和寄存器文件大小,是一个典型的软硬件联合优化问题。分块太小,复用率低;分块太大,缓存溢出。我通常会用一个小脚本枚举不同分块组合,算出每种组合下的理论访存量,再挑访存量最小的那个作为起点。3. 存储层次怎么切:片上缓存、暂存器与外部内存的分工逻辑3.1 三级存储的职责边界AI芯片的存储层次,粗分是三级:外部内存(通常是高带宽内存)、片上全局缓存(Shared Memory / L2)、本地暂存器(Register File / Scratchpad)。这三级的带宽和容量呈数量级差异,设计时最忌讳的就是职责不清。外部内存容量大但带宽有限、延迟高,适合存放权重和激活值的"冷数据";片上全局缓存带宽高、容量中等,适合做数据中转和跨计算单元的共享;本地暂存器带宽极高、容量极小,直接喂给MAC阵列。软硬件协同的关键,是让数据在这三级之间"各就各位",而不是频繁地跨级搬运。我见过不少设计,把本该放在本地暂存器的中间结果写回片上全局缓存,甚至写回外部内存,结果带宽全被这些无谓的往返吃掉了。判断一个数据该放哪一级,有个简单的原则:如果一个数据在被消费前只被一个计算单元使用,且生命周期很短,就尽量留在本地暂存器;如果被多个计算单元共享,或者生命周期跨越多个计算阶段,才考虑放到片上全局缓存。3.2 双缓冲与流水线:让搬运和计算重叠起来存储层次设计里,有一个几乎绕不开的技术叫双缓冲(Double Buffering)。原理很朴素:准备两块缓冲区,一块用于当前计算,另一块同时预取下一批数据。这样数据搬运和计算就能重叠,计算单元不用停下来等数据。但双缓冲不是免费的。它需要双倍的缓冲区容量,而且对同步机制有要求。如果预取时机没控制好,要么预取太早导致缓冲区被占满、后续数据进不来,要么预取太晚导致计算单元还是得等。在硬件上,这通常通过DMA引擎和计算单元之间的握手信号来实现;在软件上,则需要在编译器或运行时里插入合适的同步原语。实测中一个常见的坑是:双缓冲的深度设成2,但数据依赖链比较长,导致预取的数据还没用完,新的预取请求就来了,缓冲区频繁切换,反而增加了同步开销。这时候可以考虑加深缓冲深度,或者改用多级流水的方式,把搬运、计算、写回拆成更细的流水级。具体设几级,要看算子的数据依赖图和硬件的并行度,没有万能答案,得实测。3.3 片上缓存的bank划分与冲突避免片上缓存通常会被划分成多个bank,以支持并行访问。如果多个计算单元同时访问同一个bank,就会发生bank冲突,访问被串行化,带宽骤降。这个问题在矩阵转置、卷积的im2col展开等操作里特别容易触发。避免bank冲突,硬件侧可以通过增加bank数量、采用更复杂的互联网络来缓解;软件侧则可以通过调整数据布局来规避。比如,把矩阵按列优先还是行优先存储,会直接影响访问模式落在哪些bank上。我在做卷积优化时,经常会把权重和激活值的存储布局做一次重排,让同一时刻被访问的数据尽量分散到不同bank。这个重排操作本身有开销,但如果能显著降低冲突率,整体收益是正的。判断是否发生bank冲突,最直接的办法是在仿真环境里统计每个bank的访问次数,看是否存在明显的热点。如果某个bank的访问次数远高于平均值,基本可以确定有冲突。解决思路无非两条:要么改布局,要么改访问顺序。4. 编译器与硬件的握手:指令调度如何决定实际性能4.1 从计算图到指令流:中间发生了什么一个AI模型从框架里的计算图,到最终在芯片上执行的指令流,中间要经过图优化、算子融合、内存分配、指令选择、指令调度等一系列步骤。这个链条里,指令调度是最贴近硬件、也最能体现软硬件协同水平的一环。指令调度的核心任务,是在满足数据依赖的前提下,把指令重新排序,让硬件的并行资源尽可能被填满。比如,一条访存指令的延迟很长,调度器就应该在它后面插入几条不依赖它的计算指令,让计算单元在等待数据的同时有事可做。这个"插入"的时机和数量,取决于硬件的流水线深度、访存延迟和并行度。如果编译器不知道硬件的这些参数,它就只能保守调度,性能自然上不去。所以成熟的AI芯片团队,通常会让编译器团队和硬件团队共享一份硬件抽象模型,里面明确标注了各类指令的延迟、吞吐、资源占用。这份模型是软硬件协同的"合同",双方都按它来设计和优化。4.2 算子融合:省的不只是访存,还有调度开销算子融合是软件侧提升有效算力的利器。把卷积、批归一化、激活函数融合成一个算子,中间结果不用写回外部内存,直接在片上流转。这省下的访存带宽,前面算过,可能是数倍于计算本身的收益。但融合也有边界。融合后的算子,其数据依赖图会变复杂,对片上缓存和寄存器的压力更大。如果融合导致寄存器溢出,反而会引入额外的溢出/恢复开销。所以融合策略要结合硬件资源来定:寄存器多、片上缓存大的硬件,可以融合得更激进;资源紧张的硬件,就得保守一些。我在实践中总结了一个经验法则:优先融合那些计算强度低、访存量大的算子,比如逐元素操作和归一化层;对于计算强度本来就高的算子,融合的边际收益相对小,可以往后放。另外,融合后的算子如果太大,编译时间会显著增加,调试也更困难,所以建议分阶段融合,每融合一步就验证一次精度和性能。4.3 指令级并行与数据级并行的取舍硬件通常同时支持指令级并行(ILP)和数据级并行(DLP)。ILP靠的是流水线和多发射,让多条指令重叠执行;DLP靠的是SIMD或向量化,让一条指令处理多个数据。两者都能提升吞吐,但资源开销和适用场景不同。编译器在做调度时,需要决定是把一个循环展开成多条独立指令来挖掘ILP,还是向量化成一条宽指令来挖掘DLP。这个决策取决于硬件支持哪种并行、以及数据本身的特性。如果数据之间有依赖,强行展开成ILP反而会增加寄存器压力;如果数据规整且连续,向量化通常是更优解。一个实用的判断方法是:先看硬件的向量宽度和寄存器数量,再估算展开后的寄存器需求。如果展开后寄存器不够用,就退回向量化;如果向量化后仍有空闲的发射槽,再考虑适度展开。这个权衡过程,最好在编译器的调度框架里做成可配置的策略,而不是写死。5. 功耗与散热:被软件调度悄悄影响的隐形账本5.1 动态功耗从哪里来,软件能管多少芯片的动态功耗大致正比于 电容 × 电压平方 × 频率。电压和频率是硬件设定的,但翻转率(单位时间内信号翻转的次数)却和软件执行的行为密切相关。同样的计算任务,不同的指令序列、不同的数据布局,会导致不同的翻转率,进而影响动态功耗。软件能做的,主要是减少无效翻转和降低峰值功耗。比如,避免频繁地读写同一块内存(会引起位线反复充放电),尽量让数据在寄存器里完成计算;再比如,把大算力任务在时间上打散,避免所有计算单元同时满负荷运行造成功耗尖峰。这些优化,在硬件设计阶段就要预留支持,比如提供功耗管理单元、支持动态调频调压,软件才能通过配置寄存器来生效。5.2 散热约束下的频率策略散热能力决定了芯片能持续跑在什么频率上。如果软件调度让芯片长时间处于高功耗状态,温度上升,硬件为了保护自己会降频,性能反而下降。这就是所谓的热节流。软硬件协同在这里的体现,是软件要能感知硬件的热状态,并动态调整任务调度。比如,在温度接近阈值时,主动降低非关键任务的优先级,或者把任务迁移到温度较低的計算单元上。这需要硬件提供温度传感器和相应的中断/状态寄存器,软件侧则要有对应的调度策略。我见过一个案例:某加速器在跑一个长时任务时,前几秒性能很好,之后就明显掉速。排查发现是散热设计余量不足,芯片很快触发热节流。后来软件侧做了调整,把任务拆成多个短片段,片段之间插入短暂的轻负载间隔,让芯片有机会散热。虽然总任务时间没有缩短,但避免了长时间降频,整体吞吐反而更稳定。这个例子说明,功耗和散热不是纯硬件问题,软件调度有相当大的作为空间。5.3 精度与功耗的联动还有一个容易被忽略的点:数据精度直接影响功耗。用FP32做计算,数据位宽大,翻转的位数多,功耗自然高;换成FP16或INT8,位宽减半甚至更少,功耗显著下降。所以,在精度允许的前提下,尽量用低位宽数据,是软硬件协同降功耗的直接手段。但精度降低会带来数值误差,需要在模型训练或量化阶段就考虑进去。硬件侧要支持多种精度的计算单元和相应的舍入模式,软件侧则要有量化感知训练和精度校准的流程。两者配合,才能在精度和功耗之间找到最佳平衡点。6. 实测中的几个典型坑与排查思路6.1 仿真性能好、上板性能差:先查时钟和复位仿真环境里,时钟是理想的,复位是干净的。上板之后,时钟可能有抖动,复位可能不彻底,这些都会影响性能。如果上板后性能远低于仿真,第一步应该查时钟频率是否达到预期、复位是否覆盖了所有寄存器。我遇到过因为复位信号扇出太大导致部分寄存器复位延迟,进而引发流水线气泡的案例,排查了很久才定位到。6.2 带宽利用率上不去:看DMA配置和访问粒度如果实测带宽远低于理论带宽,先看DMA的配置。访问粒度太小,会导致大量的小事务,效率低下;访问地址不对齐,会触发额外的拆分。通常建议把访问粒度设成硬件支持的最大值,并确保地址对齐。另外,DMA的并发通道数也要和计算单元的并行度匹配,通道太少会成为瓶颈。6.3 多核协同效率低:查同步和共享资源争用多核AI芯片里,核间同步和共享资源(如片上缓存、外部内存控制器)的争用是性能杀手。如果多核加速比远低于核数,就要查同步机制是否高效、共享资源是否成为瓶颈。常见的优化包括:用硬件信号量代替软件轮询、给每个核划分独立的缓存区域减少争用、调整任务划分让各核负载更均衡。6.4 精度不达标:从数据布局和累加顺序查起有时候硬件跑出来的结果和软件参考实现有微小差异,导致精度不达标。这往往不是计算单元的问题,而是累加顺序不同引起的浮点误差。浮点加法不满足结合律,不同的累加顺序会得到不同的结果。解决方法是统一软硬件的累加顺序,或者在硬件里采用更高精度的累加器。这个问题在低位宽计算里更明显,需要特别注意。7. 一些个人体会做AI芯片的软硬件协同,最深的体会是:不要试图在硬件和软件之间划一条清晰的界线,然后各自为政。真正高效的团队,硬件工程师会去读编译器的调度日志,软件工程师会去翻硬件的微架构手册,双方用同一套性能模型对话。这种协作模式,比任何单方面的优化都管用。另外,性能优化永远是一个迭代过程。第一版设计不可能完美,重要的是建立一套可观测、可复现的评估流程:能准确测量每个算子的耗时和访存量,能快速定位瓶颈在哪一级存储或哪一类资源,能方便地尝试不同的分块、融合、调度策略。有了这套流程,优化就是按图索骥,而不是碰运气。最后说一个具体的技巧:在做分块和融合策略时,先用一个简化的分析模型算出理论上限,再拿实测数据去对比。如果实测和理论差距很大,说明还有优化空间;如果已经很接近理论上限,就别再死磕了,把精力放到别的地方。知道什么时候停手,也是一种经验。
1. 从"能跑就行"到"跑得省电":AI芯片软硬件协同设计的核心命题做AI芯片这行的人,大概都经历过这样一个阶段:模型在仿真环境里跑通了,精度也达标了,团队欢呼雀跃,觉得大功告成。结果一上板子,功耗直接爆表,散热压不住,频率被迫降档,实际吞吐量连理论值的一半都不到。这种"仿真猛如虎,实测二百五"的落差,几乎成了每一代AI加速器项目都要交的学费。这个系列写到第五篇,我想把话题收拢到一个最容易被低估、却最能决定产品成败的交叉地带——软硬件协同设计。注意,这里说的"协同"不是一句口号,而是指硬件微架构的每一个取舍,都要在软件栈里有对应的映射策略;反过来,软件调度和编译优化的每一个决策,也要能落到硬件资源的具体分配上。两者脱节,芯片就是一块昂贵的硅片;两者咬合,同样的面积和功耗预算下,有效算力可以差出好几倍。这篇文章适合谁看?如果你正在参与AI加速器的架构定义、RTL设计、编译器开发或者系统级性能调优,尤其是那种"硬件已经流片、软件还在补窟窿"或者"软件需求天天变、硬件改不动"的团队,这里面的思路应该能帮你少走一些弯路。我会尽量把原理讲透,把参数怎么算、方案怎么选、坑在哪里,都摊开来说。需要提前说明的是,文中涉及的具体数值和案例,都是基于行业常见实践做的合理推演,用来把逻辑讲清楚,不代表某个特定产品的真实参数。2. 算力账本要两头算:峰值算力与有效算力的鸿沟从哪来2.1 为什么纸面TOPS经常是"注水肉"几乎每一份AI芯片的规格书首页,都会印着一个醒目的算力数字,比如多少TOPS。这个数字怎么来的?通常是MAC阵列规模乘以频率再乘以2(一次乘加算两次操作)。问题是,这个数字假设了所有MAC单元在每个时钟周期都在做有效计算,且数据供给从不间断。现实中,这个假设几乎从不成立。有效算力上不去,根子往往不在计算单元本身,而在数据搬运。我习惯用一个类比:MAC阵列是厨房里的灶台,数据是食材。灶台再多,如果配菜、传菜的速度跟不上,厨师只能干等着。AI计算里,这个"传菜"就是片上存储带宽和片间/片外访存带宽。一个典型的矩阵乘法,如果数据复用率没设计好,计算单元有七成时间在等数据,那有效算力就只有峰值的30%左右。所以看一颗AI芯片,不能只看TOPS,要同时看三个数:峰值算力、片上缓存带宽、外部内存带宽。这三者之间存在一个"平衡点",业界常用计算强度(Operational Intensity,每字节数据能支撑多少次运算)来衡量。当某个算子的计算强度低于硬件的平衡点时,它就是访存瓶颈;高于平衡点时,才是计算瓶颈。软硬件协同的第一件事,就是让尽可能多的算子落在计算瓶颈区,或者通过数据复用把访存瓶颈区的算子"抬"上去。2.2 用一张表把瓶颈算清楚下面这张表是我在做架构评估时常用的一个简化模型,用来快速判断某类算子在目标硬件上到底卡在哪:算子类型典型计算强度(OP/Byte)硬件平衡点瓶颈判定优化方向大矩阵乘(GEMM)50~200约30计算瓶颈提升MAC利用率、减少流水线气泡深度可分离卷积5~20约30访存瓶颈提高数据复用、算子融合逐元素激活1~3约30严重访存瓶颈与前后算子融合、片上完成归一化层2~8约30访存瓶颈融合进卷积、减少往返访存这张表的价值在于,它把"为什么我的芯片跑ResNet快、跑Transformer慢"这类问题量化了。你会发现,很多轻量级网络里大量存在的是深度可分离卷积和逐元素操作,它们的计算强度远低于硬件平衡点,所以芯片的峰值算力根本发挥不出来。解决办法不是继续堆MAC,而是从软件侧做算子融合,把多个低强度算子合并成一个高强度算子,让数据在片上多待一会儿。2.3 数据复用:协同设计里最值钱的两个字数据复用分三个层次:时间复用(同一份数据在不同时刻被多次使用)、空间复用(同一份数据被多个计算单元同时使用)、层次复用(数据在寄存器、片上缓存、外部内存之间合理驻留)。硬件设计时,片上缓存的容量、bank划分、互联拓扑决定了复用的上限;软件设计时,循环分块(tiling)、数据布局(layout)、调度顺序决定了能否逼近这个上限。举个具体的例子。假设你要在片上做一次64x64的矩阵乘,硬件有16x16的MAC阵列和一块256KB的片上缓存。如果软件把整个矩阵一次性搬进来,缓存装不下,只能反复换入换出,带宽全浪费在搬运上。正确的做法是做分块:把大矩阵切成16x16的小块,每次只加载计算当前输出块所需的数据,让每个数据块在被换出前尽可能多地被复用。这个分块大小怎么定?它受限于片上缓存容量、MAC阵列尺寸和寄存器文件大小,是一个典型的软硬件联合优化问题。分块太小,复用率低;分块太大,缓存溢出。我通常会用一个小脚本枚举不同分块组合,算出每种组合下的理论访存量,再挑访存量最小的那个作为起点。3. 存储层次怎么切:片上缓存、暂存器与外部内存的分工逻辑3.1 三级存储的职责边界AI芯片的存储层次,粗分是三级:外部内存(通常是高带宽内存)、片上全局缓存(Shared Memory / L2)、本地暂存器(Register File / Scratchpad)。这三级的带宽和容量呈数量级差异,设计时最忌讳的就是职责不清。外部内存容量大但带宽有限、延迟高,适合存放权重和激活值的"冷数据";片上全局缓存带宽高、容量中等,适合做数据中转和跨计算单元的共享;本地暂存器带宽极高、容量极小,直接喂给MAC阵列。软硬件协同的关键,是让数据在这三级之间"各就各位",而不是频繁地跨级搬运。我见过不少设计,把本该放在本地暂存器的中间结果写回片上全局缓存,甚至写回外部内存,结果带宽全被这些无谓的往返吃掉了。判断一个数据该放哪一级,有个简单的原则:如果一个数据在被消费前只被一个计算单元使用,且生命周期很短,就尽量留在本地暂存器;如果被多个计算单元共享,或者生命周期跨越多个计算阶段,才考虑放到片上全局缓存。3.2 双缓冲与流水线:让搬运和计算重叠起来存储层次设计里,有一个几乎绕不开的技术叫双缓冲(Double Buffering)。原理很朴素:准备两块缓冲区,一块用于当前计算,另一块同时预取下一批数据。这样数据搬运和计算就能重叠,计算单元不用停下来等数据。但双缓冲不是免费的。它需要双倍的缓冲区容量,而且对同步机制有要求。如果预取时机没控制好,要么预取太早导致缓冲区被占满、后续数据进不来,要么预取太晚导致计算单元还是得等。在硬件上,这通常通过DMA引擎和计算单元之间的握手信号来实现;在软件上,则需要在编译器或运行时里插入合适的同步原语。实测中一个常见的坑是:双缓冲的深度设成2,但数据依赖链比较长,导致预取的数据还没用完,新的预取请求就来了,缓冲区频繁切换,反而增加了同步开销。这时候可以考虑加深缓冲深度,或者改用多级流水的方式,把搬运、计算、写回拆成更细的流水级。具体设几级,要看算子的数据依赖图和硬件的并行度,没有万能答案,得实测。3.3 片上缓存的bank划分与冲突避免片上缓存通常会被划分成多个bank,以支持并行访问。如果多个计算单元同时访问同一个bank,就会发生bank冲突,访问被串行化,带宽骤降。这个问题在矩阵转置、卷积的im2col展开等操作里特别容易触发。避免bank冲突,硬件侧可以通过增加bank数量、采用更复杂的互联网络来缓解;软件侧则可以通过调整数据布局来规避。比如,把矩阵按列优先还是行优先存储,会直接影响访问模式落在哪些bank上。我在做卷积优化时,经常会把权重和激活值的存储布局做一次重排,让同一时刻被访问的数据尽量分散到不同bank。这个重排操作本身有开销,但如果能显著降低冲突率,整体收益是正的。判断是否发生bank冲突,最直接的办法是在仿真环境里统计每个bank的访问次数,看是否存在明显的热点。如果某个bank的访问次数远高于平均值,基本可以确定有冲突。解决思路无非两条:要么改布局,要么改访问顺序。4. 编译器与硬件的握手:指令调度如何决定实际性能4.1 从计算图到指令流:中间发生了什么一个AI模型从框架里的计算图,到最终在芯片上执行的指令流,中间要经过图优化、算子融合、内存分配、指令选择、指令调度等一系列步骤。这个链条里,指令调度是最贴近硬件、也最能体现软硬件协同水平的一环。指令调度的核心任务,是在满足数据依赖的前提下,把指令重新排序,让硬件的并行资源尽可能被填满。比如,一条访存指令的延迟很长,调度器就应该在它后面插入几条不依赖它的计算指令,让计算单元在等待数据的同时有事可做。这个"插入"的时机和数量,取决于硬件的流水线深度、访存延迟和并行度。如果编译器不知道硬件的这些参数,它就只能保守调度,性能自然上不去。所以成熟的AI芯片团队,通常会让编译器团队和硬件团队共享一份硬件抽象模型,里面明确标注了各类指令的延迟、吞吐、资源占用。这份模型是软硬件协同的"合同",双方都按它来设计和优化。4.2 算子融合:省的不只是访存,还有调度开销算子融合是软件侧提升有效算力的利器。把卷积、批归一化、激活函数融合成一个算子,中间结果不用写回外部内存,直接在片上流转。这省下的访存带宽,前面算过,可能是数倍于计算本身的收益。但融合也有边界。融合后的算子,其数据依赖图会变复杂,对片上缓存和寄存器的压力更大。如果融合导致寄存器溢出,反而会引入额外的溢出/恢复开销。所以融合策略要结合硬件资源来定:寄存器多、片上缓存大的硬件,可以融合得更激进;资源紧张的硬件,就得保守一些。我在实践中总结了一个经验法则:优先融合那些计算强度低、访存量大的算子,比如逐元素操作和归一化层;对于计算强度本来就高的算子,融合的边际收益相对小,可以往后放。另外,融合后的算子如果太大,编译时间会显著增加,调试也更困难,所以建议分阶段融合,每融合一步就验证一次精度和性能。4.3 指令级并行与数据级并行的取舍硬件通常同时支持指令级并行(ILP)和数据级并行(DLP)。ILP靠的是流水线和多发射,让多条指令重叠执行;DLP靠的是SIMD或向量化,让一条指令处理多个数据。两者都能提升吞吐,但资源开销和适用场景不同。编译器在做调度时,需要决定是把一个循环展开成多条独立指令来挖掘ILP,还是向量化成一条宽指令来挖掘DLP。这个决策取决于硬件支持哪种并行、以及数据本身的特性。如果数据之间有依赖,强行展开成ILP反而会增加寄存器压力;如果数据规整且连续,向量化通常是更优解。一个实用的判断方法是:先看硬件的向量宽度和寄存器数量,再估算展开后的寄存器需求。如果展开后寄存器不够用,就退回向量化;如果向量化后仍有空闲的发射槽,再考虑适度展开。这个权衡过程,最好在编译器的调度框架里做成可配置的策略,而不是写死。5. 功耗与散热:被软件调度悄悄影响的隐形账本5.1 动态功耗从哪里来,软件能管多少芯片的动态功耗大致正比于 电容 × 电压平方 × 频率。电压和频率是硬件设定的,但翻转率(单位时间内信号翻转的次数)却和软件执行的行为密切相关。同样的计算任务,不同的指令序列、不同的数据布局,会导致不同的翻转率,进而影响动态功耗。软件能做的,主要是减少无效翻转和降低峰值功耗。比如,避免频繁地读写同一块内存(会引起位线反复充放电),尽量让数据在寄存器里完成计算;再比如,把大算力任务在时间上打散,避免所有计算单元同时满负荷运行造成功耗尖峰。这些优化,在硬件设计阶段就要预留支持,比如提供功耗管理单元、支持动态调频调压,软件才能通过配置寄存器来生效。5.2 散热约束下的频率策略散热能力决定了芯片能持续跑在什么频率上。如果软件调度让芯片长时间处于高功耗状态,温度上升,硬件为了保护自己会降频,性能反而下降。这就是所谓的热节流。软硬件协同在这里的体现,是软件要能感知硬件的热状态,并动态调整任务调度。比如,在温度接近阈值时,主动降低非关键任务的优先级,或者把任务迁移到温度较低的計算单元上。这需要硬件提供温度传感器和相应的中断/状态寄存器,软件侧则要有对应的调度策略。我见过一个案例:某加速器在跑一个长时任务时,前几秒性能很好,之后就明显掉速。排查发现是散热设计余量不足,芯片很快触发热节流。后来软件侧做了调整,把任务拆成多个短片段,片段之间插入短暂的轻负载间隔,让芯片有机会散热。虽然总任务时间没有缩短,但避免了长时间降频,整体吞吐反而更稳定。这个例子说明,功耗和散热不是纯硬件问题,软件调度有相当大的作为空间。5.3 精度与功耗的联动还有一个容易被忽略的点:数据精度直接影响功耗。用FP32做计算,数据位宽大,翻转的位数多,功耗自然高;换成FP16或INT8,位宽减半甚至更少,功耗显著下降。所以,在精度允许的前提下,尽量用低位宽数据,是软硬件协同降功耗的直接手段。但精度降低会带来数值误差,需要在模型训练或量化阶段就考虑进去。硬件侧要支持多种精度的计算单元和相应的舍入模式,软件侧则要有量化感知训练和精度校准的流程。两者配合,才能在精度和功耗之间找到最佳平衡点。6. 实测中的几个典型坑与排查思路6.1 仿真性能好、上板性能差:先查时钟和复位仿真环境里,时钟是理想的,复位是干净的。上板之后,时钟可能有抖动,复位可能不彻底,这些都会影响性能。如果上板后性能远低于仿真,第一步应该查时钟频率是否达到预期、复位是否覆盖了所有寄存器。我遇到过因为复位信号扇出太大导致部分寄存器复位延迟,进而引发流水线气泡的案例,排查了很久才定位到。6.2 带宽利用率上不去:看DMA配置和访问粒度如果实测带宽远低于理论带宽,先看DMA的配置。访问粒度太小,会导致大量的小事务,效率低下;访问地址不对齐,会触发额外的拆分。通常建议把访问粒度设成硬件支持的最大值,并确保地址对齐。另外,DMA的并发通道数也要和计算单元的并行度匹配,通道太少会成为瓶颈。6.3 多核协同效率低:查同步和共享资源争用多核AI芯片里,核间同步和共享资源(如片上缓存、外部内存控制器)的争用是性能杀手。如果多核加速比远低于核数,就要查同步机制是否高效、共享资源是否成为瓶颈。常见的优化包括:用硬件信号量代替软件轮询、给每个核划分独立的缓存区域减少争用、调整任务划分让各核负载更均衡。6.4 精度不达标:从数据布局和累加顺序查起有时候硬件跑出来的结果和软件参考实现有微小差异,导致精度不达标。这往往不是计算单元的问题,而是累加顺序不同引起的浮点误差。浮点加法不满足结合律,不同的累加顺序会得到不同的结果。解决方法是统一软硬件的累加顺序,或者在硬件里采用更高精度的累加器。这个问题在低位宽计算里更明显,需要特别注意。7. 一些个人体会做AI芯片的软硬件协同,最深的体会是:不要试图在硬件和软件之间划一条清晰的界线,然后各自为政。真正高效的团队,硬件工程师会去读编译器的调度日志,软件工程师会去翻硬件的微架构手册,双方用同一套性能模型对话。这种协作模式,比任何单方面的优化都管用。另外,性能优化永远是一个迭代过程。第一版设计不可能完美,重要的是建立一套可观测、可复现的评估流程:能准确测量每个算子的耗时和访存量,能快速定位瓶颈在哪一级存储或哪一类资源,能方便地尝试不同的分块、融合、调度策略。有了这套流程,优化就是按图索骥,而不是碰运气。最后说一个具体的技巧:在做分块和融合策略时,先用一个简化的分析模型算出理论上限,再拿实测数据去对比。如果实测和理论差距很大,说明还有优化空间;如果已经很接近理论上限,就别再死磕了,把精力放到别的地方。知道什么时候停手,也是一种经验。