news 2026/10/18 11:14:34

NPU数据通路优化:HBM、DMA与多bank Shared Memory的协同设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NPU数据通路优化:HBM、DMA与多bank Shared Memory的协同设计

1. 从一张架构图说起:为什么NPU的数据搬运比计算本身更值得琢磨

第一次接触NPU架构的时候,我和大多数人一样,注意力全在MAC阵列的算力峰值上——多少TOPS、支持什么精度、峰值功耗多少。直到真正上手调优一个卷积算子,才发现瓶颈根本不在乘加单元上,而是卡在数据供给这一环。算力单元大部分时间在等数据,就像一条八车道的高速公路,入口却只有一个收费站。

这个现象在业内其实不算秘密。NPU的data flow,也就是数据从外部存储一路搬到计算单元旁边的完整路径,决定了这颗芯片实际能跑出多少有效算力。标题里提到的三个关键词——HBM、DMA、多bank Shared Memory——恰好构成了这条路径上最核心的三段:片外的大容量存储、负责搬运的引擎、以及计算单元伸手就能够到的片上暂存。

我打算把这条链路拆开来讲清楚。从HBM的物理特性怎么影响访问模式,到DMA描述符怎么设计才能跑满带宽,再到Shared Memory为什么要分bank、bank冲突到底怎么产生的、怎么在写kernel的时候绕开它。整篇内容适合正在做NPU算子开发、性能调优,或者单纯想搞明白AI芯片数据通路设计的读者。不需要你手上有板子,但最好对计算机体系结构有一点基本概念,比如cache、总线、DDR这些词不陌生。

下面这张表是我自己总结的这条数据通路上各环节的核心关注点,先给个全局印象:

层级典型容量关键指标主要瓶颈
HBM8-64GB带宽、延迟行激活开销、刷新
DMA描述符队列吞吐、并发度描述符粒度、对齐
Shared Memory几十KB-几MB带宽、bank数bank冲突、容量

2. 整体数据通路设计:为什么是HBM加DMA加多bank SRAM这套组合

2.1 从冯诺依曼瓶颈到NPU的存储层次

传统CPU靠多级cache来缓解内存墙,但NPU的工作负载有个显著特点:数据复用模式高度可预测。卷积的权重、feature map的复用规律在编译期就能分析出来,所以NPU不需要像CPU那样靠硬件自动预测,而是可以用软件显式管理片上存储。这就催生了scratchpad memory(便签式存储)的设计思路,也就是我们说的Shared Memory。

这套层次的设计逻辑其实很朴素:HBM负责大容量,因为模型动辄几百MB到几个GB,片上根本放不下;DMA负责高效搬运,因为计算核心不应该浪费周期去处理地址计算和总线握手;Shared Memory负责低延迟高带宽供给,因为MAC阵列每个周期都要吃进大量操作数。

我见过一些新手会问:为什么不直接用HBM喂给计算单元?答案很简单,HBM的延迟在几百纳秒量级,而MAC阵列的周期是纳秒级,差了两三个数量级。如果每次乘加都去HBM取数,算力利用率会低到没法看。所以必须有一层靠近计算单元的SRAM做缓冲,把延迟藏起来。

2.2 三段式流水线的设计取舍

把这条通路画成流水线,大概是这个样子:HBM到Shared Memory是一段,Shared Memory到寄存器是一段,寄存器到MAC是一段。每一段都有自己的带宽和延迟特性,设计时要保证三段流水线的吞吐匹配,否则最慢的那段就是瓶颈。

这里有个常见的误区:很多人只盯着HBM带宽,觉得带宽越大越好。但实际上,如果DMA引擎的并发度不够,或者Shared Memory的bank设计不合理,HBM带宽再大也灌不进去。我实测过一个case,HBM理论带宽900GB/s,但实际算子只跑出不到200GB/s的有效带宽,问题出在DMA描述符切得太碎,每次搬运的数据量太小,总线握手开销占比过高。

提示:评估数据通路时,不要只看单点峰值,要看端到端有效带宽。从HBM读出的数据量除以算子总耗时,才是真实可用的带宽。

