news 2026/10/19 1:51:25

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

作者头像

张小明

前端开发工程师

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

1. 从一张架构图说起:为什么数据流才是NPU的命门

很多人第一次接触NPU,注意力都会被MAC阵列的规模吸引——多少乘加单元、峰值算力多少TOPS,这些数字确实好看。但真正上手写算子、调性能之后你会发现,算力从来不是瓶颈,数据喂不饱才是。一个标称几百TOPS的加速器,如果数据流设计得不好,实际跑起来可能连十分之一的利用率都摸不到。

这一章要聊的就是这件事:数据怎么从HBM里出来,经过DMA搬运,最终落到多bank的Shared Memory上,供计算单元消费。这条链路听起来简单,但里面藏着大量工程取舍——bank怎么划分、DMA怎么调度、片上缓存怎么复用、搬运和计算怎么重叠。每一个决策都会直接反映到最终的吞吐上。

我写这篇东西的出发点,是自己在做算子移植和性能调优时踩过的一堆坑。网上讲NPU架构的文章不少,但大多停留在“有HBM、有DMA、有Shared Memory”这种名词罗列层面,真正把数据流动路径讲透、把参数选择讲清楚的很少。所以我把自己的理解整理出来,适合两类人看:一是刚接触NPU、想搞明白片上数据到底怎么走的人;二是已经在写算子、但性能总是上不去、想从数据流角度找瓶颈的人。

核心关键词就三个:HBM、DMA、多bank Shared Memory。这三个东西串起来,就是NPU数据流的主干道。

2. 整体数据流设计:为什么是这三层结构

2.1 存储层次的基本盘:容量、带宽、延迟的三角博弈

任何计算芯片的存储设计,本质上都是在容量、带宽、延迟这三个维度上做妥协。HBM容量大、带宽高,但延迟也高,而且功耗不低;片上SRAM(也就是我们说的Shared Memory)延迟极低、带宽可以做得非常宽,但面积贵、容量小。中间必然需要一个搬运机制,把数据从大容量存储搬到小容量高速存储,这就是DMA的职责。

你可以把这套结构类比成厨房:HBM是冰箱,容量大但拿东西慢;Shared Memory是案板,就在手边,切菜方便但面积有限;DMA就是那个来回跑腿的人,负责把食材从冰箱搬到案板。厨师(计算单元)只负责在案板上切菜,如果跑腿的人不够快或者路线规划不好,厨师就得干等着。

这个类比里有个关键点容易被忽略:案板往往不止一块。这就是多bank Shared Memory的由来。单块案板一次只能放一样东西,多块案板可以让厨师同时处理不同食材,前提是跑腿的人知道该往哪块案板上放。

2.2 为什么不用统一大缓存

有人会问,为什么不干脆做一块巨大的片上SRAM,省掉DMA这一层?答案很直接:面积和成本。片上SRAM的位密度远低于HBM,做大了芯片面积爆炸,良率下降,成本飙升。而且大容量SRAM的访问延迟也会上升,物理上离得远的bank,走线延迟就够你喝一壶。

另一个原因是灵活性。DMA作为一个独立的搬运引擎,可以设计成高度可编程的,支持各种数据布局变换——比如把HBM里连续的NCHW张量,搬成片上计算需要的分块格式。如果让计算单元自己去HBM取数,这种布局变换会变得非常低效。

2.3 数据流设计的核心目标:让计算单元永远不等数据

整条链路的设计目标可以浓缩成一句话:让计算单元永远有数据可算。听起来像废话,但真正做到很难。因为搬运和计算的速度往往不匹配,你需要让它们重叠起来——在算当前这块数据的时候,下一块已经在搬了。这就是所谓的double buffering或者multi-buffering。

实现重叠的前提是,DMA和计算单元能并行工作,而且Shared Memory有足够的空间容纳多份数据。这就引出了后面要讲的bank划分和buffer管理策略。

提示:评估一个NPU数据流设计好不好,最直接的办法是看它在典型算子上的DMA利用率和计算利用率。如果两者都高,说明重叠做得好;如果DMA利用率低而计算单元在等,说明buffer不够或者调度有问题。

