一个将Mac Studio与两台NVIDIA GB10 Spark通过原生RDMA互联、实现CUDA与Metal跨机推理的项目。
AI translation, not an official translation. Refer to the original for technical details.
Adapted from @ashxhart# MCDMA:让一台 Mac Studio 与两台 Spark 协同工作 我的桌上摆着一台 256 GB M3 Ultra Mac Studio 和两台 NVIDIA GB10 Spark 级系统,我希望它们能在同一个模型上协同完成有意义的工作。 这正是我启动 MCDMA 的原因。两台 Spark 为我提供了 CUDA 和 Blackwell Tensor Core;Studio 则拥有庞大的统一内存池和高本地内存带宽。我想探究将它们组合在一起,能否加快本地推理速度,或者让我运行任何一台单独无法容纳的模型。 第一项任务是让这些机器通过真实的硬件 RDMA 传输数据。如今,我已有一个原生 macOS ConnectX-5 驱动和用户空间提供程序,能够在 Studio 与一台 Spark 之间完成经字节校验的 READ 和 WRITE 操作,两台机器均可作为发起方。此后,我还验证了由 Metal 和 CUDA 内核分别写入并校验的缓冲区之间的双向传输。 这为我真正关心的实验提供了一条可用的连接通路:在 CUDA 与 Metal 之间迁移运行中的请求、将更大的模型拆分到全部三台机器上,以及在某台机器本会空闲等待时为其分配有用的工作。 ## 我为什么想构建这个 提示词处理(prefill)与词元生成(decode)对模型的压力截然不同。Prefill 负责处理输入并构建注意力状态,其中有足够多的并行矩阵运算,使 Spark 的 Tensor Core 成为颇具吸引力的选择。在低批量大小下,decode 往往将大量时间花在从内存读取权重和注意力状态上。 NVIDIA 为每台 Spark 标注了 273 GB/s 的内存带宽,而苹果为 M3 Ultra 标注的带宽超过 800 GB/s。这正是我想深入研究的硬件差异。它并不能直接告诉我在某个特定模型上哪台机器更胜一筹,但它给了我充分的理由去测试请求的每个部分应在哪台机器上运行。NVIDIA 硬件规格,苹果 M3 Ultra Studio 发布公告。(https://docs.nvidia.com/dgx/dgx-spark/hardware.html) (https://www.apple.com/newsroom/2025/03/apple-unveils-new-mac-studio-the-most-powerful-mac-ever/) 在 Spark 上执行 prefill、在 Studio 上执行 decode,是最直观的首个实验。Prefill/decode 分离方案已在 NVIDIA Dynamo 等系统中实现;我的问题是,在我已拥有的 CUDA 和 Metal 机器之间,这种方案何时能带来实际收益。(https://docs.nvidia.com/dynamo/dev/knowledge-base/concepts/system-architecture/disaggregated-serving) 我还希望对 Transformer 有足够深入的理解,从而做出比为每台机器固定分配任务更明智的决策。Prefill 完成后,Spark 能否在 Studio 执行 decode 的同时起草词元、运行常驻专家,或提前开始处理下一个请求?在这变得值得之前,需要迁移多少状态? ## 硬件与约束 Studio 拥有 256 GB 内存,运行 macOS 27。每台 Spark 的名义内存为 128 GB,两台 Spark 之间已有自己的 ConnectX-7 连接。 在 Mac 一侧,一个 OWC Mercury Helios 5S Thunderbolt 5 PCIe 扩展坞内置有一张 Mellanox ConnectX-5 Ex MCX516A-CDAT 网卡。该网卡具有两个 QSFP28 端口,每个端口额定速率为 100 GbE。NVIDIA 网卡规格。(https://networking-docs.nvidia.com/connectx5enhw/specifications) 完整的拓扑结构将把每台 Spark 连接到其中一个端口。两台 Spark 还通过 USB-C 与 Studio 相连。迄今为止已验证的 RDMA 测试使用的是一条 Spark 到 Studio 的连接;同时使用网卡的两个端口尚待测试。 两个卡槽共享机箱与 Mac 之间的连接。OWC 宣传 Helios 5S 的速度最高可达 6,000 MB/s,约合 48 Gb/s,而记录显示以太网建立连接时协商速率为 40 Gb/s。卡的额定值和机箱规格均非该配置下的实测 RDMA 有效载荷速率。我已订购新的 100 GbE 线缆,届时将测量实际链路速率与吞吐量。OWC 机箱规格。(https://www.owc.com/solutions/mercury-helios-5s) 这套网络硬件花费约 800 英镑,对于一项个人实验而言是一笔不小的开支。我几乎取消了订单,后来还是决定出售第三台 Spark 来资助这项研究。我想看看自己手头的硬件究竟能被推到多远。 ## 我需要自行构建的部分 Apple 将 Mellanox 网卡识别为以太网设备,并不意味着我获得了一条可用的 ConnectX RDMA 路径。MCDMA 起初是一个实验性的 DriverKit 实现,目前已发展为原生 macOS 驱动程序和用户空间提供程序。 该驱动程序与网卡固件通信,负责映射和注册内存、创建队列、提交操作并检查硬件完成状态。ConnectX-5 现已出现在 macOS verbs 发现列表中,原生 READ 和 WRITE 操作在两个方向均通过了字节校验,包括用户空间直接提交的场景。 我不只核验了成功的返回码。测试还验证了传输的字节内容、检查了物理网卡的 RDMA 计数器,并尝试了在无权限情况下发起远程 WRITE 操作。网卡拒绝了该请求,目标缓冲区内容保持不变。 所演示的路径现已包含由 GPU 本身产生并校验的数据。Metal 内核填充了源缓冲区,网卡通过 RDMA 完成数据搬运,CUDA 内核对目标端的每个字进行了逐一校验。反向传输同样通过测试,由任意一侧主机发起的 READ 操作亦如此。 上述测试在 Studio 端使用了 Metal 共享缓冲区,在 Spark 端使用了 CUDA 映射的主机分配内存。GPU 和网卡访问同一块已注册的存储区域,无需在应用层进行有效载荷的中转拷贝。在内核提交和用户空间 BlueFlame 测试中,全部 24 项 GPU 缓冲区操作均通过验证,故意写入错误数据的检测以及保护区域检测同样通过。 我还构建并测试了一个可复用的适配器,用于对接现有的 CUDA 设备分配内存。它保留原始应用指针,并通过一块已注册的通信缓冲区执行显式 CUDA 拷贝。应用程序也可以从一开始就将通信缓冲区分配在共享存储中,从而无需有效载荷拷贝即可使用已测试的路径。 对 cudaMalloc 设备内存的直接 RDMA 注册在所测试的 Spark 上失败,Metal 私有缓冲区也不被当前的注册路径所支持。CPU 仍负责协调内存分配、GPU 工作及 RDMA 完成状态。共享缓冲区路径已可正常使用;下一步工作是将其集成到推理引擎中。 ## 当前的延迟水平 由 Mac 发起的 4 KiB RDMA 传输现测得 WRITE 延迟为 7.625 µs,READ 延迟为 6.042 µs;由 Spark 发起的传输则为 WRITE 3.680 µs,READ 5.536 µs。 这些是在原生 0.1.17 版本上三次匹配运行的汇总中位数,测试期间用户空间 BlueFlame 提交持续运行,Studio 上同时运行一个小型 Metal 保活工作负载。每项数据均包含预热后的全部 3,000 次测量,使用已注册的主机缓冲区,队列深度为 1。计时衡量的是在 GPU 活跃状态下从提交到应用层观测到完成的时间;单次完成时间存在差异。GPU 缓冲区测试已单独验证正确性,不测量 GPU 到 GPU 的延迟或模型性能。 ## 我希望借此实现的目标 第一个模型实验是完整的任务移交。对于一个能同时容纳在 Studio 和 Spark 对上的模型,我会将其权重常驻在两处:先在 Sparks 上完成预填充,然后传输 KV 缓存及其他所需状态,由 Studio 继续执行生成。 接下来我想让工作重叠进行。我最初的想法是在 Sparks 处理下一个分块的同时,以每块 25 个提示词 token 为单位移交已完成的状态。最终结果仍取决于完整的提示词,但传输可以在预填充完成之前就开始。 难点在于让各运行时保持一致。模型版本、token 位置、精度和缓存布局都必须匹配。在 CUDA 和 Metal 上加载相同的权重,并不意味着它们的缓存字节可以互换。我会将打包、转换和 GPU 同步的开销纳入结果,因为这才是真实请求所付出的代价。 ## 跨三台机器运行同一个模型 我尤为感兴趣的实验,是在 Studio 和两台 Sparks 上部署约 400 GB 常驻权重的模型。这里的 400 GB 指的是所选权重表示形式下的存储量,而非 4000 亿参数。 三台机器合计约有 512 GB 名义内存,各自独立,每台机器还需为操作系统、KV 缓存、激活值和运行时工作区预留空间。 一种可测试的布局方案是:Spark 对共承载约 200 GB 权重,Studio 承载约 200 GB。Sparks 可使用张量并行在各自层内共享计算,而 Studio 则作为流水线的另一阶段运行另一组层。 权重留在使用它的机器上,激活值跨越阶段边界传递,每个阶段为自身的层维护缓存。所有必要阶段均参与预填充和解码。 我尚未在这个集群上运行该模型。能否容纳它本身就是一个有价值的容量验证结果;要让它跑得快,则需要平衡各阶段的负载,并找到足够的并发任务来保持各阶段持续运转。 ## 为等待中的机器分配有用的工作 对于单个请求,我想尝试投机解码:一个较小的模型提出一批候选 token,目标模型同时对其进行验证,运行时提交被接受的 token。最终结果取决于有多少提案通过验证以及验证的代价。NVIDIA 投机解码文档。(https://nvidia.github.io/TensorRT-LLM/1.1.0/features/speculative-decoding.html) 对于多个请求,连续批处理让一个工作节点同时推进多个独立序列,这可以提升总吞吐量,但我也想衡量等待答案的用户实际体验到的变化。NVIDIA 批处理与调度文档。(https://nvidia.github.io/TensorRT-LLM/1.3.0rc15/features/paged-attention-ifb-scheduler.html) 我想比较固定角色分配与动态调度器两种方式——后者将下一个有用的任务分配给空闲机器。移动状态或加载权重的代价有时可能超过所节省的时间,而持有分片模型某部分的 Spark 也无法简单地放弃当前任务。 神经引擎是另一个目标。一个通过 Core ML 运行的小型兼容草稿器可以为 Metal 上的较大模型提出候选词元以供验证。我需要确认哪些操作实际运行在神经引擎上,以及每秒接受的词元数是否有所提升。苹果的 Core ML 文档。(https://developer.apple.com/documentation/coreml/mlcomputeunits) ## 利用额外的链路 两块 Spark 都通过 USB-C 连接到 Studio,我想看看这些链路能否在 QSFP 之外承载有用的数据。 一项测试将根据测量到的到达时间,同时经由两条路径发送已完成的 KV 块。另一项测试将为立即所需的状态保留较快的路径,而另一条路径则用于移动下一个请求的前缀缓存或适配器。 这需要明确的排序和所有权机制,以便消费方知道哪些字节已经就绪。现有的 USB 软件传输并非硬件 RDMA,共享控制器或内存流量可能会限制收益。衡量标准是模型等待的时间是否减少。 ## 25 项实验 我与 Claude 和 Codex 共同完善了这份实验构想清单,既借鉴了现有研究,也融入了我自己的问题。共享缓冲区的工作已通过首批正确性测试。以下是完整的实验计划,推理集成与模型基准测试仍在前方。 1. 将兼容的 KV 缓存从 CUDA 运行时迁移到 MLX,并在 Mac 上继续处理该请求。 2. 当 Spark 原本需要驱逐或重新计算注意力状态时,将其保留在 Studio 上。 3. 以原生表示形式传输紧凑的潜在注意力缓存,连同恢复所需的其他状态一并传输。 4. 尝试树形推测解码,提出多条续写候选并验证有价值的分支。 5. 以小型神经引擎草稿器配合较大的 Metal 模型作为验证器。 6. 根据提示长度、预期答案长度和测量到的排队时间来路由请求。 7. 共享兼容的前缀缓存,以避免重复处理相同的提示前缀。 8. 将混合专家模型的权重保存在不同机器上,并将激活值发送至所需的专家节点。 9. 镜像生成检查点,然后测试受控切换以及故障后的恢复。 10. 在剩余提示仍在处理过程中,流式传输已完成的预填充状态。 11. 在已验证的 CUDA/Metal 共享缓冲区基础上,在持续工作负载下测试运行时集成与可见性。 12. 比较持久 GPU Worker 响应传输与普通内核启动两种方式的差异。 13. 在 KV 缓存各部分所在位置计算注意力,再将各部分结果正确合并。 14. 将模型层划分为流水线阶段,衡量阶段均衡性与微批次处理效果。 15. 将向量索引置于另一台机器上,在相同检索质量下比较不同的检索方法。 16. 在另一台机器上预备好 LoRA 适配器,衡量加载切换的开销。 17. 跨主机测试对比解码,衡量质量提升是否值得付出额外开销。 18. 通过模型级联逐步升级处理难度较高的请求,仅在状态兼容时才复用状态。 19. 研究用于 LoRA 训练的远程优化器状态。 20. 当节省的时间超过迁移成本时,将正在运行的请求迁移至其他节点。 21. 分离控制流量与数据负载流量,并根据测量到的完成开销选择传输方式。 22. 通过内容识别相同的兼容状态,避免重复存储或传输。 23. 结合来自不同主机上模型的预测结果,并以计算成本为参照衡量质量。 24. 压缩传输的状态并计算压缩时间,包括对有损格式进行质量检查。 25. 随着负载和接受率的变化,动态调整哪台机器负责起草、哪台机器负责验证。 ## 什么能说服我 我希望得到一个可复现的模型测试结果。我将在相同工作负载适配的情况下,对单台 Spark、Spark 组对、Studio 以及组合集群进行比较,评估指标包括:首个 Token 的生成时间、每秒接受的 Token 数、总响应时间、质量以及峰值内存占用。 规模更大、只能在整个集群上运行的模型,体现的是容量层面的结果。更快的响应则需要进行端到端的比较,其中须涵盖通信与同步的开销。我将如实报告两类结果,包括那些额外机器反而拖慢性能的实验。 驱动程序为我提供了在真实硬件上探究这些问题的途径。我的下一个目标是:实现一个因 Spark 与 Studio 协同工作而更快完成的模型请求,随后进行更大模型的流水线实验。 我的下一步是准备一个 oMLX PR,将经过测试的 RDMA 传输和 GPU 缓冲区 API 集成到推理引擎中。这意味着在可行的情况下使用兼容的共享通信缓冲区,明确标注所有来自现有 CUDA 分配的拷贝操作,并将传输完成事件与模型执行流程相衔接。之后,我便可以对真实的 KV 缓存交接和分布式推理进行基准测试,涵盖所有转换与同步的开销。 如果你从事 CUDA 或 Metal 运行时相关工作,我很乐意就如何在不丢失状态、也不让收益被转换开销抵消的情况下,将一个正在运行的请求在两者之间切换这一问题交流心得。我将在推进过程中持续发布实验内容和测量数据。 驱动程序、配置指南及验证说明均已发布在 GitHub 上。欢迎在你自己的项目中尝试 MCDMA,并分享你的构建成果。这是一款实验性软件,我将持续记录哪些方案有效、哪些还需改进。(https://github.com/ashhart/mcdma)