DAK 完全拆解:绕过 HBM 预取,用 TMA 直连远端内存

7523 字
38 分钟
DAK 完全拆解:绕过 HBM 预取,用 TMA 直连远端内存

GPU 内存卸载通常被描述成一个容量问题:HBM 放不下权重和 KV Cache,就把一部分数据留在 CPU 主存,需要时再预取回来。这个描述遗漏了更棘手的一面——预取的数据最终仍要经过 HBM。远端数据先写入 HBM,计算时又从 HBM 读进 SM 的共享内存,不但没有把主存带宽与 HBM 带宽真正相加,反而让两股流量在 HBM 端口上互相争抢。

DAK(Direct Access Kernel)改变的正是这条数据路径。它不把远端内存当作等待换入的慢速仓库,而是让一部分 GPU SM 通过 Tensor Memory Accelerator(TMA)把权重或 KV Cache tile 从主存直接搬进共享内存;另一部分 SM 同时从本地 HBM 取数。只要两条路径的工作量分配得当,算子看到的就不再是“先搬运、后计算”,而是本地与远端带宽并行供数。

这项工作由 Cornell University 的 Shouxu Lin、Zhiyuan Guo 与 Jiaxin Lin 于 2026 年 4 月发布。论文不仅给出两个直接访问 kernel,还回答了三个决定方案能否落地的问题:不同算子应卸载多少、远端 TMA 请求如何避免拖垮本地 HBM、不可被 GPU L2 缓存的主存数据如何避免成倍重复读取。

卸载为什么不只是“显存不够就搬到 CPU”#

推理阶段主要有两类常驻数据:模型权重与 KV Cache。前者随参数量增长,后者随批大小和上下文长度近似线性增长。以 FP16 表示的 671B 参数模型仅权重就需要约 1.34 TB;论文还举例指出,70B 模型在 batch size 为 16、上下文为 100K 时,KV Cache 可以超过 100 GB。单卡 HBM 很容易同时遭遇容量不足与带宽不足。

分层内存系统提供了一条看似自然的出路。GPU 保留高带宽、低容量的 HBM,CPU 一侧提供较大但较慢的主存,中间由 PCIe、NVLink-C2C 或未来的 CXL 类互连连接。以论文采用的 GH200 为例,测试系统包含 96 GB HBM3、480 GB 主存、约 4.0 TB/s HBM 带宽和 900 GB/s 双向 NVLink-C2C 带宽。远端层显著扩充容量,但能否转化为推理性能,取决于数据如何穿过互连。

设参与某个算子的权重或 KV 数据总量为 CC,必须放在主存中的比例为 xx。xx 不是一个随意的调优参数。若模型与 KV Cache 总占用为 MM,可用 HBM 容量为 HH,忽略运行时工作区后,系统至少要满足:

xglobal≥max⁡(0,M−HM)x_{\mathrm{global}} \geq \max\left(0, \frac{M-H}{M}\right)

xglobalx_{\mathrm{global}} 是全局容量约束,却不意味着每个算子都应该采用相同的卸载比例。注意力和线性层的算术强度不同,同一种算子在 prefill、decode、不同 batch size 下也可能跨过 memory-bound 与 compute-bound 的边界。DAK 的关键判断是:容量只规定“总共必须卸载多少”,具体卸载到哪些算子,应该由性能模型决定。

预取路径的三笔隐性成本#

FlexGen 一类系统采用按层预取。执行第 XX 层时,把第 X+1X+1 层需要的数据从主存复制到 HBM;理想情况下,通信被当前层的计算遮蔽。问题在于,这种重叠只在时间线上看起来并行,数据路径并没有独立。

按层预取与 DAK 细粒度直访的时间线
按层预取与 DAK 细粒度直访的时间线

图中上半部分是预取式路径。绿色权重复制和蓝色 GPU 计算之间仍有通信气泡与计算气泡。增加双缓冲或把预取深度从一层扩到多层,确实能缩短空隙,却必须在 HBM 里预留更大的 staging buffer;这部分空间本可用于更多权重、KV Cache 或更大的 batch。为了提高权重复用率,FlexGen 还会让多个 micro-batch 依次通过同一层,这有利于离线吞吐,却会增加首 token 延迟和逐 token 延迟。

