☰
AI芯片软硬件协同设计全链路实践:从带宽规划到量化部署
2026/10/11 1:08:30 网站建设 项目流程

做AI芯片这些年,我越来越觉得,真正难的从来不是把某个算子的硬件做得有多快,而是把一个模型从PyTorch一路搬到芯片上,端到端跑得又快又稳。硬件设计、指令集、编译器、算子映射、片上存储与带宽分配,这些环节在AI芯片项目里是同一个问题,你没法单独谈其中任何一个。这篇文章就围绕某AI芯片项目X的软硬件协同设计过程,把从核选型、带宽设计、编译工具链,到模型量化部署和联调排错这几段完整链路整理出来。

内容偏实战,涉及不少具体数据和方案取舍,适合正在做AI芯片架构、NPU工具链、推理引擎部署的工程师参考;如果只是刚接触这一行,也能从里面看到一块芯片从算力指标到跑起真实模型中间到底发生了什么。

1. 设计思路:AI芯片为什么必须软硬件一起看

1.1 算力、带宽、功耗的三角约束

AI芯片的账,归根结底是算力、带宽、功耗三个变量的账。很多人以为评估芯片第一件事是看标称TOPS,其实做过项目就知道,真正卡脖子的经常不是算力,而是数据能不能在规定时间内搬进搬出。一个中等规模的检测模型,在一帧时间里要做的卷积乘加运算是固定的,但如果数据不是从片上存储直接取,而是要跨DDR去读,有效计算时间就会被访存等待吃掉一大半。功耗也一样,DDR读写一次消耗的能量往往比一次乘加高一个数量级。

数据搬移在整芯片功耗里的占比,我见过最多的能到七成。所以设计指标里除了TOPS,还必须把有效带宽、访存功耗、buffer命中率这些数据一并定下来。后面这几个才是决定“标称算力到底能兑现几成”的关键。我们当时先锁定了边缘视觉场景,选了典型目标检测和图像分类模型做基准,按层统计算子类型、计算量和访存量,最后发现卷积占大头,激活和池化次之,全连接在检测头里已经被1x1卷积替代了。基于这些统计再去定核数、带宽和互联方式,后面每一步才有据可依。

1.2 软件栈完整度决定芯片好不好用

芯片“好不好用”,七成由软件工具链决定。这个比例听起来夸张,但工作时间长了会发现不夸张。硬件提供的是算子执行能力,可如果编译器不能把PyTorch模型自动映射成指令序列,用户就得手工写汇编,部署一个模型要两三个星期,芯片基本没有竞争力。

我见过两颗峰值算力差不多的AI芯片,最后市场表现天差地别,差距就在工具链:前者输入ONNX模型,几个小时能出可运行的二进制;后者需要用户自己拆算子,连自家的技术支持都不太愿意碰。所以设计AI芯片,从一开始就不能只给硬件定规格,还要同步定义“软件要怎么落”。指令集设计要想到编译器怎么生成,内存地址布局要想到编译器怎么规划,片上buffer大小要想到一个tile能装下多少行特征图。软硬件在这里是一套契约,任何一方单独改动,另一端就要跟着返工。

1.3 先锁场景再定架构

不少团队做芯片先定算力指标,等流片回来再想跑什么模型,结果存储和互联扛不住目标负载,想改已经来不及。我们当初先跑了这样一个统计流程:把几个目标模型的每一层展开,记录算子类型、输入输出尺寸、计算量和访存量,然后按算子聚成一张表。

这张表很有说服力。卷积类算子计算密度高,但访存压力同样大;拼接、切分、变形这些算子几乎不耗算力,却要来回搬数据,在带宽紧张时反而容易成为瓶颈。架构上就必须给这类算子留出足够的高效数据搬运路径,而不是简单堆算力。先锁场景再定架构,听起来是句废话,真正做到的项目其实不多。

2. 硬件端的关键设计:核、带宽与互联

2.1 核选型怎么定:为什么开发现阶段用中核更划算

最开始规划里也有大核方案,单核塞几千个MAC阵列,想着一次卷积能出更多结果。但实际评估之后,我们在开发现阶段选了一个中等规模的中核作为最小系统。原因很简单:算力越高,对带宽和片上存储的配比要求越苛刻。一个大核跑3x3卷积,一个cycle要喂进去几百个输入像素,数据供不上就只能空转,标称算力再高也兑现不了。

