TurboBus PCIe Bandwidth Pooling

导言

GPU Memory Offloading 有一个很反直觉的现象:应用可能把大部分时间耗在 PCIe 搬运上,同一台服务器却仍有多数 PCIe 带宽处于空闲。问题不在于服务器缺少链路,而在于每块 GPU 只能使用自己名下的那条链路;不同 GPU、不同作业的搬运阶段又往往没有同时到来。

TurboBus 的核心思想,是把 NVLink、NVSwitch 等高速 Scale-Up Fabric 当成一条节点内的转发背板:繁忙 GPU 先把数据送到邻居 GPU,再借邻居的空闲 PCIe 链路访问 Host Memory。本文沿着论文 Figure 1、4、5、6、7 解释这条思路怎样从一张直觉图落成带隔离、流水、调度和 API 的系统,并区分实验已经证明的收益与仍受硬件拓扑、NUMA 和工作负载限制的外推。

瓶颈链路为何仍然空闲

GPU 显存不足时,系统会把暂时用不到的对象放到 Host Memory,需要时再通过 PCIe 搬回来。TurboBus 关注的对象包括三类:

  • 按需模型加载:权重常驻 Host Memory,请求到来后逐层或整体载入 GPU。
  • KV Cache 卸载推理:Prefill 后把 KV Cache 放到 Host Memory,Decode 时按需取回。
  • 内存受限训练:把梯度和 Optimizer State 卸载到 CPU,更新后的权重再传回 GPU。

这是一笔明确的交换:用容量更大、价格更低的 Host Memory,换取额外的数据搬运。论文的 copy-free what-if 实验把 Copy 替换为空操作,用两次运行的差值估计不可忽略的传输成本。在其设置中,PipeSwitch 的 TTFT 变为 copy-free 的 1.7–3.0 倍,FlexGen 吞吐约为 copy-free 的十分之一,ZeRO-Offload 的迭代时间最多增加 24%。

真正促成 TurboBus 的,不只是“PCIe 很慢”,而是下面这个组合:

单条链路已经成为瓶颈,但服务器所有链路的总容量没有被同时用满。

论文用 NVML 观测 ZeRO-Offload 与 FlexGen:PCIe 3.0 x16 的标称容量为 16 GB/s,四组方向流量的平均利用率只有 8.39%–26.78%;超过 60% 的时间,链路带宽不高于 2 GB/s。空闲来自两个尺度:

  1. 作业内错峰:ZeRO-Offload 的不同 Rank 分别拥有不同层的 Optimizer State;反向传播逐层产生梯度,各 Rank 的 D2H 与 H2D 搬运并不同步。
  2. 作业间错峰:同机部署的计算密集、显存带宽密集或另一项 Offloading 作业,不会在相同时刻占满各自 PCIe 链路。

先看论文用来概括问题的第一张图。

