首页 / 资讯中心 / 文章详情

AI芯片软硬件协同设计全流程解析:从架构到量产调优

AI芯片软硬件协同设计全流程解析:从架构到量产调优 ★ FEATURED ARTICLE
1. 项目概述AI芯片的软硬件设计到底在解决什么问题做AI芯片这行最容易被问的一句话是“你们到底是做硬件还是做软件”答案通常是都做而且最难的部分恰恰是软硬件之间那条模糊的边界。我做AI芯片软硬件协同设计已经有几年时间这个主题我从硬件架构、编译栈、运行时调度一路做到量产芯片的现场调试踩过的坑比写过的代码还多。这篇文章是系列的第10篇也是我个人觉得最值得沉淀的一篇——因为它不再讲单个模块怎么设计而是把整个系统的设计思路、软硬件接口、验证方法和调优经验串起来讲清楚一个AI加速器从概念到跑通模型的全过程。这个主题适合三类读者一是刚入行、想搞懂AI芯片整体框架的工程师二是做算法模型部署、想理解硬件约束的算法工程师三是从传统SoC设计转行做AI加速器的老芯片人。这篇文章不会贴大段RTL代码那是后端和验证团队的事我重点讲的是设计决策背后的为什么以及那些在文档里查不到、只有实际流片后才能体会到的教训。AI芯片软硬件设计之所以复杂核心在于目标和约束截然不同。软件工程师希望编译器和运行时越灵活越好算子怎么组合都行硬件工程师希望把存储和计算资源压榨到极致最好数据能像流水线一样精准地在每个周期被消费掉。这两者的矛盾就是整个项目最核心的工程问题。本文要讲的就是如何在这种矛盾中找到平衡点以及在这条路上我总结出来的那些真正有效的办法。2. 硬件架构确定计算核心与数据流的四种思路硬件的计算核心选型是整个项目最早也是代价最高的决定。很多团队在这个环节靠PPT讲故事但真正动手做的时候会发现选择一个计算架构等于选择了一整套编程模型、编译器策略和性能边界。我在参与某跨平台AI加速器项目时第一轮架构评估就花了整整两个月因为几个选项各有利弊谁都没法拍板说哪个绝对正确。2.1 脉动阵列、SIMD还是可重构阵列脉动阵列Systolic Array是当前AI推理加速器里最常见的计算核心形态。它的核心思路是让数据像心跳一样一拍一拍地在处理单元PE之间传递每个PE只做最简单的乘加运算。这种方式的最大优势是数据复用效率极高——权重或激活值可以在阵列里流动复用访存次数大幅降低。举个例子在一个16x16的脉动阵列上跑卷积权重从左边进入激活值从上方进入中间每个PE的乘加结果向下累加这比每个周期都从存储里取数要省太多功耗和带宽。但它的问题也很明显如果卷积核大小、步长、通道数与脉动阵列的物理尺寸不匹配计算效率会断崖式下降。我见过一个真实的案例某团队把脉动阵列设计得很好跑3x3卷积效率接近90分但一跑深度可分离卷积Depthwise Convolution就只剩30多分因为这种算子的数据流形态和脉动阵列天然不搭。于是他们不得不在编译器里面做一大堆算子分解和重组把不规则的算子硬凑到阵列上编译时间从几分钟变成几十分钟。SIMD单指令多数据向量单元则是另一条路偏传统像CPU和GPU的ALU那样按向量宽度并行执行。这种方案灵活性强几乎什么算子都能跑但存储压力和指令开销大极致性能上通常比脉动阵列差一截。很多芯片走的是混合路线大算子走脉动阵列不规则算子走SIMD单元。这种设计可以兼得但代价是编译器要维护两套代码生成路径调度复杂度直线上升我在实际项目中体会特别深。2.2 稀疏计算要不要做硬件支持稀疏计算是我特别想说的一点。模型剪枝和权重重构在算法侧已经很成熟但在硬件侧稀疏支持是一个成本和收益极其不对等的选项。理论上稀疏计算可以跳过一半以上的乘加操作省功耗、提吞吐。但硬件要精确知道哪些权重是零、哪些激活值可以跳过需要额外的索引存储、控制逻辑和动态调度机制面积开销并不小。我在一个图像处理Demo项目里做过一次对比。同等算力面积预算下非稀疏设计的峰值算力比带稀疏支持的版本高出约25%但在实际跑剪枝后的模型时稀疏版本的内存带宽压力更小整体能效反而更高。结论是什么稀疏支持不是万能的它只有在模型剪枝率稳定超过50%、并且编译器能够把稀疏格式的排布真正利用起来时才值得做硬件实现。否则硬件上那一堆用于跳过零值的控制逻辑全是白烧的功耗。2.3 存储层次和数据流方向的设计存储是整个AI加速器真正的隐形霸主。很多人低估了SRAM的布局和容量对性能的巨大影响只看重峰值算力。实际上片上SRAM的读写带宽、Bank冲突概率、与计算阵列之间的连接方式是决定实际利用率的核心。我记得我们做第一款芯片仿真的时候功能仿真全部通过但跑到Cycle级性能仿真时发现算力利用率只有理论峰值的40%。定位了一个星期最终发现是激活值缓存和权值缓存的Bank结构互相冲突两个单元同时访问同一Bank导致频繁的stall。数据流方向同样是设计时就要定死的决策。是权重静止Weight Stationary还是激活值静止Activation Stationary决定了数据在片上存储和计算阵列之间搬运的主要方向。权重静止更适合那些权重复用率高的算子比如全连接层激活值静止则更适合通道数多、激活数据量大的CNN层。我们当时综合考虑了一组目标模型的实际统计特征最终选择了权重静止为主、激活静止为辅的混合数据流代价是控制逻辑更复杂调度器需要维护两套数据搬移模式。3. 软件栈构建让硬件真正跑起来的隐形工程如果说硬件是AI芯片的骨架软件栈就是神经系统。芯片流片回来能不能在三个月内把主流模型跑起来看的完全是软件团队的本事。这个板块我想讲讲编译器、运行时和量化工具这三件套以及它们各自难在哪。3.1 编译器的两层抽象与算子映射AI芯片的编译器本质上做两件事第一件是把深度学习框架的计算图做变换优化第二件是把图中的每个算子映射到硬件支持的原语上。这两件事最好是分开的两层结构不然一个大模型下来编译时间根本没法看。我比较推荐的做法是采用多层中间表示IR设计上层IR负责图优化处理算子融合、死代码消除、常量折叠这类与硬件无关的逻辑下层IR贴近硬件抽象层负责处理循环分块Tiling、内存分配和指令生成。算子融合是一个特别有价值的优化。举个例子一个卷积后面跟一个ReLU再加一个残差连接如果软件能把它融合成一个整体那中间那两次数值搬运就全省掉了。在硬件上我们专门设计了支持融合算子的执行流水线让计算单元的写回能直接作为激活函数的输入不需要绕一圈到片上存储。算子映射的核心是Tiling。一个大的卷积计算如果直接放到有限的片上存储里执行早就溢出了。所以编译器需要把它切成一个个小块让每个小块的数据刚好能塞进片上SRAM。这个切分的大小和顺序直接影响数据搬运次数。我在实际调优中总结的规律是Tiling尺寸不应该追求某个指标的最大化而应该以循环内数据复用率为优先。一个2KB的Tile如果能让某个权重在这组数据里被复用8次就比一个4KB但只复用4次的Tile好得多。3.2 运行时调度与多任务并发运行时Runtime负责的是将编译产物排队发到芯片上执行同时管理内存和中断。看起来简单但多任务并发时问题就来了。多个模型的请求同时到来硬件资源怎么分是时间片轮转还是把不同算子分配到不同的计算单元并行执行我们当时做了一个很小的实验同一张芯片同时跑一个人脸检测模型和一个超分模型。不做任何调度优化的时候两个任务的总吞吐是1.6倍的单个任务优化调度后把检测模型里大量的卷积与超分模型里的像素重排分配到不同算力单元上吞吐提升到了1.9倍。这0.3倍的提升就来自于运行时对任务粒度的把控能力。最理想的调度是在指令级做到算子间依赖的精确分析但这要求编译器把数据依赖信息透传给运行时而现实中绝大多数编译器不愿意做这种深度的接口绑定。3.3 量化与精度管理的操盘经验量化是我最想吐槽又不得不认真对待的部分。硬件上支持INT8和INT16精度软件侧就需要把FP32模型转成低比特表示。这个流程看起来简单——跑几个校准集统计一下激活值的范围算出scale和zero point然后按公式量化。实际上量化的失败往往发生在人最意外的位置。Normalization层是一个典型的坑。卷积输出后接BatchNorm在训练时是独立算子但在推理时通常会被融合进卷积的权重里。这个融合在FP32下没有问题但融合后的权重范围往往比原来大好几倍。如果你在融合之前就完成了量化参数统计那用INT8去表示融合后的倍数权重精度直接崩了。正确的顺序应该先融合再统计再量化。这种顺序问题是文档上不会写、只有亲手踩过才会记住的细节。我给量化模块定了一条铁律任何算子的量化参数统计必须基于真正在板上运行的优化后计算图而不是原始模型的中间表示。凡是违反这条规矩的都付出了流片后重新改代码的代价。4. 软硬件协同验证不流片也能抓出90%的Bug硬件没回来之前软件不能干等。协同验证就是在这个阶段把未来一半以上的问题提前暴露出来的手段。说实话很多队伍的验证策略不过是仿真通过就完事结果板卡一上电各种时序、接口、调度问题一起涌出来。真正有效的协同验证要在三个层次同时推进。4.1 指令集模拟器与性能模型指令集模拟器ISS是软件团队最先拿到手的工具它不关心时序只关心功能是否正确。软件团队可以在上面先调通模型的计算图编译和运行保证算子逻辑没有功能错误。但我们很快发现单纯的功能仿真有个致命问题它无法暴露因为存储带宽竞争、流水线停顿导致的性能问题。所以性能模型Performance Model必须并行开发。它采用Cycle近似的方式对每类指令给出不同周期的消耗模型内存访问根据Bank冲突和带宽占用打分。我们的性能模型精度做到和RTL仿真在核心算子上误差在5%以内这样软件团队能非常早地发现调度策略是否合理。很多大厂的芯片团队都有一套自己的性能模型工具链这套东西对软件开发效率的提升怎么强调都不过分。4.2 FPGA原型平台与真实数据流性能模型终究是模型等到RTL初版稳定后FPGA原型平台就派上用场了。我们使用的能力级FPGA平台集成了DDR模拟存储接口把整个加速器的RTL代码跑在几百兆赫兹上虽然相比最终芯片的频率低不少但数据流是真实的。这个阶段测试的价值在于软件栈第一次接触真实的硬件行为很多在仿真模型里被掩盖的边界条件全都暴露了出来。让我印象最深的一个Bug是写回调指针的错误。软件侧给运行时传了一个地址本意是让DMA把计算结果写到某个输出缓存不巧这个地址在硬件中被解释成了某个控制寄存器的写地址。功能仿真里这个地址是合法的所以没有任何报错但在FPGA平台上这一写直接把DMA的状态机打乱了后面所有传输都停在等待状态。这种问题只有真实的地址译码逻辑和真实的总线协议才会触发纯靠仿真几乎是发现不了的。4.3 软硬件接口定义留足冗余接口是软硬件协同里最需要花心思的部分。寄存器定义、中断号分配、DMA描述符格式、内存屏障含义所有这一切都必须在设计阶段就确定并且有一份人人都能看懂的统一文档。我见过最痛的接口事故硬件团队定义了一个深度学习专用指令期望软件通过寄存器写入一个64位立即数但软件团队以为这个立即数存放在内存描述符中结果两边的数据一直对不上。排查了三天最后发现就是文档里一句话没写明白。我现在的习惯是所有软硬件接口必须做到三点第一每个字段都有明确的编码方式和默认值第二接口版本号必须固化变更必须走正式的评审流程第三给驱动提供一层薄薄的抽象让上层软件永远不直接操作物理寄存器。凡是嫌麻烦省掉这三步的项目后面都会在Debug阶段付出数倍的时间代价。5. 调优实战从60分到90分的四个关键动作芯片和软件Stack都通了之后整个项目的重头戏就从开发转向了调优。系统能跑起来和系统能跑得快中间隔着一条巨大的鸿沟。这条鸿沟必须靠数据说话而不是靠感觉。以下是我在实际项目调优中反复验证过的四个关键动作按逻辑顺序逐个展开。5.1 确定性能基线并绘制剖析图谱调优的第一步永远是把当前状态彻底量化。我们做了一个专门的性能剖析套件用来测量从指令发射到数据落回的每个阶段耗时。这个套件输出一张延迟分解图计算脉动阵列占用多少时间存储读写占据多少时间指令排队等待占了多久DMA搬运占了多久。有了这张图你会清楚地看到瓶颈在哪而不是拍脑袋说“我觉得是计算不够快”。我记得很清楚第一次对典型的卷积网络做剖析时发现计算单元本身的利用率已经到82%但端到端的总延迟比理论推算多了接近一倍。剖析结果揭示了原因DMA搬运和计算之间时间重叠率极低搬运做完后计算阵列往往要等十几个周期才能拿到数据。这种问题在架构上称为流水线气泡是最常见的性能杀手之一。5.2 流水线重叠让搬运和计算并行起来从剖析结果出发我们的第一个优化动作就是增加流水线深度。原始设计中DMA、计算、写回三个阶段的指令是串行发射的改动后的方案允许提前预取下一组输入数据也就是软件在启动当前计算的同时预先向DMA发出下一组数据的读取请求。这个模式在编译器上叫做双缓冲Double Buffering在运行时上表现为指令流水线的peek与commit机制。实现之后实测端到端延迟缩短了26%。这个成绩算不上惊艳但它验证了一个重要规律存储器墙远比算力墙更早到来。很多团队执着于增加计算阵列规模结果DMA带宽跟不上算力白搭。我们的经验是先测量再布局如果剖析显示计算已经空闲等待那就说明瓶颈在存储侧加算力反而是负优化。5.3 针对目标模型做算子融合裁剪第二个核心优化动作是算子融合的深度定制。我们经过剖析发现在Transformer类模型中LayerNorm的耗时占比高达11%。LayerNorm本身的运算是逐通道的统计量计算在脉动阵列上跑效率极低。硬件上我们没有为LayerNorm做专门加速单元但编译器层面做了两件事一是把LayerNorm前接的加法和后接的Scale/Shift操作全部融合减少中间结果在片上的写回与读取二是将这个融合后的整体映射到SIMD向量单元上执行而不是硬塞进脉动阵列。这里有一个关键点融合不能只从计算图上做还必须让硬件的Flow都匹配。编译器必须知道融合后的算子是在哪个执行单元上跑以便为它分配正确的寄存器文件和存储端口。我们浪费了将近一周的时间才把融合后软硬件资源分配彻底理顺因为当时的编译器设计里算子融合发生在高层图优化阶段而硬件资源映射发生在低层指令生成阶段两个阶段的信息隔离让融合后的优化信息无法传递下去。最终靠修改中间表示结构让融合信息一路透明地传到指令生成层才真正跑通。5.4 量化精度与性能之间的权衡第四个动作是量化策略的调整。默认情况下我们对所有算子一视同仁地使用INT8表示。但剖析显示某个关键深度模型的精度在INT8量化后下降了0.7个百分点超出了客户的接受范围。我们先将该模型中的重要敏感算子切换为FP16计算精度恢复到了可接受水平但代价是存储占用增加了46%吞吐下降约18%。进一步的优化方向是混合精度动态调整编译期对每个算子做数值范围扫描为那些动态范围极宽的算子单独选择精度而不是一刀切。我们在一个目标跟踪模型上尝试后其实效果不算好因为动态范围宽泛的算子在模型里分布过于分散有限的混合精度支持并不能带来显著收益。反倒是保留全INT8推理但在训练时加入伪量化模拟QAT让模型本身对低比特更鲁棒的方法最终取得了最好的效果。这个经验也让我把更多精力转向了训练侧的协同优化。6. 量产阶段才会遇到的坑与我的排查思路这块内容放到系列最后讲是因为它的实用性是前面所有章节的集合。芯片进入量产阶段问题不再是仿真里的逻辑错误而是大量环境相关、边界场景相关的疑难杂症。很多问题在实验室里绝对复现不出来但一到客户环境就准时出现。我按自己的真实排查经验整理成一个小型问题库方便各位遇到类似情况时快速对照。6.1 高频复现与低频随机故障的分类思路遇到问题第一步不是马上看代码而是先分类。我习惯先把故障分成两类高频固定复现和低频随机抖动。高频复现的问题通常是确定性缺陷比如寄存器配置错误、某个算子的边界计算有误这类问题通过增加日志和定向复测往往在一个工作日内就能定位。低频随机的问题则可能是时序裕量不足、电压温度波动、动态电源噪声之类的不确定因素这类问题最折磨人因为它需要长时间压测配合调试探针采集数据。我们遇到过的一个典型案例是某批芯片在客户现场跑目标检测模型时平均每8到10小时出现一次异常掉出。实验室里连续跑了两周都没有复现后来采用了长时间电压拉偏测试把核心电压调到规格下限以下终于在48小时内复现出来了。定位发现是某个时钟域在数据翻转率极高的模式下时钟树的偏移Skew变得不可控导致寄存器建立时间不足。这个根因和软硬件设计都有点关系但最终靠的是验证团队在DDR控制器的输入级增加了一级同步寄存器解决的。6.2 日志体系要设计不是碰到问题了才加量产调试里我最推荐的一件小事是在设计初期就规划好日志框架。很多芯片项目的日志只是无脑往UART打印量一多就把带宽占满反而连核心信息都丢了。好的日志体系应该是分级的错误级信息第一时间上报驱动警告级信息记录到环形缓冲区调试级信息默认关闭只在特定环境变量打开时输出。有次排查一个很隐蔽的软件逻辑问题打开调试日志后发现应用层的某个数值在跨过4096边界时硬件中的地址映射表没有做处理导致计算越界。这类问题如果没有前期日志体系的支撑光靠仿真复盘来猜可能要多花好几周时间。6.3 客户场景回灌与压力测试量产阶段的另一类必修课是客户场景回灌。单纯在实验室里跑自己准备的模型是不够的真实的客户场景会有各种我们想不到的组合。我的做法是建立一个统一的“场景回灌测试平台”把客户提供的离线模型包、数据样例、运行参数组合全部倒入按照指定的顺序运行并记录结果。这个平台起初只是一个小脚本后来越做越完善加入了自动比对输出结果和自动上报失败用例的功能成了团队里最有用的工具之一。我不建议轻易跳过回灌测试哪怕压力测试已经做了几百小时。因为回灌测试捕捉到的往往是真实的使用方式而实验室压力测试总是带有一点点理想的色彩。这个环节做扎实了量产后的现场支持压力会大幅下降这是一个被反复验证过的结果。最后再分享一个与调优无关、但和职业体验强相关的感受软硬件协同设计做到最后拼的不是谁RTL写得快也不是谁编译优化做得深而是谁能在边界条件出现时在最短时间内建立正确的排查路径。AI芯片领域的技术更新很快但工程方法的内核是稳定不变的。现在我在做新的芯片项目时仍然会把本文提到的那套剖析、分层、回灌的流程从第一天就开始构建而不是等项目烧到一半才想起它的好处。
阅读完成 · 觉得有帮助?
咨询建站