更根本的代价来自 HBM 的重复经过。传统路径分两步:

  1. 主存中的远端数据经过互连写入 HBM。
  2. 计算 kernel 再从 HBM 把同一份数据读入 SMEM。

复制式卸载与直接访问的数据路径
复制式卸载与直接访问的数据路径

对内存受限的解码 kernel 来说,后台预取写与前台计算读会竞争 HBM 资源。论文测得,后台复制可让 GH200 的本地内存性能下降最多约 10%;在论文讨论的 PCIe 消费级配置中,下降约为 4% 到 7%。因此,远端链路提供的带宽不是无条件增量。若每个远端字节都要写入再读出 HBM,系统同时付出了互连流量、HBM 写流量和 HBM 读流量。

DAK 将远端 tile 直接送到 SMEM,省掉中间落盘式的 HBM staging。图中的蓝色虚线路径与本地 HBM 到 SMEM 的红色路径可以由不同 SM 并行使用。这带来三个结果:

  • 远端读取不再制造额外 HBM 写入,两级带宽才有机会真正聚合。
  • 不需要按预取深度常驻 staging buffer,释放出的 HBM 可以保留更多模型数据。
  • 搬运粒度从“下一层”缩小到 kernel 内部的 tile,可以用软件流水线覆盖远端访问延迟。

论文用实测与理论曲线对比了两条路线。在较低卸载比例下,直接访问的有效带宽可以逼近 HBM 与远端路径的聚合上限;预取方案则从一开始就受额外 HBM 流量和流水气泡影响。

直接访问与预取的有效带宽边界
直接访问与预取的有效带宽边界

这里不能把“直接访问更好”误解成远端内存与 HBM 等速。当 xx 继续增大,越来越多数据必须穿过较慢的主存链路,最终性能仍会下降。DAK 消除的是不必要的中转与空泡,不会消除物理互连本身的带宽上限。

整体架构:全局容量约束,逐算子选择比例#

DAK 的执行过程可以分成放置与运行两个阶段。放置阶段先根据模型、KV Cache、工作区和 HBM 容量得到全局卸载比例,再为每个线性层和注意力算子计算独立比例。运行阶段用 SplitK_GEMM 和 SplitK_FlashAttn 替换原算子,让本地 tile 与远端 tile 在同一次算子执行中并行供数。

DAK 为不同算子分配不同卸载比例
DAK 为不同算子分配不同卸载比例

图中的示意系统需要把约 36% 的总体数据放到主存,但三个算子并没有都取 36%。QKV 投影可以卸载 75%,注意力 KV Cache 只卸载 25%,输出投影卸载 33%。这些数字用于说明逐算子分配,并不是适用于所有模型的固定配置。实际比例取决于矩阵形状、batch、序列长度、GPU 算力、本地带宽和互连带宽。

这个区分非常重要。若对所有层统一设置 36%,本来能够用计算完全隐藏远端读取的算子可能没有承担足够的容量,而对远端带宽敏感的算子却被迫卸载过多。全局比例解决“放得下”,逐算子比例解决“跑得快”。

一个矩阵如何同时住在 HBM 与主存#

考虑矩阵乘法:

C=ABC = A B

论文的记号中,A∈RM×KA\in\mathbb{R}^{M\times K} 表示权重或 KV 相关矩阵,B∈RK×NB\in\mathbb{R}^{K\times N} 与 C∈RM×NC\in\mathbb{R}^{M\times N} 表示隐藏状态。DAK 沿 AA 的 MM 维切成若干形状为 m×Km\times K 的 tile row,再把一部分 tile row 放在主存,其余留在 HBM。

矩阵 A 沿 M 维切分并跨两级内存放置
矩阵 A 沿 M 维切分并跨两级内存放置

沿 MM 维切分有一个直接好处:每个 tile row 对应输出 CC 中一组独立的行块。负责远端 tile 的 SM 与负责本地 tile 的 SM 可以各算各的,不需要先把两边结果做跨 SM 求和。图中 t0,t1t_0,t_1 对应主存中的权重行,其余输出 tile 对应 HBM 中的权重行。