3. HBM侧的关键细节:不只是“大内存”那么简单

3.1 HBM的物理特性对数据流的影响

HBM(High Bandwidth Memory)通过硅中介层与计算die互连,位宽可以做到非常宽(比如1024bit甚至更宽),这是它能提供高带宽的根本原因。但高带宽不等于任意访问模式都能跑满。HBM内部由多个channel和bank组成,连续访问和随机访问的实际带宽差距可能有好几倍。

这意味着DMA在搬运数据时,访问模式非常关键。理想情况下,DMA应该以大块连续的方式读取HBM,充分利用burst传输。如果搬运的粒度太小、地址太分散,HBM的实际带宽会大打折扣。

我在实际项目里遇到过一个典型问题:某个算子的输入张量在HBM里是跨步存储的(stride很大),DMA按元素逐个搬运,结果HBM带宽利用率只有理论值的30%不到。后来改成先做一次布局重排,把数据整理成连续块再搬,带宽利用率直接拉到80%以上。

3.2 地址对齐与突发长度

HBM访问对地址对齐有要求。通常建议DMA的搬运起始地址按burst长度对齐(比如256字节或512字节),这样每次访问都能命中完整的burst,不浪费带宽。如果起始地址没对齐,硬件可能需要进行read-modify-write,效率会明显下降。

突发长度(burst length)的选择也需要权衡。太短,命令开销占比高;太长,灵活性差,可能搬了用不上的数据。一般经验是,搬运粒度控制在几KB到几十KB之间比较合适,具体要看Shared Memory的bank大小和计算单元的分块需求。

3.3 多通道并行访问

HBM通常有多个独立通道,DMA如果能把搬运请求分散到不同通道上,可以进一步提升有效带宽。这要求DMA引擎支持多通道并发,或者至少能把大块搬运拆分成多个子任务,分别落到不同通道的地址范围上。

这里有个实操技巧:如果数据在HBM里的物理地址分布已知,可以在软件层面把大张量切分成多个子块,让DMA描述符分别指向不同通道的地址区间。这样即使DMA引擎本身不支持自动通道交织,也能通过软件调度实现类似效果。

4. DMA引擎:数据流的调度中枢

4.1 DMA描述符的结构与编程模型

DMA的工作方式是“描述符驱动”的。每个描述符描述一次搬运任务:源地址、目的地址、搬运长度、步长信息、以及一些控制标志。软件把描述符链写好,DMA引擎按顺序执行。

一个典型的描述符包含这些字段:

字段说明常见取值
src_addrHBM侧源地址按burst对齐
dst_addrShared Memory目的地址按bank对齐
length搬运字节数几KB到几十KB
src_stride源侧步长连续时为0
dst_stride目的侧步长连续时为0
flags中断、链式等控制位按需设置

描述符链的设计直接影响DMA的调度效率。如果每个描述符只搬很小一块,描述符本身的读取和解析开销就会占比过高。一般建议单个描述符搬运的数据量不要太小,同时用链式描述符减少软件干预。

4.2 搬运与计算的重叠机制

实现重叠的核心是双缓冲或多缓冲。具体做法是:把Shared Memory划分成若干块buffer,DMA往buffer A写的时候,计算单元从buffer B读。等A写完了,计算单元切到A,DMA去写B,如此循环。

这里的关键参数是buffer数量和每块buffer的大小。buffer太少,重叠不充分;buffer太多,Shared Memory不够用,而且管理开销上升。实践中,2到4块buffer是比较常见的配置。

还有一个细节:DMA写buffer和计算单元读buffer之间需要同步。通常用信号量或屏障机制来协调,确保计算单元不会读到还没写完的数据,DMA也不会覆盖正在被读的数据。这个同步如果做得太粗,会引入不必要的等待;做得太细,又会增加控制开销。

4.3 DMA调度策略:优先级与抢占