中核方案单核配1024个MAC,主频500MHz,标称算力约1TOPS(int8);四核组网后整颗芯片约4TOPS。对边缘视觉场景,这个量级已经能覆盖大多数实时推理需求,而且每个核对带宽的要求更温和,用128bit数据位宽、400MHz频率的DDR接口就能喂饱。选核的过程还牵出一个细节:核的规模定了之后,指令集、寄存器文件、累加器位宽都要跟着定。我们给累加器留了32bit定点累加,支持int8乘int8加int32累加,长时间累加不容易溢出错位。

我个人的体会是,对中小规模芯片项目,先用中核把全栈跑通,等编译器、工具链和应用都沉淀得差不多了,再考虑堆更大规模的计算阵列,这个节奏是最稳的。

2.2 带宽账怎么算:从Memory到AI Engine的数据通路

Memory到AI Engine之间的带宽,是这类芯片最容易被低估的一环。我们用的办法是先用SystemC写TLM级仿真模型,把DMA、AXI总线、DDR模型都搭起来,直接跑完整应用,而不是等RTL写好了再去验证。RTL仿真跑一个全模型推理要几十万个cycle,太慢;SystemC几分钟能跑完,而且这个阶段拿到的带宽和等待周期数据,已经足够指导架构决策。

理论带宽看起来很好算:128bit位宽跑400MHz,峰值6.4GB/s。但SystemC仿真统计出来的实际有效带宽只有70%到80%,原因包括仲裁开销、bank冲突、读改写等待。我们把每个tile的数据搬运时间和计算时间放到一起对比,发现如果输入数据是fp32,一个目标检测模型单帧的数据搬移量大概在200MB量级,按每帧33ms预算,需求带宽要到6GB/s出头,已经贴着理论峰值。改成int8量化之后,单帧数据量降到约50MB,需求带宽降到1.5GB/s,余量一下就出来了。这个账直接决定了我们敢不敢把量化作为默认部署方案。

为了掩盖访存延迟,每个计算核里都加了prefetch buffer,用双缓冲结构。计算核在跑第k个tile时,DMA已经在把第k+1个tile往另一块buffer里搬。只要搬移时间小于计算时间,DMA就被完美掩盖。这个设计在SystemC模型里验证过,搬移和计算的并行程度直接影响端到端时延,差一级tile调度都能看到几个百分点的变化。

2.3 片内核间互联:低核数场景不要过度设计

多核之间怎么互联,很多人第一反应是上Mesh网络或者复杂NoC。但四核这个规模,其实没必要。数据跨核流动大多数发生在相邻层之间,卷积的输出作为下一层的输入,数据从一个核的局部SRAM传到相邻核的局部SRAM,用一块共享的交换SRAM就够了。

我们最后用的是分组总线加局部交叉开关的混合结构,每两个核共享一路接口,跨组的数据走全局总线。实测下来,四核配置下互联延迟可以忽略,面积和功耗都比Mesh省得多。这里有个关键决策:跨核数据一致性不要交给复杂缓存协议,而是直接用显式DMA搬运。AI负载的访存模式高度规律,编译器在编译期就能写出精确的搬运序列,不像通用CPU那样需要硬件去猜缓存。显式搬运虽然增加编译器工作量,但换来的是时序高度可预测,这对实时推理场景非常重要。

2.4 NPU的指令集与执行流水怎么定

NPU的特征可以概括成三块:矩阵运算单元处理卷积和全连接,向量单元处理激活、池化和逐元素运算,DMA单元负责数据搬运。指令集就按这三块分成三类:计算指令、搬运指令、同步控制指令。计算指令包括CONV、MATMUL、ELEW_ADD、ACT等;搬运指令包括LOAD、STORE、DMA_CPY;控制指令包括SYNC、WAIT、BRANCH。指令字长度统一取96bit,保留扩展位,后面新增算子不用整条线跟着改。

