☰
AI芯片软硬件协同设计:从架构到工具链的系统工程
2026/10/28 21:17:40 网站建设 项目流程

AI 芯片的软硬件设计,从来不是一条"硬件先流片、软件再接入"的流水线。我在这个领域做了十多年,见过太多从架构选型到工具链落地的一线项目,也踩过不少坑——芯片回片之后发现理论算力 200 TOPS,模型一跑起来连一半都发挥不出来,这种情况十有八九不是硬件不够强,而是软硬件从一开始就没咬合。这个系列写到第 6 篇,话题开始从单点的架构、工具链细节转向系统级的软硬件协同。先说结论:我做过的项目里,凡是在立项之初就把软硬件当成一个整体设计的,后面都走得很顺;凡是把两边割裂开推进的,几乎都在回片后付出过代价。这一篇就围绕"AI 芯片的软硬件设计"展开,适合刚入行的学生和工程师建立全局视野,也适合正带队做芯片项目的同学拿来做排查清单参考。

1. 为什么软硬件协同是AI芯片绕不过去的一道关

1.1 算法半年一变,硬件三年一发

AI 算法演进的速度快得惊人。Transformer 刚火起来的时候,很多芯片还在为卷积神经网络优化数据路径;等到大模型成为主流,矩阵乘算子的规模、精度需求、甚至非规则计算(比如稀疏注意力、MoE 路由)全都变了。硬件从需求定义、架构设计、RTL 编码到流片,一般至少两到三年;而算法框架的迭代常常以月为单位。如果一个团队只盯着某一代模型来固化硬件结构,基本等于在赌"模型不会变",但历史反复告诉我们,这种赌赢的概率很低。

这就是为什么 AI 芯片比通用 CPU 更需要把"可编程性"放在头等位置。不同层级的灵活性都得有:指令集要能覆盖新的算子形态,片上存储分布最好支持不同的数据调度方式,互联网络也要对更多通信模式保持容忍。可编程性不是免费的,它要牺牲面积和功耗,但设计目标是在"常用算子跑得足够快"和"未来新算子不至于完全跑不动"之间找一个平衡。真正做过芯片的人都有体会:回片后最怕的不是性能差,而是新模型结构一来,硬件压根没法适配,那种无力感比性能差更让人头疼。

1.2 理论算力好看,实际利用率才是真金白银

很多项目汇报的 PPT 上都会写"算力达到 XX TOPS",但真正决定客户体验的是实际跑模型的效率,也就是硬件利用率——算力峰值到底发挥了多少。利用率的公式很简单:实际有效计算时间除以总运行时间。但它背后牵涉数据搬运、等待同步、计算单元空闲这一大堆问题,每一个环节掉链子,利用率都会被拖下去。

拿卷积举例:一个 3x3 的卷积如果实现得草率,可能需要反复从片外 DDR 读输入特征图和权重,而读一次数据的时间比做一次乘加快不了多少。精心设计数据复用之后,同一份输入可以被多个 PE 重复使用,片外带宽消耗立刻降一个量级。所以说,200 TOPS 的理论标称与实际能跑出 120 TOPS 还是 80 TOPS,差距往往不在 MAC 阵列本身,而在存储和数据路径的配合。这也是我在评估一款 AI 芯片时,从来不看峰值算力,先看 benchmark 实测和 roofline 分析的原因。标称数字只是理论天花板,真正决定能不能打的是把"理论"变成"实际"的那一套系统设计。

1.3 软硬件在AI芯片里到底怎么分工

简洁地说,硬件负责提供算力底座、存储层次和同步机制,软件负责把上层模型转换到这些硬件资源上,用最高效的方式调度起来。两者之间的分界线就是指令集以及硬件暴露出来的寄存器和控制接口。如果把整个系统比作一家餐厅,硬件是厨房设备和灶台,软件就是菜谱和主厨的调度方案——设备再好,菜谱不匹配,后厨照样出不了菜。

这个分工如果没在项目初期达成共识,后面就会出现经典的互相甩锅场景:硬件说软件没把数据喂足,软件说硬件的 DMA 搬一次数据要花几百个周期,设计不合理。这本质上不是人的问题,而是软硬件的"接口契约"没有提前定义清楚。什么时候定义清楚?在架构探索阶段,而不是 RTL 已经开写之后。我在 4.1 节会详细拆解接口分层的问题,这里先记住一个原则:软硬件分工的最高优先级是"让接口稳定、语义明确、边界清晰"。

2. 硬件侧真正决定成败的几个设计点

2.1 算力阵列:MAC排布要跟数据流走

表面上看,算力阵列无非是把一堆 MAC(乘加单元)摆在一起,摆多摆少似乎只要面积和功耗允许。但 MAC 阵列怎么组织,直接决定了数据流怎么走、每个 cycle 能喂进去多少数据,以及最终能跑满几成利用率。

