跳到正文
Hacker News· dougcalobrisi·· 7 小时前AI 评分22

在 Linux 上通过 PCIe 交换机为四块 32 GB 显卡配置 Resizable BAR

Fitting Four 32 GB Resizable Bars Behind a PCIe Switch on Linux

AI 导读

一位开发者在 Broadcom PEX88096 PCIe 交换机后接入四块 AMD Radeon AI PRO R9700 和两块 RTX 3090,通过 GitHub 上的 gpu-rebar-layout 内核模块在 GPU 驱动加载前将每块 R9700 的 BAR 设为 32 GB 并重新扫描,使四块卡全部获得完整 BAR。

正文 · AI 翻译

我的 PCIe 交换机 是主板唯一一个 x16 插槽中的 Broadcom PEX88096,它后面接着四块 AMD Radeon AI PRO R9700 和两块 RTX 3090,来自我的 PCIe 拆分器文章,而装机文章里有整台机器的介绍。

交换机给每块卡分配八条通道,并让这些卡之间可以直接互相拷贝,而不必经过 CPU。AMD 的 ROCm 称之为点对点(P2P),只有当每块卡的完整 32 GB 都被映射时它才会启用。在 Linux 上,这意味着每块卡都要有 32 GB 的可调整大小 BAR。基址寄存器(BAR)把 CPU 地址空间的一段范围映射到设备的内存上。可调整大小 BAR 是 BIOS 选项,它让一块卡的 BAR 可以是完整的 32 GB,而不是默认的 256 MB。

在装机文章发布后,有几个人想要这个模块,所以它已经以 gpu-rebar-layout 的名字放在 GitHub 上。它依赖内核内部机制,而这些机制在任何版本中都可能变化,所以加载前请先阅读 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:无法分配;没有空间 🔗

这块主板的 BIOS 没有针对单块卡的可调整大小 BAR 设置。Linux 可以给某些卡分配大 BAR,而让其他卡保持小 BAR,只要它在任何 GPU 驱动加载之前完成即可。在六块卡全部插上且关闭可调整大小 BAR 的情况下,我希望由 Linux 来完成分配。计划是把每块 R9700 的 BAR 大小寄存器设为 32 GB,移除交换机所挂接的 CPU 端口,然后重新扫描,这样内核就会按照新的大小重新布局整个交换机。

每块 R9700 的 32 GB BAR 后面跟着一个 doorbell BAR,用于驱动写入以告知显卡有新工作的寄存器。开启可调整大小 BAR 时,BIOS 把它设为 256 MB。Linux 则把它保持在 2 MB。

内核给交换机分配了约 128.6 GB 的窗口。按照 Linux 7.0 的布局方式,把 doorbell 放在每个 BAR 之后,这四个窗口共占 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 内核补丁测试文章(即将发布)覆盖。

Cartoon: a fenced parking lot under a sign reading switch window has four bays, each painted 32 GB. Two blue stretch limousines labelled R9700 are parked inside, each towing a small orange trailer labelled 2 MB that sits in its own bay, so the two of them fill the lot. Two more R9700 limos with trailers wait outside the gate, sweating. A grey robot attendant in a cap smiles at the gate holding a clipboard reading 4 x 32 GB = fits.

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 的空隙。

Three rows on one scale of the switch’s 64-bit window from 160 GB, with dashed lines at 224, 288, 352 and 416 GB. Legend: 32 GB BAR in blue, 2 MB doorbell as a dark sliver, padding in pale blue, got no BAR in orange, 3090 windows as grey slivers. The first row, What the kernel added up: 4 × (32 GB + 2 MB) + 2 × 288 MB = 128.6 GB, packs card 1 to card 4 end to end, each a 32 GB BAR plus its doorbell, and an orange dashed line marks window ends here just past 288 GB. The second row, Doorbell after each BAR, BARs on 32 GB boundaries: 224 GB, puts card 1 at 160 GB and card 2 at 224 GB with a dashed gap after each, while card 3 and card 4 sit past the end of the window in orange, with the note two R9700s placed, and the rest of the switch got nothing. The third row, The module’s padding: every window a multiple of its alignment, has each card followed by its doorbell and a padding block, card 1 to card 4 filling 160 to 416 GB, then two grey 3090 slivers, with the note each R9700 window exactly 64 GB, then two 3090 windows of 512 MB. Footer: Linux 7.0.6. Unpadded, the switch window was 0x2800000000-0x48247fffff. Doorbells and 3090 windows are drawn wider than to scale.

这是一个已知的回归问题。Nikolas Britton 在九月于 linux-pci 上报告了它,那个讨论串的标题几乎逐字描述了我的问题。

维护这段代码的 Ilpo Järvinen 有一个修复,位于一个包含 14 个补丁的资源放置修复系列中,截至 11 月 6 日已到 v5,尚未合并。我测试了它,并向 linux-pci 邮件列表发送了 Tested-by。有了它,四块 R9700 都获得了各自的 32 GB。

即使有了这个修复,这块主板仍然需要某种东西来设置 BAR 大小并在 GPU 驱动加载前重新扫描,因为 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 🔗

  1. 先在 linux-pci 邮件列表中搜索你的 can't assign 错误。可能已经有人报告过,甚至修复过。
  2. 在 GPU 驱动加载前调整 BAR 大小并重新扫描,这意味着要从 initramfs 中执行。
  3. 不要移除或重新扫描已经加载了驱动的 GPU。那让我的机器锁死了。
  4. 如果一块显卡获得了它的 BAR 而下一块没有,看看大 BAR 旁边的小 BAR。
  5. 启动后几分钟再检查一次显卡,以防它们进入休眠后无法唤醒。

这一篇我得到了帮助。Claude 和我一起编写了模块,通过 SSH 在机器上运行了命令和测试,并帮助我起草了这篇文章。我做了实际工作,阅读了所有文字,核对了数字,并重写了任何听起来像聊天机器人的内容,所以错误在我。

来源 🔗

来源:Hacker News · doug.sh