执行流水设计上,核心原则是“简单、可预期”。深度学习算子访存模式固定,不像通用CPU那样需要乱序发射、分支预测这些机制。我们做成顺序发射、多级流水,计算指令和DMA指令可以并行发射,DMA请求挂到队列里由DMA引擎异步执行。这样编译器好写,硬件验证也好做。另外有一条经验:指令集里一定要留一个故障状态寄存器,记录每个核最后一条指令地址和错误类型。否则出了问题只能靠猜,联调效率会非常低。

3. 软件工具链:编译器、运行时与部署链路

3.1 从ONNX到芯片指令的四个阶段

工具链的输入是现代框架导出的模型,而不是C代码,这是AI芯片和传统处理器最大的区别。内部编译器的流程分四段:模型解析、图优化、算子映射、指令生成与内存规划。

模型解析读入ONNX(也支持自定义IR),建一张计算图;图优化阶段做算子融合、常量折叠、布局转换;算子映射阶段把每个计算节点匹配到硬件算子模板,匹配不到的就拆成多个基础算子;指令生成阶段把每个tile的计算转换成指令序列,并完成寄存器分配、地址重映射、DMA序列生成。最终产出的是一个纯内存布局的二进制,运行时只需要把它拷贝到片上并触发启动。

最花时间的还是算子模板库。每个模板要支持多种strides、通道数、padding组合,边界条件一大堆。初期我们只把卷积、ReLU、MaxPool、Gemm、Softmax等十几个常见算子配齐,就能跑通大多数视觉模型。新算子进来后的流程是固定三步:先看能不能用现有算子组合出来,不能就补模板,模板上板后跑自动生成的全组合测试。这一步是最容易返工的环节,尤其边界条件很容易漏。

3.2 算子融合怎么省带宽:CONV+BN+ReLU合体的原理

融合优化省的不是计算量,而是带宽。以CONV+BN+ReLU三段为例,如果拆开执行,卷积的中间结果要写回DDR,再被BN读出来,BN结果又写一次,ReLU再读一次。一次完整前向会多出好几轮中间张量的读写。推理阶段的BN可以完全折叠进卷积权重和偏置里:把BN的scale、shift与卷积的w、b合成新的权重和偏置,公式上完全等价;ReLU则通过累加器输出后的截断电路实现。三段指令变成一条CONV指令,中间张量只停留在片上buffer,DDR访问次数直接减少约三分之二。

实测下来,融合后某些网络的端到端时延能降两到三成,功耗也降了,因为省了大量内存读写。这里有个坑:不是所有层都能融合。分支结构里同一个节点被多个后续节点使用,就必须保留中间结果;split/concat这类算子也经常让融合规则变得很复杂。编译器实现融合算法时要用读写集冲突检测来防止踩坑,否则可能出现两个算子改同一个buffer的隐性bug,排查起来非常痛苦。

3.3 静态内存规划与显式DMA调度

内存规划是整个编译阶段和硬件联系最紧密的部分。我们采用静态分配策略:所有buffer的地址偏移在编译期就算好,运行时不做动态分配。硬件上没有MMU,动态分配要维护分配器,容易产生碎片,实时性也难保证。静态规划的做法是,把每个算子需要的输入、输出、权重buffer地址画成一张生命周期表。一个算子的输出如果马上被下一个算子消费,就分配同一块地址,实现内存复用;如果中间跨很长计算才用到,就安排在后面复用。

DMA调度也放在编译阶段完成。根据每个tile的尺寸,编译器把DMA序列插入指令流,双缓冲的切换方向、等待点都由指令显式控制。这样做的好处是所有时序都在二进制里可见,调试时对着指令一条条看,就能知道哪里多等了一个周期。代价是编译器工作量大,需要处理大量对齐、边界和bank冲突细节。

编译器在设计时还要与硬件约定数据格式。输出feature map默认16字节对齐,如果一行有效数据是13字节,也要pad到16字节。这类约定必须写在架构spec里,编译器、DMA、硬件模板三方按同一个版本执行,否则数据错位问题会层出不穷。

3.4 多核负载划分与运行时调度

多核阶段最省力的做法是让编译器静态划分子图,而不是在运行时动态做负载均衡。比如一个检测网络前四段卷积分别在四个核上跑,中间一次总线上数据交换,再并行跑后面两段。每个核执行完整的局部指令流,runtime负责把编译产物下发、同步核间启动、管理程序计数器。同步机制用编译期插入的SYNC指令,核执行到SYNC停下来等栅栏,所有核到齐后再继续。静态划分的好处是行为可复现,不会因为调度抖动带来时延波动。