主流方案大致分三类:GPU 常用的 SIMT(单指令流多线程)风格,把很多计算线程映射到大量 ALU 上,灵活但控制开销大;TPU 经典的脉动阵列,数据在阵列里像流水线一样逐级传递,数据复用率高,但结构偏向规则的矩阵运算;还有很多 NPU 采用可配置的 MAC 阵列,通过不同的数据流模式支持卷积、矩阵乘以及某些非规则算子。选择哪一类,要结合芯片定位来看:如果主打推理大模型,矩阵乘占比高,脉动阵列或者粗粒度阵列更划算;如果还要兼顾 CNN 和各类自定义算子,普通 SIMD 风格或可重构阵列的通用性更好。这里没有银弹,需要拿真实模型做算子占比统计,再拍板。

还有一个容易被忽略的点:MAC 阵列的位宽不一定非要统一。大模型时代,FP16 和 INT8 混合精度是常态,有些芯片甚至支持 INT4/FP8 格式。如果阵列能支持不同精度模式切分,一个 16x16 的 MAC 阵列在 INT8 模式下可以切成两个计算组,利用率会灵活很多。这类微架构上的取舍,同样取决于目标模型集的精度分布。

2.2 存储层级:片上数据搬移才是功耗大头

芯片设计里常说"算力很便宜,搬数据很贵"。这句话在实测功率里表现得很直观:一次乘加操作只需要若干 pJ,但把同一份数据从 DDR 搬到片上,消耗可能是计算本身的几十倍甚至上百倍。因此存储层级的设计目标,就是尽量让更多数据待在离计算单元更近的地方,反复使用。

常见的层级是这样:每个 PE 簇配一块不算小的 SRAM(Scratchpad 或 Local Buffer),多个 PE 簇之间通过全局片上存储做中间结果交换,DMA 负责在片内和片外 DDR 之间搬数据。设计的关键是 buffer 大小和 tiling(算子切分)配合。比如一个大矩阵乘,把输出分块成适合片上 buffer 的大小,让 input 和 weight 的每个元素在片上被重复利用多次,片外带宽需求立刻大幅降低。这块工作适合用量化思维来做:先算清楚某个算子对片外带宽的峰值要求,再倒推 buffer 容量和 DMA 位宽。

很多团队在存储这环上的第一个失误就是"buffer 越大越好"抢面积。实际上 buffer 翻倍带来的收益很快会边际递减,关键还是数据复用模式。如果一个算子的访问模式天然是流式的,buffer 再大也救不了;反过来,如果是高复用算子,适度增加 buffer 就能把片外带宽砍掉一大截。要结合算子特征逐类分析,而不是空喊"加 cache"。

2.3 互联和同步:多核片上通信的隐形瓶颈

到了多核场景,设计难点就从计算单元转移到了互联和同步。传统的总线结构在核数少的时候够用,核数一多总线就成了瓶颈,于是大家换上 NoC(片上网络)或 ring、crossbar 这类结构。总线适合少量数据广播和短交换,NoC 更适合并行流式传输,但协议更复杂,延迟也更高。这中间的选择没有绝对答案,完全取决于你对片上流量模型的预判。

同步是更隐蔽的坑。多核并行跑大模型时,需要同步的事件非常多:各核计算完某个中间层、DMA 搬完一块数据、buffer 被释放回池子里……如果同步机制写得过于简单,很容易出现"一个核早完成了,另一个核还在等 DMA"的空转时间。我见过一个项目,整体利用率从 55% 提升到 80%,最大改动不是换 MAC 阵列,而是把一次同步等待从轮询改成了事件触发机制,光这一项就省掉了大量等待周期。硬件上多设计一点异步事件通道,软件调度起来会顺溜得多。

3. 软件栈:编译器、算子库、运行时一个都不能少

3.1 编译器在AI芯片里干的三件事

很多人以为编译器就是把模型文件翻译成芯片指令,其实 AI 编译器的工作内容要丰富得多。核心可以分成三块:图优化、算子映射、内存规划。

图优化是在计算图层面做的,最常见的是算子融合。Conv+BN+ReLU 三段在 PyTorch 里是三个算子,实际跑的时候完全可以融合成一个 kernel,省去中间结果写回和读出的两趟数据搬运。这个优化往往像白捡一样提升性能。算子映射则是把每个算子变成硬件能执行的循环和指令序列,这里关键在选择合适的数据布局(NHWC 还是 NCHW,或者特定硬件偏好的 layout),以及决定维度在哪一层循环展开。不同 layout 直接影响 DMA 搬运和片上访问的连续性,差一个维度顺序,性能可能差出 30%。内存规划则是在更高层面决定哪些中间结果留片上、哪些得搬到片外、什么时候搬,相当于给数据流动定全局方案。