2.3 为什么Shared Memory要分bank

Shared Memory分bank的根本原因和DDR分bank类似:单端口存储每个周期只能服务一次访问,如果多个计算单元同时要取数,就会排队。分bank之后,只要访问落在不同bank上,就能并行服务。

但NPU场景和GPU有个区别:NPU的访问模式往往更规整,编译器可以提前规划。所以NPU的Shared Memory bank设计有时候会更激进,比如bank位宽做得更宽,或者支持多播。这些设计选择背后都是对典型算子访问模式的统计结果。

3. HBM侧的关键细节:访问粒度、行缓冲与刷新开销

3.1 HBM的物理结构决定了访问不能太碎

HBM(High Bandwidth Memory)通过硅中介层和芯片连在一起,位宽极大,通常1024bit以上,靠宽总线堆出高带宽。但它的内部结构和DDR一样,是bank、row、column的层次。一次访问要先激活一行(row activate),把整行读到行缓冲,再从行缓冲里选列。

这个特性意味着:如果连续访问落在同一行,效率极高;如果频繁跨行,每次都要重新激活,开销很大。HBM的行大小通常在1KB到2KB量级,所以DMA搬运的数据块最好大于这个尺寸,并且地址连续。

我做过一个对比实验:同样搬运1MB数据,按4KB块连续搬和按256B块随机搬,有效带宽差了将近三倍。前者能充分利用行缓冲,后者几乎每次都在激活新行。

3.2 刷新开销:那个容易被忽略的固定成本

HBM是DRAM,需要定期刷新,刷新期间不能响应访问。这个开销在带宽利用率高的时候占比不大,但在低负载或者访问模式不好的时候会变得明显。更麻烦的是,刷新时机不受控制,可能正好卡在关键路径上。

实际调优时,我一般会建议把大块连续搬运安排在流水线的稳定阶段,避免在算子启动和收尾的敏感时期做大量HBM访问。编译器调度时也会考虑这一点,把DMA的发起时机和计算重叠起来。

3.3 访问对齐:不是玄学,是硬性要求

HBM访问有对齐要求,通常要求地址对齐到总线位宽。比如总线位宽是256bit,那地址最好是32字节对齐。不对齐的访问会触发读改写,带宽直接打对折甚至更惨。

这个坑我在早期项目里踩过。当时DMA描述符里的源地址没做对齐,结果性能只有预期的一半。后来查手册才发现,非对齐访问会被拆成两次。解决办法很简单:在数据布局阶段就把tensor的起始地址对齐到256字节,同时保证每个维度的stride也是对齐的。

对齐情况有效带宽说明
256B对齐100%理想情况
128B对齐~85%轻微拆分
64B对齐~60%明显拆分
非对齐<50%读改写

4. DMA引擎:描述符设计、并发度与流水线重叠

4.1 DMA描述符的粒度怎么定

DMA搬运不是一次搬一个字节,而是按描述符来。一个描述符通常包含源地址、目的地址、搬运长度、stride等信息。描述符的粒度直接决定了DMA引擎的效率和灵活性。

粒度太粗,灵活性差,遇到非连续数据就要多次搬运;粒度太细,描述符数量爆炸,引擎处理描述符本身的开销就上来了。我的经验是,描述符的搬运长度最好在1KB到16KB之间,这个区间既能保证行缓冲命中率,又不会让描述符队列太深。

对于卷积这种有大量非连续访问的场景,可以用多维描述符。比如一个描述符描述一个二维块,包含行数、行内长度、行间stride。这样硬件可以自动展开,不需要软件生成大量一维描述符。

4.2 并发度:多通道DMA怎么配合

高端NPU通常有多个DMA通道,可以并行搬运不同的数据块。但并发度不是越高越好,因为HBM的带宽是共享的,多个通道同时抢带宽反而可能互相干扰。

我一般会这样配置:权重搬运用一个通道,feature map用另一个通道,输出写回用第三个通道。三个通道的优先级根据流水线阶段动态调整。比如计算阶段权重已经就位,就可以降低权重通道的优先级,把带宽让给feature map。