静态划分的弱点也很明显:如果不同核的子图算力差别大,会出现一个核满载、其他核空转的负载不平衡。解决办法是细粒度拆分,把耗时的卷积层按输出通道切成两半,分别映射到两个核上,让负载尽量均衡。这个切分原则仍然是编译期处理,运行时不需要额外逻辑。跨核拼接会多一点同步开销,但实测下来收益远大于开销。

4. 深度学习模型在NPU上部署的关键点

4.1 算子覆盖:一个新算子要过三道关

模型能不能在NPU上顺畅跑起来,第一道关卡是算子覆盖率。前期把常见分类和检测模型里的算子做了统计,排在最前面的永远是Conv、ReLU、MaxPool、Concat、Reshape、Gemm、Softmax这几类。把这些算子做成高质量硬件模板,覆盖面基本就够用了。

但总有例外。有一次遇到一个检测模型里有自定义算子,算子库没有现成实现,用子图展开拆成基础算子勉强能跑,但整层推理速度掉了将近四成。后面把这类场景做成专项,从三个维度去支持:硬件模板、编译器映射规则、单元测试用例。每新增一个算子,都要过这三关才算真正可用。后来还总结出一条经验:算子覆盖率不能只看数量,要看权重。在常用模型里高频出现的算子,优先级永远高于数量多但冷门的算子。

常见算子与硬件映射的对应关系,初期设计可以参考这样的表:

算子类型典型实现硬件资源优化关注点
卷积类CONV模板矩阵运算单元通道对齐、tile切分
激活类ReLU/Sigmoid模板向量/比较单元饱和截断、查表
池化类MaxPool模板向量单元窗口边界处理
全连接MATMUL模板矩阵运算单元权重布局
拼接/切分COPY模板DMA/向量单元地址对齐
归一化类BN折叠到卷积编译器优化精度保持

4.2 量化方案:先跑通fp32,再碰int8

部署精度调优的顺序非常重要。我踩过坑,所以一直记着一条铁律:先用fp32把整个链路跑通,结果对得上之后,再引入量化。原因很直白,如果fp32结果已经不对,说明是硬件或链路问题,这时候再引入量化只会让问题更复杂,没法定位。

int8量化流程本身不复杂:拿一批校准图片喂给模型,统计每层数值范围,确定scale和zero point,再把权重离线转成int8。per-channel量化比per-tensor精度损失小很多,因为不同通道的数值范围差异大,用同一scale会放大误差。硬件累加单元支持int32累加时,per-channel在实现上并不难。实测下来,大部分模型直接做PTQ精度下降能接受,少部分敏感层保留fp32,用混合精度方案解决。量化带来的收益是双份的:int8相比fp32在同一硬件上吞吐更高,数据搬移量减半,恰好缓解带宽压力,这也是我们敢把量化当默认部署方案的原因。

4.3 动态形状怎么处理:固定形状是硬约束

NPU流水线体系最怕动态shape。硬件上地址偏移是编译期算好的,输入尺寸一变,所有地址都要重算,运行时装不下的场景只能fail。我们在设计阶段就对场景做了限制:输入分辨率是一个固定集合,比如1280x720和640x640,每个分辨率在编译期生成一份二进制,推理启动时按实际分辨率选一份加载,运行中不允许变。

NLP类模型更麻烦,序列长短不齐,我们统一padding到最大长度,再按长度分桶减少浪费。这个取舍听着有点硬,但从实时推理角度看是合理的。边缘设备的输入分辨率本来就相对固定,与其在硬件上支持动态shape然后背上高额代价,不如把约束前置到部署配置里。当然这意味着部署流程要更严谨,模型发布前必须把所有分辨率配置都验证一遍,少一个都可能上线后翻车。

4.4 端到端实测数据怎么看

用FPGA原型验证平台跑一个目标检测模型,batch=1,这是最典型的真实负载。单核配置下端到端推理一帧耗时约5.2ms,折合约192帧每秒;双核配下去到2.9ms,接近线性加速;四核按理论理想情况应该到1.3ms,但实际只到1.6ms,扩展效率约81%。瓶颈在带宽一致性:四个核同时从DDR取数据,总线仲裁冲突上来,DMA有效吞吐掉了一些。