这也解释了 DAK 为什么强调“每个 SM 只走一条数据路径”。若同一 SM 在一个工作片段中交替从主存和 HBM 取权重,请求队列、延迟与带宽行为会混在一起,既难建模也难做拥塞控制。DAK 按 tile 分派 SM:host SM 负责远端 tile,HBM SM 负责本地 tile。两组 SM 并行工作,最后写入输出矩阵的不同区域。

host SM 与 HBM SM 的分工及生产者消费者流水
host SM 与 HBM SM 的分工及生产者消费者流水

注意力的切法略有不同。SplitK_FlashAttn 沿 batch 维划分 KV Cache:一部分请求的 KV 留在 HBM,另一部分请求的 KV 放在主存。这样每个请求的注意力仍能在一个内存层内完成,避免把同一条序列的 softmax 归一化拆成复杂的跨层归并。它保持精确注意力语义,卸载本身不引入量化或稀疏近似。

矩阵切分还受执行波对齐约束。假设某组有 ntn_t 个 tile、分到 nsn_s 个 SM,所需执行波数为:

w=⌈ntns⌉w = \left\lceil \frac{n_t}{n_s} \right\rceil

若 ntn_t 不能被 nsn_s 良好整除,最后一波只有少数 SM 工作,其他 SM 空等,这就是尾部气泡。DAK 在满足目标卸载比例的同时调整 tile 与 SM 的分配,使每一波尽量饱满。论文的消融显示,这项 alignment 优化在不同矩阵形状上可带来最高约 1.2 倍吞吐提升。

TMA 如何把远端延迟藏进 kernel 内部#

直接访问并不等于让普通 CUDA 线程逐元素执行 host load。那样会让地址计算与 load 指令占用线程发射槽和寄存器,远端高延迟也很难被少量同步线程隐藏。DAK 复用 Hopper 开始提供的 TMA,将多维 tile 搬运交给专用异步数据通路。

一个 SM 内部采用 warp specialization:

  • producer warp 构造并发起 TMA 请求,把下一块 AA 从主存或 HBM搬到 SMEM,同时取得相应的 BB tile。
  • consumer warp 等待当前 SMEM slot 就绪,然后让 Tensor Core 或 CUDA Core 执行矩阵运算。
  • producer 在 consumer 计算当前 slot 时填充另一个 slot,形成双缓冲或多级环形缓冲。

抽象后的 kernel 主循环可以写成:

预装前若干 tile 到 SMEM slots
for each output tile:
等待当前 slot 的 TMA 完成
consumer 对当前 tile 执行矩阵运算
producer 向空闲 slot 发起下一次 TMA
轮换 slot
写回输出 tile

TMA 的价值不只是“复制更快”,而是把搬运从计算 warp 的指令流中解耦出来。只要某个 tile 的计算时间足以覆盖下一个 tile 的传输时间,远端延迟就不会完整暴露在关键路径上。与按层预取相比,这种重叠发生在同一个 kernel 的 tile 粒度,不需要猜测下一层何时开始,也不需要把完整层权重先塞进 HBM。

TMA 本来面向 global memory 到 shared memory 的多维异步搬运。DAK 的硬件发现是:当 global address 指向可由 GPU 访问的 host-homed memory 时,TMA 同样能够让数据经互连直接到达 SMEM。论文称这是首次将 TMA 用于这种远端卸载路径。需要注意,这一能力依赖 Hopper 及之后架构的 TMA、统一地址可达性以及相应互连,并不是旧 GPU 上只改几行软件就能获得的通用技巧。

卸载比例的数学模型#

只实现直访 kernel 还不够。若主存链路只有 HBM 的一小部分带宽,把过多 tile 分给 host SM,远端路径会成为长板中的短板;把过少 tile 分给 host SM,又可能浪费可并行利用的带宽。

对算子 ii 定义:

  • CiC_i:要读取的权重或 KV 数据量。
  • xix_i:其中放在主存的比例。
  • BgB_g:GPU 本地 HBM 的可用带宽。
  • BhB_h:主存到 GPU 的有效远端带宽。
  • Tcomp,iT_{\mathrm{comp},i}:算子纯计算时间。

由于本地和远端两条路径并行,二者的传输时间分别为:

Tg,i=(1−xi)CiBgT_{g,i}=\frac{(1-x_i)C_i}{B_g}Th,i=xiCiBhT_{h,i}=\frac{x_iC_i}{B_h}

理想重叠下,算子时间由三者中的最大值决定:

Ti(xi)=max⁡(Tcomp,i,Tg,i,Th,i)T_i(x_i)=\max\left(T_{\mathrm{comp},i},T_{g,i},T_{h,i}\right)

论文用有效带宽统一描述不同算子:

EBi(xi)=CiTi(xi)\mathrm{EB}_i(x_i)=\frac{C_i}{T_i(x_i)}

这里的 EB 不是某条物理总线的原始带宽,而是“本算子涉及的数据量除以完成时间”。它可以高于单独的远端带宽,因为本地 HBM、远端链路和计算正在并行工作。

memory-bound 与 compute-bound 算子的卸载曲线
memory-bound 与 compute-bound 算子的卸载曲线

memory-bound 算子的峰值#

若 xi=0x_i=0 时 Tcomp,i<Ci/BgT_{\mathrm{comp},i}<C_i/B_g,算子由本地内存读取限制。随着少量数据被移到主存,本地传输量下降,远端路径同时加入供数,EB 上升。最优点出现在两条传输时间相等:

(1−xi)CiBg=xiCiBh\frac{(1-x_i)C_i}{B_g}=\frac{x_iC_i}{B_h}

约去 CiC_i 并整理可得:

xi⋆=BhBh+Bgx_i^{\star}=\frac{B_h}{B_h+B_g}

此时两边恰好同时完成,理论有效带宽为 Bg+BhB_g+B_h。若继续增大 xix_i,远端读取时间 xiCi/Bhx_iC_i/B_h 超过本地路径,EB 反而按 Bh/xiB_h/x_i 下降。

以论文 GH200 的标称测试参数作直觉计算,若取 Bg=4.0B_g=4.0 TB/s、Bh=0.9B_h=0.9 TB/s,则平衡点约为:

x⋆≈0.94.0+0.9≈18.4%x^{\star}\approx\frac{0.9}{4.0+0.9}\approx18.4\%

这不是实际 kernel 的固定最佳值,因为协议损耗、矩阵形状、并发请求与拥塞都会改变可用带宽;它说明了一条原则:对 memory-bound 算子,适量卸载可以加速,超过带宽平衡点则开始减速。

compute-bound 算子的无损区间#

若算子在 xi=0x_i=0 时由计算限制,少量卸载不会改变总时间,因为本地与远端传输都被 Tcomp,iT_{\mathrm{comp},i} 遮蔽。定义该算子的计算等效带宽:

Bi=CiTcomp,iB_i=\frac{C_i}{T_{\mathrm{comp},i}}

远端读取开始暴露到关键路径的临界点满足:

xiCiBh=Tcomp,i\frac{x_iC_i}{B_h}=T_{\mathrm{comp},i}

于是:

xi⋆=BhBix_i^{\star}=\frac{B_h}{B_i}

在 xi≤xi⋆x_i\leq x_i^{\star} 的区间里,EB 维持在 BiB_i,也就是“可以免费卸载”的容量区间。一旦越过阈值,算子转成远端内存受限,性能开始随 xix_i 增加而下降。

decode 与 prefill 为什么不能套同一比例#

decode 注意力读取不断增长的 KV Cache,其计算量和内存流量都随 B⋅L⋅DhB\cdot L\cdot D_h 增长,算术强度保持在常数量级,通常严格 memory-bound。decode 中的线性层则会随 batch 增大获得更高权重复用,可能从 memory-bound 转向 compute-bound。

prefill 注意力的计算量近似为 O(BL2Dh)O(BL^2D_h),KV 流量近似为 O(BLDh)O(BLD_h),算术强度随 LL 增长;长 prompt 下它可能 compute-bound。prefill 的线性层算术强度近似随 B⋅LB\cdot L 增长,通常也更偏计算受限。因此,“注意力优先卸载”或“权重优先卸载”都不是普遍规则,必须结合阶段和形状判断。

三阶段贪心分配为什么是最优的#

设全局卸载比例为 RR,所有算子的卸载数据量必须满足:

∑iCixi=R∑iCi\sum_i C_i x_i = R\sum_i C_i

端到端目标是最小化各算子延迟之和:

min⁡{xi}∑iCiEBi(xi)\min_{\{x_i\}}\sum_i \frac{C_i}{\mathrm{EB}_i(x_i)}s.t.0≤xi≤1\text{s.t.}\quad 0\leq x_i\leq1