![TurboBus 借用邻居 GPU 的空闲 PCIe 链路](https://pic.shaojiemike.top/shaojiemike/2026/08/4e74e561ed06cea95e040c4d25fe6014.png){ width=90% }
论文 Figure 1:左侧只有源 GPU 的 PCIe 链路繁忙;右侧通过 Scale-Up Fabric 把数据转发到邻居 GPU,使多条 PCIe 链路同时工作。原图与英文图注完整保留。

图 1 左侧的黄色 GPU 受限于自己的 PCIe 链路,其余三条链路虽然空闲,却属于别的 GPU。右侧没有提高任何一条 PCIe 链路的速率,而是增加了访问 Host Memory 的路径:源 GPU 保留直连路径,同时通过 Scale-Up Fabric 把一部分数据交给邻居,再从邻居的 PCIe 链路进出 Host Memory。

这张图故意省略了最难的部分。跨作业时,源作业没有权限读写邻居 GPU;大对象不能完整缓存在 Relay GPU;H2D 与 D2H 可能争用同一条 NVLink;大权重还可能堵住更小的 KV Cache 请求。“能借路”只是物理机会,TurboBus 的系统贡献是把这次借用变成隔离、有限开销且可调度的公共服务。

哪些拓扑真的能借路

借路成立的关键,不是服务器上“有很多 GPU”,而是同时存在两类路径:

  • GPU 到 Host Memory 的 PCIe 链路相对较慢,而且不同 GPU 能暴露出彼此独立的上行容量;
  • GPU 之间的 Scale-Up Fabric 足够快,Relay Hop 不会代替 PCIe 成为新瓶颈。
![TurboBus 面向的三类多 GPU Scale-Up 拓扑](https://pic.shaojiemike.top/shaojiemike/2026/08/2469da01ff9057c29b931b260c0de621.png){ width=90% }
论文 Figure 4:DGX V100 与 H800 通过 NVSwitch 形成交换式 Scale-Up Fabric,RTX 3090 使用 GPU 间直连拓扑。原图与英文图注完整保留。

图 4 展示了论文验证的三类机器。DGX V100 与 H800 先把 GPU 接入 NVSwitch;RTX 3090 机器则用点对点 NVLink。对 TurboBus 而言,两者都能提供一跳 Relay Path,但可借到的 PCIe 容量仍由底层 PCIe Switch 与 Host 侧拓扑决定。

以论文的 V100 测试床为例,GPU 间 NVLink 单方向带宽为 50 GB/s,PCIe 3.0 x16 为 16 GB/s。一次 Relay 的路径是:

1
2
D2H:Source GPU --NVLink--> Relay GPU --PCIe--> Host Memory
H2D:Host Memory --PCIe--> Relay GPU --NVLink--> Sink GPU

Relay Hop 比最终 PCIe Hop 快,因此可以被流水隐藏。双向并发的要求更严格:同一方向的 NVLink 最多同时承载两个 PCIe 速率的数据流,所以论文要求 Scale-Up 带宽至少约为 PCIe 的两倍,才能在不让 NVLink 成为瓶颈的前提下同时运行 H2D 与 D2H。

有 NVLink 不等于一定能池化

TurboBus 启动时根据 nvidia-smi topo -m 构造 Link Group、GPU 到 PCIe Link 的映射与 Relay Capability。若两个 GPU 只通过同一个 PCIe Switch 共享同一条上行链路,Relay 并没有暴露新的 Host 带宽,系统会把它们划入不能互相增益的组。NUMA、UPI、PCIe Switch Uplink 和内存控制器也可能先于 PCIe Link 饱和。

这给出了一个比“NVLink 比 PCIe 快”更完整的判据:可池化容量等于新路径真正暴露的独立慢速资源,而不是 Relay GPU 的数量。

从借路到系统服务

跨作业共享最先遇到的不是性能问题,而是所有权问题。普通进程不能访问另一个作业的 GPU 地址空间,也不应该因为借用一条 PCIe Link 获得这种权限。TurboBus 因此没有让客户端彼此连接,而是引入每节点一个特权 Daemon。

![TurboBus 的客户端与特权 Daemon 架构](https://pic.shaojiemike.top/shaojiemike/2026/08/7396e0078d5bd7ed734cffabfea6e88c.png){ width=90% }
论文 Figure 5:Daemon Core、Executor 与 Scheduler 分别承担隔离、块级 Relay 执行和并发调度,应用只通过 Library API 提交请求。原图与英文图注完整保留。

图 5 把论文的五个挑战映射为三个组件:

  • Daemon Core 解决跨作业隔离(C1):它能看到节点内所有 GPU,客户端只能提交自己的 Tensor 与搬运请求,不能直接访问 Relay GPU。
  • Executor 解决 Relay 开销与链路利用率(C2、C3):它把一次大传输切成 Block,沿直连和 Relay Path 流水执行。
  • Scheduler 解决双向并发与请求公平性(C4、C5):它决定 H2D、D2H 何时共用 NVLink,也决定大小请求怎样交错。

控制面和数据面被明确分开。客户端与 Daemon 通过 UNIX Domain Socket 传递请求元数据;Payload 不经过 Socket,也不在 CPU 侧重复 Copy:

  • Host Tensor 从 Linux /dev/shm 分配,Daemon 对同一共享内存区域执行 mmap
  • Device Tensor 由客户端导出 CUDA IPC Handle,Daemon 用 cudaIpcOpenMemHandle() 打开同一段 GPU Memory;
  • Relay GPU 只物化当前正在流动的少量 Block,不缓存完整的模型权重或 KV Cache。

论文实现约 3,900 行代码,包括约 2,500 行 C++ Daemon、1,100 行 C++ Client Library 与 300 行 Python Frontend。Daemon 的 Listener Thread 用 Edge-Triggered epoll 接收请求,Worker Thread 运行 Scheduler 与 Executor;传输请求通过 cudaLaunchHostFunc 接入既有 CUDA Stream 依赖链。Daemon 不可用时,接口回退为源 GPU 自有链路上的 cudaMemcpy,牺牲池化加速但保留传输正确性。

这里的中心化设计有一个很现实的取舍:要跨作业调度,就需要一个同时看见所有路径与请求的控制点;但这个控制点也成为需要额外保护、监控和容错的特权服务。论文展示了进程隔离与直接 Copy 回退,没有给出恶意客户端、Daemon 重启恢复或生产级资源配额的完整评估。

块级流水如何填满链路

最朴素的 Relay 是 Store-and-Forward:先把整个对象从 Source 搬到 Relay GPU,再从 Relay 搬到 Host。这个方案同时有两个问题:多 GB 对象会占满 Relay 显存,NVLink 与 PCIe 两个阶段也只能串行工作。

TurboBus 把“调度一整个 Transfer”改成“调度固定大小的 Block”。这一步同时改变了 Buffer 生命周期、流水方式与路径分配粒度。

![TurboBus Executor 的直连路径、Relay 流水和块级路径分配](https://pic.shaojiemike.top/shaojiemike/2026/08/71025037db66d991e2d2f8388c9b3934.png){ width=100% }
论文 Figure 6:从左到右依次展示 H2D/D2H 直连与 Relay Path、块级流水与双向同步、Transfer-Level 和 Block-Level 路径分配。原图与英文图注完整保留。

图 6 需要分三部分读。

直连与转发

图 6(a) 中,同一条 PCIe Link 对不同请求扮演不同角色。D2H 时,路径 1 是 GPU0 的直连,路径 2 先经 NVLink 到 GPU2,再借 GPU2 的 PCIe;H2D 时两者反过来。TurboBus 不把源 GPU 的直连当成特殊 Fast Path,而是把它与所有 Relay Path 一起交给 Allocator。

系统只做 Single-Hop Relay。多跳不会发现直接邻居之外的新 PCIe Link,只会叠加 NVLink 时延和调度复杂度。

双缓冲流水

图 6(b)(i) 表示不同大小的整次传输无法对齐,短传输结束后会留下 Pipeline Bubble。图 6(b)(ii) 把它们统一切成 Block:Relay GPU 一边通过 PCIe 送出当前 Block,一边通过 NVLink 接收下一个 Block。

在 V100 测试床上,Profiler 选择 16 MB Block;小于半个 Block、即 8 MB 的传输直接绕过 Relay Pipeline。每个方向需要两个 Buffer 实现一边填充、一边排空,H2D 与 D2H 合计四个 Buffer,因此 Relay 显存开销固定为:

$$
M_{relay}=2\ \text{directions}\times2\ \text{buffers}\times16\ \text{MB}=64\ \text{MB}.
$$

这里实际物化的是四个 Relay Buffer;完整 Payload 仍留在客户端 Host/Device Tensor 中,Block 只在流水经过 Relay GPU 时短暂存活。Block Size 也不是跨平台常数:论文在 RTX 3090 上仍选择 16 MB,在 H800 上选择 32 MB,原则是找到达到峰值吞吐的最小 Block,以平衡 Launch 开销、Buffer 占用和流水预热/排空时间。

图 6(b)(iii) 还展示了双向同步。作者最初让两个线程分别驱动 H2D 与 D2H,虽然二者使用独立 Copy Engine,吞吐仍比单方向下降最多 40%;论文推测原因是 CUDA Runtime 内部锁让一个 Stream 的同步影响了其他 Stream。最终实现用单一控制线程统一 Launch,在固定 Time Slot 边界同时同步所有活跃 Stream,避免独立 cudaStreamSynchronize 的相互干扰。

路径分配

图 6(c) 的 Transfer A 有 6 个 Block,Transfer B 有 2 个,共有 4 条路径。Transfer-Level 分配让 A 在 Stage 2 只占两条路径,另外两条空闲;B 仍要等到 Stage 3。Block-Level 分配则在每轮查看所有 Ready Block,直接用 B 的两个 Block 填满 A 留下的空位。

论文的微基准中,这个改动把吞吐提高最多 20%。它反映出一个比“把大对象切小”更重要的思想:只有调度粒度也缩小到 Block,固定大小流水才真正转化为全局链路利用率。

调度既要并发也要克制

把路径放进一个池后,请求之间开始互相影响。TurboBus 在两个维度采用不同策略。

H2D 与 D2H 之间,系统允许二者同时使用同一 NVLink。Single-Hop 约束保证一条有向 NVLink 最多承载两个 PCIe 速率的数据流;当 NVLink 带宽大于两倍 PCIe 时,两者并发仍由 PCIe 限速。图 6(b)(iii) 的统一 Time Slot 同步负责消除 Runtime 干扰。

同一方向的大小请求之间,系统利用 Offloading 请求大小呈双峰分布的特点:

  1. Chunking:大 Transfer 一次只允许一个 Chunk 进入 FCFS 队列,完成后再提交下一个。小请求最多被一个 Chunk 阻塞,不必等完整的大 Transfer。
  2. Token-Gated Admission:多个大 Transfer 若同时平分路径,会让所有请求一起变慢;Token 按到达顺序只允许一个大 Transfer 持续提交 Chunk。小请求不需要 Token,可以插入执行。

Chunking 把小请求的最坏等待从“一个完整大 Transfer”压到“一个 Chunk”,Token 则避免大请求互相拖慢。论文微基准显示,两种机制分别使测试请求的完成时间下降 15%–27%。这不是严格的全局最优调度,而是一组利用请求大小结构、实现成本较低的规则。

接入代码改变了什么

TurboBus 并不透明拦截任意 cudaMemcpy。应用要把参与池化的 Host/Device Tensor 交给它分配,并用 MemPair 发起传输。

![原生 PyTorch Copy 与 TurboBus API 对比](https://pic.shaojiemike.top/shaojiemike/2026/08/919d7059785d7582096da5248e0fded6.png){ width=90% }
论文 Figure 7:原生 Tensor 分配与 `copy_()` 被 TurboBus 的共享 Tensor、`MemPair` 和会话 API 替换。原图与英文图注完整保留。

图 7 左侧的 PyTorch 代码直接创建 CPU/GPU Tensor,再调用异步 copy_();右侧增加 tb.init()tb.clean(),并做三处替换:

  • create_host_tensor() 创建可被 Daemon mmap 的 Host Tensor;
  • create_dev_tensor() 创建可通过 CUDA IPC 共享的 Device Tensor;
  • pair(host, dev).to_dev()to_host() 向 Daemon 提交 H2D/D2H 请求。

因此,“最小改动”更准确地说,是无需重写模型计算图和控制流,但必须替换目标数据路径上的内存分配与 Copy API。如果既有框架在内部创建临时 Tensor、复用自定义 Allocator,或把 Copy 藏在 Fused Operator 中,接入成本会高于图 7 的独立示例。截至 2026 年 8 月 28 日,论文、作者发表列表和 SIGCOMM 项目页均未给出公开代码仓或 Artifact 链接,公开检索也未找到官方实现,因此本文无法独立验证 pip 安装、框架兼容范围和回退路径的实际行为。

实验证据支持到哪里

论文用一个 Amdahl 风格模型给收益划上限。设原始时间为:

$$
T=T_c+T_d,
$$

其中 $T_c$ 是计算及其他时间,$T_d$ 是不能被计算隐藏的传输时间;令 $f=T_d/T$,可用 PCIe Link 数为 $N$,理想池化后的上限为:

$$
T_{opt}=T_c+\frac{T_d}{N},\qquad
\text{Speedup}_{opt}=\frac{1}{1-f+f/N}.
$$

这个式子先给出一个重要边界:TurboBus 只能缩短 $T_d$,无法消除 $T_c$。FlexGen 的不可重叠传输占比约 90%,四条链路的理论上限可接近 3 倍;ZeRO-Offload 的传输占比约 24%,同样的四条链路也只有约 1.2 倍上限。

论文的主测试床是四张 32 GB V100、两个可池化 PCIe 3.0 x16 Link 的 DGX;RTX 3090 与八卡 H800 主要用于跨 PCIe 代际、Block Size 和扩展性的微基准。全部实验启用 NVIDIA MPS。端到端工作负载均使用 TP=1;论文认为 TP>1 的同步组内收益有限,机会主要存在于 TP Group、DP Replica 与共置作业之间。

场景 设置与对照 TurboBus 结果 不能直接外推的部分
按需模型加载,Serverless Emulation 4 个独立 GPU 请求流,Llama-3-8B,Poisson 到达 P50 TTFT 降低 17%–25% 相比 PipeSwitch+DeepPlan,P99 最多反而增加 10%
按需模型加载,隔离环境 Llama-3-8B、Llama-2-13B,1 条直连 + 1 条 Relay Link TTFT 降低 22%–40%,距分析最优值不超过 5% 是无外部干扰的两链路上限实验
两个 FlexGen 实例互相 Relay OPT-6.7B、OPT-13B,90% KV Cache Offload 80% 以上迭代优于 Vanilla,吞吐最高提高 50% 其余迭代最多下降 3 tokens/s;不是每次都获益
FlexGen 借共置作业链路 共置训练或推理作业 FlexGen 吞吐最高 1.6 倍,捐出链路的作业开销小于 1% 结论依赖共置作业没有把 PCIe/NVLink 放在关键路径上
ZeRO-Offload 训练 4 卡 DP,GPT-J、Llama-2-7B、Phi-3-mini 迭代时间降低 4%–7% 只达到分析上限的 33%–41%,收益受较低传输占比限制

论文证明得最扎实的是:在它覆盖的三代 PCIe 与三类 Offloading 工作负载上,Relay 的固定开销足够小,池化确实能转化为端到端收益。但“最高 1.6 倍”不是所有 LLM Workload 的通用加速比;它来自特定 FlexGen Offload Policy 和共置组合。

我的判断与适用边界

我认为 TurboBus 最有价值的地方,不是又做了一套更快的 memcpy,而是提出了一种节点内 I/O 视角:Scale-Up Fabric 不只承载 Collective,也可以成为连接多个慢速出口的 Relay Backplane。只要系统满足“单资源受限、总体闲置、存在更快横向路径、所有权可安全重构”四个条件,原本静态绑定的链路就可能变成可调度资源。

这条思路真正落地时,限制同样清楚:

  • Scale-Up Fabric 必须足够快:只有 PCIe Peer-to-Peer、没有 NVLink/NVSwitch 的机器会让 Relay 与原流量争用同一层 PCIe,收益消失。
  • Host 侧路径必须有余量:H800 八链路实验在同一 NUMA 内扩展较好,跨到远端 NUMA 后因 UPI 与内存位置限制,八链路 H2D 反而从四链路的 3.6 倍降到约 3 倍。
  • D2D 流量不能占满关键 NVLink:TurboBus 不调度 NCCL Collective;若 D2D 已经饱和或位于关键路径,Relay 会与它竞争,论文把这类联合调度留给未来工作。
  • 集成式 CPU–GPU 不属于当前范围:Grace Hopper/Blackwell、MI300A 等平台用高带宽一致性互联替代 PCIe Host Path,TurboBus 的 PCIe Relay 不再解决主要瓶颈。
  • 生产级隔离仍需补证据:论文展示了特权 Daemon、共享内存、CUDA IPC 与回退机制,但没有公开 Artifact,也没有报告恶意租户、Daemon 故障恢复、配额和审计方面的实验。

因此,判断一个平台是否值得引入 TurboBus,不应先问“有几张 GPU”,而应依次测量:每个作业不可隐藏的 H2D/D2H 占比、各 PCIe Link 的时间序列、GPU 间与 Host 侧路径带宽、NUMA 位置,以及 NCCL/D2D 是否已经占据 Scale-Up Fabric。只有这些账本同时出现可借容量,图 1 中那几条红色箭头才会成为真实收益。

总结

TurboBus 从一个利用率悖论出发:Offloading 作业被自己的 PCIe Link 卡住,节点内其他 Link 却因为阶段错峰和作业异构而空闲。它通过特权 Daemon 重构跨作业所有权,通过 Scale-Up Fabric 增加 Host Memory 路径,再用 Block Streaming、Block-Level Allocation、双向同步、Chunking 与 Token 把这些路径变成可用的共享带宽。

现在可以确认的是,这个设计在论文覆盖的 V100、RTX 3090、H800 微基准与三类端到端工作负载中获得了可测收益,并把 Relay 开销控制在接近分析上限的范围。尚不能确认的是,它在公开代码、复杂框架 Allocator、Scale-Up 与 NCCL 同时重载、跨 NUMA 大规模池化以及恶意多租户环境中的表现。

回到最初的问题,空闲带宽并不是真的“不可用”。它只是被静态所有权切碎了。TurboBus 的贡献,是用更快的横向 Fabric 和足够细的调度粒度,把这部分碎片重新拼成了一条数据路径。

参考资料

Author

Shaojie Tan

Posted on

2026-08-27

Updated on

2026-08-28

Licensed under