考试通知

AI芯片软硬件协同设计实战:从指令集到编译器的全链路优化

AI芯片软硬件协同设计实战:从指令集到编译器的全链路优化 1. 从一颗芯片的诞生说起为什么软硬件必须一起设计一颗AI芯片从概念到量产中间要经过架构设计、RTL实现、物理设计、流片、封装、驱动开发、编译器适配、框架对接等十几个大环节。很多刚入行的朋友会下意识地把这些环节分成两拨硬件工程师管前面软件工程师管后面中间画一条线各干各的。这条线画得越清楚项目死得越快。我参与过几个AI加速器的完整落地周期踩过最深的坑几乎都来自同一个根源——硬件和软件在早期没有对齐。硬件团队花了大半年优化了一个矩阵乘法单元理论峰值算力翻了三倍结果编译器团队发现这个单元的指令格式和现有算子库完全不兼容要重新写一套代码生成后端又花了四个月。这四个月里芯片在实验室里跑着功耗和面积都已经固化软件团队只能硬着头皮去适配一个“已经定型”的硬件。所以这篇内容想聊的核心就一件事AI芯片的软硬件协同设计到底怎么协同协同在哪些具体环节每个环节的决策依据是什么。适合正在做AI加速器、边缘推理芯片、或者对芯片全栈设计感兴趣的读者。不管你是做数字IC设计的、做编译器的、还是做算法部署的都能从中找到和自己工作直接相关的部分。2. 软硬件协同设计的整体思路拆解2.1 为什么不能先做硬件再做软件传统通用CPU的设计流程里硬件定义好指令集之后软件生态可以慢慢跟上因为CPU的指令集是通用的编译器、操作系统、应用软件有足够的时间去适配。但AI芯片不一样AI芯片的指令集和微架构高度定制化专门为特定的计算模式比如卷积、矩阵乘、注意力机制服务。如果硬件团队闭门造车定义了一套自以为优雅的指令集软件团队拿到之后发现根本没法高效地映射主流算子那这颗芯片的实际利用率可能连理论峰值的20%都不到。我见过一个典型的案例某团队设计了一款面向边缘推理的加速器硬件支持稀疏计算但稀疏格式是自定义的编译器团队拿到之后发现PyTorch和ONNX导出的稀疏张量格式和硬件要求的格式对不上中间需要做一次格式转换这个转换在CPU上跑的时间比推理本身还长。最后这个稀疏特性在实际部署中被关掉了硬件面积白花了。注意AI芯片的硬件特性如果不能在软件栈中被高效利用就等于不存在。硬件团队每增加一个特性都要问一句编译器怎么用这个特性框架怎么导出这个特性2.2 协同设计的三个层次软硬件协同设计不是一句口号它具体体现在三个层次上。第一个层次是指令集与算子库的协同。硬件定义指令集的时候软件团队要同步定义算子库的映射方案。比如硬件有一个4x4的矩阵乘加单元那软件团队就要确保卷积、全连接、注意力机制里的矩阵乘都能拆解成4x4的粒度。如果算子的维度不是4的倍数怎么处理边界这些细节在指令集定义阶段就要想清楚。第二个层次是存储层次与数据流的协同。AI芯片的瓶颈往往不在计算而在数据搬运。片上缓存多大、带宽多少、DMA怎么设计这些硬件参数直接决定了软件端的数据复用策略。如果硬件团队把缓存做得很小软件团队就要在算子融合上做更多工作把中间结果尽量留在片上。如果缓存做得很大但带宽不够软件团队就要设计更激进的数据预取策略。第三个层次是编译流程与运行时调度的协同。编译器生成的代码最终要在硬件上跑运行时的任务调度、内存分配、同步机制都要和硬件的并行结构匹配。比如硬件有多个计算核心编译器就要做任务划分和流水线编排运行时就要做核心间的同步和通信。这三个层次不是孤立的而是相互制约的。指令集决定了算子库的形态算子库决定了编译器的优化空间编译器决定了运行时的调度策略运行时又反过来影响硬件资源的利用率。所以协同设计是一个迭代的过程不是一次性的对齐会议就能解决的。2.3 一个可参考的协同设计流程基于我自己的经验一个比较稳妥的协同设计流程大致是这样的算法与 workload 分析先搞清楚这颗芯片要跑什么模型ResNet、BERT、还是Diffusion不同模型的算子分布、内存访问模式、计算密度差异很大。硬件架构探索根据workload分析结果确定计算单元的类型和数量、存储层次的大小和带宽、互连结构。指令集与编程模型定义硬件团队和软件团队一起定义指令集、寄存器布局、内存模型、同步原语。编译器与算子库原型软件团队基于指令集定义快速搭建编译器原型和关键算子库验证硬件设计的可行性。协同仿真与性能建模用周期精确的仿真器或者性能模型跑真实模型看瓶颈在哪里反过来调整硬件参数。RTL实现与软件迭代硬件进入RTL阶段后软件团队继续在仿真平台上迭代编译器和算子库。流片与硅后验证芯片回来后软件团队在真实硅片上做性能调优和bug修复。这个流程里第3步和第4步是最关键的。很多团队在第3步草草了事结果第4步发现指令集有致命缺陷不得不回炉重造浪费大量时间。3. 核心细节解析与实操要点3.1 指令集设计别把编译器当敌人指令集是软硬件之间的契约。设计指令集的时候硬件团队容易陷入一个误区把指令集设计得越灵活越好觉得这样软件团队想怎么用就怎么用。但实际上过于灵活的指令集往往意味着编译器很难做优化因为优化空间太大编译器不知道该怎么选。我个人的经验是AI芯片的指令集应该适度定制。比如对于矩阵乘这种核心操作与其设计一条通用的“矩阵乘指令”不如设计几条固定维度的“矩阵乘加指令”比如4x4、8x8、16x16。这样编译器只需要做维度匹配和边界处理不需要做复杂的指令选择。另一个关键点是指令的粒度。指令太细编译器要生成大量指令取指和译码的开销就上去了指令太粗灵活性就差遇到不规则的算子就抓瞎。比较合理的做法是采用分层指令集底层是细粒度的标量指令用于控制流和地址计算中层是向量指令用于元素级操作上层是矩阵/张量指令用于核心计算。实操心得定义指令集的时候让编译器团队的人坐在旁边每定义一条指令就问一句“这条指令你打算怎么生成”。如果编译器团队说“这个我得想想”那这条指令大概率有问题。3.2 存储层次设计带宽比容量更重要AI芯片的存储层次通常包括寄存器文件、片上缓存SRAM、片外内存DRAM。设计的时候容量和带宽要一起考虑但优先级不同。对于计算密集型的算子比如大矩阵乘带宽是瓶颈。因为计算单元再快数据供不上也是白搭。这时候片上缓存的带宽要足够高通常要做到计算单元峰值算力的数倍才能保证计算单元不饿死。对于访存密集型的算子比如LayerNorm、Softmax容量和带宽都重要。这些算子需要把整个张量读进来做归约如果片上缓存放不下就要反复读DRAM延迟和功耗都上去了。一个常用的设计方法是双缓冲把片上缓存分成两块一块用于当前计算一块用于预取下一块数据。这样计算和搬运可以重叠提高利用率。双缓冲的深度取决于计算时间和搬运时间的比例如果搬运时间大于计算时间双缓冲也救不了得加带宽。3.3 编译器后端从计算图到硬件指令编译器后端的工作是把高层计算图比如ONNX、TorchScript转换成硬件能执行的指令序列。这个过程包括算子融合、内存分配、指令调度、寄存器分配等步骤。算子融合是AI编译器里最关键的优化之一。比如ConvBNReLU如果不融合就要把Conv的结果写回内存再读出来做BN再写回再读出来做ReLU。融合之后中间结果留在寄存器或片上缓存里省了两次内存读写。对于大模型这种融合能带来数倍的性能提升。内存分配的策略也很重要。AI芯片的片上缓存通常不大怎么把有限的缓存分配给不同的张量直接影响性能。常用的策略是生命周期分析如果一个张量在某个时间点之后不再被使用它占用的缓存就可以回收给其他张量。编译器要做的就是在计算图里分析每个张量的生命周期然后做内存复用。指令调度要考虑硬件的并行结构。如果硬件有多个计算核心编译器就要把算子拆分成多个子任务分配到不同核心上同时插入同步指令。如果硬件支持流水线编译器就要把算子编排成流水线让不同阶段重叠执行。3.4 运行时系统别让调度拖了后腿运行时系统负责在芯片上实际执行编译器生成的代码。它要管理内存、调度任务、处理同步、响应中断。对于AI芯片运行时系统还要处理动态shape、多模型并发、优先级调度等场景。动态shape是一个很头疼的问题。很多AI芯片在硬件设计时假设shape是固定的但实际部署中输入shape经常变化。如果运行时系统不能高效处理动态shape就要在每次shape变化时重新编译延迟很高。比较好的做法是多态编译编译器生成多个版本的代码覆盖常见的shape范围运行时根据实际shape选择最接近的版本。多模型并发也很常见。比如在一个边缘设备上同时跑目标检测和语音识别两个模型。运行时系统要能合理分配计算资源和内存避免两个模型互相抢资源。常用的策略是时间片轮转或者优先级抢占具体选哪种取决于业务场景。4. 实操过程与核心环节实现4.1 从PyTorch模型到硬件指令的完整链路假设我们有一个简单的卷积神经网络要在自研AI芯片上部署。整个链路大致是这样的第一步模型导出。用PyTorch训练好模型后导出为ONNX格式。这一步要注意opset版本不同版本的ONNX对算子的支持不一样。如果模型里有自定义算子需要自己写ONNX的扩展。第二步图优化。编译器前端对ONNX图做优化包括常量折叠、死代码消除、算子融合。算子融合的规则可以自定义比如把ConvBNReLU融合成一个FusedConv算子。第三步量化。如果硬件支持INT8计算就要做量化。量化包括权重量化和激活量化。权重量化可以离线做激活量化需要校准数据。量化的关键是选择合适的scale和zero_point让量化误差最小。第四步算子映射。把优化后的图里的每个算子映射到硬件指令。比如FusedConv映射到一条矩阵乘指令加一条向量指令。这一步要考虑硬件的计算单元维度和存储层次。第五步内存分配。分析每个张量的生命周期分配片上缓存和片外内存。对于生命周期不重叠的张量可以复用同一块内存。第六步指令调度。把映射后的指令序列编排成硬件能执行的顺序插入同步和DMA指令。如果硬件支持多核心还要做任务划分。第七步代码生成。生成最终的二进制或者汇编代码加载到硬件上执行。这个链路里每一步都有优化空间但最重要的是第四步和第五步。算子映射决定了硬件利用率的上限内存分配决定了实际能达到多少。4.2 一个具体的矩阵乘映射案例假设硬件有一个16x16的矩阵乘加单元我们要计算一个64x64的矩阵乘。最直接的做法是把64x64的矩阵乘拆成16个16x16的子矩阵乘每个子矩阵乘用一条指令完成。但这样做的问题是每次子矩阵乘的结果都要写回内存然后下一次子矩阵乘再读出来累加内存访问量很大。更好的做法是分块累加把A矩阵按行分成4块每块16行把B矩阵按列分成4块每块16列。然后计算A的每一块和B的每一块的乘积累加到结果矩阵的对应位置。这样每个16x16的子矩阵乘的结果可以留在寄存器里等所有累加完成后再写回内存。内存访问量从16次写16次读降低到1次写。这个映射策略的关键是寄存器 blocking。硬件的寄存器文件要足够大能放下一个16x16的累加器。如果寄存器不够就要把累加器放到片上缓存性能会下降。注意矩阵乘的映射策略和硬件的寄存器文件大小、片上缓存带宽、计算单元维度都强相关。没有一种通用的最优策略必须针对具体硬件做调优。4.3 性能建模与瓶颈分析在硬件流片之前软件团队需要用性能模型来评估设计。性能模型可以是周期精确的仿真器也可以是解析模型。周期精确的仿真器精度高但速度慢适合验证关键算子解析模型速度快但精度低适合做设计空间探索。一个常用的解析模型是roofline模型。它把性能表示为计算量和访存量的函数。对于计算密集型的算子性能受限于峰值算力对于访存密集型的算子性能受限于带宽。通过roofline模型可以快速判断一个算子在给定硬件上的理论上限。实际操作中我会用roofline模型做初步筛选找出可能的瓶颈算子然后用周期精确的仿真器做详细分析。比如发现某个算子的实际性能远低于roofline上限就要看是指令调度的问题、内存分配的问题、还是硬件本身的问题。4.4 硅后调优的实战经验芯片回来之后软件团队要在真实硅片上做调优。这时候会发现很多在仿真阶段没暴露的问题比如时序违例、功耗超标、缓存一致性bug。硅后调优的第一步是建立基准。跑一组标准模型记录每个算子的延迟、吞吐、功耗。然后和仿真结果对比找出差异大的算子。第二步是定位瓶颈。用性能计数器PMU采集硬件运行时的数据看计算单元的利用率、缓存的命中率、DMA的带宽利用率。如果计算单元利用率低可能是数据供不上如果缓存命中率低可能是内存分配策略有问题。第三步是迭代优化。根据瓶颈分析结果调整编译器的优化策略或者运行时的调度策略。比如发现某个算子的缓存命中率低可以调整内存分配把频繁访问的张量放到更近的缓存里。硅后调优是一个反复迭代的过程通常要持续几个月。我的经验是前两周能解决80%的性能问题剩下的20%需要反复打磨。5. 常见问题与排查技巧实录5.1 算子不支持怎么办这是最常见的问题。硬件定义指令集的时候不可能覆盖所有算子遇到不支持的算子通常有几种处理方式软件模拟在CPU上跑不支持的算子把结果传回芯片。这种方式简单但性能差适合不频繁的算子。算子拆解把不支持的算子拆成多个支持的算子。比如GELU可以拆成多个基础操作。硬件扩展如果某个算子很常用可以考虑在下一代硬件里加支持。但这需要评估面积和功耗开销。我个人的建议是在硬件设计阶段就做一次算子覆盖率分析统计目标模型里所有算子的出现频率确保高频算子都有硬件支持。低频算子可以用软件模拟兜底。5.2 精度不达标怎么排查AI芯片通常支持INT8甚至INT4计算量化会带来精度损失。如果量化后模型精度下降太多排查思路是这样的逐层对比把量化模型和浮点模型的中间层输出做对比找出误差最大的层。校准数据检查量化用的校准数据是否覆盖了实际输入的分布如果校准数据偏了量化参数就不准。量化粒度调整从per-tensor量化改成per-channel量化通常能提升精度。混合精度对精度敏感的层用FP16其他层用INT8。实操心得量化精度问题往往不是单一原因造成的而是多个因素叠加。建议先用逐层对比定位问题层再针对性地调整量化策略。5.3 性能不达预期怎么定位性能不达预期通常有几个原因计算单元利用率低、内存带宽不够、指令调度不合理、同步开销太大。排查的时候我会先看计算单元利用率。如果利用率低于50%说明计算单元在等数据。这时候要看DMA的带宽利用率和缓存的命中率。如果DMA带宽跑满了说明带宽是瓶颈如果缓存命中率低说明内存分配有问题。如果计算单元利用率高但整体性能还是不行就要看指令调度。可能是流水线没有编排好导致计算和搬运没有重叠。这时候要检查编译器的调度策略看能不能把DMA指令提前。同步开销也是常见的性能杀手。如果硬件有多个核心核心间的同步如果太频繁开销会很大。这时候要考虑能不能减少同步点或者用更轻量的同步机制。5.4 常见问题速查表问题现象可能原因排查方法解决思路计算单元利用率低数据供不上看DMA带宽和缓存命中率增加带宽或优化数据复用精度下降明显量化误差大逐层对比浮点和量化输出调整量化粒度或混合精度算子不支持指令集覆盖不足统计算子出现频率软件模拟或算子拆解多核性能不线性同步开销大看同步指令的占比减少同步点或优化同步机制动态shape延迟高每次重新编译看编译时间占比多态编译或shape分桶功耗超标计算单元空转看PMU的功耗数据动态电压频率调整或任务调度5.5 几个容易踩的坑第一个坑是过度依赖仿真。仿真环境再精确也和真实硅片有差异。硅后一定会遇到仿真没暴露的问题所以软件团队要提前准备好调试工具和性能分析工具。第二个坑是忽略编译器团队的反馈。硬件团队有时候会觉得编译器团队“太挑剔”但实际上编译器团队的反馈往往是最真实的。如果编译器团队说某个指令很难用那大概率是真的难用。第三个坑是过早优化。在硬件架构还没定型的时候不要花太多时间做极致的软件优化因为硬件一改优化就白做了。先保证功能正确再逐步优化性能。第四个坑是忽视端到端的性能。单个算子优化得再好如果端到端流程里有瓶颈整体性能还是上不去。要始终关注端到端的延迟和吞吐。6. 工具链与团队协作的实操建议6.1 仿真平台的选择与搭建仿真平台是软硬件协同设计的基础设施。常见的仿真平台包括指令集仿真器ISS、周期精确仿真器CAS、FPGA原型。指令集仿真器速度快适合做功能验证和编译器调试。周期精确仿真器精度高适合做性能分析和瓶颈定位。FPGA原型速度介于两者之间适合做系统级验证。我的建议是三者结合日常开发用ISS性能调优用CAS系统验证用FPGA。ISS和CAS最好共享同一套指令集定义避免不一致。6.2 版本管理与持续集成AI芯片的软硬件协同设计涉及多个团队版本管理很重要。硬件团队有RTL代码软件团队有编译器、算子库、运行时。这些组件的版本要严格对应否则会出现“编译器生成的代码在旧版硬件上跑不了”的问题。持续集成也很关键。每次硬件或软件有更新都要自动跑一遍回归测试确保没有破坏现有功能。回归测试的用例要覆盖典型模型和边界情况。6.3 团队协作的沟通机制软硬件协同设计最大的挑战不是技术而是沟通。硬件团队和软件团队的思维方式不同硬件团队关注面积、功耗、时序软件团队关注性能、灵活性、开发效率。如果没有好的沟通机制很容易各说各话。我见过比较有效的做法是联合设计评审。每周开一次会硬件团队和软件团队一起过一遍当前的设计决策软件团队提需求硬件团队评估可行性。评审的产出要文档化避免口头承诺。另一个做法是交叉培训。让硬件工程师学一点编译器原理让软件工程师学一点数字设计基础。不需要精通但至少能听懂对方在说什么。6.4 文档与知识沉淀AI芯片的软硬件协同设计周期长、涉及面广文档很重要。指令集手册、编程模型指南、编译器优化策略、性能调优手册这些文档要随着设计迭代持续更新。我个人的习惯是每解决一个性能问题或者bug就写一篇简短的记录包括问题现象、排查过程、解决方案。这些记录积累起来就是团队最宝贵的知识库。7. 从项目实践里攒下的几点体会做AI芯片的软硬件协同设计最深的体会是没有银弹。每个项目都有不同的约束条件不同的workload不同的团队构成照搬别人的方案往往行不通。重要的是理解协同设计的基本原则然后根据实际情况灵活调整。另一个体会是早期投入很值得。在架构设计阶段多花时间做软硬件对齐比后期返工要划算得多。我见过太多项目因为早期沟通不足导致后期大量返工甚至错过市场窗口。还有一点是性能是调出来的不是设计出来的。硬件设计决定了性能的上限但实际能达到多少取决于软件优化的深度。同样的硬件不同的编译器优化策略性能可能差好几倍。所以软件团队的实力往往决定了芯片的最终竞争力。最后分享一个小技巧在项目早期可以先用FPGA或者仿真平台跑一个端到端的demo哪怕性能很差也能帮助团队建立信心发现流程里的问题。这个demo不需要很复杂跑通一个简单的卷积网络就行。有了这个demo后续的优化就有了基准团队也知道该往哪个方向努力。
← 返回资讯列表 预约报考咨询 →
NEXT STEP

看完公告,下一步怎么走?

把报考交给靠谱的人:材料预审、批次抢报、考前辅导、复审提醒,全程有人跟。

进入报考专题