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

AI芯片软硬件协同设计:从架构取舍到性能调优的工程实践

AI芯片软硬件协同设计:从架构取舍到性能调优的工程实践 ★ FEATURED ARTICLE
1. 先看清楚AI芯片软硬件协同到底在解决什么问题1.1 算法迭代节奏远快于芯片迭代周期做过AI芯片的人应该都有一种共同的焦虑算法论文更新得快到离谱而芯片的迭代慢到让人心慌。一个新型网络结构出来往往不只是换了层数或者通道数有时候会冒出全新的算子、新的数据排布方式、新的控制流模式。对软件团队来说这可能只是框架里一个新算子但对硬件团队来说这可能意味着新的数据通路、新的指令需求甚至新的片上互联策略。芯片从架构定义到拿到样片周期通常以年为单位。如果中间还要经历仿真验证、后端实现、流片、封装、测试那更是漫长的旅程。这中间任何一个环节出现返工影响都不是几周能消化掉的。更麻烦的是你在定义芯片架构的那一刻参考的算法很可能已经被更新的结构取代。所以AI芯片设计天然带着一个错位问题硬件实现的能力总是要滞后于算法产生新需求的速度。所以软硬件协同设计的真正意义不是让硬件去追无穷变化的新算法而是把算法里的“共性计算”提炼出来用有限且稳固的硬件结构去承载。我习惯把它比喻成物流公司。电商运营可以天天换促销方案但物流公司不会每天改一次仓库结构。它能做的是把仓配能力抽象成干线、分拣、末端配送几个稳定模块然后用流程系统去适配各种促销玩法。AI芯片也是一样卷积、矩阵乘、归一化、激活这些是稳定模块算法再怎么变也大体还是这些基础动作的不同组合。软件的作用就是把新组合翻译成硬件能理解的动作序列。这个逻辑想透了就明白软硬件协同的第一落点一定是对目标负载做计算特征的提炼而不是一上来就画框图。1.2 硬件的“物理账本”乘法器、带宽与精度的取舍芯片设计的残酷之处在于它处处是取舍而取舍的核心是物理账本。面积就那么大功耗有上限时钟频率也有上限你总得决定把晶体管花在哪。AI计算的核心动作看着不多无非乘加、激活、池化、归一化、数据搬移但不同模型对这几类动作的比例差异非常大。硬件团队经常争论一个问题同样是十万个单位的晶体管预算是用来堆乘法阵列还是用来堆片上存储如果都堆到乘法阵列上理论峰值算力很好看如果堆到存储上跑真实模型的时候有效算力可能会更高因为数据不用反复从外部DDR读入计算单元不用空转等待。这个取舍没有放之四海皆准的答案完全取决于芯片的目标场景。如果是云端训练算力密度和互联带宽都很重要如果是边缘推理能效比和存储带宽的匹配往往才是决定体验的关键。我见过不少团队在项目初期拍脑袋定了“算力必须多大、存储必须多少”的指标结果做到一半发现瓶颈根本不在计算单元而在片上数据供给不足于是只能回头调架构成本极高。靠谱的做法是拿真实模型提前跑Profile统计计算密度、访存密度、算子分布再根据这些数据反推资源比例。顺序不能反否则后面全是补丁。精度是另一个容易在早期被轻视的账本。FP32实现简单对齐生态容易但面积和功耗都大。FP16用在训练场景相对稳定推理端则普遍在往INT8甚至INT4靠。量化说起来只是把数据从32位变成8位真正做起来会牵扯出缩放因子、溢出保护、反量化插入等一系列问题。硬件能不能高效支持混合精度决定了软件团队后续的优化空间有多大。如果芯片定义阶段对混合精度的考虑过于乐观后面所有软件适配都会被束缚住手脚。1.3 软硬件协同不是选项是生存策略一个挺残酷的行业事实是一款AI芯片的成败很多时候不由峰值算力决定而由“开发者拿到芯片之后能不能在两周内把模型跑起来”决定。过去那种只卖芯片、软件随便做做的模式在今天这个生态密度极高的市场里基本走不通。软件栈在很多时候已经变成芯片的一部分。没有工具链的芯片本质上是一块没有灵魂的硅片。软件栈的意义是让上层开发者看到的不是寄存器、指令编码和内存映射而是卷积层、Transformer Block、计算图里的算子节点。抽象得好芯片就“好用”抽象得差即便底层算力再强也很难被真实项目接纳。这也是为什么现在很多AI芯片团队的软件人数甚至超过硬件人数因为软件栈的工程量和对细节的要求一点不比硬件少。还有一个经常被忽视的点是反馈机制。模型运行慢原因可能涉及硬件的仲裁策略、缓存命中率、调度器的同步开销也可能只是编译器生成代码太保守。如果软件栈缺少细粒度的性能观测能力开发者只能对着黑盒猜。我早期参与过一个项目芯片跑某个经典分类模型时平均利用率只有三成硬件团队坚持说逻辑仿真没问题软件团队后来用片上计数器定位到某个仲裁器的轮询策略在高并发时产生大量空转周期。这种问题靠纸面推演几乎不可能发现。所以说软硬件协同真正落地至少要做三件事统一的任务模型、充分的观测机制、迭代式调优闭环。2. 硬件侧算力路径与数据通路的取舍2.1 计算单元的三条路线脉动阵列、SIMD与稀疏加速硬件侧的第一次核心决策是计算单元用什么组织形式。业内主流路线大致有三类每一类都有明显的性格也有各自需要付出的软件代价。第一类是脉动阵列。大量乘加单元排成二维阵列数据从阵列边缘流入在计算单元之间流动的过程中完成乘加中间结果不用频繁写回存储。它特别适合卷积和矩阵乘法这种数据复用率很高的负载结构规整面积效率高。但脉动阵列对数据布局很挑剔编译器要把数据按特定Pattern提前排好否则阵列内部会大量空转。可以说它把一部分复杂度从硬件转嫁给了编译器和运行时。第二类是向量处理单元。一条指令对一批数据做同样的操作灵活度高更接近传统处理器的风格编译器对接相对容易。代价是控制逻辑和调度资源的开销更大功耗面积不如纯阵列结构划算。很多芯片实际采用混合体重计算密度的任务走阵列灵活的小算子走通用向量核两边各取所长。第三类是稀疏加速路径。经过剪枝的神经网络权重里常有大量零于是硬件通过稀疏检测跳过零值计算节省能耗。但稀疏加速的好处并不总是能兑现当零值分布不规则时索引开销会抵消跳过计算省下的时间。我的建议是不要一上来就赌稀疏加速。先把稠密场景下的基础性能做扎实再把稀疏作为增量特性加入。否则验证复杂度和编译器适配难度会同步暴涨。三种路线的对比如下技术路线核心优势主要短板最适合的负载脉动阵列数据复用高、面积效率好对数据布局敏感编译器压力大卷积、矩阵乘等规整计算向量并行单元灵活易编程、对接方便功耗面积开销偏高多样化算子、控制密集场景稀疏加速跳过零计算、节省能耗索引开销大动态稀疏难处理剪枝后模型、高稀疏度场景选择计算单元路线时核心判断标准只有一个目标负载里的算子在数据复用率、规则度、稀疏比上是怎么分布的。我见过有团队一开始就押注某一种高级结构结果跑通用模型时优势不明显反而在编译器适配环节多花了几个季度。先保守、后激进是AI芯片架构选型里一条比较稳的经验。2.2 “内存墙”问题的破解逻辑逐层缓冲与近数据计算AI算力提升的速度长期快于访存带宽的提升速度。当计算单元规模上去了真正的瓶颈往往不再是计算本身而是数据能不能及时送到计算单元嘴边。这个“高速计算碰见低速存储”的矛盾业内常叫内存墙。破解内存墙最主流的手段是分层存储。计算单元旁边配一个小而快的寄存器堆外层是一级或两级的片上SRAM再往外才是DDR或者HBM。每一级存储的目标都是把数据尽量留在离计算单元近的地方让同一个数据被反复复用。举个例子卷积计算里权重和输入特征如果能被充分复用外部存储的访问次数能降低一个数量级。编译器要做的重要工作之一就是把循环切块成能被片上缓存容纳的Tile减少换入换出。实际项目里缓存一致性开销往往被低估。多个计算核共享数据的时候如果硬件没有足够高效的同步机制软件只能频繁插入Barrier。Barrier一多流水线就断利用率损失立刻显现。更好的做法是在片上设计生产者-消费者结构的流水让一个核算出的中间结果直接被下一个核消费免去反复写回DDR再读回的开销。这种数据流式调度对编译器分析能力要求更高但性能收益也最直观。2.3 多核互联与同步开销单核算力做到一定程度就必然走向多核。多核之间的互联结构决定了数据吞吐上限和延迟底线。AI芯片里的互联通常不是简单拉几条总线而是根据实际访存模式定制的片上网络需要支持广播、多播、点对点、归约等各种通信模式。很多AI算子天然容易拆分。卷积可以把输入特征图切到不同核矩阵乘可以把大矩阵分块分给不同核并行算。但拆分之后必然带来通信和同步。两个核算完各自的块如果后面要拼接或者做全局归约通信量一旦安排不好整体性能就会被拖垮。所以软硬件协同设计时要把通信原语直接在指令集和运行时层面表达出来让上层的调度器可以直接操纵这些原语而不是在驱动里硬拼。任务同步粒度是另一个工程难点。粒度太粗空闲等待变大粒度太细同步本身的开销变大。硬件设计可以通过异步事件队列、乱序完成中断等机制来缓解但最终仍要依赖调度器把任务切分和同步频率调到一个最佳平衡点。多核并发的调试通常也是仿真阶段最头疼的部分因为并发Bug复现成本高随机时序会让问题时隐时现。这点放到第五部分详细说。3. 软件侧框架、编译器、运行时到底管多少事3.1 指令集设计给编译器铺路而不是给编译器挖坑指令集常被当成纯硬件的事情但它其实直接决定了软件团队未来几年的幸福感。一套对编译器友好的指令集一般具备三个特征。第一表达能力强一条指令能表达一个有意义的复杂操作而不是把操作切得稀碎。第二规则可预测指令的延迟、副作用对上层可见且稳定。第三接口分层清晰既能表达粗粒度的张量操作也能表达细粒度的标量控制。我最怕看到的是“单条大而美”的指令。一条指令试图同时完成取数、计算、写回、更新状态听起来很强大但编译器要做指令调度和寄存器分配时副作用约束会带来巨大痛苦。反过来指令太碎也有问题一条卷积被拆成几百条微操作程序体积膨胀执行效率下降。比较健康的设计是分层指令集粗粒度张量指令处理大块计算细粒度向量和标量指令做控制与兜底两层之间有清晰的边界和转换规则。指令编码也要预留扩展位。AI算子还在快速演化指令集如果没有预留类型字段或者没有定长编码格式后期加一条新指令会非常麻烦甚至可能破坏解码器的时序。我在早期一个项目里吃过这个亏当时为了省面积把Opcode压缩得太紧后来想加一种新的数据模式几乎没有空地可插只能拿旧指令组合模拟效率差强人意。好的指令集和危险的指令集有时只差几个决策设计维度好设计危险设计指令粒度张量指令标量指令分层单条指令塞入过多隐藏副作用行为规则性延迟和副作用稳定可预测隐藏依赖乱序副作用难以追踪可扩展性预留Opcode和类型字段编码过于紧凑后期无扩展空间3.2 算子调度与算子融合的收益逻辑软件栈里最能体现实力的部分是编译器。编译器先要对计算图做图优化。以典型卷积块为例卷积、偏置相加、激活函数经常连在一起如果编译器原样翻译每一步中间结果都要写回内存再读出来算力利用率会非常难看。算子融合就是把这些连续运算合成一个复合算子内核让中间结果不出片上缓存直接流向下一个计算节点。实践中这往往是把一个模型从低利用率拉到高利用率的第一个大优化。算子调度是第二件大事。一个算子里可能有很多可并行的维度比如卷积的批次、通道、空间位置。编译器要在计算核数量和片上内存约束下决定怎么切块、怎么排循环、怎么做流水。这本质上是个搜索空间巨大的优化问题。工程上很少一上来就全局搜索最优更现实的是先用代价模型做一轮启发式切分再挑少数关键算子做自动调优实测跑一遍选出最优版本缓存下来以后直接命中缓存。概念上算子融合前后的差异可以这样看# 融合前Conv - Bias - ReLU每一步都把中间结果写回内存 out conv(x, w, b) out bias_add(out, bias) out relu(out) # 融合后一次遍历完成中间结果留在片上缓存 for tile in tiles(x): acc conv_tile(tile, w, b) acc bias_add_tile(acc, bias) out_tile relu_tile(acc) write(out_tile)还有一个容易被忽略的经验不要迷信全自动生成代码。某些特定模型的并行拆分策略资深工程师手写的调度版本往往比自动生成的性能更好。两者其实不冲突编译器要做的是把常规优化自动化同时把非常规优化路径通过可编程接口开放给专家。软件栈如果能提供灵活的扩展机制有时候比做一个全自动的“神级编译器”更接地气。3.3 驱动与运行时容易忽略却最常翻车的地方聊AI芯片软件栈大家容易把聚光灯打在编译器和框架对接上但实际项目里故障最多的层往往是驱动与运行时。驱动负责管理设备内存、提交任务队列、处理中断运行时负责给每个模型实例分配算力、排队、调度、监控。这两层离硬件最近出问题时最难看又直接影响系统稳定性。拿DMA传输来说硬件通常会提供DMA部件来做数据搬移。DMA的触发条件、地址长度对齐、内存屏障要求驱动里只要有一个细节处理不严就可能出现偶发性数据错误。单核单任务调试时几乎不出现多核多任务并发跑起来就随机爆发。排查这类问题像是在捉迷藏因为错误往往不会稳定复现。运行时调度也是重灾区。很多推理场景需要多个模型并发共享同一颗芯片比如视频流里同时跑检测、跟踪、超分。如果运行时只做静态资源切分某个模型的负载波动变大时另一个模型的延迟会跟着剧烈抖动。理想方案是支持动态重配比如任务级抢占、内存冷热迁移、算力伸缩。但这些动态能力反过来对硬件也有要求上下文切换要有快速保存和恢复机制硬件设计之初没考虑软件再怎么绕也补不回来。4. 从算法到物理芯片的实现路径4.1 第一步用算法Profile定硬指标软硬件协同设计不是一上来就画电路图。比较规范的流程是先选定两三个目标模型覆盖训练、推理、检测、分类、生成等不同形态再写脚本统计分析这些模型的计算量分布、算子类型分布、访存量、并行度和稀疏度指标然后换算成硬指标。举一个简单的例子。假设目标模型最重的计算集中在矩阵乘法上总乘加次数已经通过Profile算出来了。如果期望延迟是十毫秒那需要算力目标是多少TOPS一除就能得到。访存量同理如果数据表明某个分类模型几乎每次推理都要把权重完整读一遍那么权重存储的带宽和容量设计就应该重点倾斜。这些推导必须有实际数据支撑而不是拍脑袋定寄存器数量。我特别想提醒的是不只盯平均指标要看P99甚至P99.9。推理服务用户能感受到的往往是最差延迟。如果架构只优化平均水平输入尺寸一变高并发一来延迟抖动会很刺眼。定义指标时最好把典型尺寸、最大尺寸和尺寸切换频率都列出来再据此决定缓存大小、调度策略和带宽余量。4.2 第二步架构选型与微架构验证指标定了之后架构师会并行探索多个备选微架构。A方案可能是大阵列加高带宽B方案是多核流水加灵活调度C方案在两者之间折中。每种方案都要做周期级仿真拿前面提取的真实模型算子去跑估算性能和面积功耗。我做架构评估时养成了一个习惯就是准备一个“最差情况算子”。不能只挑最好算的矩阵乘来估要拿注意力、不规则稀疏、动态形状这类最难处理的算子去压测看新架构在坏场景下到底跌多惨。很多芯片标称算力很猛一碰到动态形状就怂上层框架的自动优化也没法发挥。最差情况算子在工具链还不完善时不太容易暴露所以架构评审阶段最好把它列为必须交付的测试项。微架构阶段的功耗建模也值得重点关注。AI芯片往往是功耗受限产品峰值算力好看但散热压不住整机只能降频跑实际性能和额定值差一大截。所以降频策略、局部关断机制、能效曲线尽量在架构阶段就纳入评估别等到物理设计阶段才手忙脚乱。4.3 第三步工具链和软件栈怎么跟着硬件并行铺这是一个经常犯、又代价极高的错误等硬件验证完才开始做软件。芯片设计周期这么长如果软件栈不能在模拟器上提前调通等流片回来才发现软硬件接口理解对不上就只能秋后算账一遍遍改整体节奏被拖垮。正确姿势是让软件栈与硬件设计同步进行。硬件RTL还没写完时先用功能模拟器把指令集模拟出来软件团队在上面做编译器、驱动和运行时开发。等RTL稳定之后再做指令级对拍同一段程序在高精度模拟器和RTL仿真器上分别跑比对结果和延迟把硬件实现的错误提早抓出来。再往后可以用FPGA原型验证平台做更接近真实时序的验证。这样的并行开发很依赖一份稳定的“硬件抽象接口文档”。软硬件团队先在上面达成一致指令编码、内存布局、中断号、设备寄存器地址、同步原语全部白纸黑字定下来。接口一旦稳定两边就能各干各的。每次改动都要走变更评审哪怕只是一个寄存器位的含义调整也可能让驱动里的某个宏出现偏差。这套接口管理流程我觉得是软硬件协同里最容易被低估的“流程基建”。4.4 第四步集成到已有AI框架的适配策略芯片软件栈最终要接进上层AI生态。主流的深度学习框架基本都带一套计算图中间表达常见做法是以算子注册的方式把自家实现挂进框架把模型里的节点替换成自己的算子实现。为了兼容已有模型库还要有图改写和回退机制能自动替换的节点就替换替换不了的特殊节点先退回CPU参考实现。这个阶段最忌全量自研。一个新芯片团队如果把框架里所有算子都自己实现一遍成本极高质量也不好保证。更务实的是聚焦高频和高计算量算子比如卷积、矩阵乘、归一化、激活把性能做到极致边缘算子先用CPU实现保证正确性后续再逐步替换。用户跑一个复杂模型如果90%的计算时间都落在核心算子上边缘算子哪怕性能一般整体效果也过得去。这条路线符合二八法则能让软件栈在早期更快达到可用状态。5. 真实项目里的高频坑与排查思路5.1 精度对不齐到底在哪里丢精度几乎每个AI芯片项目都会遇到“仿真结果和参考框架输出不一致”的问题。精度对不齐大概是软硬件联调里最磨人的问题。我的排查顺序通常是自底向上先查数据格式解析。输入图片通道顺序对不对归一化因子是多少是否需要转置。这些很低级但出现频率出奇高。再查中间张量精度。每层输出是FP32还是FP16累加器位宽是否足够有没有因为累加精度不足导致误差逐层放大。然后查量化参数。INT8推理的缩放因子是逐张量还是逐通道是否存在离群值导致量化范围被撑大。最后查异步和乱序。数据写回顺序和同步屏障有没有对齐缓存里的数据是不是在错误的时间被读到。精度排查很依赖工具链的可观测性。芯片如果支持片上快照能导出每一层中间结果那排查就是对照实验。如果只能看到最终输出就像蒙着眼睛解谜。所以我前面强调的观测机制真不是可选项而是诊断抓手。5.2 理论算力很高实际利用率上不去“标称几百TOPS跑起来只能用三成”这是AI芯片圈最常见的抱怨之一。算力利用率上不去的原因通常归为三类症状常见原因排查方向计算单元长期等数据片上缓存太小、外部带宽不足查等待周期占比优化Tile切分大量计算核空闲算子并行度不足、批次太小增大Batch多算子流水重叠频繁等待同步Barrier过密、锁竞争调整同步粒度用异步指令掩盖延迟解决利用率问题的关键是要有足够细的性能计数。把一次推理的时间拆成计算时间、数据搬移时间、同步等待时间三类再去看最大的一项优化方向往往就清楚了。最怕的是没有计数器全靠经验和猜。我经历过一个项目一开始所有人都认定瓶颈在带宽加了计数器才发现同步等待占比最高整个团队的努力方向都错了。5.3 仿真与实片表现不符还有一类很磨人的问题是仿真阶段各项指标都达标一上实片性能就缩水。造成偏差的原因很多。功耗和散热是最常见的一种仿真假定频率恒定实片高负载跑一会儿温度上去就降频。另一类是时钟与复位的问题仿真用的理想时钟实片上有抖动和跨时钟域的不确定延迟会让某些极端时序路径出问题。还有制造偏差不同芯片个体之间存在性能差异量产不做分级就会影响体验。应对这些问题验证阶段就不能只跑“功能正确”的用例还要专门设计高负载高温度的压力测试。让芯片跑到最高频、最大功耗、最热工况看软硬件组合能扛多久。硬件没有这些压力数据软件团队连优化空间在哪里都不知道。压测这一环其实很考验软硬件的配合能力两边数据一凑很多隐藏问题就浮出水面。6. 一些可以少走弯路的建议6.1 共建接口契约仓库软硬件团队一定要有一个共同维护的接口契约仓库里面放着指令集手册、寄存器定义、内存布局、错误码表、同步原语说明。所有版本变更都走评审式流程。很多团队的矛盾本质上是对同一份接口的两边理解不一致到联调阶段才爆发。用一个清晰、单点维护的契约仓库能提前化解大半问题。6.2 给软件团队一个功能模拟器给软件栈团队一个“假硬件”是性价比最高的投资。硬件RTL还没就绪时先做一个支持指令集、能跑算子、带基本性能统计的功能模拟器。软件团队在这个模拟器上把编译器、驱动、运行时、量化工具全部跑通等真实芯片回来主要工作就变成性能打磨而不是功能调试。那省下来的时间往往是以月计的。6.3 让尾部延迟和能效曲线进入决策只看平均算力和平均延迟会掩盖大量真实问题。建议每个AI芯片项目立项时都建立一个目标负载基准套件包含真实模型、批量尺寸、输入分辨率分布。所有架构选型和软件优化都围绕这套基准做决策。基准不完美但它提供了一个所有人可以共同参照的锚点总比各说各话强。6.4 算法与硬件之间保持小步迭代算法团队和硬件团队不要只在评审会上碰头最好建立一个小步迭代的沟通机制。算法团队有新结构想法时硬件资源不太可能立刻跟上但算法团队如果了解硬件的“禀赋”在模型结构设计阶段就能偏向硬件友好比如把动态尺寸改成静态形状或者复用已有算子的组合。这种软性对齐经常比被迫改硬件省力得多。我在实操中越来越体会到AI芯片的软硬件设计本质上是磨合与妥协的艺术。技术方案当然重要但真正决定项目走向的往往是一些反复被验证的工程习惯明确分工界面、建立观测手段、用小步迭代快速试错。最后再分享一个小技巧把性能计数器当成一等公民来设计。很多AI芯片的性能计数器列表重要性一点不比核心计算单元低。没有这些计数器优化就像盲人摸象。这个内容后续还可以这样扩展把编译器和工具链的适配过程单独拆开讲包括算子库编写技巧、量化工具设计、性能剖析器打磨那又是一个大课题。至少在当前这个阶段我希望上面这些经验能让你少走一些弯路。
阅读完成 · 觉得有帮助?
咨询建站