在 Linux 上让四块 32 GB Resizable BAR 穿过 PCIe 交换机
我的 PCIe 交换机是一块插在主板唯一一个 x16 插槽里的 Broadcom PEX88096,四块 AMD Radeon AI PRO R9700 以及我 PCIe 拆分器那篇文章里的两块 RTX 3090 都接在它后面,整机组装文章里有完整说明。
交换机给每块卡分配八个通道,并让卡之间无需经过 CPU 就能互相拷贝。AMD 的 ROCm 称之为点对点(P2P),只有当每块卡的整个 32 GB 都被映射时它才会开启这项功能。在 Linux 上,这意味着每块卡都要有一个 32 GB 的 Resizable BAR。基址寄存器(BAR)把 CPU 地址空间的一段映射到设备的内存上。Resizable BAR 是 BIOS 里的一个选项,它让卡的 BAR 可以是完整的 32 GB,而不是默认的 256 MB。
组装文章发布后有人想要这个模块,所以它现在在 GitHub 上,名叫 gpu-rebar-layout。它依赖的内核内部机制在任何版本中都可能变化,所以加载前请先读 README。邮件列表上有一个补丁系列修复了尺寸计算的 bug,但即使它合入之后,这块主板仍然需要模块的 resize 和 rescan,并且要关掉它的 padding(pad=0)。
为什么 BIOS 无法映射六个 32 GB 的 BAR 🔗
加上 3090 之后,主板卡在 POST LED 的白色 VGA 灯上,因为每个 32 GB 的 BAR 都要占用 BIOS 地址空间的 64 GB。地址空间那篇文章里有具体计算。
Linux 7.0 的桥窗口尺寸计算 bug:can't assign; no space 🔗
这块主板的 BIOS 没有针对每块卡的 Resizable BAR 设置。Linux 可以在任何 GPU 驱动加载之前,给某些卡分配大 BAR,而让其他卡保持小 BAR。把六块卡全部插上并关闭 Resizable BAR 后,我想让 Linux 来完成分配。计划是把每块 R9700 的 BAR 尺寸寄存器设为 32 GB,移除交换机所挂接的 CPU 端口,然后重新扫描,让内核按照新的尺寸重新布局整个交换机。
每块 R9700 的 32 GB BAR 之后紧跟着一个 doorbell BAR,用于驱动写入、告知卡有新工作的寄存器。开启 Resizable BAR 时,BIOS 把它设为 256 MB。Linux 则把它保持在 2 MB。
内核给交换机分配了约 128.6 GB 的窗口。在每个 BAR 之后放置 doorbell、也就是 Linux 7.0 的布局方式下,四个窗口合计跨度为 224 GB(三个 64 GB 槽位,加上最后一块卡的 32 GB 及其 doorbell)。两块 R9700 拿到了它们的 BAR,交换机其余部分什么都没拿到。
pci 0000:1b:00.0: bridge window [mem size 0x800200000 64bit pref]: can't assign; no space
这发生在 7.0.6 上。未打补丁的 v7.4-rc1 内核在对照运行中放进了四块中的三块,那次运行由 Linux 内核补丁测试文章(即将发布)覆盖。