这里有个细节:DMA通道之间的仲裁策略很关键。如果是轮询仲裁,可能出现某个通道饿死的情况;如果是优先级仲裁,低优先级通道可能一直拿不到带宽。实际调优时要看具体硬件的仲裁机制,必要时通过软件调度来规避。

4.3 流水线重叠:让DMA和计算同时跑

数据通路设计的精髓在于重叠。理想情况下,DMA搬运第N+1块数据的同时,计算单元在处理第N块数据。这样HBM带宽和计算算力都能被充分利用。

实现重叠的关键是双缓冲或者多缓冲。Shared Memory里开两块区域,一块给当前计算用,一块给DMA写入。计算完成后交换角色。这个机制听起来简单,但实际实现时要注意同步:DMA写完要通知计算单元,计算单元用完要通知DMA可以覆盖。

注意:双缓冲的同步如果靠轮询,会浪费周期;如果靠中断,会有延迟。很多NPU用硬件信号量或者barrier指令来做,效率最高。写kernel时要确保同步指令插在正确的位置,否则要么死锁,要么数据竞争。

5. 多bank Shared Memory:bank冲突的成因与规避

5.1 bank冲突到底是怎么产生的

Shared Memory分bank之后,每个bank独立服务访问。如果同一个周期内,多个访问请求落在同一个bank的不同地址上,就会冲突,只能串行处理。这就是bank冲突。

举个具体例子:假设有32个bank,每个bank位宽4字节,总位宽128字节。如果32个线程分别访问地址0、4、8、...、124,正好每个bank一个,无冲突。但如果32个线程都访问地址0、128、256、...,这些地址的bank索引相同(因为bank索引通常是地址除以bank位宽再模bank数),就会全部落到同一个bank,冲突32次。

5.2 常见的冲突模式与规避方法

最常见的冲突模式是stride访问。比如矩阵转置时,按列访问就会产生stride冲突。解决办法有两种:一是padding,在每行末尾加几个填充元素,改变stride,让bank索引错开;二是swizzle,把地址做异或或者位重排,打散bank分布。

padding的方法简单直接,但浪费存储空间。swizzle更优雅,但需要编译器和硬件配合。我在实际项目里更常用padding,因为可控性强,调试也方便。

冲突模式典型场景规避方法代价
stride冲突矩阵转置padding存储浪费
广播冲突权重共享多播支持硬件复杂度
随机冲突稀疏访问swizzle地址计算开销

5.3 多播:当多个计算单元要同一份数据

卷积里有个典型场景:多个输出通道共享同一份输入feature。如果每个计算单元都去Shared Memory读一遍,bank压力很大。多播机制允许一次读取把数据同时送给多个消费者,大幅降低bank访问次数。

多播的实现通常是在Shared Memory输出端加一个广播网络。硬件检测到多个请求地址相同时,只读一次,然后复制多份。这个机制对卷积非常友好,能显著提升有效带宽。

但多播也有代价:如果请求地址不完全相同,硬件还是要分别处理。所以编译器要尽量把共享访问组织成地址完全一致的形式,才能吃到多播的红利。

6. 实操过程:手写一个数据搬运与bank优化的小例子

6.1 场景设定与参数计算

假设我们要实现一个简单的矩阵乘法C = A × B,A是64×256,B是256×64,C是64×64。数据放在HBM里,我们要通过DMA搬到Shared Memory,再喂给计算单元。

先算一下数据量:A是64×256×4字节=64KB,B是256×64×4字节=64KB,C是64×64×4字节=16KB。Shared Memory假设有256KB,足够放下A和B的一块tile。

分块策略:把A按行分块,每次搬16行,即16×256×4=16KB。B按列分块,每次搬16列,即256×16×4=16KB。这样每次计算16×16的输出块。

6.2 DMA描述符配置

对于A的搬运,源地址是A的起始地址加上行偏移,目的地址是Shared Memory的A缓冲区。由于A是行优先存储,16行连续数据在HBM里是连续的,可以用一个一维描述符搞定,长度16KB。