观察上一节的分段曲线,就能得到 DAK 的三阶段策略。

第一阶段,把卸载预算给尚未到峰值的 memory-bound 算子。这一段每增加一点 xix_i 都会减少本地数据量,并通过远端并行带宽降低算子时间;此时把同样预算给 compute-bound 算子只会保持时间不变。通过交换论证可以证明,只要仍有 memory-bound 算子处于上升段,把预算从 compute-bound 算子挪给它就不会更差,而且通常会严格变好。

第二阶段,所有 memory-bound 算子到达 Bh/(Bh+Bg)B_h/(B_h+B_g) 后,将剩余预算分配给仍处于无损区间的 compute-bound 算子。它们的 TcompT_{\mathrm{comp}} 尚能遮住远端读取,所以增加卸载量不增加执行时间。

第三阶段,若容量压力大到所有算子都越过各自转折点,额外主存流量已经无法被计算或本地带宽遮蔽。此时必须在远端受限区继续分配。在所有相关算子均保持在各自转折点之上的条件下,其远端延迟项之和与固定的总卸载字节数成正比,额外预算如何分摊不再改变理想模型中的总时间。

这个“最优”有明确边界:证明建立在算子串行求和、带宽稳定、两级传输可理想重叠、性能曲线服从上述分段模型等假设上。真实 GPU 还有 tile 离散化、执行波尾部、共享内部队列与地址对齐,因此 DAK 仍需要 profiler 和 kernel-level 修正。贪心算法解决的是宏观比例,不能代替底层调度。

论文在 batch size 512 的混合负载上比较了贪心与统一比例。卸载比例低于 60% 时,贪心策略最高约快 1.5 倍;超过 60% 后,两者逐渐收敛,因为大部分操作都已落入远端互连受限区。

逐算子贪心分配与统一卸载比例对比
逐算子贪心分配与统一卸载比例对比

远端 TMA 请求为什么会拖慢本地 HBM#

直访绕过 HBM staging,不代表远端与本地路径在 GPU 内部完全隔离。DAK 发现,无约束地增加远端请求,会连带降低 SM 到 HBM 的带宽。远端并发量可粗略写成:

Qremote∝NSM,host⋅NinflightQ_{\mathrm{remote}}\propto N_{\mathrm{SM,host}}\cdot N_{\mathrm{inflight}}

NSM,hostN_{\mathrm{SM,host}} 是负责主存 tile 的 SM 数量,NinflightN_{\mathrm{inflight}} 是每个 SM 同时挂起的 TMA 请求数。增加前者可以暴露更多并行度,增加后者可以遮蔽长延迟;但二者乘积太大时,远端请求会在内部互连中堆积。

每个 SM 的远端在途请求过多会降低聚合带宽
每个 SM 的远端在途请求过多会降低聚合带宽

GH200 实验固定 100 个 SM 读取 HBM,并逐步增加 host SM。host SM 超过 8 个后,本地 HBM 带宽明显下降。固定 SM 数、增加每个 SM 的在途请求也出现相同趋势。论文没有公开 GPU 内部实现,因而把根因谨慎表述为推测:NVLink-C2C 饱和后,未完成的远端请求可能占满 L2 MSHR、路由 buffer 或其他共享资源,使本地请求也无法及时推进。

DAK 为此引入类似网络拥塞窗口的静态 credit:

  • 限制每个 host SM 的最大在途 TMA 数,只保留足以填满远端链路的并发度。
  • 限制 host SM 总数,避免为了处理少量远端 tile 启动过多请求源。
  • 在 kernel 启动前做轻量参数扫描,为特定硬件和矩阵形状选择窗口,而不在 kernel 内动态调整。

之所以不用类似 TCP 的运行时增减窗口,是因为 GPU kernel 内做复杂反馈控制会引入同步、计数与分支开销。论文的离线 profile 把控制成本移出热路径。消融实验中,拥塞控制在六组矩阵形状上带来 1.06 到 1.22 倍有效带宽提升。

DAK 拥塞控制在不同矩阵形状上的收益
DAK 拥塞控制在不同矩阵形状上的收益