0x800200000 是 32 GB 加 2 MB,即一块卡的窗口。内核把交换机的窗口尺寸算成了其子项的直接求和:其中四个加上两块 3090 的 288 MB 窗口。
这个函数是 drivers/pci/setup-bus.c 里的 pbus_size_mem()。对桥的每个子项,它把子项的大小,或者如果对齐值更大则用对齐值,加到总和里。
r_size = resource_size(r);
size += max(r_size, align);
当每个子项的大小都是其对齐值的整数倍时,这样算就不会留出间隙,普通 BAR 正是如此,因为它们是 2 的幂。这里一块 GPU 的窗口是 32 GB 加 2 MB,对齐值是 32 GB,所以求和时漏掉了从一块卡的 doorbell 到下一块卡起始之间将近 32 GB 的间隙。
这是一个已知的回归问题。Nikolas Britton 在九月于 linux-pci 上报告了它,那个讨论帖的标题几乎逐字描述了我的问题。
文章标题:在 Linux 上通过 PCIe 交换机容纳四条 32 GB 可调整大小的 BAR
正文: 维护这段代码的 Ilpo Järvinen 在一个包含 14 个补丁的资源放置修复系列中提供了修复,截至 11 月 6 日已到 v5,尚未合并。我测试了它,并向 linux-pci 邮件列表发送了 Tested-by。有了它,全部四块 R9700 都获得了它们的 32 GB。
即使有了这个修复,这块主板仍然需要在 GPU 驱动加载之前设置 BAR 大小并重新扫描,因为 amdgpu 在加载时调整大小无法扩展其他卡已经占据的窗口。
一个填充 BAR 的内核模块 🔗
内核已经知道如何在不改变 BAR 大小的情况下提高其对齐要求,但在 pci=resource_alignment= 启动选项中指定设备使用的是调整大小模式。在 R9700 上请求 32 GB 对齐也会增大它的小 BAR,包括 512 KB 的寄存器 BAR,而它在 4 GB 以下无法对齐到 32 GB。
解决办法是一个树外模块,它从 initramfs(内核启动时使用的微型早期启动文件系统)运行,在任何 GPU 驱动加载之前执行。对于每块匹配的卡,它设置 BAR 大小寄存器,然后移除交换机上方的根端口并重新扫描。
在扫描和地址分配之间,它遍历交换机下的每个总线段。它将每个较小的 64 位可预取 BAR 的对齐要求提高到该段上最大的 64 位可预取 BAR 的大小,并对其他内存 BAR 作为单独一组做同样的处理。每个 R9700 窗口恰好变成 64 GB,每个 3090 窗口恰好变成 512 MB。一旦每个窗口都是其对齐要求的整数倍,内核的分配器就会完成剩下的工作。
重启后,R9700 位于 160、224、288 和 352 GB,3090 紧随其后,amdgpu 报告了整块卡。
amdgpu 0000:14:00.0: [drm] Detected VRAM RAM=32624M, BAR=32768M
借助这台机器已有的 ACS 重定向设置(在 R9700 交换机端口上使用 pci=disable_acs_redir=),R9700 之间的 P2P 在每一对上都以 14.0 GB/s 运行,这是八通道 gen4 链路在实际中的表现,而全部四块同时以环形复制时达到了 54.7 GB/s。
如果你的 GPU 无法全部获得它们的 BAR 🔗
- 首先在 linux-pci 邮件列表中搜索你的 can't assign 错误。可能已经有人报告过,甚至修复过。
- 在 GPU 驱动加载之前调整 BAR 大小并重新扫描,这意味着要从 initramfs 执行。
- 不要移除或重新扫描已经加载了驱动的 GPU。那会让我的机器锁死。
- 如果一块卡获得了它的 BAR 而下一块没有,看看大 BAR 旁边的小 BAR。
- 启动后几分钟再检查一次这些卡,以防它们进入睡眠后没有唤醒。
这次我得到了帮助。Claude 和我一起编写了模块,通过 SSH 在机器上运行命令和测试,并帮助我起草了这篇文章。我做了实际工作,读了所有文字,核对了数字,并重写了任何听起来像聊天机器人的内容,所以错误是我的。
来源 🔗
- gpu-rebar-layout,该模块的源码和 README
- PCI: bridge window undersized when child bridge windows are larger than their alignment,包含回归报告和 Ilpo Järvinen 回复的 linux-pci 帖子
- PCI: Resource placement algorithm fixes (v5),Ilpo 的系列,包括 "PCI: Fix composite resource sizing" 和 "PCI: Place resources to either edge of the window"
- drivers/pci/setup-bus.c,pbus_size_mem()
- pci_request_resource_alignment(),pci=resource_alignment= 背后的两种对齐模式
多 GPU 构建系列中的更多内容
- PCIe Bifurcation vs PCIe Switches and Retimers for Multi-GPU Builds
- Six GPUs on One x16 Slot: R9700s and RTX 3090s Behind a PCIe Switch
- Counting PCI Address Space for Resizable BAR on Six GPUs
- GPU P2P On vs Off: Tensor Parallel LLM Serving on Four R9700s
- gpu-unlock on Two RTX 3090s: Faster Prefill, No Decode Gain
相关文章
- Six GPUs on One x16 Slot: R9700s and RTX 3090s Behind a PCIe Switch
- GPU P2P On vs Off: Tensor Parallel LLM Serving on Four R9700s
- Counting PCI Address Space for Resizable BAR on Six GPUs