业界现在普遍用中间表示(IR)来做这一套。原因是顶层框架太多(PyTorch、TensorFlow、Paddle),底层硬件又各异,中间加一层类似 LLVM IR 的抽象,就能写一遍优化逻辑,分别映射到不同后端。TVM、MLIR 这两条技术路线本质上都是在构建这种抽象层。以我的经验,自研 AI 芯片的编译器最好站在这些开源生态的肩膀上开始,而不是从零造轮子——员工成本高,而且生态接口跟不上。

3.2 手写算子库为什么仍然不可替代

既然编译器能做图优化和算子映射,为什么每一家芯片公司还在大力维护算子库,连卷积、矩阵乘这种核心算子都手写汇编?原因很简单:通用编译生成的代码,在极致性能场景下往往打不过针对特定硬件手工调优的 kernel。

从数学上看,一个矩阵乘需要利用片上 memory 和计算单元的完美流水,手动调优可以把数据搬运和计算重叠到几乎零等待;而通用编译器面对的是更大范围的问题,很难在每个细节上都做极致。所以现实中的分工是:编译器把高层优化做完,输出到算子库;算子库针对每个形状的算子做专门调优,保证常见尺寸下都有可接受的性能。这套"编译器生成代码 + 手写特化 kernel"混合的体系,已经是行业默认方案。

我在实际项目里比较大的体会是:算子库要优先把 tensormapped 形状登记最全、hot path 覆盖最广。很多芯片回片后性能不达标,不是编译器不行,就是算子库只覆盖了大路货形状,一旦遇到非对齐尺寸就走 fallback 路径,性能断崖式下跌。这个问题在架构设计阶段就要想好:硬件是否支持非对齐访问,软件能否自动 pad,都会影响后面的工作量。

3.3 运行时调度:静态图、动态图与异步流水

算子库和编译器都属于"生成能执行的代码",运行时则管"这些代码怎么跑起来、资源怎么分配"。这里又要回到框架层:训练场景多用动态图(PyTorch 默认的 eager 模式),方便调试但每次都要解释执行;推理部署几乎无一例外转成静态图,因为静态图把整个模型的调度顺序、内存分配都预先规划好了,少了大量运行时解释开销。

在芯片运行时里还有两个值得注意的点,一个是 Buffer 的规划策略,另一个是异步流水。比如多个算子前后依赖关系明确时,可以把当前算子的计算和下一个算子的 DMA 预取重叠起来,形成常规意义上的软件流水线。这个重叠度做得越好,硬件利用率就越接近理论峰值。我见过不少性能优化的项目,本质上都是把"等待 DMA 回来"的时间一点点消掉——听起来不酷,但全部加起来提升巨大。

4. 软硬件接口怎么定,协同流程怎么走

4.1 接口分层:指令集、寄存器与硬件抽象层

软硬件接口是协同设计中最重要的一张"契约"。接口要回答几个问题:软件通过什么方式下发计算任务?算子的参数和地址怎么传?DMA 从哪块 buffer 搬到哪块 buffer?多核如何同步?我通常建议用下面几层来定义接口,下面用一段 DMA 描述符的伪代码示意,方便理解硬件向软件暴露的信息粒度。