当多个计算任务共享一个DMA引擎时,调度策略就很重要了。简单的做法是FIFO,先来先服务。但实际场景中,不同任务的紧急程度不同,有些任务的计算单元在等数据,有些则还在处理上一批数据。这时候如果能给紧急任务更高的DMA优先级,整体利用率会更好。

更高级的做法是支持DMA任务抢占。当一个高优先级任务需要搬运时,可以打断正在进行的低优先级搬运,等紧急任务完成后再恢复。这需要DMA引擎保存和恢复上下文,硬件复杂度更高,但在多任务场景下收益明显。

注意:DMA抢占虽然好用,但会带来上下文切换开销。如果抢占太频繁,反而会降低整体效率。建议只在任务优先级差异明显、且高优先级任务确实紧急时才启用。

5. 多bank Shared Memory:片上数据的高速公路

5.1 bank划分的底层逻辑

Shared Memory之所以要分bank,核心目的是支持并行访问。如果所有数据都在一个大块里,同一时刻只能服务一个访问请求,带宽就上不去。分成多个bank之后,只要访问落在不同bank上,就可以并行进行。

bank的划分方式通常有两种:按地址低位交织(interleaving)和按地址高位分块(blocking)。交织方式下,连续地址会轮流落到不同bank,适合顺序访问;分块方式下,一段连续地址固定在一个bank,适合随机访问。实际设计中,往往两种方式结合使用。

bank数量不是越多越好。bank多了,每个bank的容量就小,可能放不下一个完整的数据块;而且bank的译码逻辑和互连复杂度也会上升。常见的bank数量在8到32之间,具体取决于计算单元的并行度和数据块大小。

5.2 bank冲突:性能的隐形杀手

bank冲突是Shared Memory性能的头号敌人。当多个访问请求落到同一个bank时,硬件只能串行处理,带宽直接打折。冲突的根源通常是访问模式设计不当。

举个典型例子:假设有16个bank,计算单元需要同时读取一个16x16矩阵的某一列。如果矩阵按行存储,每一列的元素地址间隔是16,正好都落在同一个bank上(因为16是bank数的整数倍),这就是最坏情况的bank冲突。

解决办法通常是padding——在每行末尾加几个填充元素,打破地址的规律性。比如16x16矩阵按行存储时,每行实际分配17个元素的空间,这样列访问的地址间隔变成17,就不会都落到同一个bank了。代价是浪费了一些存储空间,但换来的带宽提升非常值得。

5.3 多bank下的数据布局设计

数据在Shared Memory里的布局,直接决定了计算单元能不能高效访问。布局设计要考虑两个因素:计算单元的访问模式和bank的并行能力。

对于矩阵乘法这类算子,常见的做法是把矩阵分块(tiling),每个tile放到不同的bank上。计算单元处理一个tile时,只需要访问对应的bank,不会和其他tile的访问冲突。同时,tile内部的数据布局也要考虑,确保计算单元读取时不会产生bank冲突。

这里有个经验:布局设计最好和DMA的搬运格式协同考虑。如果DMA搬进来的数据布局就已经是计算单元需要的格式,就不需要在Shared Memory里再做一次重排,省时省力。这就要求在写DMA描述符时,就把目标布局考虑进去。

6. 完整数据流实操:从HBM到计算单元的端到端走一遍

6.1 场景设定与参数计算

假设我们要实现一个矩阵乘法算子,输入矩阵A是MxK,矩阵B是KxN,输出矩阵C是MxN。NPU的Shared Memory总容量是256KB,分成16个bank,每个bank 16KB。计算单元一次能处理16x16的输出tile。

先算一下数据分块。为了计算一个16x16的输出tile,需要A的16xK行和B的Kx16列。假设K=256,那么A的tile是16x256,B的tile是256x16。以fp16存储,A的tile是8KB,B的tile是8KB。一个输出tile需要16KB的输入数据。

256KB的Shared Memory,如果全部用来做双缓冲,可以放8组这样的tile。但实际不会全用,因为还要留空间给输出和其他临时数据。假设我们用4组buffer做输入双缓冲,每组16KB,共64KB,剩下的空间给输出和其他用途。

