ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI芯片软硬件协同设计全解析:从算力目标到工具链实战

AI芯片软硬件协同设计全解析:从算力目标到工具链实战 做芯片这行的人都有体会AI芯片和传统芯片最大的不同不是你多画了几个乘法器而是从第一天起软件脑子里想的东西和硬件脑子里想的东西就不在同一个维度。AI芯片的软硬件设计本质上是把算法工程师写的网络模型、芯片工程师画的寄存器传输级逻辑、编译器工程师做的优化pass都拉到一条船上让它们朝同一个方向使劲。这篇文章是系列的第一篇我会从整体思路讲起把AI芯片软硬件协同设计这件事拆开来说清楚适合刚接触AI芯片的架构师、嵌入式软件开发、算法工程师也适合准备入行但不知道怎么下手的同学。这里我先把话说在前面AI芯片最难的点大概率不在硬件的峰值算力而在于软件栈能不能端到端跑通以及最终系统的能效比能不能落在可接受范围内。硬件方案做得再漂亮编译器不会表达调度器不灵活流片回来只能当摆设。软硬件协同设计就是为了尽量避免这种悲剧。1. AI芯片软硬件设计到底在做什么1.1 先想清楚芯片算什么算法要什么AI芯片的“AI”不是指芯片自己能学习而是指它要被用于运行AI算法尤其是深度神经网络。早期大家图省事直接用CPU去跑卷积后来发现慢得离谱功耗也压不住于是开始给算法“开小灶”在芯片里专门做一堆乘法器、加法器、累加器让它们按卷积或者矩阵乘法的规律排布数据喂进去结果吐出来。这就是AI芯片的基础形态。但这里有个非常关键的认知AI算法每个月甚至每周都在变。今天大家还在调ResNet明天可能就换成了Transformer再过一阵子又开始研究状态空间模型。如果芯片只针对某一种算子做死硬件算法一换芯片就废了。所以AI芯片的硬件设计一定要留出可编程的余地而软件栈的任务就是把新算子“翻译”成硬件能执行的一连串指令和调度方案。这叫一套组合拳缺一不可。从硬件角度最核心的资源是计算单元、片内存储、外部访存带宽和互联总线。从算法角度最核心的需求是算力、内存占用、访存带宽、时延和精度。软硬件设计做的事就是在这两组需求之间找交点并且把这个交点在面积、功耗、成本、开发周期上做到可接受。1.2 “软硬件协同”不是口号是一连串取舍很多人以为软硬件协同就是把软件和硬件两个团队叫到一起开会然后各干各的。实际上这是工程上最危险的想法。真正的软硬件协同设计是每一层都互相对齐需求算法要这一版网络能在芯片上跑到多少帧硬件决定我要不要为了某个算子增加一条指令编译器决定我该用什么样的数据排布方式去喂计算阵列运行时决定多核之间怎么分配任务。举一个最容易理解的例子卷积运算访存量很大硬件上如果做成朴素乘法器阵列每个输出点都要反复读输入数据片上存储再大也不够用。于是硬件工程师设计了数据复用结构让一个输入数据可以被相邻多个计算单元共享。但问题来了这种复用结构只对特定卷积形状友好。编译器如果不知道这个结构或者没有对应的指令去描述它就只能把卷积拆成最保守的单指令流数据复用完全失效硬件设计的优势全白费。这就是典型的软硬件脱节。所以设计初期的取舍都是在“灵活性”和“效率”之间摇摆。固定功能单元效率高但算法一变就废通用可编程阵列灵活但控制逻辑和片上网络占了大量面积功耗也上去了。软硬件协同设计实际就是不断回答类似的问题这个算子值得为它做专用硬件吗编译器能不能高效地生成它的调度运行时能不能把多核负载均衡每一步都要带着成本意识去做。2. 从需求到规格AI芯片设计的起点2.1 用算法负载分析来定算力目标算力目标不是老板拍脑袋定的也不是拿别家芯片的demo参数抄一遍就行的。它必须从你目标场景里的模型集合出发用一种叫“算法负载分析”的方式算出来。先说算力的单位。大家常说的TOPS代表每秒一万亿次操作但具体是乘法还是乘法加加法各家口径不一样。业内一般用一个MAC乘加两个操作作为基本单位。假设某个卷积层有输入特征图尺寸 $28\times28$通道数 $C_{in}32$卷积核 $3\times3$输出通道 $C_{out}64$那么它的计算量大约是 $28\times28\times32\times3\times3\times64$ 次MAC换算过来大概是1440万次MAC也就是2880万次浮点或定点乘加操作。把网络所有层加起来就得到单次推理的MAC总数。然后设定目标帧率比如边缘摄像头希望实时跑30fps那每秒需要的MAC总数就是单次推理MAC乘30。再考虑一个夸张但现实的因素——芯片实际运行时的平均计算利用率很难达到100%一般能达到40%~60%就已经很优秀了。最终目标TOPS就要按“峰值 TOPS 应用总MAC / 60% / 目标帧率”来匡算。我做过一个很直观的优化经验把ResNet-50跑到500fps级别的边缘芯片单次推理大约3.8G MAC全网络需要的有效算力大约是1.9T MAC/s如果利用率只能到50%峰值就要做到4T MAC/s左右。就是这么来来回回算的。模型单次推理计算量目标帧率平均利用率所需峰值算力MobileNetV3约 0.6G MAC60 fps40%90 GMAC/sResNet-50约 3.8G MAC30 fps50%228 GMAC/sYOLOv5s约 4.4G MAC60 fps45%587 GMAC/s2.2 把访存量算清楚比算力更重要很多团队第一次做AI芯片会把注意力全放在TOPS上结果流片回来发现计算阵列闲得要死数据却堵在总线上排队。访存瓶颈是AI芯片最常见的隐形杀手。神经网络运行过程中权重和中间激活都要不断读写。比如一个卷积层输出256个通道的feature map每个通道大小112×112如果全部放到DRAM里来回搬哪怕DRAM带宽再高也架不住几十层连续读写。所以设计初期一定要做访存量估算。权重访存相对容易算模型所有层的参数总量乘上每帧推理次数。中间激活访存复杂一些和调度方式强相关因为硬件如果能把某几层留在片内SRAM里连续做完就不需要每层都写DRAM。一个好的软件栈通过算子融合、层间融合可以把DRAM访存量降一半以上。我通常会在设计阶段就用Python写一个很小的带宽模型脚本输入各层shape和调度策略输出期望的最小DDR带宽。这个脚本不复杂但价值极大。它能告诉你这颗芯片到底该用DDR4还是LPDDR4X甚至要不要用HBM或者干脆把片上SRAM做大一点。软件和硬件的边界往往就是在这种反复拨算盘的过程中定下来的。3. 硬件架构核心从指令集到计算阵列3.1 计算引擎怎么选脉动阵列还是可重构MAC阵列硬件方案里最受关注的是计算引擎组织方式。目前主流AI芯片内部大致有两派思路脉动阵列和可重构MAC阵列也有些人把它们混合着用。脉动阵列的思路是让数据“流”过一个个处理单元像车间流水线一样上一站算完的数据直接递到下一站。它有规则、省功耗、时钟好收敛特别适合矩阵乘法和规则卷积。但它的缺点是形状固定遇到非规则算子比如padding很多、通道数不规则利用率就会下降。另一种方案是可重构MAC阵列每个处理单元更像一只可编程的小瑞士军刀可以配置成不同拓扑结构灵活性高代价是控制逻辑更复杂面积和功耗会上去。我自己做下来最怕的不是选哪种阵列而是编译器能不能把算子的计算循环“折叠”到阵列上。如果编译器生成的指令流没法充分填充每个计算单元的空闲周期再好的阵列也是白搭。所以架构上要留一些编译器能看到的信息比如块级shape、循环顺序、复用方向否则编译器只能黑盒使用硬件。3.2 片上存储与数据流设计是隐形大头计算阵列不干活的时候问题多半出在存储和搬运上。AI芯片普遍采用多级存储结构寄存器堆、分层SRAM、片上共享缓存、DDR/HBM。每一级的容量、带宽和延迟都不一样。软硬件协同的核心之一就是让数据尽量在低层级里被复用而不是动不动就跑到DRAM。举个例子卷积计算一个输出像素需要周围一个3×3窗口输入。如果编译器能按行分块把相邻几行输入提前放到SRAM里那么同一个输入像素可以被多个输出通道复用。如果程序傻乎乎地每次从DRAM读原始数据总访存量会被放大好几倍。硬件上的DMA控制器、多 bank 的SRAM划分、总线仲裁策略都必须配合这种分块调度。我在这里踩过特别痛的坑硬件设计时SRAM分成了4个bank编译器没意识到bank冲突结果两个核同时访问同一个bank产生大量等待周期整芯片性能直接腰斩。后来在工具链里增加了bank感知的地址分配才把性能救回来。这听起来像软件bug但根子上的问题是硬件设计时没有在总线接口上提供足够的并行访问通道也没有在指令集里暴露bank信息。4. 软件编译器与运行时让硬件跑起来的关键4.1 算子到底怎么落到硬件上AI芯片的软件栈最核心的部分是编译器和运行时它们共同把PyTorch/ONNX模型逐步分解成硬件指令。流程大致是这个样子先把通用模型解析成图做图优化再把图中的每个算子映射成一条或者一组硬件指令然后做内存分配和数据搬运调度最后生成可执行的二进制放到板子上跑。这里最容易产生争论的点是“算子融合”。比如卷积后面一般跟BatchNorm和ReLU朴素做法是卷积算完写回内存读出来做BatchNorm再写回再读出来做ReLU。融合方案则是把BatchNorm的参数离线换算到卷积权重和偏置里在线性层后直接夹一个ReLU单元中间数据可以始终留在片上寄存器里。对于带宽受限的芯片这种融合带来的收益非常明显有时能省掉好几倍的DRAM访问。作为编译器工程师我一般先做一层“规范化”处理把所有形状表达统一再做“切片”和“调度”。具体来说就是看硬件计算阵列尺寸能不能容纳整个卷积如果不能就把通道维度或输出空间维度拆成小块映射到多个计算周期上。调度顺序又直接决定数据复用率。这个阶段如果硬件架构文档里没有把存储层级和访问规则讲清楚编译器组就只能靠猜最后流片回来一堆性能问题。4.2 指令集与中间表示为了让编译器能稳定地生成指令硬件必须定义一套中间表示或者指令集架构ISA。很多AI芯片用类VLIW指令一条指令同时包含多个运算单元、访存单元、DMA控制器的操作。这样做的好处是并行性高调度效率好坏处是编译器压力大指令长度和编码复杂度也高。我常用的一个思路是硬件指令集不做太复杂的操作而是把复杂动作拆成简单的原语。比如“加载数据到SRAM”、“从SRAM搬数据到计算阵列”、“执行矩阵乘”、“把结果写回”。每个原语可以附带一些并行控制和地址偏移信息。编译器负责把这些原语组装成高效的流水线运行时只负责按指令顺序喂给硬件。这样软硬件职责清晰出了问题也好定位。一个典型的运行库流程会包含这些阶段模型解析、离线映射、编译、生成二进制、运行时加载、内存Load/Store、DMA搬移、计算触发、同步等待。为了调试我还会在编译过程中导出中间指令的反汇编视图这样在板子上跑出问题时可以逐条对照指令和硬件信号。4.3 量化与定点数软硬件必须一起过的坎AI芯片为了能效通常不会去做复杂浮点运算而是把网络量化到INT8甚至更低精度。硬件上提供一个乘法器做的是“定点数乘加”但算法模型本来就是32位浮点直接截断必然会掉点。软硬件协同在这里体现在硬件提供饱和截断、非对称偏移、per-channel缩放等支持软件在离线阶段通过量化校准和量化感知训练来减小精度损失。我建议所有做AI芯片的团队在硬件设计阶段就明确量化参数格式。比如per-tensor scale够不够需不需要per-channel激活是否必须是无符号8位这些决定会直接影响编译器在布署模型时插入多少scale计算。如果硬件支持太弱编译器只能在CPU侧做一堆补偿性能立刻打折扣。5. 软硬件协同设计流程实操5.1 硬件还没回来软件怎么先行我的强烈建议是不要等RTL冻结甚至流片回来再搞软件。芯片开发周期动辄一两年软件栈如果没有提前开发芯片一到手大家只能一起对着模拟器发呆。正确做法是在硬件架构定义基本清晰后立刻用一个性能模拟器或者指令集模拟器把软件栈开发环境跑起来。实际项目中我们一般先写一个轻量级的C模拟器模拟每条指令的执行时间和资源占用。这个模拟器不需要覆盖全部时序细节但必须能模拟计算阵列和存储系统的行为。然后编译器生成二进制在模拟器上跑通一个小模型验证指令调度和内存分配逻辑。等RTL环境有了再做协同仿真验证逐步修正模拟器和真实硬件之间的差异。这样做还有个额外的好处它逼着硬件团队提前把指令集和行为描述写成文档而不是等RTL写完之后倒推。文档越清楚编译器组和硬件组的沟通成本越低。我见过太多团队直到RTL freeze前还在改ISA文档结果软件组全线返工这是最贵的时间黑洞。5.2 搭建一个最小可用的开发闭环我们内部一般会搭一个非常朴素的开发闭环包含几个部分模型转换脚本、离线量化工具、编译器命令行、模拟器、板端运行时以及一个简单的profile工具。流程大概是这样的先用ONNX导出训练好的模型然后调用量化工具生成定点权重和配置再执行编译命令生成可执行镜像最后放到模拟器或者开发板上跑。命令通常长这样aipc import model.onnx --output model.aig aipc quantize model.aig --mode int8 --calib calibrate_data.bin --output model_int8.aig aipc compile model_int8.aig --target aipu_v1 --opt level3 --output model.bin--opt level3在我这里表示开启所有能开的算子融合和调度优化。第一次跑通一个最小模型时你看这块板子就像看刚出生的孩子一样亮完LED心里还是没底一定要接着做性能分析。说白了真刀真枪验证一个AI芯片的开发门槛不在能不能把分类模型跑出个正确结果而在能不能在指定的功耗、时延、帧率下稳得住。6. 工具链与开发环境实战6.1 一套可用的AI芯片工具链必须包括什么很多团队以为工具链就是给客户一个编译器其实远远不够。一套能实际用于生产的AI芯片工具链至少要包含下面这些模块。模块主要职责关键点模型解析器把ONNX、PyTorch、TensorFlow等模型读入统一IR保证算子覆盖处理版本兼容图优化器算子融合、常量折叠、冗余消除直接影响带宽和计算效率量化工具校准、混合精度、量化感知仿真降低精度损失编译器算子分解、循环变换、指令调度、内存分配充分挖掘硬件性能反汇编与debug工具检查二进制指令、查看中间状态定位硬件/编译器问题运行库加载模型、驱动DMA、同步计算系统集成效率profiler统计PE占用率、访存流量、耗时明细指导性能优化缺少任何一块客户开发AI应用都会非常痛苦。比如模型解析器覆盖率低很多模型根本导入不了没有反汇编工具一个运行时错误根本查不出是哪条指令在“闯祸”。6.2 调试和性能分析技巧性能分析不能光看“整芯片跑了多少ms”要看每一层每一类资源的具体占用。我会习惯性先看PE利用率再看SRAM命中率和DRAM流量最后看是否有流水线停顿。方法是在模型里给每一层打印独立事件汇总成表格。如果发现PE利用率低优先怀疑两个事一是计算循环被拆得太细数据搬运的指令开销太大二是卷积padding或通道对齐导致上万个计算周期全部空转。解决办法是把通道数对齐到计算阵列的整数倍或者调整循环顺序利用行缓冲复用数据。如果发现DRAM流量异常高就要看算子融合有没有生效。特别是在多个连续卷积层之间如果编译器没做层间融合每一层都把中间结果搬出去再搬回来DRAM流量至少翻倍。这个优化是纯软件收益而且效果立竿见影我每次做新芯片适配都会先检查这块。7. 常见问题与排查实录7.1 性能怎么提不上去这是我在项目里被问得最多的一个问题。排查顺序我会严格这么来先看是不是访存瓶颈再看是不是量化精度问题然后再查计算阵列利用率。现象可能原因排查动作单帧耗时过长DRAM访问过多查看DRAM带宽计数器和每层SRAM命中率某层耗时异常计算阵列空闲等待数据检查DMA请求是否被仲裁阻塞整芯片利用率低指令调度串行化看流水线是否有多条指令同时活跃结果正确但性能差编译器生成了低效指令反汇编人工检查关键循环有一次我们做视频检测芯片峰值算力够但实际帧率只有预期的一半。查profiler发现卷积层全部正常但连续三四个卷积层之间把中间特征图写回了DDR。原因是指令集里缺少“零拷贝”的层间传输指令编译器为了省事直接落DRAM。后来增加了一个片上传输原语性能立刻翻倍。7.2 量化后模型掉点怎么办量化导致掉点先别急着把整个网络改成FP16。我会先查是不是校准数据集太单一导致scale估算偏差大再查是不是某个层的激活数值范围极端集中比如存在很大的离群点如果这两个都正常再考虑混合精度。硬件一般会提供部分算子以高精度计算的能力软件层面可以显式指定某些层使用FP16、某些层使用INT8。这种混合量化在实践里效果很好但要注意扫描工具别把网络结构当流水账要尊重数据流依赖关系。另外还有一个容易被忽略的点量化是在训练后离线做的但如果训练时没有做量化感知训练权重分布和量化尺度之间可能已经出现偏差。最简单的补救是加入一个微调阶段把量化噪声的压力用训练再吸收一轮通常能找回大部分精度损失。7.3 地址对齐和存储冲突问题AI芯片对地址对齐要求特别严格DMA突发传输长度、SRAM bank的划分、跨步地址都有一套规则。编译器分配内存时如果无视这些规则很可能出现奇奇怪怪的非法访问或者性能抖动。我遇到过最隐蔽的问题相邻两层的中间张量被分配到同一个SRAM bank集合虽然地址不同但bank冲突导致访问时间变长整机功耗和延迟同时升高。解决方法是把内存分配器的策略从“按需分配”改成“地址绑定bank感知分配”。这一步最好在硬件设计阶段就把bank数量和冲突规则文档化软件按规则生成映射表。如果你正在开发一款新芯片我强烈建议硬件组尽早提供一份“存储访问约束文档”别让软件组去读调试波形反推规则。8. 写在后面做AI芯片软硬件协同的一点体会我做了几颗AI芯片之后最大的感受是真正拉开项目差距的不是谁家乘法器更先进而是软件栈和硬件架构的契合程度。很多团队在PPT上讲“软硬协同”实际工作中却把硬件组、编译器组、算法组活活拆成三个部门出了问题互相甩锅。一个简单有效的做法是让架构师、编译器负责人和算法负责人共同签字技术决策任何涉及到指令集、存储层次、量化格式的设计变更都要三方同时评审否则就别碰。另外如果你们团队还没有自己的性能模型请一定先写一个。哪怕很简陋也能帮你在早期就发现哪些算子不划算、哪些调度方式会撞墙。我在实际项目里吃过“硬件定义一年软件适配三个月”的教训也经历过“软件提出指令需求硬件秒改设计”的爽快。前者让人崩溃后者才是一个团队该有的节奏。这个系列接下来我还会聊指令集设计的具体细节、编译器怎么实现算子融合、以及怎么从边缘应用一步步反推芯片规格。如果你也在做AI芯片软硬件相关的工作欢迎带着自己的项目经验来交流这行里最值钱的永远不是某个IP而是踩坑后沉淀下来的判断力。
返回列表