对于B的搬运,B是行优先存储,但我们要搬的是16列,这些列在HBM里不连续,每列之间间隔64×4=256字节。所以需要用二维描述符:行数256,每行搬16个元素(64字节),行间stride 256字节。

// 伪代码:配置B的二维DMA描述符 dma_desc.src_addr = B_base + col_offset * 4; dma_desc.dst_addr = smem_B_buffer; dma_desc.row_count = 256; dma_desc.row_size = 16 * 4; // 64字节 dma_desc.src_stride = 64 * 4; // 256字节 dma_desc.dst_stride = 16 * 4; // 目的端连续存放

6.3 Shared Memory的bank规划

假设Shared Memory有32个bank,每个bank位宽4字节。A缓冲区按行存放,每行256个元素,正好是256×4=1024字节,是32的倍数。这意味着A的每一行起始地址的bank索引都是0,如果计算单元按列访问A,就会产生严重的bank冲突。

解决办法:给A的每一行加padding。原本每行256个元素,改成260个元素,多出的4个元素做填充。这样每行起始地址偏移260×4=1040字节,1040除以4再模32等于4,bank索引每行错开4个。按列访问时,相邻行的同一列落在不同bank上,冲突大幅减少。

B缓冲区类似处理。B的每一列在Shared Memory里是连续的,按行访问B时也可能冲突。给B的每行也加padding,从16个元素加到20个。

6.4 同步与流水线

双缓冲的实现:Shared Memory里开两组A和B缓冲区,一组给计算用,一组给DMA写。用一个硬件信号量标记哪组可用。

流程大概是:DMA写buffer0,写完置信号量;计算单元等信号量,读到后开始算buffer0,同时DMA写buffer1;计算完buffer0后置信号量,DMA可以覆盖buffer0,计算单元转去算buffer1。如此循环。

提示:双缓冲的深度可以根据HBM延迟和计算时间调整。如果HBM延迟远大于计算时间,可能需要三缓冲甚至四缓冲来填满流水线。

7. 常见问题与排查技巧实录

7.1 有效带宽远低于理论值

这是最常见的问题。排查思路按顺序来:先看DMA描述符的搬运长度是否太小,如果小于1KB,优先加大;再看地址是否对齐,非对齐访问会直接砍半;然后看访问模式是否连续,跨行频繁的话考虑调整数据布局;最后看DMA通道之间是否互相干扰,尝试调整优先级或错开搬运时机。

我遇到过一个case,理论带宽800GB/s,实测只有150GB/s。查了一圈发现是描述符长度只有256字节,而且地址没对齐。改成4KB对齐搬运后,直接拉到600GB/s以上。

7.2 bank冲突导致计算单元等数据

如果计算单元的stall周期很高,而DMA侧看起来正常,大概率是bank冲突。用性能计数器看Shared Memory的冲突次数,如果冲突率高,就要检查访问模式。

常见的冲突来源是stride访问和广播缺失。先确认数据布局是否加了padding,再看编译器有没有生成多播指令。有时候手动调整一下循环顺序,把最内层循环的访问变成连续访问,冲突就消失了。

7.3 DMA和计算之间的同步死锁

双缓冲的同步如果写错,很容易死锁。典型症状是DMA和计算互相等对方的信号量。排查时先确认信号量的初始状态,再检查wait和post的配对是否正确。

我一般会在仿真环境里先跑一个小case,把信号量的状态打印出来,确认流水线能正常流转。上板之后再跑大case,避免在硬件上调试死锁,效率太低。

问题现象可能原因排查手段解决方向
带宽低描述符碎、非对齐看描述符长度和地址加大粒度、对齐
计算stallbank冲突看冲突计数器padding、swizzle
死锁同步错误仿真打印信号量检查wait/post配对
数据错竞争看时序波形加barrier

7.4 一些不太常见但很坑的问题

HBM刷新导致的偶发性能抖动,这个很难复现,但确实存在。如果发现性能曲线有周期性毛刺,可以查一下刷新周期是否和算子执行周期有共振。