6.2 DMA搬运描述符的编写

DMA需要把A的tile和B的tile从HBM搬到Shared Memory。A的tile在HBM里是原始矩阵A的一部分,地址不是连续的(因为A是MxK,取16行的话,每行之间间隔K个元素)。所以DMA描述符需要支持步长搬运。

一个可能的描述符配置:

// 搬运A的16x256 tile dma_desc_t desc_a = { .src_addr = A_base + row_start * K * sizeof(fp16), .dst_addr = smem_base + buffer_idx * TILE_SIZE, .length = 256 * sizeof(fp16), // 每行搬运256个元素 .src_stride = K * sizeof(fp16), // 行间步长 .dst_stride = 256 * sizeof(fp16), // 目的侧连续 .num_rows = 16, // 搬运16行 .flags = DMA_FLAG_CHAIN };

B的tile类似,但B是KxN,取256行16列,步长是N。

6.3 计算与搬运的重叠调度

调度逻辑大致是这样的:

  1. DMA搬运第0组A、B tile到buffer 0
  2. DMA搬运第1组A、B tile到buffer 1(与上一步可以并行,如果DMA支持多任务)
  3. 计算单元等待buffer 0就绪,开始计算
  4. 计算单元计算buffer 0的同时,DMA搬运第2组到buffer 2
  5. 计算单元计算完buffer 0,切到buffer 1,同时DMA搬运第3组到buffer 0(复用)
  6. 如此循环

这个调度里,buffer的数量决定了能重叠多深。2组buffer只能重叠一级,3组可以重叠两级,以此类推。但buffer越多,同步逻辑越复杂,而且Shared Memory占用越大。

6.4 实测数据与调优记录

在一个实际项目里,我用类似的配置跑矩阵乘法,初始版本的DMA利用率只有45%,计算利用率60%。分析后发现两个问题:一是DMA描述符的搬运粒度太小,每个描述符只搬4KB,描述符解析开销占比高;二是bank冲突严重,A的tile按行存储时,计算单元读取列方向数据产生大量冲突。

调整方案:把搬运粒度加大到16KB,减少描述符数量;在A的tile每行末尾加8个元素的padding,打破bank冲突。调整后DMA利用率提升到78%,计算利用率提升到85%。整体吞吐提升了约60%。

这个案例说明,数据流的调优往往不是单一因素,而是多个细节叠加的结果。搬运粒度、bank布局、buffer数量、同步策略,每一个都值得仔细推敲。

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

7.1 DMA带宽上不去,怎么定位

先看DMA描述符的搬运粒度。如果单个描述符搬运的数据量小于1KB,描述符开销很可能就是瓶颈。试着加大粒度,看带宽是否提升。

再看HBM访问模式。如果源地址跨步很大,或者访问不连续,HBM的实际带宽会远低于理论值。可以用性能计数器看HBM的row hit rate,如果命中率低,说明访问模式对HBM不友好。

最后看DMA引擎本身是否成为瓶颈。有些DMA引擎的并发能力有限,多个任务排队时会出现拥塞。这时候需要调整任务优先级,或者把大任务拆分成多个小任务并行提交。

7.2 Shared Memory bank冲突怎么发现和解决

bank冲突通常表现为计算单元利用率下降,但DMA利用率正常。因为数据已经搬到了Shared Memory,但计算单元读的时候被bank冲突卡住了。

发现方法:用性能计数器统计bank冲突次数。如果冲突次数占总访问次数的比例超过10%,就值得优化。

解决方法:调整数据布局,加入padding;或者改变计算单元的访问顺序,让同一时刻的访问分散到不同bank。有时候,把数据在Shared Memory里做一次转置,就能大幅减少冲突。

7.3 双缓冲不生效,计算和搬运没有重叠

最常见的原因是同步做得太保守。比如计算单元在开始计算前,等待所有DMA任务完成,而不是只等当前buffer就绪。这会导致DMA和计算串行执行,重叠完全失效。

检查方法:看时间线上DMA活动和计算活动是否有交叠。如果没有交叠,就是同步粒度太粗。