核数理想时延实测时延扩展效率
单核5.2ms5.2ms100%
双核2.6ms2.9ms90%
四核1.3ms1.6ms81%

这张表格说明两件事:多核收益不是线性的,架构上要时刻给带宽余量留空间;另外实测单核利用率只有71%,而不是接近100%,这也正常,因为计算和搬移不可能完全重叠,总会有边界等待。能效比方面,整卡int8模式下跑这个模型能到4TOPS/W左右,在同功耗等级下比通用方案高一截,这正是AI芯片的价值点。

5. 联调实录:常见问题与排查技巧

5.1 一张速查表帮你定位大多数问题

长时间联调下来,我整理了一张问题速查表。看到现象,对号入座,大部分问题能在半小时内定位。

现象最可能原因检查方向
推理结果全乱/错误数据格式或地址对齐错误检查NCHW/NHWC布局、DMA源/目标地址、16字节对齐
结果接近正确但有偏差量化敏感层丢失精度检查per-channel scale、混合精度配置
性能远低于预期中间张量过多落DDR看trace里的DMA周期占比、确认融合图是否生效
多核跑不满负载不均衡或总线冲突检查子图划分、SYNC等待周期统计
偶发crash同步缺失或缓存污染检查SYNC/WAIT指令、中断处理流程
内存不够载入失败静态内存规划冲突检查buffer生命周期重叠、权重压缩策略

排查时最常用的是片上计数器。我们在每个核里放了周期计数器、DMA等待计数器、buffer miss计数器,可以直接读到硬件层面哪一块在等待。这类统计信息在设计初期很容易被当成“非功能需求”砍掉,但真正联调起来,它们能把问题缩小到单个指令周期级别,省下的调试时间远超那点面积。

5.2 调试三板斧:golden比对、trace分析、回归集

第一板斧是golden比对。我们在SystemC模型、软件模拟器、RTL三个环境里跑同一个模型,约定每个算子的中间输出完全一致。任何一个环境结果不一致,就能立刻定位是硬件行为还是软件模型的问题。这一步做扎实了,后面联调能少走很多弯路。

第二板斧是trace分析。编译器的指令流里每一拍都能导出DMA搬移和计算执行的时序,把trace导出来和理想调度对比,哪里多等几个周期一目了然。第三板斧是回归集。每次硬件版本变化都跑一遍代表性模型集合,分类、检测、分割各几个,防止改一个模块把另一个模块的时序搞坏。

调试顺序上还有一条铁律:先让结果对,再让性能快。很多人上来就调性能,查了半天发现数据根本算错了,白费功夫。先把正确性验证到bit级,再回头调流水线和DMA,效率高得多。这个顺序我每次都会给团队强调,因为它代表的是排错逻辑:数据错和时序慢是两个独立问题,混在一起查,怎么都查不明白。

5.3 一点工程上的心得

最后再分享一个小技巧:在开始写RTL之前,先把SystemC仿真、软件模拟器和FPGA原型这三级验证平台搭起来,每一级都要能跑通同一个端到端模型,产出golden数据。这个做法前期投入看似很大,但越到后期越值。任何一层改动了,另外两层立刻能告诉你影响范围。我做的几次架构调整中,这套三级验证体系从没让人失望过。

另一个经验是尽量提前预埋trace端口和统计计数器,哪怕刚开始觉得用不上,后面一定会用上。有一回为定位偶发的DMA时序冲突,我们在仿真里加了大量监视器,最后还是靠一个原本以为多余的性能计数器,发现是bank冲突导致多等了32个周期。如果当初没留这个口子,这个问题可能要多花三周。

这套流程用到后面,真正沉淀下来的不是某一颗芯片的代码,而是从架构评估到模型部署的一整套方法论。新的模型来了,先看算子覆盖,再跑golden,接着量化,最后压性能。每一步都有数据支撑,就不会总在同一个地方摔倒。这也是我做AI芯片软硬件设计以来最想强调的一点:每一个取舍背后都应该有一个可测量的依据,工具链、回归集、统计计数器的价值,往往比多塞几个计算单元更值得先投入。

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

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

立即咨询