typedef struct dma_desc { uint32_t src_addr; // 源地址(片上或片外) uint32_t dst_addr; // 目的地址 uint32_t length; // 搬运字节数 uint32_t src_stride; // 源地址步长 uint32_t dst_stride; // 目的地址步长 uint32_t trigger_event; // 启动条件,等待某事件 uint32_t done_event; // 完成后触发的事件 } dma_desc_t;

硬件定义指令集和关键寄存器,软件在此基础上搭建一个硬件抽象层(HAL),把寄存器操作封装成库函数,应用工程师不需要直接碰寄存器;最上面是编译器和运行时统一调用 HAL 接口。接口层级的抽象太低,软件写起来痛苦;抽象太高,又容易屏蔽掉一些硬件特性,导致性能发挥不出来。所以这个度一般由系统架构师掌控。比较好的做法是在架构探索阶段就拉软件团队一起评审接口,而不是等 RTL 冻结以后再补文档。

4.2 架构探索阶段:先用性能模型把账算明白

在 RTL 还没开写的时候,架构团队通常会上一个性能模型(cycle approximate simulator 或者函数级模拟器),把指令执行延迟、DMA 带宽、buffer 容量这些关键参数建模进去。软件团队基于这个模型提前开发编译器后端和算子库原型,跑 benchmark。这个过程虽然是"假的",但它的价值不可估量——每一次架构选型都能拿数据说话,而不是靠吵架定胜负。

这个阶段最值得做的一件小事是:把目标模型拆成算子清单,逐个套用 roofline 模型,看看每个算子是计算密集还是访存密集。roofline 模型很直观:横轴是算术密度(每字节数据参与了多少次运算),纵轴是能达到的性能,斜率是带宽,天花板是峰值算力。计算密集的算子,优化重心放在 MAC 利用率和数据复用上;访存密集的算子,则可能要从降低精度、压缩数据或者减少中间存储的角度下手。把这份清单整理出来,架构决策就有了数据支撑,而不是拍脑袋。

4.3 软件前置与回片bring-up的衔接

硬件流片周期很长,软件团队如果干等,项目节奏根本扛不住。常规做法是:在 RTL 仿真和 FPGA 原型阶段,就让软件团队在仿真器和 FPGA 上跑通编译好的模型,一边验证性能模型,一边摸清工具链的上限。FPGA 原型速度慢,但用来验证指令集和接口的正确性绰绰有余。真芯片回来以后,bring-up 的第一周通常是让硬件团队先跑内置自检(比如 memory BIST、寄存器读写回环),验证各模块工作正常,然后交给软件团队跑"冒烟模型",把整个推理流程走一遍。

这里的衔接期经常成为进度黑洞。为了减少风险,我的建议是提前定好"软件就绪门禁":在流片之前,仿真器和 FPGA 上跑通的模型必须包含几种典型网络结构,并达到预先约定的性能目标。真芯片一回来,只要冒烟测试通过,就可以直接对照预达标值和实测值,快速判断问题出在硬件时序、性能模型参数偏差还是软件适配层。

5. 十年踩坑实录:软硬件协同里的经典问题

5.1 一张速查表解决大部分debug场景

在实际项目里,绝大多数问题并没有那么高深,而是非常典型的几种模式。我整理了一张排查表,基本覆盖了常见的低利用率、异常卡死、数据错误问题。

现象最可能的原因排查思路
整体利用率低,计算单元空闲DMA 数据喂不上,搬移带宽不足或同步等待过长先看各核各周期状态统计,确认是内存瓶颈还是同步空转
跑大模型时偶发数据错误片上 buffer 越界,DMA 地址错位查 buffer 地址分配表,对比访问日志定位越界点
多核跑同一层时死锁同步机制设计不当,或 buffer 释放顺序依赖检查事件依赖图,确认有没有环形等待
算子库调优后性能不升反降边界分支开销过大,或 shape 不匹配特化条件添加 shape 分发,保证走特化路径的输入尺寸一致
片上内存不够跑大模型tiling 切分不合理,或全局 buffer 规划保守开启 layer-wise 内存复用,按生命周期交错分配

排查的第一步永远不是改代码,而是拿到真实的运行轨迹。硬件如果有片上的性能计数器(performance counter),一定要用起来,并把它对接到 profiler 工具里;没有这层硬件支持,调试难度会陡增。这也是为什么我强烈建议硬件设计时哪怕多用一点面积,也要把性能计数器做进去——这是软硬件协同里投入产出比最高的投资之一。

5.2 几条值得写进团队规范的经验

最后分享几条我踩过坑换来的经验。

一是软硬件接口的变更必须走闭环。任何寄存器语义、buffer 地址分配、指令编码的修改,都要走正式变更流程,同步软件团队并更新模拟器;否则等来的就是仿真器上跑通、真芯片上数据错乱这种最折磨人的 bug。二性能回归测试要自动化。每次工具链或硬件微码更新后,自动跑同一套 benchmark,比较性能曲线,防止某个优化引入隐性回退。三不要把"兼容性"只留给软件适配层。如果架构能预留一些通用 bit、扩展指令槽,未来新算子扩展时硬件不用改动,软件多一层适配就能接住。四软硬件团队要共同持有"端到端样例",不要各写各的 demo——交叉验证比交接口头承诺靠谱得多。

补充一个实用小技巧:构建一套端到端性能日记机制,每次跑 benchmark 都自动记录版本、硬件配置、软件 commit ID、性能数据。很多难以复现的偶发问题,靠这套日记能直接回溯出是哪个 commit 引入了变化。这个习惯虽然前期成本有点高,但后期几乎每次排障都会受益。

我个人在实际项目里最深的体会是:AI 芯片的软硬件设计,说到底不是两拨工程师的接力,而是一根需要两边共同握稳的绳子。硬件定架构时多想想软件能不能把它喂饱,软件做优化时也多回头看看硬件到底能支撑到什么程度,项目反而会顺利很多。如果你正准备启动一个 AI 芯片相关项目,我建议第一件事不是选架构,而是把性能模型、算子清单和软硬件接口草案一起摆到桌上讨论——很多坑,在那一张桌面上就已经能避免一大半了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询