这项结果还有一个系统层含义:聚合带宽不是简单的 Bg+BhB_g+B_h。只有远端请求没有污染本地路径时,两者才近似可加。若共享资源发生反压,继续追求更高远端并发反而会降低总带宽。

不可缓存主存与读放大#

即使 GH200 提供 CPU-GPU cache coherence,论文观察到 host-homed memory 的直接读取仍绕过 GPU L2。PCIe 系统也有同样问题。于是,当多个 SM 需要同一块远端权重 tile 时,L2 不能像处理 HBM 数据那样把第一次读取留给后续 SM 命中,每个 SM 都会在互连上重新取一次。

论文固定远端矩阵为约 98 MB,改变隐藏状态矩阵的 NN 维。测得结果如下:

NNhost-GPU 流量读放大
256102.76 MB1.05 倍
512205.52 MB2.10 倍
1024411.04 MB4.19 倍
2048822.08 MB8.39 倍
40961.64 GB16.78 倍

远端数据本身没有变大,流量却随消费者数量接近线性增长。batch 越大、同一权重 tile 被更多输出 tile 复用,这个问题越严重。若不处理,GEMM 原本希望通过复用提高算术强度,反而会把复用次数变成跨互连的重复传输次数。

DAK 用 TMA multicast 与 host-locality-first scheduling 配合解决:

  1. 把会消费同一远端 tile 的 SM 组织到同一个 thread block cluster,并保证它们同时驻留。
  2. 只由 cluster 中一个 producer 发起远端 TMA,把 tile 穿过 host-GPU 互连一次。
  3. 数据到达 GPU 后,通过片上 NoC 和 Distributed Shared Memory 广播到多个 SM 的 SMEM。
  4. 各 consumer 从自己的 SMEM slot 计算,不再重复访问主存。

调度目标因此从传统的“H​BM/L2 局部性优先”变成“远端 tile 局部性优先”。昂贵的跨封装传输只做一次,复制发生在带宽更高、距离更短的片上网络。

TMA multicast 消除大 batch 下的远端读放大
TMA multicast 消除大 batch 下的远端读放大

在 7168×71687168\times7168 权重矩阵实验中,N=512N=512 时 multicast 相对无 multicast 提升约 1.3 倍;N=1024N=1024 时提升扩大到约 2.5 倍。收益随 NN 增大,正好与读放大随消费者数量增长的分析一致。

实现落地:两个替换算子与 CUDA Graph#

论文实现约包含 1300 行 Python 和 2700 行 CUDA C++,并借助 CUTLASS/CUTE 构建底层 kernel。面向框架暴露两个 PyTorch 模块:

  • SplitK_GEMM 替换 nn.Linear,负责跨 HBM 与主存放置的权重矩阵。
  • SplitK_FlashAttn 对齐 PyTorch SDPA 接口,负责按 batch 切分的 KV Cache。

这种接口把模型结构与内存放置分开。模型初始化时替换对应层,placement 模块决定 tile 所在内存层,运行时 kernel 根据映射选择本地或远端 TMA 描述符。它不是透明 UVM:开发者获得可预测的 tile 级路径,代价是必须使用定制算子。

decode 每步工作量较小,Python 与 CPU launch 开销可能盖过 kernel 优化。DAK 预先用 PyTorch StaticCache 分配 KV Cache,并捕获 CUDA Graph;后续 token 通过 graph replay 发射,避免每步重新走 Python 调度和大量 kernel launch。论文也指出,OPT-6.7B 上 FlexGen 的部分劣势来自小模型下启动开销占比变大,而 DAK 和 vLLM 使用 CUDA Graph 消除了这部分开销。因此,比较结果既反映数据路径,也混合了运行时优化差异。

开源仓库当前默认面向 GH200 的 sm_90a 构建,也提供 RTX PRO 6000 Blackwell 的 sm_120a 构建参数;split attention 的 -gencode 仍需手工调整。不同架构需要替换 CUTE MMA atom,并重新校准 TMA chunk、SM 数与最大在途请求。它更接近研究原型和硬件协同 kernel,而不是任意 CUDA GPU 上即装即用的通用后端。

实验结果应该怎样读#

论文在两套单 GPU 系统上做离线 batched inference:

平台本地显存本地带宽主存host-GPU 链路
GH20096 GB HBM34.0 TB/s480 GB900 GB/s NVLink-C2C
RTX 6000 Pro Blackwell96 GB GDDR71.8 TB/s512 GB64 GB/s 单向 PCIe Gen5

工作负载包含 OPT-30B、OPT-6.7B 和 Llama-2-7B,每个请求解码 32 个 token,默认 prompt 长度为 32。基线包括 FlexGen、论文实现的 vLLM-prefetch,以及依赖 UVM page fault 与迁移的 vLLM-uvm。指标主要是 TPOT 与有效带宽:

EB=模型数据规模TPOT\mathrm{EB}=\frac{\text{模型数据规模}}{\mathrm{TPOT}}

在 batch size 8、主要卸载权重的实验中,DAK 在 GH200 的卸载比例扫描中保持最高 EB。OPT-30B 在 10% 卸载比例时达到约 3300 GB/s。论文正文针对不同模型与比例报告 1.5 到 5 倍的局部对比,而摘要用更保守的总体表述给出相对先进卸载基线最高 3 倍的 NVLink-C2C 收益、1.8 倍的 PCIe 收益。二者分母与配置不同,不能把所有最大值拼成同一个结论。

OPT-30B 在 GH200 上随卸载比例变化的性能
OPT-30B 在 GH200 上随卸载比例变化的性能

PCIe 平台的远端带宽只有 64 GB/s,远低于 1.8 TB/s GDDR7。在 0% 到 40% 的低中卸载范围,DAK 相对 FlexGen 与 vLLM-prefetch 仍有约 1.3 到 3 倍收益;超过 40% 后三者趋于接近,因为权重读取最终都被 PCIe 物理带宽限制。直接访问可以避免额外中转,不能把 64 GB/s 变成 HBM 级带宽。

batch size 512 时,KV Cache 增至约 45 GB,线性层与注意力分别表现出 compute-bound 和 memory-bound 特征。DAK 相对基线最高提升约 2.1 倍,收益来自三项叠加:两级带宽并行、逐算子比例分配、multicast 抑制大 batch 的权重读放大。

论文还让真实容量需求决定比例,而不是人为扫描。OPT-30B 的模型权重约 55.6 GB;当 batch 与 prompt 增大时,KV Cache 从 0.7 GB 增至 186.03 GB,全局卸载比例从 0% 增至 60%。DAK 在这些配置上相对 vLLM 提升 1.06 到 1.83 倍,相对 FlexGen 提升 1.53 到 21 倍。21 倍出现在特定高压力配置,并不等于常规请求都能获得同等加速。

真实容量约束下 DAK、vLLM 与 FlexGen 的有效带宽
真实容量约束下 DAK、vLLM 与 FlexGen 的有效带宽

DAK 与 PagedAttention、UVM、存储卸载的边界#

DAK 解决的是物理数据路径和跨层带宽利用,不替代上层 KV 内存管理。

  • PagedAttention 解决 KV Cache 的逻辑分块、非连续映射、共享与写时复制,减少显存碎片。DAK 决定这些数据实际位于 HBM 还是主存,以及 kernel 如何直接读取。两者可以组合;前者可参考 PagedAttention 完全拆解。
  • UVM 提供统一地址与按页迁移,但 4 KB page fault、迁移串行化和不可控抖动不适合规则的大矩阵热路径。DAK 同样利用 GPU 可寻址的主存,却用显式 tile、TMA 和静态放置避开按需缺页。
  • DualPath 让空闲网络路径参与 KV Cache 加载,关注跨推理节点的缓存传输;DAK 关注单机 CPU-GPU 分层内存和 SMEM 直达。共同点是都不把数据移动视为计算前的附属阶段,而是把多条物理路径纳入调度。
  • H³ 混合内存架构 把层次扩展到高带宽闪存,解决更大容量的长上下文问题。DAK 的 tile 直访思想提供了 kernel 侧启发,但闪存延迟远高于主存,不能直接照搬同一流水深度与比例模型。

因此,DAK 最适合的不是“任何显存不足场景”,而是 GPU 能以细粒度访问远端内存、互连带宽足以参与供数、算子可按 tile 独立切分、硬件又支持 TMA 与 cluster multicast 的系统。PCIe 也能获益,但适用卸载比例窗口明显更窄。

局限与仍需验证的问题#