还有DMA描述符队列溢出。如果软件生成描述符的速度超过硬件消费速度,队列会满,DMA引擎会stall。这时候要么降低描述符生成速率,要么加大队列深度。

最后是Shared Memory的容量规划。如果tile切得太大,Shared Memory放不下,就要回退到更小的tile,但这样DMA搬运次数增加,带宽压力更大。这个平衡点需要根据具体硬件的Shared Memory容量和HBM带宽来算。

8. 写在最后:几个我踩过的坑和常用技巧

关于HBM访问,我的经验是尽量让DMA搬运大块连续数据,单次搬运不要小于4KB。如果数据布局不允许,宁可先在HBM里做一次重排,也不要让DMA去搬碎片。

DMA描述符的设计上,能用多维就用多维,让硬件去展开循环,比软件生成一堆一维描述符效率高得多。描述符的队列深度要留足余量,至少能覆盖两到三个流水线阶段。

Shared Memory的bank规划,padding是最简单有效的办法。padding的大小要算清楚,让相邻行的bank索引错开,错开的量最好是bank数的互质数,这样冲突最少。

双缓冲的同步,能用硬件信号量就别用软件轮询。硬件信号量的延迟是周期级的,软件轮询可能浪费几十个周期。如果硬件不支持,那就把轮询循环写得紧凑一点,减少开销。

最后,性能调优一定要有数据支撑。不要凭感觉猜瓶颈在哪里,用性能计数器把HBM带宽、DMA吞吐、bank冲突率、计算单元利用率都打出来,哪个低就查哪个。我见过太多人凭直觉优化了半天,结果瓶颈根本不在他以为的地方。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 11:13:21

深度学习实战:用PyTorch从零搭建图像分类模型全流程复盘

这标题刚看到的时候我也愣了一下。d2ld2ld2l&#xff0c;乍一看像是随手敲的键盘乱码&#xff0c;但干这行久了反而觉得这种“乱码”往往藏着点东西。我第一反应是把它当成一个自嘲式的项目代号&#xff1a;d2l 就是 do to learn / deep learning 的语感&#xff0c;重复三遍其…

作者头像 李华
网站建设 2026/10/18 11:07:20

AI辅助写作如何降低AIGC痕迹?10个实用工具与人工工序全解析

说实话&#xff0c;我见过太多读专科的同学&#xff0c;AI用得比谁都溜&#xff0c;最后却栽在“一眼假”上。交上去的课程论文&#xff0c;老师扫了两行就抬头问一句“这是你自己写的吗”&#xff0c;你辛辛苦苦用AI整理资料、搭框架的功夫&#xff0c;全毁在这几个字上。今天…

作者头像 李华
网站建设 2026/10/18 11:04:36

告别手工调参:用PSO自动搜索SVM最优超参数

简介&#xff1a;采用粒子群算法优化支持向量机超参数的Python实现&#xff0c;面向有Python基础、希望提升SVM分类性能的机器学习学习者。粒子群通过模拟鸟群觅食&#xff0c;在解空间中搜索最优惩罚因子C与核参数gamma&#xff0c;相比人工调参更高效&#xff0c;适合多模态和…

作者头像 李华
网站建设 2026/10/18 11:04:29

K-Means聚类在新生儿败血症生命体征画像与ICU时长分析中的应用

1. 从一张新生儿监护床旁的困惑说起新生儿重症监护室里&#xff0c;监护仪每秒钟都在吐数据。心率、呼吸、血氧、体温、血压&#xff0c;一个宝宝一天下来就是几万条记录。但真正让临床医生头疼的&#xff0c;不是数据太少&#xff0c;而是数据太多、太杂&#xff0c;根本看不过…

作者头像 李华
网站建设 2026/10/18 11:03:04

用语言模型搞定多步骤实验设计:流程、踩坑与落地指南

需要承认一个现实&#xff1a;实验室里真正还在用"单因素轮换法"做实验设计的人&#xff0c;比我们想象中多得多。很多人听到"实验设计"四个字就皱眉&#xff0c;觉得那是统计学专家才玩得转的领域。但最近一年&#xff0c;我开始尝试把语言模型塞进这个流…

作者头像 李华