<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[[p2p 卍解！]在 X99 TF板上打开2*7900 XTX 的 PCIe P2P卡间通信]]></title><description><![CDATA[<p dir="auto"><img src="https://upload.lcz.me/uploads/853fba8f-40ee-41a4-862b-1767f68bbc10.jpeg" alt="image.jpeg" class="img-markdown  img-fluid" /></p>
<p dir="auto">记得论坛里老特不止一次地舌战群儒，激扬文字。探讨P2P对于目前各大模型pp,token生成速度的影响。也确实有多位实测仿佛就目前，各项数据提升不大。</p>
<p dir="auto">这点上我站老特，p2p这么底层的东西，它对于速度的提升绝非是线性的。打通了卡间的P2P，再通过一系列的优化叠加，最终呈现的效果才能让人咋舌。</p>
<p dir="auto">所以我花了两天时间，把能踩的坑全踩了个遍，回来为社区作下贡献。<br />
没有一蹴而就的事情，直言让AI抄作业，没一份能直接打通的。</p>
<p dir="auto">但是，又离不开论坛中诸位大佬在贴子中仙人指路，让我一个对底层技术栈相当生疏的人，在拜读高论后，也有一战的勇气。</p>
<p dir="auto">在此，谢过。<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f918.png?v=ec85ec55bc1" class="not-responsive emoji emoji-android emoji--the_horns" style="height:1.4em;width:auto;vertical-align:middle" title=":the_horns:" alt="🤘" /></p>
<p dir="auto"><a href="https://lcz.me/topic/2599">https://lcz.me/topic/2599</a><br />
<a href="https://lcz.me/topic/2525">https://lcz.me/topic/2525</a><br />
<a href="https://lcz.me/topic/1941">https://lcz.me/topic/1941</a><br />
<a href="https://lcz.me/topic/1896?_=1793023990696">https://lcz.me/topic/1896?_=1793023990696</a><br />
<a href="https://lcz.me/topic/1749?_=1793023990700">https://lcz.me/topic/1749?_=1793023990700</a><br />
<a href="https://lcz.me/topic/1941?_=1793023990703">https://lcz.me/topic/1941?_=1793023990703</a></p>
<p dir="auto">－－－－－－－－－－－－－－－－－分割线－－－－－－－－－－－－－－－－－－－－－</p>
<blockquote>
<p dir="auto">工程复现报告 · 2026-10-26</p>
<p dir="auto">不改内核、不改 <code>amdgpu.ko</code>：用一个 18 行的 kretprobe 模块绕开 <code>pci_p2pdma</code> 的芯片组白名单，再用一份拓扑 XML 骗过 RCCL 的 PHB 策略。下面是完整环境、复刻过程、十八次翻车，以及可以照抄的关键参数。</p>
</blockquote>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>指标</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td>远端写 / 异步 SDMA 单向</td>
<td><strong>5.23 GB/s</strong></td>
</tr>
<tr>
<td>双向异步拷贝聚合</td>
<td><strong>6.66 GB/s</strong></td>
</tr>
<tr>
<td>RCCL busbw</td>
<td><strong>4.0 → 5.1 GB/s（+27%）</strong></td>
</tr>
<tr>
<td>内核 / 驱动重编译</td>
<td><strong>0 次</strong></td>
</tr>
<tr>
<td>持久系统改动</td>
<td><strong>1 个 modprobe 配置 + 1 个 12 KB 模块</strong></td>
</tr>
</tbody>
</table>
<hr />
<h2></h2>
<h2>1. 本机环境：一台典型的 X99 双卡工作站</h2>
<p dir="auto">这台机器的定位很明确：上一代服务器平台、单路 22 核、128 GB 内存、两张 7900 XTX 做本地推理。它不在 AMD 的 P2P 支持矩阵里，但硬件通路本身并不缺——缺的只是软件层的一道白名单。</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>层级</th>
<th>项目</th>
<th>本机值</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>系统</td>
<td>发行版 / 内核</td>
<td>Ubuntu 24.04.5 LTS / <code>7.0.0-38-generic</code></td>
<td>原版 HWE 内核，未替换</td>
</tr>
<tr>
<td>CPU</td>
<td>处理器 / 插槽</td>
<td>Intel Xeon E5-2696 v4 @ 2.20 GHz</td>
<td>Broadwell-EP，1 路，22 逻辑核（HT 关闭）</td>
</tr>
<tr>
<td>主板</td>
<td>Host Bridge</td>
<td><code>8086:6f00</code></td>
<td>X99 / C610 芯片组，P2P 白名单里没有它</td>
</tr>
<tr>
<td>内存</td>
<td>容量</td>
<td>125 GiB 可用</td>
<td>DDR4 ECC，四通道</td>
</tr>
<tr>
<td>GPU</td>
<td>计算卡 ×2</td>
<td>Radeon RX 7900 XTX，<code>1002:744c</code>，gfx1100</td>
<td>各 25,753,026,560 B ≈ 23.98 GiB</td>
</tr>
<tr>
<td>GPU</td>
<td>亮机卡</td>
<td>GeForce GTX 750 Ti，<code>10de:1380</code></td>
<td>nvidia 专有驱动，接显示器</td>
</tr>
<tr>
<td>PCIe</td>
<td>GPU 链路</td>
<td>16 GT/s ×16（Gen4 ×16）</td>
<td>两张卡各自跑到满宽</td>
</tr>
<tr>
<td>PCIe</td>
<td>上行瓶颈</td>
<td>8 GT/s ×8（Gen3 ×8）</td>
<td><strong>单方向理论 7.88 GB/s，全报告的性能天花板</strong></td>
</tr>
<tr>
<td>PCIe</td>
<td>交换芯片</td>
<td>AMD Navi 10 XL，<code>1002:1478</code> / <code>1479</code></td>
<td>03:00.0 / 04:00.0 与 06:00.0 / 07:00.0</td>
</tr>
<tr>
<td>IOMMU</td>
<td>模式</td>
<td>VT-d Translated，60 个 domain</td>
<td><strong>不是 passthrough；高位 BAR 能被翻译靠它</strong></td>
</tr>
<tr>
<td>BAR</td>
<td>显存映射基址</td>
<td><code>0x3820_0000_0000</code> / <code>0x3830_0000_0000</code></td>
<td>≈ 61.6 TiB，远高于 44 位 DMA 掩码</td>
</tr>
<tr>
<td>DMA</td>
<td>GPU 掩码</td>
<td>44 bit</td>
<td>amdgpu 默认值，未抬高</td>
</tr>
<tr>
<td>软件</td>
<td>ROCm</td>
<td>7.2.0</td>
<td><code>/dev/kfd</code>、renderD128 / renderD129 正常</td>
</tr>
<tr>
<td>软件</td>
<td>RCCL</td>
<td>2.27.7</td>
<td>不是参考贴里的 2.30.4</td>
</tr>
</tbody>
</table>
<h3></h3>
<h3>PCIe 拓扑</h3>
<pre><code class="language-mermaid">flowchart TB
  CPU["Xeon E5-2696 v4 · host bridge 8086:6f00"]
  R0["根端口 00:02.0 · AtomicOp 32/64/128CAS 全 +"]
  R1["根端口 00:03.0 · AtomicOp 32/64/128CAS 全 +"]
  S0["AMD Navi10 交换 · 03:00.0 · 上行 Gen3 x8"]
  S1["AMD Navi10 交换 · 06:00.0 · 上行 Gen3 x8"]
  G0["7900 XTX #0 · 05:00.0 · BAR 61.6 TiB"]
  G1["7900 XTX #1 · 08:00.0 · BAR 61.6 TiB"]
  PCH["X99 PCH 00:1c.x · AtomicOp 全 -"]
  NIC["GTX 750 Ti / 网卡"]
  CPU --&gt; R0 --&gt; S0 --&gt; G0
  CPU --&gt; R1 --&gt; S1 --&gt; G1
  CPU -.-&gt; PCH -.-&gt; NIC
  G0 ---|P2P 数据路径| G1
</code></pre>
<p dir="auto">图 1：本机 PCIe 拓扑。两张计算卡各挂一个根端口，中间各经过一颗 AMD Navi 10 XL 交换芯片；PCH 只接亮机卡和网卡。</p>
<blockquote>
<p dir="auto"><strong>为什么这个拓扑很关键</strong></p>
<p dir="auto">两块 GPU <strong>都不在 PCH 下</strong>，而是各自直连 CPU 根端口。因此 topic 2525 里那条「PCH 端口 AtomicOp Completer 全 <code>-</code> → 原子写失败」的故障链，在本机<strong>根本走不到</strong>。真正拦路的是另一个东西：<code>pci_p2pdma</code> 的芯片组白名单。</p>
</blockquote>
<h3>改造前的基线</h3>
<p dir="auto">原版内核、未做任何修改时，两张卡之间没有任何直连语义，所有跨卡拷贝都被运行时拆成「写回主机内存 + 从主机内存读回」。</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>指标</th>
<th>结果</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>hipDeviceCanAccessPeer(0→1 / 1→0)</code></td>
<td><strong>0 / 0</strong></td>
<td>无法 <code>hipDeviceEnablePeerAccess</code></td>
</tr>
<tr>
<td>dmesg 关键行</td>
<td>—</td>
<td><code>PCIe P2P access is not supported by the chipset</code></td>
</tr>
<tr>
<td>单向跨卡拷贝（512 MB）</td>
<td>2.75 GB/s</td>
<td>16 / 128 / 512 MB 均稳定在 2.71–2.76</td>
</tr>
<tr>
<td>双向同时拷贝</td>
<td>2.72 GB/s 合计</td>
<td>每个方向约 1.36 GB/s</td>
</tr>
<tr>
<td>RCCL allreduce（128 MB）</td>
<td>4.01 GB/s</td>
<td>P2P 关闭，走 SHM</td>
</tr>
<tr>
<td>可纠正 AER（启动 3 分钟内）</td>
<td>315 / 384 条</td>
<td>只在通往 GPU1 的 00:03.0 → 06:00.0 路径；GPU0 路径 0 条</td>
</tr>
</tbody>
</table>
<h2>2. 两篇参考贴：一篇排除法，一篇给钥匙</h2>
<p dir="auto">整个方案不是从零设计出来的，而是把两篇帖子的结论拼起来：先用 topic 2525 排除「PCH 原子写」这条故障链，再用 topic 2599 找到真正的门槛。</p>
<h3>2.1 topic 2525：PCH 平台的坑，本机不适用</h3>
<h3>2.2 topic 2599：真正的门槛只有一道白名单</h3>
<p dir="auto">参考平台是 Intel Arrow Lake-S + 双 Radeon AI PRO R9700，走的是 Gen5 x8。帖子的关键发现是：amdgpu 判断对端显存可不可达，只看 <code>pci_p2pdma_distance()</code> 的返回值；而该函数会在芯片组不在 <code>pci_p2pdma_whitelist</code> 时直接返回负数。参考机用 DKMS 给 amdgpu 加了一个 <code>amdgpu_p2p_force</code> 模块参数，跳过这道判断。</p>
<p dir="auto">本机的 <code>8086:6f00</code> 同样不在白名单里，<code>cpu_supports_p2pdma()</code> 也是 false。所以<strong>门槛完全一致</strong>。差异只在物理带宽：</p>
<blockquote>
<p dir="auto"><strong>选路结论</strong></p>
<p dir="auto">topic 2525 用来<strong>排除错误方向</strong>，topic 2599 用来<strong>提供正确钥匙</strong>。本机不需要处理 atomics 与 401，只需要回答一个问题：怎样在没有 DKMS 重编的前提下，让 <code>pci_p2pdma_distance_many</code> 返回一个非负值。</p>
</blockquote>
<hr />
<h2>3. 复刻路线：从「重编内核」转向「符号级劫持」</h2>
<h3>3.1 为什么放弃重编内核</h3>
<p dir="auto">最初的计划是照参考项目改内核：在 <code>pci_p2pdma_whitelist</code> 里加上 <code>8086:6f00</code>，打开 <code>CONFIG_HSA_AMD_P2P</code>，再抬高 DMA 掩码。两次编译都失败在「能不能进系统」这一步，细节放在第四章。真正让我改变路线的是成本对比：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>路线</th>
<th>成本与失败模式</th>
</tr>
</thead>
<tbody>
<tr>
<td>重编内核</td>
<td>1.9 GB 模块、455 MB initrd、108 MB <code>amdgpu.ko</code>；一次失败就要救援模式；每次内核升级都要重来</td>
</tr>
<tr>
<td>符号劫持</td>
<td>一个 12 KB 模块，18 行 C；不碰内核镜像、不碰 initrd、不碰 GRUB；卸载即回滚</td>
</tr>
</tbody>
</table>
<p dir="auto">而且两者在语义上<strong>完全等价</strong>：amdgpu 并不关心距离具体是几，只关心它是不是负数。参考贴的 <code>p2p_force</code> 参数本质也是跳过同一个判断。</p>
<h3>3.2 18 行代码：把距离改成 1</h3>
<p dir="auto"><code>pci_p2pdma_distance_many()</code> 是内核导出的符号，可以用 kretprobe 在它返回时改写寄存器里的返回值。模块只挂这一个符号，因此影响面被限制在调用它的驱动（本机就是 amdgpu）。</p>
<pre><code class="language-c">// p2p_force.c — 绕过 X99/Broadwell-EP 的 pci_p2pdma 芯片组白名单
// amdgpu 只通过 pci_p2pdma_distance_many() 的返回值判断对端可不可达，
// 这里用 kretprobe 把该调用强制返回正值（距离 = 1）。

# 1) 编译（需要当前内核的 headers）
make -C /lib/modules/$(uname -r)/build M=$PWD modules

# 2) 安装到当前内核的 extra 目录
sudo cp p2p_force.ko /lib/modules/$(uname -r)/extra/
sudo depmod -a

# 3) 保证 amdgpu probe 之前 kprobe 已就位
echo 'softdep amdgpu pre: p2p_force' | sudo tee /etc/modprobe.d/p2p-force.conf
</code></pre>
<p dir="auto">Makefile 只需要一行 <code>obj-m += p2p_force.o</code>。安装完重启，<code>p2p_force</code> 会在开机约 5.8 秒时先于 amdgpu 加载。这一步的顺序不能错：如果 amdgpu 已经 probe 完，再加载模块就来不及了。</p>
<h3>3.3 它为什么能一路改变 SDMA 的行为</h3>
<p dir="auto">这个返回值不是只影响一行日志，而是贯穿了 amdgpu 与 KFD 的三层判断。理解这条链路，才能明白为什么「只改一个函数」就能让 SDMA 和 GPU kernel 都开始直连对端显存。</p>
<pre><code class="language-mermaid">flowchart LR
  Q["pci_p2pdma_distance_many()&lt;br/&gt;白名单无 8086:6f00 → 返回 -1"]
  X["kretprobe 在返回处改成 1"]
  A["amdgpu_device_is_peer_accessible() = true"]
  B["amdgpu_dma_buf_attach() · peer2peer = true"]
  C["KFD kfd_mem_attach_dmabuf() · 映射对端 VRAM BO"]
  D["SDMA / GPU kernel 直接访问 peer BAR"]
  Q --&gt; X --&gt; A --&gt; B --&gt; C --&gt; D
</code></pre>
<p dir="auto">图 2：一个返回值的因果链。若距离为负，<code>peer2peer</code> 会被置为 false，dma-buf 退回系统内存，跨卡拷贝重新变成主机中转。</p>
<p dir="auto">对应的内核位置是 <code>amdgpu_device.c</code> 的 <code>amdgpu_device_is_peer_accessible()</code> 与 <code>amdgpu_dma_buf.c</code> 的 <code>amdgpu_dma_buf_attach()</code>；KFD 侧则是 <code>amdgpu_amdkfd_gpuvm.c</code> 里的 <code>kfd_mem_attach_dmabuf()</code>。三处共用同一个距离判断，所以劫持一次，三处同时放行。</p>
<h3>3.4 生效证据</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>检查项</th>
<th>改造前</th>
<th>改造后</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>hipDeviceCanAccessPeer</code></td>
<td>0 / 0</td>
<td><strong>1 / 1</strong></td>
</tr>
<tr>
<td>dmesg 芯片组告警</td>
<td>每次都打印 <code>not supported by the chipset</code></td>
<td>消失</td>
</tr>
<tr>
<td>KFD p2p link</td>
<td><code>p2p_links_count = 0</code></td>
<td><code>count = 1</code>，<code>type 2</code>（PCIe），<code>weight 40</code>，<code>max_bandwidth 8000</code>，<code>flags 3</code></td>
</tr>
<tr>
<td>DMA / AER 错误</td>
<td>—</td>
<td>压测全程 0 条 DMAR / 0 条新增 AER</td>
</tr>
<tr>
<td>逐字节校验</td>
<td>—</td>
<td>双向 64 M float <code>bad = 0</code>；30 轮 256 MB soak 全对</td>
</tr>
</tbody>
</table>
<h3>3.5 第二道门：RCCL 的 PHB 策略</h3>
<p dir="auto">内核把 P2P 打开后，<code>hipMemcpy</code> 和自写 kernel 已经能直连，但 RCCL 仍然打印 <code>via SHM/direct/direct</code>。原因不是硬件，而是 RCCL 自己的一条平台规则。</p>
<p dir="auto">在 RCCL 2.27.7 的 <code>ncclTopoCheckP2p()</code> 里，对 Intel x86 平台会把 <code>p2pLevel</code> 强制设为 <code>PATH_PXB</code>。本机两块卡分属两个根端口，拓扑算出来的路径类型是 <strong>PHB(8)</strong>，比 PXB(5) 更「远」，于是判定不可直连。这也解释了为什么手动设置 <code>NCCL_P2P_LEVEL=PHB</code> 或 <code>SYS</code> 都没用——用户值随后被这条 Intel 规则覆盖。</p>
<pre><code class="language-mermaid">flowchart TB
  subgraph BEF["默认拓扑：PHB(8) &gt; PXB(5)"]
    direction TB
    b0["CPU"] --- b1["00:02.0"] --- b2["05:00.0"]
    b0 --- b3["00:03.0"] --- b4["08:00.0"]
  end
  subgraph AFT["覆盖拓扑：同一虚拟桥下 = PXB(5)"]
    direction TB
    a0["虚拟桥 0000:00:01.0"] --- a1["03:00.0 → 05:00.0"]
    a0 --- a2["06:00.0 → 08:00.0"]
  end
  BEF --&gt;|"p2p = 0"| S1["via SHM/direct/direct"]
  AFT --&gt;|"p2p = 1"| S2["via P2P/IPC"]
</code></pre>
<p dir="auto">图 3：覆盖拓扑只改变 RCCL 的路径分类，不改任何硬件或系统设置。两块卡的上行交换芯片被挂到同一个虚拟 PCI 桥下，路径就从 PHB 变成 PXB。</p>
<p dir="auto">具体做法是给 RCCL 一份 <code>NCCL_TOPO_FILE</code>。核心是在 <code>&lt;cpu&gt;</code> 下插入一个虚拟桥，再把两张卡（连同它们的交换芯片）挂进去：</p>
<pre><code class="language-xml">&lt;system version="2"&gt;
  &lt;cpu host_hash="0xab3b8373478cf719" numaid="0" affinity="3fffff"
       arch="x86_64" vendor="GenuineIntel" familyid="6" modelid="79"&gt;
    &lt;pci busid="0000:00:01.0" class="0x060400" vendor="0x8086" device="0x6f02"
         subsystem_vendor="0x0000" subsystem_device="0x0000"
         link_speed="8.0 GT/s PCIe" link_width="8"&gt;

      &lt;pci busid="0000:03:00.0" class="0x060400" vendor="0x1002" device="0x1478"
           link_speed="8.0 GT/s PCIe" link_width="8"&gt;
        &lt;pci busid="0000:05:00.0" class="0x030000" vendor="0x1002" device="0x744c"
             link_speed="16.0 GT/s PCIe" link_width="16"&gt;
          &lt;gpu dev="0" sm="48" gcn="gfx1100" arch="38911" rank="0" gdr="0"/&gt;
        &lt;/pci&gt;
      &lt;/pci&gt;

      &lt;pci busid="0000:06:00.0" class="0x060400" vendor="0x1002" device="0x1478"
           link_speed="8.0 GT/s PCIe" link_width="8"&gt;
        &lt;pci busid="0000:08:00.0" class="0x030000" vendor="0x1002" device="0x744c"
             link_speed="16.0 GT/s PCIe" link_width="16"&gt;
          &lt;gpu dev="1" sm="48" gcn="gfx1100" arch="38911" rank="1" gdr="0"/&gt;
        &lt;/pci&gt;
      &lt;/pci&gt;
    &lt;/pci&gt;

    &lt;!-- 原网卡节点保留 --&gt;
    &lt;pci busid="0000:0b:00.0" class="0x020000" vendor="0x10ec" device="0x8168"&gt;
      &lt;nic&gt;
        &lt;net name="enp11s0" dev="0" latency="0" speed="1000" port="0"
             guid="0x0" maxconn="65536" gdr="0"/&gt;
      &lt;/nic&gt;
    &lt;/pci&gt;
    &lt;nic&gt;
      &lt;net name="tailscale0" dev="1" latency="0" speed="10000" port="0"
           guid="0x1" maxconn="65536" gdr="0"/&gt;
    &lt;/nic&gt;
  &lt;/cpu&gt;
&lt;/system&gt;
</code></pre>
<p dir="auto">启动时必须满足另一个条件：<strong>一进程一卡</strong>。</p>
<pre><code class="language-bash">NCCL_TOPO_FILE=/home/jay/work/p2pforce/topo-pxb.xml \
  torchrun --nproc_per_node=2 torch_allreduce.py
</code></pre>
<blockquote>
<p dir="auto"><strong>为什么必须多进程</strong></p>
<p dir="auto">单进程 <code>ncclCommInitAll</code> 同时初始化两张卡时，跨卡 IPC 导入会报 <code>invalid device context (201)</code>，这是 HIP 同进程跨卡的限制。拆成两个进程后，<code>hipIpcOpenMemHandle</code> 正常，RCCL 才会真正建立 <code>via P2P/IPC</code> 通道。</p>
</blockquote>
<blockquote>
<p dir="auto"><strong>两道门，互相独立</strong></p>
<p dir="auto">内核 P2P 与 RCCL 拓扑是<strong>两个独立开关</strong>：内核开了但拓扑不覆盖，RCCL 仍走 SHM；拓扑覆盖了但内核没开，RCCL 会尝试 P2P 然后失败。回滚时也可以分别回退，互不影响。</p>
</blockquote>
<hr />
<h2>4. 翻车全记录：十八次踩坑</h2>
<p dir="auto">这一章按阶段列出所有失败。每一条都写清楚「现场长什么样、根因是什么、最后怎么处理」，因为绝大多数坑会在别人复现时原样重演。</p>
<h3>4.1 内核路线：三次失败，直接改变方案</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>翻车</th>
<th>现场</th>
<th>根因与处理</th>
</tr>
</thead>
<tbody>
<tr>
<td>自编 <code>6.8.12-p2p44</code> 进不了系统</td>
<td>模块 1.9 GB，initrd 455 MB，<code>amdgpu.ko</code> 108 MB；GRUB 后卡在 <code>Loading initial ramdisk</code>，从未进系统</td>
<td>编译带 DEBUG_INFO，体积失控；救援模式卸载，放弃该内核</td>
</tr>
<tr>
<td><code>7.0.0-38-p2p44</code> 只装了一半</td>
<td>只安装了 initrd 与 System.map，<code>vmlinuz</code> 缺失；GRUB 项还带 <code>nomodeset</code>（会让 amdgpu probe 返回 -22）</td>
<td>条目指向不存在的内核文件，属于不可用项；清理 GRUB 与残留文件</td>
</tr>
<tr>
<td>高 MMIO 窗口搬移补丁</td>
<td>在 <code>arch/x86/pci/acpi.c</code> 里搬移高位 MMIO 窗口，能让 44 位掩码下访问 61 TiB BAR</td>
<td>不在参考项目内，风险高且无验证手段；从源码撤回</td>
</tr>
</tbody>
</table>
<h3>4.2 用户态环境：ABI、依赖、路径</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>翻车</th>
<th>现场</th>
<th>根因与处理</th>
</tr>
</thead>
<tbody>
<tr>
<td>pip 下载 34 kB/s</td>
<td><code>pip install</code> 卡在几十 kB/s，几小时下不完 rocm 轮子</td>
<td>官方源慢；切清华镜像后 25 MB/s</td>
</tr>
<tr>
<td>torch 与 sgl_kernel ABI 不匹配</td>
<td>torch 2.9.1+rocm6.4 加载预编 <code>sgl_kernel</code> 报 <code>undefined symbol c10::hip::getCurrentHIPStream</code></td>
<td>版本错配；改装 torch 2.13.0+rocm7.2，并用 <code>setup_rocm.py install</code> 重编 sgl_kernel</td>
</tr>
<tr>
<td>旧 <code>.so</code> 遮蔽新编模块</td>
<td>重编后仍报同一个符号错误</td>
<td><code>site-packages/sgl_kernel</code> 里残留旧文件；整目录删除后重装</td>
</tr>
<tr>
<td>sglang 两次 exit 137</td>
<td>第一次缺 <code>tvm_ffi</code>；第二次缺 <code>ninja</code> 可执行文件</td>
<td>补 <code>apache-tvm-ffi</code>；把 venv 的 <code>bin</code> 加入 PATH</td>
</tr>
<tr>
<td>后台启动的 sglang 被连带杀死</td>
<td><code>nohup</code> 启动后 shell 一结束，进程消失，日志文件不存在</td>
<td>改用后台任务直接跑前台命令并重定向日志</td>
</tr>
</tbody>
</table>
<h3>4.3 系统操作：显存、模块与权限</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>翻车</th>
<th>现场</th>
<th>根因与处理</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>p2p_bw</code> 直接 OOM</td>
<td><code>strata.service</code> 正占着每卡 25.6 GB 显存，测试程序分配失败</td>
<td>测试前 <code>systemctl stop strata.service</code>，测完再 start</td>
</tr>
<tr>
<td><code>modprobe -r amdgpu</code> 失败</td>
<td><code>Module amdgpu is in use</code>，无法热重载</td>
<td>改为重启让 amdgpu 重新 probe</td>
</tr>
<tr>
<td>检查脚本要求 sudo 密码</td>
<td><code>sudo -n</code> 全部失败，脚本中断</td>
<td>polkit 允许直接 <code>systemctl start/stop</code>，不需要 sudo</td>
</tr>
<tr>
<td><code>pkill -f "sglang.launch_server"</code> 杀掉了自己的 shell</td>
<td>命令本身的命令行也包含该模式</td>
<td><code>pkill -f</code> 会匹配到执行它的 shell；改用 <code>pgrep</code> 定向取 PID 再停</td>
</tr>
</tbody>
</table>
<h3>4.4 测试方法：最危险的一类坑</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>翻车</th>
<th>现场</th>
<th>根因与处理</th>
</tr>
</thead>
<tbody>
<tr>
<td>soak 首轮 <code>bad = 469152</code></td>
<td>跨设备填充与拷贝之间没有同步，校验读到半成品</td>
<td>每次跨设备操作后加 <code>hipDeviceSynchronize()</code></td>
</tr>
<tr>
<td>allreduce 正确性误判</td>
<td>原地累加 24 次后值变成 2²⁴，<code>bad</code> 恰好等于元素总数</td>
<td>另起一个 <code>ones</code> 缓冲做单次归约校验，<code>bad = 0</code></td>
</tr>
<tr>
<td>单进程跨卡 IPC 报 201</td>
<td><code>hipIpcOpenMemHandle</code> 在同一进程、两张卡之间失败</td>
<td>确认是 HIP 同进程跨卡限制；改用两进程验证成功</td>
</tr>
<tr>
<td><code>NCCL_P2P_LEVEL=PHB/SYS</code> 无效</td>
<td>设置了环境变量，RCCL 仍打印 <code>via SHM</code></td>
<td>读 RCCL 源码发现 Intel x86 强制 PXB；改用 <code>NCCL_TOPO_FILE</code></td>
</tr>
<tr>
<td>误判「SDMA 仍走主机中转」</td>
<td>同步 hipMemcpy 只有 2.5–2.9 GB/s，据此写下错误结论</td>
<td><code>AMD_LOG_LEVEL=4</code> 证明是单条 HSA Copy 直达 peer BAR；慢在同步 API 与方向不对称，结论已改写</td>
</tr>
<tr>
<td>源码拉取多次空返回</td>
<td>WebFetch 取 RCCL 文件两次空返回 / 路径错误；ROCR 源码多次取不到</td>
<td>先取到 develop 分支文件；ROCR 改用本地 <code>libhsa-runtime64.so</code> 字符串与运行时日志实证</td>
</tr>
</tbody>
</table>
<h3>4.5 三次最贵的翻车，以及它们换来的规则</h3>
<blockquote>
<p dir="auto"><strong>教训一 · 自编内核的成本被严重低估</strong></p>
<p dir="auto">失败的代价不是「多编译一次」，而是<strong>机器可能进不去系统</strong>。455 MB initrd 卡在加载阶段时，没有任何用户态工具可用，只能靠救援模式。相比之下，符号劫持的失败模式温和得多：最坏情况是模块加载失败，系统照常启动。</p>
</blockquote>
<blockquote>
<p dir="auto"><strong>教训二 · 环境变量不等于策略</strong></p>
<p dir="auto"><code>NCCL_P2P_LEVEL</code> 看起来正是为这个场景设计的，但 RCCL 在 Intel 平台上会用一条更晚、优先级更高的规则覆盖它。判断「为什么没生效」时，<strong>读源码比查文档快</strong>，而读源码又比反复试环境变量快。</p>
</blockquote>
<blockquote>
<p dir="auto"><strong>教训三 · 带宽低 ≠ 路径错</strong></p>
<p dir="auto">同步 <code>hipMemcpy</code> 的 2.5 GB/s 和无 P2P 时的 2.7 GB/s 太接近，很容易得出「SDMA 没直连」的结论。真正可靠的证据是运行时日志：<code>AMD_LOG_LEVEL=4</code> 下只有一条 <code>HSA Copy</code>，src/dst 都是设备虚拟地址，且 <code>forceSDMA=1</code>。<strong>先用日志证明路径，再用带宽衡量效率。</strong></p>
</blockquote>
<hr />
<h2>5. 成功关键参数清单：可以照抄的部分</h2>
<p dir="auto">这一章是给复现者用的。下面每一行都是本机实际生效的值；换机器时需要重新核对的是 <strong>BusID、BAR 基址、根端口能力和拓扑路径</strong>，其余大多可以沿用。</p>
<blockquote>
<p dir="auto"><strong>抄作业前先确认三件事</strong></p>
<p dir="auto">① 两张卡都不在 PCH 下，且根端口 AtomicOp 能力为 <code>+</code>；② IOMMU 是 <strong>Translated</strong> 而不是 passthrough；③ 显存 BAR 虽然高于 DMA 掩码，但能被 IOMMU 正常翻译。三条都满足，才值得继续。</p>
</blockquote>
<h3>5.1 一张总表</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>层</th>
<th>参数 / 对象</th>
<th>本机值</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>平台</td>
<td>Host Bridge</td>
<td><code>8086:6f00</code></td>
<td>不在 p2pdma 白名单，是全部问题的起点</td>
</tr>
<tr>
<td>平台</td>
<td>上游 PCIe</td>
<td>8 GT/s ×8（Gen3 ×8）</td>
<td>单侧理论 7.88 GB/s，决定性能上限</td>
</tr>
<tr>
<td>平台</td>
<td>显存 BAR 基址</td>
<td><code>0x3820_0000_0000</code> / <code>0x3830_0000_0000</code></td>
<td>≈61.6 TiB；对端 BAR 直填的目标地址</td>
</tr>
<tr>
<td>平台</td>
<td>IOMMU / DMA 掩码</td>
<td>VT-d Translated / 44 bit</td>
<td>高位 BAR 可被翻译，是直连成立的前提</td>
</tr>
<tr>
<td>内核模块</td>
<td>劫持符号</td>
<td><code>pci_p2pdma_distance_many</code></td>
<td>amdgpu 判断对端可达性的唯一入口</td>
</tr>
<tr>
<td>内核模块</td>
<td>返回值</td>
<td><code>1</code>（非负）</td>
<td>等效「同一 P2P 域」</td>
</tr>
<tr>
<td>内核模块</td>
<td>模块名 / 参数</td>
<td><code>p2p_force</code> / <code>maxactive = 32</code></td>
<td>无模块参数，纯符号劫持</td>
</tr>
<tr>
<td>内核模块</td>
<td>加载顺序</td>
<td><code>softdep amdgpu pre: p2p_force</code></td>
<td>必须在 amdgpu probe 之前就位</td>
</tr>
<tr>
<td>KFD</td>
<td><code>p2p_links_count</code></td>
<td><code>1</code></td>
<td>真实 p2p link 出现的标志</td>
</tr>
<tr>
<td>KFD</td>
<td><code>type / weight / flags</code></td>
<td><code>2</code>（PCIe）/ <code>40</code> / <code>3</code></td>
<td>KFD 拓扑对链路的分类</td>
</tr>
<tr>
<td>KFD</td>
<td><code>max_bandwidth</code></td>
<td><code>8000 MB/s</code></td>
<td>KFD 上报的链路能力</td>
</tr>
<tr>
<td>RCCL</td>
<td>版本 / 进程模型</td>
<td>2.27.7 / 一进程一卡</td>
<td>单进程跨卡 IPC 会报 201</td>
</tr>
<tr>
<td>RCCL</td>
<td>拓扑覆盖</td>
<td><code>NCCL_TOPO_FILE=topo-pxb.xml</code></td>
<td>把路径从 PHB 改成 PXB</td>
</tr>
<tr>
<td>RCCL</td>
<td>虚拟桥</td>
<td><code>0000:00:01.0</code>，class <code>0x060400</code>，vendor <code>0x8086</code>，device <code>0x6f02</code></td>
<td>让两块卡落在同一个 PXB 下</td>
</tr>
<tr>
<td>RCCL</td>
<td>GPU 节点</td>
<td><code>dev=0/rank=0</code>、<code>dev=1/rank=1</code></td>
<td>与 CUDA/HIP 设备序一致</td>
</tr>
<tr>
<td>运行时</td>
<td>测试前状态</td>
<td><code>strata.service</code> 停止</td>
<td>避免 25.6 GB/卡 的显存占用导致 OOM</td>
</tr>
<tr>
<td>运行时</td>
<td>sglang 关键参数</td>
<td><code>--tp-size 2</code>、<code>--disable-custom-all-reduce</code>、<code>--attention-backend triton</code>、<code>--mem-fraction-static 0.85</code></td>
<td>本机缺 <code>amdsmi</code>，CustomAllreduce 关闭，走 RCCL</td>
</tr>
<tr>
<td>运行时</td>
<td>不需要的变量</td>
<td><code>HIP_HOSTCALL_ALLOW_MISSING</code>、<code>NCCL_P2P_LEVEL</code></td>
<td>一个 stock CLR 不认，一个会被 Intel 规则覆盖</td>
</tr>
</tbody>
</table>
<h3>5.2 判定「真的成功」的阈值</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>判据</th>
<th>成功值</th>
<th>失败/未生效值</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>hipDeviceCanAccessPeer</code></td>
<td><strong>1 / 1</strong></td>
<td><strong>0 / 0</strong></td>
</tr>
<tr>
<td>dmesg</td>
<td>无 <code>not supported by the chipset</code></td>
<td>每次 probe 都打印</td>
</tr>
<tr>
<td>KFD <code>p2p_links_count</code></td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>RCCL 日志</td>
<td><code>isAllDirectP2p 1</code> / <code>via P2P/IPC</code></td>
<td><code>via SHM/direct/direct</code></td>
</tr>
<tr>
<td>单向带宽（Gen3 ×8）</td>
<td><strong>≥5 GB/s</strong></td>
<td>2.5–2.8 GB/s（主机中转）</td>
</tr>
<tr>
<td>参考平台（Gen5 ×8）</td>
<td>27–29 GB/s</td>
<td>—</td>
</tr>
</tbody>
</table>
<p dir="auto">注意：带宽阈值随平台变化。Gen3 ×8 上 5.2 GB/s 已经是 66% 链路效率，不要拿 Gen5 的 27 GB/s 当及格线。</p>
<h3>5.3 最小可用配置</h3>
<p dir="auto">如果只想复现「能用」的状态，实际需要落地的文件只有三个，外加一条启动命令：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>文件</th>
<th>路径</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>p2p_force.ko</code></td>
<td><code>/lib/modules/$(uname -r)/extra/p2p_force.ko</code></td>
<td>kretprobe 模块</td>
</tr>
<tr>
<td><code>p2p-force.conf</code></td>
<td><code>/etc/modprobe.d/p2p-force.conf</code></td>
<td><code>softdep amdgpu pre: p2p_force</code></td>
</tr>
<tr>
<td><code>topo-pxb.xml</code></td>
<td>任意可读路径，运行时用 <code>NCCL_TOPO_FILE</code> 指定</td>
<td>RCCL 路径分类覆盖</td>
</tr>
</tbody>
</table>
<hr />
<h2>6. 性能实测：5.23 GB/s 的天花板与 66% 的效率</h2>
<p dir="auto">以下数据全部在 P2P 生效、<code>strata.service</code> 停止、显存空闲的状态下测得。单侧 Gen3 ×8 的理论值是 7.88 GB/s，所有数字都应该放在这个上限下读。</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>路径</th>
<th>数据量</th>
<th>带宽</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>远端写（GPU0 kernel 写 GPU1）</td>
<td>1 GB ×10</td>
<td><strong>5.23 GB/s</strong></td>
<td>单向最强，约 66% 链路效率</td>
</tr>
<tr>
<td>远端读（GPU0 kernel 读 GPU1）</td>
<td>1 GB ×10</td>
<td>3.25 GB/s</td>
<td>读方向受 completion 回程影响</td>
</tr>
<tr>
<td>gfx push（读远端 + 写本地）</td>
<td>512 MB ×20</td>
<td>2.76 GB/s</td>
<td>双向合计约 5.5 GB/s</td>
</tr>
<tr>
<td>gfx pull（GPU1 读 GPU0 + 写本地）</td>
<td>512 MB ×20</td>
<td>2.76 GB/s</td>
<td>与 push 对称</td>
</tr>
<tr>
<td>双向 <code>hipMemcpyAsync</code></td>
<td>512 MB ×10</td>
<td><strong>3.33 GB/s / 方向</strong></td>
<td>合计 <strong>6.66 GB/s</strong>，本次最高聚合</td>
</tr>
<tr>
<td>SDMA <code>hipMemcpy</code>（同步）</td>
<td>1 GB ×10</td>
<td>2.53–2.94 GB/s</td>
<td>同步 API 路径偏慢</td>
</tr>
<tr>
<td>8 流并发 SDMA 1→0</td>
<td>1 GB ×10</td>
<td>1.47 GB/s</td>
<td>8 条流争用 2 个 SDMA 引擎</td>
</tr>
</tbody>
</table>
<h3>6.1 SDMA 的取证：快慢是两回事</h3>
<p dir="auto">同步 <code>hipMemcpy</code> 的数字很容易让人以为 SDMA 没有直连。实际用 <code>AMD_LOG_LEVEL=4</code> 抓运行时日志，每次跨卡拷贝只有<strong>一条</strong> HSA Copy，src/dst 都是设备虚拟地址，并带 <code>forceSDMA=1</code>：</p>
<pre><code class="language-text">:4:rocblit.cpp:549 HSA Copy copy_engine=0x1, dst=0x763102000000, src=0x7630dde00000,
   size=536870912, forceSDMA=1, engineType=4, ...
</code></pre>
<p dir="auto">如果是主机中转，日志里应该出现 D2H + H2D 两条拷贝，或出现主机地址。实际没有。真正拉开差距的是 API 与方向：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>调用方式</th>
<th>方向</th>
<th>带宽</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>hipMemcpyAsync</code>（单流）</td>
<td>GPU0 → GPU1（引擎在 GPU1，读 GPU0）</td>
<td><strong>5.23 GB/s</strong></td>
</tr>
<tr>
<td><code>hipMemcpyAsync</code>（单流）</td>
<td>GPU1 → GPU0（引擎在 GPU0，读 GPU1）</td>
<td>3.39–3.58 GB/s</td>
</tr>
<tr>
<td><code>hipMemcpy</code>（同步）</td>
<td>同上两个方向</td>
<td>1.45–2.37 GB/s</td>
</tr>
<tr>
<td>torch <code>dst.copy_(src)</code>（异步）</td>
<td>两个方向</td>
<td><strong>5.14–5.15 GB/s</strong></td>
</tr>
</tbody>
</table>
<p dir="auto">方向不对称和内核日志里的 AER 分布一一对应：只有通往 GPU1 的路径（<code>00:03.0 → 06:00.0 → 08:00</code>）有 315 / 384 条可纠正 <code>[12] Timeout</code>，GPU0 那条路径为 0。写是 posted，不等完成，两个方向都接近 5.2 GB/s；读需要 completion 回程，会被链路重传拖慢。带 <code>pci=noaer</code> 启动只是关闭上报，不是修复。</p>
<h3>6.2 RCCL：从 4.0 到 5.1</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>传输</th>
<th>Channel</th>
<th>64 MB</th>
<th>256 MB</th>
<th>正确性</th>
</tr>
</thead>
<tbody>
<tr>
<td>默认（PHB）</td>
<td><code>via SHM/direct/direct</code></td>
<td>3.95 GB/s</td>
<td>4.08–4.27 GB/s</td>
<td><code>bad = 0</code></td>
</tr>
<tr>
<td><code>NCCL_TOPO_FILE</code></td>
<td><code>via P2P/IPC</code></td>
<td><strong>5.09 GB/s</strong></td>
<td><strong>5.07–5.11 GB/s</strong></td>
<td><code>bad = 0</code></td>
</tr>
</tbody>
</table>
<p dir="auto">相对无 P2P 基线，RCCL busbw 提升约 <strong>27%</strong>；双向拷贝聚合从约 3.0 GB/s 提升到 6.66 GB/s，约 2.2 倍。这是链路层能看到的最大收益。</p>
<h3>6.3 TP2 端到端：为什么只快了 1–3%</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>指标</th>
<th>TP2 / SHM</th>
<th>TP2 / P2P</th>
<th>变化</th>
</tr>
</thead>
<tbody>
<tr>
<td>单流解码</td>
<td>18.54 tok/s</td>
<td>18.71 tok/s</td>
<td>+0.9%</td>
</tr>
<tr>
<td>并发 1 / 2 / 4</td>
<td>16.78 / 31.66 / 53.04</td>
<td>16.94 / 31.88 / 53.69</td>
<td>+0.7–1.2%</td>
</tr>
<tr>
<td>并发 8</td>
<td>100.77 tok/s</td>
<td><strong>103.45 tok/s</strong></td>
<td>+2.7%</td>
</tr>
<tr>
<td>并发 8 的 p50 延迟</td>
<td>5.074 s</td>
<td>4.942 s</td>
<td>−2.6%</td>
</tr>
<tr>
<td>2.5K / 4.9K 预填</td>
<td>408.9 / 703.4</td>
<td>375.5 / 724.4</td>
<td>噪声范围</td>
</tr>
<tr>
<td>KV 池</td>
<td>198410</td>
<td>198410</td>
<td>不变</td>
</tr>
</tbody>
</table>
<blockquote>
<p dir="auto"><strong>怎么读这个结果</strong></p>
<p dir="auto">每个 token 约 54 ms（18.7 tok/s），其中两次 10 KB 量级的 allreduce 合计约 4 ms，通信只占 <strong>约 7%</strong>。带宽从 4.0 提到 5.1 GB/s，端到端自然只涨 1–3%。要让 P2P 产生明显收益，需要通信占比更高的场景：大 batch allreduce、EP/MoE 的 all-to-all、多卡 cudagraph 融合，而不是单机小模型 TP2 解码。</p>
</blockquote>
<hr />
<h2>7. 限制与适用边界</h2>
<p dir="auto">能用不等于可以无脑用。下面每条都是本机实际观察到的边界，跨平台复现时优先级不同，但都值得先想清楚。</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>边界</th>
<th>表现</th>
<th>建议</th>
</tr>
</thead>
<tbody>
<tr>
<td>PCIe 代数</td>
<td>上游交换芯片只有 Gen3 ×8，单侧 7.88 GB/s；实测写 5.23 GB/s（66%），读 3.25 GB/s</td>
<td>不要用 Gen5 的 27–29 GB/s 当预期；先看 <code>current_link_speed / width</code></td>
</tr>
<tr>
<td>方向不对称</td>
<td>GPU0 读 GPU1 明显慢于反向，与 315 / 384 条可纠正 AER 的分布一致</td>
<td>读密集负载优先考虑反向放置；持续监控 <code>dmesg</code> 的 AER 计数</td>
</tr>
<tr>
<td>高位 BAR + IOMMU</td>
<td>BAR 在 61.6 TiB，高于 44 位 DMA 掩码；本机靠 VT-d Translated 工作</td>
<td>不要关 IOMMU、不要切 passthrough；换平台需重新验证翻译路径</td>
</tr>
<tr>
<td>kretprobe 的脆弱性</td>
<td>依赖内核符号名与调用约定；内核升级后可能失效</td>
<td>升级内核后重跑验证清单；不要把 <code>.ko</code> 跨版本复制</td>
</tr>
<tr>
<td>RCCL 拓扑覆盖</td>
<td>BusID、class、vendor/device 都是本机值；换机器必须重写</td>
<td>只改路径分类，不改硬件；每台机器维护自己的 XML</td>
</tr>
<tr>
<td>单进程限制</td>
<td>同进程跨卡 IPC 报 201</td>
<td>RCCL 必须一进程一卡；训练/推理框架要支持多进程启动</td>
</tr>
<tr>
<td>收益场景</td>
<td>TP2 解码通信占比 ~7%，端到端只有 1–3% 提升</td>
<td>优先用于通信密集场景；不要为了跑分而开 P2P</td>
</tr>
<tr>
<td>官方支持</td>
<td>绕过白名单属于未支持配置，ROCm 升级可能改变行为</td>
<td>把它当实验特性；生产环境保留回滚路径</td>
</tr>
</tbody>
</table>
<hr />
<h2>8. 复现步骤与回滚</h2>
<p dir="auto">下面的顺序按「先备份、再启用、后验证」排列。不要跳过备份：唯一持久改动虽然只有一个 conf 文件，但模块加载顺序出错时，最省事的恢复手段仍然是重启。</p>
<h3>8.1 备份与基线记录</h3>
<pre><code class="language-bash"># 备份原版 amdgpu 模块与模块列表
mkdir -p ~/p2p-rollback-$(date +%Y%m%d)/module
cp /lib/modules/$(uname -r)/kernel/drivers/gpu/drm/amd/amdgpu/amdgpu.ko.zst \
   ~/p2p-rollback-$(date +%Y%m%d)/module/
lsmod &gt; ~/p2p-rollback-$(date +%Y%m%d)/lsmod.before
sha256sum ~/p2p-rollback-$(date +%Y%m%d)/module/amdgpu.ko.zst

# 记录基线：应当看到 0 / 0 与芯片组告警
dmesg | grep -i "p2p\|atom" | tail -20
</code></pre>
<h3>8.2 启用内核侧 P2P</h3>
<ol>
<li>确认内核 headers 与当前内核一致，编译 <code>p2p_force.ko</code>。</li>
<li>复制到 <code>/lib/modules/$(uname -r)/extra/</code> 并执行 <code>depmod -a</code>。</li>
<li>写入 <code>/etc/modprobe.d/p2p-force.conf</code>，内容为 <code>softdep amdgpu pre: p2p_force</code>。</li>
<li>重启。amdgpu 已在用，<code>modprobe -r</code> 不会成功；重启是唯一干净路径。</li>
<li>开机后检查 <code>lsmod | grep p2p_force</code>、<code>dmesg | grep p2p_force</code>，再做 <code>canAccessPeer</code> 验证。</li>
</ol>
<pre><code class="language-bash">make -C /lib/modules/$(uname -r)/build M=$PWD modules
sudo cp p2p_force.ko /lib/modules/$(uname -r)/extra/
sudo depmod -a
echo 'softdep amdgpu pre: p2p_force' | sudo tee /etc/modprobe.d/p2p-force.conf
sudo reboot
</code></pre>
<h3>8.3 启用 RCCL 侧 P2P</h3>
<pre><code class="language-bash"># 多进程，一进程一卡
NCCL_TOPO_FILE=/home/jay/work/p2pforce/topo-pxb.xml \
  torchrun --nproc_per_node=2 torch_allreduce.py

# 期望日志
#   isAllDirectP2p 1
#   via P2P/IPC
</code></pre>
<h3>8.4 验收清单</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>检查</th>
<th>命令 / 位置</th>
<th>期望</th>
</tr>
</thead>
<tbody>
<tr>
<td>模块已加载</td>
<td><code>lsmod \| grep p2p_force</code></td>
<td>有输出，且 amdgpu 在其后</td>
</tr>
<tr>
<td>hook 生效</td>
<td><code>dmesg \| grep p2p_force</code></td>
<td><code>hooked, force return 1</code></td>
</tr>
<tr>
<td>P2P 可达</td>
<td><code>hipDeviceCanAccessPeer</code></td>
<td><code>1 / 1</code></td>
</tr>
<tr>
<td>KFD 链路</td>
<td><code>/sys/class/kfd/kfd/topology/nodes/*/p2p_links</code></td>
<td><code>count = 1</code>，<code>8000 MB/s</code></td>
</tr>
<tr>
<td>RCCL 路径</td>
<td>启动日志</td>
<td><code>isAllDirectP2p 1</code>、<code>via P2P/IPC</code></td>
</tr>
<tr>
<td>正确性</td>
<td>逐字节校验 / allreduce</td>
<td><code>bad = 0</code></td>
</tr>
<tr>
<td>稳定性</td>
<td><code>dmesg</code>、AER 计数</td>
<td>无新增 DMAR 错误；AER 不增长</td>
</tr>
</tbody>
</table>
<h3>8.5 回滚</h3>
<pre><code class="language-bash"># 彻底恢复：删除模块与 conf，重建依赖，重启
sudo rm -f /etc/modprobe.d/p2p-force.conf \
           /lib/modules/$(uname -r)/extra/p2p_force.ko
sudo depmod -a
sudo reboot
</code></pre>
<blockquote>
<p dir="auto"><strong>回滚注意</strong></p>
<p dir="auto">只执行 <code>rmmod p2p_force</code> <strong>不会</strong>让已经 probe 的 amdgpu 退回无 P2P 状态，因为距离判断在 probe 阶段已经完成。要彻底恢复必须重启。RCCL 侧不需要动系统：不设置 <code>NCCL_TOPO_FILE</code> 就恢复默认 SHM 传输。</p>
</blockquote>
<hr />
<h2>9. 附录：文件清单与术语</h2>
<h3>9.1 本机文件清单</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>文件</th>
<th>路径</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>p2p_force.c</code></td>
<td><code>/home/jay/work/p2pforce/p2p_force.c</code></td>
<td>kretprobe 模块源码</td>
</tr>
<tr>
<td><code>p2p_force.ko</code></td>
<td><code>/lib/modules/7.0.0-38-generic/extra/</code></td>
<td>已安装模块</td>
</tr>
<tr>
<td><code>topo-pxb.xml</code></td>
<td><code>/home/jay/work/p2pforce/topo-pxb.xml</code></td>
<td>RCCL 拓扑覆盖</td>
</tr>
<tr>
<td><code>p2p_bw.cpp</code> / <code>p2p_bw2.cpp</code></td>
<td><code>/home/jay/work/p2ptest/</code></td>
<td>push / pull / sdma / bidir、远端读写与多流测试</td>
</tr>
<tr>
<td><code>p2p_verify.cpp</code></td>
<td><code>/home/jay/work/p2ptest/</code></td>
<td>逐字节校验与 soak</td>
</tr>
<tr>
<td><code>torch_allreduce.py</code></td>
<td><code>/home/jay/work/p2ptest/</code></td>
<td>torchrun 双进程 RCCL 测试</td>
</tr>
<tr>
<td><code>RESULTS.md</code></td>
<td><code>/home/jay/work/p2ptest/</code></td>
<td>十一节原始测试记录</td>
</tr>
<tr>
<td><code>README.txt</code></td>
<td><code>/home/jay/p2p-rollback-20261026/module/</code></td>
<td>原版模块备份与回滚说明</td>
</tr>
</tbody>
</table>
<h3>9.2 术语</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>术语</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>P2P</td>
<td>Peer-to-Peer，一张 GPU 直接读写另一张 GPU 的显存，不经过主机内存</td>
</tr>
<tr>
<td>p2pdma</td>
<td>内核的 P2P DMA 框架；<code>pci_p2pdma_distance_many()</code> 判断两个设备能否直连</td>
</tr>
<tr>
<td>kretprobe</td>
<td>内核函数返回探针，可在函数返回时修改寄存器返回值</td>
</tr>
<tr>
<td>PHB / PXB</td>
<td>RCCL 的拓扑路径类型：PHB 表示跨根端口经主机桥，PXB 表示同一 PCIe 交换域</td>
</tr>
<tr>
<td>SDMA</td>
<td>AMD GPU 的复制引擎，可不占用计算单元搬数据</td>
</tr>
<tr>
<td>AER</td>
<td>PCIe 高级错误报告；可纠正 Timeout 会拖慢需要 completion 的读操作</td>
</tr>
</tbody>
</table>
<hr />
<h2>来源与参考</h2>
<ol>
<li><a href="http://lcz.me" rel="nofollow ugc">lcz.me</a>，《TP2 的坑（PCH 平台必看）》——PCH AtomicOp Completer、hidden hostcall 与 401 故障链。<br />
<a href="https://lcz.me/topic/2525">https://lcz.me/topic/2525</a></li>
<li><a href="http://lcz.me" rel="nofollow ugc">lcz.me</a>，双 R9700 / Intel Arrow Lake-S 的 P2P 实现记录——<code>amdgpu_p2p_force</code>、p2pdma 白名单与 Gen5 实测带宽。<br />
<a href="https://lcz.me/topic/2599">https://lcz.me/topic/2599</a></li>
<li>本机测试记录 <code>RESULTS.md</code>（第一至十一节），包含全部原始命令、日志与逐轮数据。<br />
<code>/home/jay/work/p2ptest/RESULTS.md</code></li>
<li>Linux 7.0 amdgpu 驱动源码：<code>amdgpu_device.c</code>、<code>amdgpu_dma_buf.c</code>、<code>amdgpu_amdkfd_gpuvm.c</code>（本地只读分析）。<br />
<code>/home/jay/work/ksrc7/linux-hwe-7.0-7.0.0/drivers/gpu/drm/amd/amdgpu/</code></li>
</ol>
<hr />
<p dir="auto"><em>X99 P2P REPRODUCTION · 2026-10-26 · 数据来自本机实测，未做外推</em></p>
]]></description><link>https://lcz.me/topic/2607</link><generator>RSS for Node</generator><lastBuildDate>Mon, 26 Oct 2026 18:58:41 GMT</lastBuildDate><atom:link href="https://lcz.me/topic/2607.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 26 Oct 2026 16:04:53 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to [p2p 卍解！]在 X99 TF板上打开2*7900 XTX 的 PCIe P2P卡间通信 on Mon, 26 Oct 2026 16:27:08 GMT]]></title><description><![CDATA[<p dir="auto">大佬让我在这里看到了二十年前的互联网精神</p>
]]></description><link>https://lcz.me/post/28070</link><guid isPermaLink="true">https://lcz.me/post/28070</guid><dc:creator><![CDATA[Yu Nickolas]]></dc:creator><pubDate>Mon, 26 Oct 2026 16:27:08 GMT</pubDate></item></channel></rss>