第一,评测是离线批推理,不是带动态到达、优先级、取消请求和 SLO 的在线服务。真实服务关心 P50/P99 TTFT、TPOT 抖动与吞吐之间的约束。静态 offload ratio 和离线 profile 在请求形状快速变化时是否仍接近最优,需要在线系统实验。

第二,模型集合以 OPT-30B、OPT-6.7B 和 Llama-2-7B 为主,没有覆盖更现代的 GQA、MLA、MoE 或强量化模型。GQA/MLA 会改变 KV Cache 与权重流量比例;MoE 每步只访问部分专家,路由不确定性会破坏静态 tile 放置;INT4/FP8 权重则降低字节量并提高算术强度,使原来的转折点整体移动。

第三,所谓“可证明最优”针对论文的分段带宽模型。模型没有显式表示不同算子间的 cache 状态、NUMA 放置、页面粒度、TMA descriptor 开销、功耗、多个并发模型的互相干扰。实际系统依赖 profile 校准,跨机器复制配置并不可靠。

第四,host memory 绕过 GPU L2 是 multicast 必要性的来源,但不同 CPU-GPU 一致性实现、未来 CXL.cache 设备或新一代 GPU 的缓存策略可能改变这一前提。multicast 本身也受 thread block cluster 规模、DSMEM 容量与同步成本限制。

第五,论文展示了吞吐与 TPOT,却没有系统报告功耗和每 token 能耗。把数据跨 NVLink-C2C 或 PCIe 持续送入 GPU,可能减少 HBM 流量,却增加互连与主存功耗。对部署成本的完整判断还需要能效数据。

从 DAK 得到的系统设计启示#

DAK 最有价值的结论并不是某个固定加速倍数,而是重新定义“卸载”。传统卸载的抽象是换入换出:远端层只负责容量,计算仍以 HBM 为唯一入口。DAK 的抽象是异构供数:每一级内存既贡献容量,也在 kernel 执行期间贡献带宽。

这个抽象成立需要四层协同:

  1. 算法层知道哪些操作 memory-bound,哪些操作有可免费卸载的 compute-bound 区间。
  2. 放置层把全局容量缺口转换为逐算子比例,而不是统一百分比。
  3. kernel 层按独立输出 tile 切分工作,用 warp specialization 把远端传输藏在计算后面。
  4. 体系结构层控制共享互连拥塞,并用 multicast 把远端复用转移到片上网络。

少任何一层,理论上的带宽相加都可能落空。只改放置算法而继续预取,会有 HBM staging;只写直访 kernel 而不控请求,会让远端流量拖慢本地 HBM;只限制拥塞而不做 multicast,大 batch 会制造十几倍读放大;只做底层优化而统一卸载比例,又会把容量压给最敏感的算子。

最终可以用一句话概括 DAK:它没有让远端内存变快,而是让远端内存不再先假装成 HBM。通过 TMA 直达 SMEM、逐算子比例、拥塞窗口与 cluster multicast,主存第一次以独立供数通路参与 LLM kernel,卸载也从容量补丁变成了可调度的带宽资源。

参考资料#

  1. DAK: Direct-Access-Enabled GPU Memory Offloading with Optimal Efficiency for LLM Inference
  2. DirectAccessKernel 官方代码仓库
  3. CUDA C++ Programming Guide:Tensor Memory Accelerator
  4. NVIDIA Hopper Architecture In-Depth
  5. NVIDIA Grace Hopper Superchip
  6. FlexGen: High-Throughput Generative Inference of Large Language Models with a Single GPU
  7. Understanding Data Movement in Tightly Coupled Heterogeneous Systems: A Case Study with the Grace Hopper Superchip
  8. FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

DAK 完全拆解:绕过 HBM 预取,用 TMA 直连远端内存
https://pinghaoyang.com.cn/aigc/posts/dak-direct-access-kernel/
作者
平昊阳
发布于
2026-09-27
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
平昊阳
乘长风,破巨浪, 展鸿图于未央!
--
总访问量
--
访客数
公告
欢迎来到我的个人博客!欢迎关注交流吖!
更多相关公告,见
「社交-留言」。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
站点统计
文章
212
分类
26
标签
291
总字数
2,407,536
运行时长
0 天
最后活动
0 天前

文章目录