解决:把同步细化到每个buffer级别。计算单元只等待它即将消费的那个buffer,DMA也只等待它即将写入的那个buffer空闲。其他buffer的搬运和计算可以自由并行。

7.4 问题速查表

现象可能原因排查方向解决思路
DMA带宽低搬运粒度小看描述符大小加大单次搬运量
DMA带宽低HBM访问不连续看row hit rate重排数据布局
计算利用率低bank冲突看冲突计数器加padding或改布局
计算利用率低数据未就绪看同步等待时间细化同步粒度
无重叠同步太粗看时间线按buffer同步
无重叠buffer不足看buffer数量增加buffer

提示:排查数据流问题时,时间线工具是最好的朋友。把DMA活动、计算活动、同步事件都画在一条时间线上,瓶颈一目了然。很多问题不需要复杂分析,看一眼时间线就清楚了。

8. 一些踩坑之后的个人体会

数据流调优这件事,最忌讳的就是一上来就调参数。我见过不少人,听说buffer数量影响性能,就把buffer从2加到4,结果Shared Memory不够用,反而引入了更多换入换出。正确的做法是先搞清楚瓶颈在哪,再针对性地调。

另一个体会是,DMA描述符的设计比想象中重要。很多人把DMA当成一个简单的memcpy引擎,描述符写得很随意。但实际上,描述符的粒度、链式结构、地址对齐,都会显著影响DMA的实际效率。花时间把描述符设计好,往往比调其他参数收益更大。

还有一点,多bank Shared Memory的布局设计,最好在算子开发初期就考虑进去,而不是等到性能出问题了再回头改。因为布局一变,DMA描述符、计算单元的访问代码都要跟着改,返工成本很高。前期多花点时间设计,后期省很多事。

最后分享一个小技巧:如果bank冲突实在难以避免,可以考虑用Shared Memory的广播机制。当多个计算单元需要读取同一个地址的数据时,硬件可以广播给所有请求者,只占一次bank访问。这在某些特定算子(比如卷积里的权重共享)中非常有用。

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

FreeLLMAPI 上手:把 34 家免费模型接入同一个 OpenAI 兼容接口

FreeLLMAPI 上手:把 34 家免费模型接入同一个 OpenAI 兼容接口 【免费下载链接】freellmapi 7.4 billion tokens per month. 34 free LLM providers. 635 free model endpoints. All behind one /v1 endpoint, plus any custom OpenAI-compatible endpoint. Smart r…

作者头像 李华
网站建设 2026/10/19 1:48:20

酷秒神马9.0源码深度解析:从架构重构到任务调度与缓存设计

先说结论:酷秒神马9.0版2026最新源码系统,从代码层面看,不是一次小修小补的迭代。我拿到这套源码包后做的第一件事不是急着跑Demo,而是直接看目录结构和核心依赖的变化。整套系统的主版本号从8.x跳到9.0,意味着有非兼容…

作者头像 李华
网站建设 2026/10/19 1:47:40

macOS 安装 MySQL 完整教程

macOS 安装 MySQL 完整教程(Homebrew) 本文记录在 macOS 上用 Homebrew 安装 MySQL 的完整过程,包含国内网络下载失败的解决方法,以及安装后如何启动服务、设置密码。本机环境:macOS 26.3.1,Homebrew 6.0.1…

作者头像 李华
网站建设 2026/10/19 1:47:19

A comprehensive study of LLM-based argument classification: from LLAMA through GPT-4o to Deepseek-R1

文章主要内容总结 本文是一项关于基于大型语言模型(LLMs)的论点分类(Argument Classification)的综合性研究,核心内容包括: 研究背景与目标:论点挖掘(Argument Mining, AM)是跨学科领域,旨在自动识别论点组件(如前提、主张)及组件间关系(支持、反对、中立)。随着…

作者头像 李华
网站建设 2026/10/19 1:46:34

STM32+华为云IoT+Qt全链路智能家居系统设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/19 1:46:03

STM32F103定时器寄存器深度解析:从计数原理到PWM与输入捕获实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华