如果你问一个做过AI芯片相关项目的人:整个流程里最头疼的是什么?我猜大半不会说是RTL难写,也不会说是算法难调,而是硬件和软件之间那道看不见却又真实存在的墙。硬件团队把流水线设计得自认为很聪明,软件团队拿到文档开始写编译器之后才发现,一堆看似微小的架构取舍,直接锁死了优化的上限。这几年我经历过好几个AI芯片项目,这个系列的第三篇,我想把“软硬件协同设计”这件事从头到尾拆一遍,重点放在一个边缘推理芯片原型(下文就叫它模拟项目X)上。适合正在做芯片架构、工具链、AI系统部署的工程同学,也适合马上要入行的学生——看这篇文章至少能帮你建立一张“AI芯片项目到底是怎么发生的”地图。
第一颗芯片回片之后,我们跑了一个主流残差网络模型,峰值算力标称很漂亮,实际整体利用率只有不到四分之一。不算冷门案例,很多AI芯片团队都掉进过同一个坑。区别只在于,有人把这笔账算在了软件头上,有人愿意回头从架构定义重新审视。这篇文章想说的就是后者:软硬件设计从来不是两条独立的流水线,而是同一条约束链的两端。
1. 为什么硬件和软件不能分开做设计
1.1 一个典型故事的拆解
模拟项目X是我们几年前的边缘推理芯片原型,定位是INT8精度下做到大约20 TOPS的计算能力,功耗预算控制在5W附近。硬件团队熬了一年多,从架构到RTL到后端,日历一页一页翻,终于流片回来。软件团队早就提前进场,把推理框架的适配写了七七八八,结果一测基准模型,算子级的平均利用率只有21%。
看波形、看计数器,每个乘加单元都活着,每个周期也都在翻转,但系统层面大量周期消耗在“等数据”上。片外DDR带宽的规划是按常规视频流场景估的,等真正跑神经网络的时候,权重一遍又一遍地从DDR搬进来,片上SRAM又不够放足够大的分块,所有线程都在排队等内存事务。
软件团队后来做了很多尝试:换循环分块、加双缓冲、做算子融合,甚至手工写过几个关键算子的汇编级调度。每一项都能提升几个百分点,但天花板始终摆在那里,谁都打不破。因为瓶颈是在硬件定义阶段就写进去的,软件再怎么使劲,也只是在已经画好的小格子里跳舞。
这不是某一家公司的问题,而是流程问题。硬件先定稿、软件后适配的模式,放在CPU时代是合理的,因为CPU的指令集和语义足够稳定;但放到AI芯片上,这条路基本走不通。原因很简单:AI芯片是专用电路,它的一切都是为特定计算模式优化的,如果这些模式没在硬件定义阶段和软件团队对齐,后面就是灾难。
1.2 为什么AI芯片比CPU更需要协同
通用CPU的设计哲学是“软件写死假定一个稳定接口”,指令集一旦定下来,软件生态就得全部遵守。AI芯片不一样,它的本质是用电路去实现主流算法里反复出现的那些模式:卷积的滑窗重叠、Transformer里的矩阵乘、归一化层的逐元素操作。硬件团队在做架构决策的时候,本质上是在押注“这些模式长什么样”。
拿什么来押注?最好的材料就是软件团队手里的真实模型、真实输入尺寸、真实精度要求。我见过一个项目,架构师花了大力气把矩阵乘阵列做得非常大,认为算力翻倍性能就翻倍,却忽略了Transformer里的Softmax和LayerNorm这些低计算密度算子全得靠通用向量单元跑,结果整模型端到端效率被这些非核心算子拖垮。
用一个生活化的类比:CPU像是厨房里那把多功能刀,切菜、削皮、开瓶盖都能干,效率中等但全能;AI芯片更像一台专门的面条机,压面条极快,但你非要让它去切西红柿,就得先把西红柿也做成面条的形状。所以问题从来不是“这台面条机压得快不快”,而是“你要切的到底有多少是面条”。软硬件协同设计的第一步,不是选多先进的工艺,而是让硬件团队和软件团队对着同一批模型,得出同一个答案:业务负载到底长什么样。
1.3 协同设计分两个维度
很多人理解的协同设计就是“开会对齐需求”,实际工程里的协同要具体得多,我把它分成两个维度。
架构协同指的是指令集、存储层级、数据通路这些硬件资源和算子映射方式之间的咬合。典型问题包括:支持哪些数据格式、累加器有多宽、片上SRAM分几个bank、DMA能不能做到计算和搬运重叠。这些都是编译器能不能生成高效代码的前提。
实现协同则更靠近物理层面,包括关键路径时序、功耗墙、时钟门控策略与编译调度策略之间的妥协。举个例子:有一次我们在RTL里为了降低频率压力,把关键路径多切了一级流水,看起来只是时序更好收敛,但对编译器来说,每条指令的延迟多了一个周期,原本精心设计的双缓冲流水被这个额外的延迟打乱,调度器重新排之后整体性能掉了8%。这种坑不会出现在任何一本架构教科书里,但每个做过芯片的项目都会遇到。
很多团队只做了架构协同,把实现协同留到流片之后去补救,结果一版一版迭代都在偿还当初没对齐的债。
2. 硬件侧最影响软件发挥的三个决策点
2.1 计算单元组织:脉动阵列、张量核还是向量单元
计算阵列的选择是第一位的架构决策。不同组织方式的差异,用一张表看得很清楚:
| 组织方式 | 计算密度 | 编程难度 | 数据复用效率 | 适合场景 |
|---|---|---|---|---|
| 脉动阵列 | 很高 | 高,需要深度编译调度 | 极高,适合规则数据流 | 大尺寸矩阵乘/卷积 |
| 粗粒度张量核(MAC块) | 高 | 中等,依赖编译器分块 | 高 | 主流CNN/Transformer算子 |
| 通用向量单元 | 中 | 低,手写友好 | 中 | 逐元素算子、不规则访存 |
| 标量单元 | 低 | 低 | 低 | 控制流、地址计算 |
脉动阵列能做到极高的计算密度,因为每个乘加单元只跟相邻单元通信,不依赖随机访存。代价是编译器必须把计算分解成极其规则的块,权重排布、数据时序、累加回收路径都得精确编排。一旦模型里出现Gather、动态形状这类不规则操作,脉动阵列就开始空转。
粗粒度MAC块是更务实的选择。它本质上是一个小规模的计算阵列,由编译器显式地搬运数据并分块执行,灵活性和可持续性能之间有个不错的平衡。我见过不少团队选择32x32或者64x64的MAC阵列,配合一个较小的向量单元处理归一化、激活、逐元素操作,整体效果比单纯追求大阵列要好得多。
选择时要问一个问题:编译器团队有没有能力给这套阵列生成合格的调度?如果没有,宁可选择密度稍低但调度友好的方案。硬件算力做得再高,软件表达不出来,等于白做。
2.2 存储层级:谁搬数据、搬多少
存储体系是决定软件优化空间的最大单一因素。片上有多少SRAM、分几个bank、DMA有多少通道、片外带宽有多少,这套组合直接决定了软件能把数据重用做到什么程度。
我常用一个具体计算来说明这个问题。假设计算单元是32x32的MAC阵列,频率1GHz,跑一个M=N=K=1024的INT8矩阵乘:
- 总计算量是 2 x 1024 x 1024 x 1024 = 2G FLOPs。
- 矩阵A和B各约1MB,输出C约1MB,如果所有数据只搬一次,访存量是3MB。
- 计算时间大约0.98毫秒,如果片外带宽是10GB/s,3MB的搬运只要0.3毫秒,理论上可以实现计算受限。
但如果软件不做数据重用,而是每个小分块都从DDR独立搬数据,按32x32的块来算,需要搬的总量会膨胀到96MB左右,访问时间9.6毫秒,是计算时间的十倍。算力再高也被访存储量活活拖死。
所以硬件团队必须给软件提供足够大的SRAM来放权重的可重用块。我见过不少项目在“堆算力”和“加SRAM”之间选择了前者,结果整模型效率一塌糊涂。加密存储的成本更高,但对真实负载的收益往往比算力翻倍更实在。SRAM容量不够,编译器就算把循环分块和各种复用策略写到极致,也没有用武之地。
2.3 精度与数字格式:硬件面积和量化策略互相拉扯
精度设计是硬件和软件博弈最密集的地方。INT8、FP16、BF16各自的硬件代价差异很大,面积、功耗、动态范围、尾数精度各有权衡。这里我想强调一个很多人忽略的细节:累加器宽度。
INT8乘法结果的范围是-16384到16384附近,如果累加器只做INT16,累加两三个数就可能溢出。如果我们做一个常用的卷积计算,一个输出点需要累加64个乘积,这个累加范围远远超过INT16的表示能力。硬件如果只给INT16的路径,软件就必须频繁插入量化和截断操作,性能掉落非常明显。成熟的架构会提供INT32累加路径,让软件可以放心地做长累加,只在最后写回时按需截断。
权重的量化粒度也是一个容易撕扯的点。软件量化可以做per-tensor或per-channel,per-channel精度高但需要硬件在计算路径上支持少量的逐通道修正。如果硬件只支持per-tensor,模型精度损失会让软件团队反复调参,还不一定调得回来。
还有一个概念叫“计算格式”和“存储格式”分离。权重可以按INT8存入DDR,读进片上之后按INT8计算,但累加器按INT32跑,写回SRAM时再按软件指定的round策略回到INT8。这种分离设计给了软件很大的回旋空间,但它必须在硬件定义阶段就做进去,后面补几乎不可能。
2.4 流水线能力:异步DMA和计算重叠的硬件基础
这个决策点很容易被第一次做AI芯片的团队漏掉。指令集里如果只有LOAD、COMPUTE、STORE同步指令,没有异步DMA队列,没有完成通知事件,软件想做“搬运下一块数据的同时计算当前块”的流水,就会完全表达不出来。
我见过一款芯片,硬件团队觉得“反正编译器会调度”,结果发现指令模型里DMA操作一启动就会阻塞取指,直到搬运完成才放行下一条指令。软件团队想尽办法也做不到搬运和计算重叠,最后的实测性能几乎打了个对折。
硬件侧至少需要提供三个能力:独立的DMA请求队列,允许软件连续下发多个搬运任务;完成事件或中断机制,让计算单元知道某块数据已经就绪;存储器的多缓冲别名机制,让软件可以用两块或三块缓冲交替装载,而不是等一块用完了才能开始下一段搬运。这些看起来都是“小功能”,但它们是软件性能的地基。没有这个地基,什么调度算法都是空中楼阁。
3. 软件侧真正在做的事情:翻译与编排
3.1 从计算图到硬件指令的层层翻译
软件侧的工作核心是两层:把模型“翻译”成硬件能执行的形式,再把翻译结果“编排”到流水线上。翻译过程通常经过多层IR:
- 框架层:计算图,节点是卷积、矩阵乘、激活。
- 算子层:一个节点对应一个或多个kernel。
- 循环层:把kernel展开成多重循环,决定数据的遍历顺序。
- IR层:进一步细分为循环IR、内存IR、指令IR。
- 汇编或驱动层:生成目标硬件的最终指令流,处理寄存器分配、DMA下发、同步屏障。
每一层都可能引入额外开销。比如框架侧默认的数据布局通常是NCHW,但硬件最喜欢的可能是分块排布的NC1HWC0,如果编译器不插入布局转换,每个算子访问的效率就会很低;插入布局转换,又意味着额外的内存搬运。这个取舍没有标准答案,只能根据硬件存储结构和模拟器结果来定。
翻译过程的另一个核心是把每个算子拆成“搬运、计算、写回”三个阶段,然后想办法把这三个阶段在不同数据块上重叠。这是软件性能优化的主要来源,也是后面讲到双缓冲的原理基础。
3.2 算子融合:为什么值得做,需要什么前提
算子融合大概是软件侧最划算的优化之一。一个卷积后面跟着批归一化、ReLU、残差相加,如果硬件不支持融合,每个中间张量都要写回DDR再读一次。
我算过一笔账:一个56x56x64的INT8特征图,单写回再读一遍就是约400KB的额外流量。一张典型的残差网络里有几十个这样的结构,累计的DDR读写量非常可观。融合之后,卷积输出留在片上SRAM,直接遍历每个元素做scale、shift、ReLU、残差相加,一进一出省掉大量片外流量。我实测过一个项目,仅仅做了批归一化加ReLU的融合,端到端推理延迟就降了10%左右。
但融合不是万能的,它对硬件有明确前提:MAC单元和向量ALU之间要能直接访问同一块SRAM,并且要有同步机制保证数据的生产-消费顺序,否则编译器只能保守地插入内存屏障,融合收益被同步开销吃掉。硬件定义阶段如果不给软件留出这个口子,软件团队再想做融合也做不了。
3.3 数据布局与多核调度的隐藏成本
数据布局是最容易被低估的优化点。主流的软件栈会用框架默认的布局,但硬件真正高效的模式往往是某种分块布局。把NCHW格式的权重重排成硬件友好的tile-major格式,这个过程本身就是一个算子在跑。如果碰巧硬件还要求权重按特定方向重排,layout转换的代价可能占到总时间的十分之一以上。
我提醒做架构的人一句:当你在吸引一个硬件能力时,一定要让软件团队同时评估“如果要使用这个能力,需要在数据布局上付出什么转换成本”。有些硬件能力名义上很强,但软件为了适配它必须先跑一遍昂贵的重排,整体收益最后是负的。
多核调度的难点在于任务切分粒度和同步开销。核数越多,同步代价越高。最典型的“尾部效应”:一个算子算完,分配给8个核的任务,前面7个核都结束了,最后一个核还在跑,整体效率等于被最慢的那个块拖住了。解决办法是让调度器支持把算子切得更细,让负载均衡更平滑,但这需要硬件提供分块级的同步原语,而不是只在算子级别提供barrier。又是硬件决定软件空间的例子。
下面是一段粗略的双缓冲调度示意,实际上高性能调度器还要处理bank冲突、多核划分与事件依赖,远比这个复杂:
def schedule_tiles(tiles, hw): for i, tile in enumerate(tiles): if i + 1 < len(tiles): hw.dma_load(tiles[i + 1], buf[(i + 1) % 2]) if i > 0: hw.dma_store(tiles[i - 1], buf[(i - 1) % 2]) hw.compute(tile, buf[i % 2])这段代码里只有两级缓冲,真实设计里常有三级流水:load下一块、compute当前块、store上一块同时进行。硬件要是只能提供两级缓冲,那软件的流水深度就只能跟着降。
4. 一次软硬件协同设计的完整流程复盘
4.1 第一步:从热点分析出发,而不是从架构参数出发
模拟项目X的架构定义阶段,我们做了一件事:把5个有代表性的模型放到性能模型里跑profile,看清楚算子分布。表格大概长这样:
| 模型类型 | 计算密集算子占比 | 逐元素/规约算子占比 | 存储器受限算子占比 |
|---|---|---|---|
| 残差类CNN | 75 | 15 | 10 |
| 轻量移动卷积网络 | 60 | 24 | 16 |
| Transformer编码器 | 70 | 8 | 22 |
这个表告诉我们两件事:矩阵乘和卷积是不可动摇的核心;但逐元素操作也不是能忽略的尾巴。如果芯片只有MAC阵列而没有灵活的向量单元,卷积后面的归一化和激活就会把阵列拖入功耗深渊,算力再高也发挥不出来。
热点分析的另一层价值是帮助团队在“提高算力”和“提高数据供给”之间做判断。我们当时看到多数算子的arithmetic intensity其实不够高,于是把更多的预算放在SRAM容量和带宽上,而不是一味扩大MAC阵列。
4.2 第二步:联合搜索硬件参数与软件策略
在这个阶段,我们让架构师和编译器工程师坐在同一张桌上,用周期近似模拟器做参数扫描。扫描的维度包括:
- MAC阵列尺寸:16x16、32x32、64x16。
- 片上SRAM容量:512KB、1MB、2MB。
- 编译器循环分块大小:8、16、32、64。
- DMA突发长度:32B、64B、128B。
- 是否启用算子融合和分块级同步。
每一组配置都要在模拟器上跑完测试模型集,记录平均算子利用率和整模型端到端利用率。这组数据后来被我们内部反复引用:
| 配置 | 平均算子利用率 | 整模型端到端利用率 |
|---|---|---|
| A:32x32阵列 + 512KB SRAM + 分块16,无融合 | 71% | 52% |
| B:32x32阵列 + 1MB SRAM + 分块32,启融合 | 92% | 81% |
A到B之间没有动MAC阵列,只是加了SRAM、改了软件分块策略、开了融合,整模型效率提升接近30个百分点。这个结论看起来简单,但团队里不少人在项目初期是倾向于“把算力翻倍”的。预算翻倍的那颗芯片如果按A配置做,可能流片回来之后软件团队又会陷入救火的循环。
4.3 第三步:指令模拟器、FPGA、样片三级验证
协同设计的验证路径,我用三级跳来形容。指令模拟器先验证指令语义和基本性能趋势,速度快,可以跑完整模型。FPGA做系统级集成验证,验证DMA路径、中断、时钟域、复位这些真实系统问题,但FPGA频率低,性能数据只能按cycle数来衡量,不能直接把秒数推成主频。样片最接近真实,但可观测性最差,几乎完全依赖片上性能计数器。
我建议把周期近似模拟器当作性能准绳,FPGA做功能验证,两者结果互相校验。我们有一次发现模拟器预测和FPGA测量差出20%以上,后来定位到原因是FPGA上DDR控制器的仲裁策略模拟器没建模。这种“第三层验证”能逼着性能模型不断修正,越到后期越值钱。
4.4 流片后的第一次性能救援
模拟项目X回片后,跑一个基础Transformer编码器时,某层矩阵乘的利用率只有45%。用性能计数器一查,ALU的空闲周期反而很高,DMA一直在忙,stall集中分布在一个特征上:上一算子所有输出都写完了才发完成事件,下一算子的DMA小组才开始灌入数据。同步粒度是算子级的,太粗了。
修复方案是把同步粒度从“算子”拆到“tile分块”。每个tile完成后,下一算子就能立刻开始搬这一块的输入,而不必等整个算子结束。为了保证一致性和边界正确,我们加了分块级的barrier。改动在编译器侧进行,硬件没有动,利用率从45%拉到了82%。这就是协同设计最好的回报:硬件已经定型,但软件对硬件同步语义的理解深入一格,就能挖出这么多性能。
5. 协同时代最容易踩的坑
5.1 峰值算力陷阱
标称20 TOPS和真实可持续算力不是一回事。热设计功耗、供电纹波、频率保护,任何一个因素都会让实际运行频率往下掉。我们实测过不少芯片,持续跑矩阵乘时,因为功耗墙触发降频,可持续算力可能只有标称的六成。
软件侧可以做的补救是功耗感知调度:把高功耗算子和低功耗算子交错排布,避免整个die同一时间满负荷运转。硬件侧则要提供电流和温度计数器,让调度器有感知的依据。没有这些计数器,软件只能靠猜,猜错的结果就是降频和性能抖动。
5.2 内存带宽的“理论值”只是参考
DDR的理论带宽数字看起来很够用,实际有效带宽要打不少折扣。刷新开销、bank冲突、读写切换,每一项都可能吃掉一些效率。DDR4-3200的理论带宽超过50GB/s,但连续读写的有效带宽通常在70%到80%,随机短传输甚至只有一半。
软件团队写DMA调度时,一定要按有效带宽设计缓冲区大小和流水深度,而不是按理论值。硬件团队也尽量让片内接口支持outstanding请求和多通道并发。突发长度至少要覆盖64B以上,否则传输效率会一路向下。
5.3 量化精度与硬件舍入策略不一致
这是软硬件协同设计里最阴险的坑。软件团队在浮点模拟器上验证量化模型,认为INT8下精度损失只有0.3%。到了真实芯片上一测,精度掉了2%。为什么?因为硬件的舍入模式和软件模拟器的假设不一样。
有的硬件在MAC阵列输出写入SRAM前会做四舍五入到最近偶数,有的直接截断,有的还会因为某种低功耗优化临时切换舍入模式。软件模拟器如果不知道这些规则,量化模型在模拟器上越准,到了芯片上反而越偏。我们在项目里定了一条铁律:架构文档必须附带“各数据通道位宽与舍入模式表”,软件模拟器里必须实现与硬件一一对应的行为,并且每改动一次硬件,这个表就要更新,模拟器也要同步更新。
5.4 两个团队的语言壁垒
硬件工程师说“信号、时序、bank”,软件工程师说“算子、张量、框架”。这两个语言体系如果不主动对齐,所有协同设计方法都是空谈。我们后来建了一张共同数据字典,编译器的中间表示和RTL里的信号名一一对应,同一个名称在两边文档里指向同一个东西。
流程上也有一个建议:性能模型应该由既懂硬件又懂软件的集成工程师统一维护,每周做一次性能模型校准会议。任何架构改动都必须附带软件侧的改动清单,不允许“硬件先freeze、软件再进场”的流程继续发生。这是组织层面的协同,但它的重要性不亚于任何技术决策。
6. 工具链与可观测性:决定芯片能不能用起来的最后一公里
6.1 模拟器的三层选择逻辑
模拟器的选择不是越精确越好,而是看当前阶段需要回答什么问题。
| 模拟器类型 | 速度 | 时序精度 | 主要用途 |
|---|---|---|---|
| 功能模拟器 | 每秒千万级IR | 无 | 功能验证、软件早期开发 |
| 周期近似模拟器 | 每秒万到十万条IR | 流水、存储、功耗模型 | 性能预测、架构探索 |
| RTL仿真 | 极慢 | 精确到cycle | 硬件时序与行为验证 |
架构探索阶段最忌讳直接用RTL仿真去跑完整模型,那会慢到让你放弃思考。更合理的做法是“采样+细粒度分析”混合:用功能模拟器跑完整应用确认行为,把热点片段拿去做周期近似模拟器细粒度分析。我们后来发现,80%的性能问题都集中在不到20%的算子里,把精力花在这些算子的精细分析上,效率远高于均匀撒网。
还有一个实操建议:在架构探索阶段就要定义好性能模型的API,后面每次改动都通过API接入,而不是每个架构改动都重新写一遍模拟器。否则性能模型会变成一次性代码,失去持续校准的价值。
6.2 性能计数器的设计时机
性能计数器必须在RTL早期就定义好,后期补会让硬件调试痛苦十倍。最小集合至少包括四类:
- 计算stall周期:等待MAC阵列或其他计算单元。
- 存储器stall周期:等待DMA、片外访存或bank冲突解除。
- 依赖stall周期:等待某个事件或屏障。
- 功耗/降频周期:等待频率爬升或功耗回落。
把这些计数器按kernel名和tile号聚合出来,再配合一段简单的Python脚本生成热图,定位问题就会快很多。这里给一个非常简化的示例,说明实际分析脚本的思路:
import csv from collections import Counter stall_type = Counter() with open("perf_cnt.csv") as f: for row in csv.DictReader(f): stall_type[row["stall_type"]] += int(row["cycles"]) for name, cycles in stall_type.most_common(): print(f"{name}: {cycles}")真实的trace文件里会带block号、算子名、tile坐标,聚合维度更多。但核心思路是一样的:先把stall分类,再往下钻到具体的算子、具体的tile、具体的硬件状态,性能问题基本无处可藏。
6.3 死锁与数据竞争的调试思路
芯片上的死锁调试比纯软件死锁难在不能随便加日志,所以我通常按这个顺序做:
第一步,把所有异步特性关掉,全同步模式跑一遍,确认基础功能正确。第二步,打开异步DMA,这时候如果出错,优先怀疑DMA完成事件和数据写回的顺序问题。硬件里DMA的中断通知有时比数据真正落位提前,一定要有内存屏障保证读写顺序。第三步,打开多核,重点检查共享缓存bank和环形缓冲的伪共享,两个核同时写相邻地址会让整块缓存行不断失效。第四步,打开低功耗模式,检查时钟门控是否把DMA的请求给“饿死”了。
每一步都要构造最小复现用例。一个看起来随机的数据竞争,缩小到最小case之后往往就是一个同步原语位宽不够或者事件id分配冲突。这个调试链条很长,但每做一次,对软硬件接口的理解就深一层。这也是为什么我一直强调可观测性设计:计数器、trace buffer、断点指令,这些看起来不产生算力的功能,实际上最能缩短回片后的调试周期。
做了几轮AI芯片软硬件协同设计,我越来越确信一句话:没有糟糕的硬件,只有没被软件吃透的硬件;反过来,再强的软件也救不了一颗带宽和存储配错的芯片。软硬件设计本质上是在同一条约束链上找平衡,架构师必须能读懂编译器输出的IR,软件工程师必须清楚硬件流水线上每一级在做什么,这个要求听起来高,但做不到的话,项目就会陷入无休止的互相甩锅。
最后分享一个小技巧:如果你正处在一个“硬件差不多了、软件还没开始”的项目里,第一件事不是优化算子,而是先跑一遍端到端profiling,把每个算子从当前硬件上能拿到的算力上限写出来。这个过程通常只需要几天,但它能让你立刻看清整个项目的天花板在哪,后续所有精力都可以放进最值得的地方。别迷信峰值算力,别把软件团队当成硬件定型之后的救火队,这两条真的值很多流片费。