![]()
引 言
自 2022 年起,NVIDIA 開源了其 Linux GPU 內核模塊代碼。得益于此,我們有機會一窺 GPU 內部的運行邏輯。近期,開源組織 tinygrad 發布了一項支持部分高性能顯卡 GPUDirect PCIe P2P 功能的工作。值得注意的是,在 NVIDIA 的官方驅動中并未支持這些顯卡的 GPUDirect PCIe P2P 功能,但從硬件設計而言,它們完全具備該能力。
我們以此為契機,從本次驅動限制與解鎖的角度,深入了解 GPUDirect PCIe P2P 的實現細節。本文包含以下內容:
本次解鎖 GPUDirect PCIe P2P 功能的思路;
GPUDirect PCIe P2P 的不同種類及其對比;
結合驅動代碼解析 GPUDirect PCIe P2P 的實現邏輯。
GPUDirect P2P 背景
在 GPUDirect P2P 技術出現之前,多 GPU 系統的通信模式普遍依賴 「CPU 中轉」,即數據回彈(Bounce Buffer)機制:當 GPU A 需要向 GPU B 傳輸數據時,需先將數據從 GPU A 的顯存拷貝到系統內存(Host Memory),再由 CPU 調度,將數據從系統內存拷貝到 GPU B 的顯存。這一過程不僅帶寬瓶頸顯著、延遲高,還會大量浪費 CPU 資源。
![]()
GPUDirect P2P 技術主要用于單機 GPU 間的高速通信。簡而言之,它使得 GPU 之間可以通過總線直接訪問目標 GPU 的顯存,從而避免了通過 CPU Host Memory 作為中轉的消耗,大大降低了數據交換的延遲。
![]()
P2P Support Patch:解鎖思路
本次開源社區針對部分 GPU 的 P2P 能力做出了解鎖,主要可以參考以下兩個項目:
https://github.com/tinygrad/open-gpu-kernel-modules/
https://github.com/aikitoria/open-gpu-kernel-modules
3.1. 原廠驅動限制 GPUDirect P2P 的方式
被限制 P2P 能力的 GPU 主要為 PCIe 版 GPU(即不支持 NVLink)。PCIe 支持 GPUDirect P2P 有 Mailbox 與 BAR1 兩種路徑,驅動會針對這兩種路徑分別施加限制。有關 P2P 不同類型的相關內容可以參考本文第 4 部分。
原廠驅動主要通過以下 4 個核心卡點對 GPU 的 P2P 能力做限制:
ReBAR 啟用的限制
BAR1 P2P 的核心前提是 「全顯存靜態映射到 BAR1」,而小 BAR1 空間無法滿足要求。驅動通過 NVreg_EnableResizableBar 內核模塊參數,默認跳過 ReBAR 配置。即使 BIOS 開啟該功能,驅動也不會把 BAR1 空間放大到顯存空間大小。
HAL 函數架構鎖
BAR1 P2P 的核心實現函數(如映射創建 / 刪除、能力檢測等)僅會綁定給專業級架構。而對于一些 Blackwell 架構 GPU 的 HAL 實現,驅動會直接返回不支持。
GMMU 孔徑限制
一部分高性能 GPU 的 GMMU 硬件上限制了 GMMU_APERTURE_PEER 的 aperture 類型。原廠驅動在構建 P2P 頁表時會觸發 MMU 錯誤(例如 cudaErrorMapBufferObjectFailed)。
SKU 鎖
驅動默認 P2P 類型為 Mailbox P2P,而一些高性能 GPU 的 Mailbox 硬件被鎖定。驅動會通過 SKU 標記,直接判定一部分 GPU 不支持 P2P;同時,原廠驅動又不會給這些 GPU 走 BAR1 P2P 的流程,因此直接返回不支持。
3.2. Patch 核心改動
開源社區的一組 Patch 以 BAR1 方式支持 GPU P2P,核心 Commit 如下:
https://github.com/NVIDIA/open-gpu-kernel-modules/commit/9e39420
https://github.com/NVIDIA/open-gpu-kernel-modules/commit/176269d
https://github.com/tinygrad/open-gpu-kernel-modules/issues/29-2765260985
下面結合代碼看一下支持 P2P 的主要思路。
3.2.1. 強制開啟 ReBAR 能力檢測
}Patch 將這段判斷去掉,繼續執行后續的 ReBAR 檢測與配置:在原廠驅動中,只要用戶沒有手動通過內核參數開啟 NVreg_EnableResizableBar=1,驅動會直接跳過 ReBAR 的配置流程。即使 GPU 和 BIOS 已經開啟了 ReBAR,也只會保留默認的 256MB 小 BAR1 空間。注釋掉這段代碼后,驅動則會檢測 GPU 的 ReBAR 能力,將 BAR1 空間重配置為等于顯存總大小,為 BAR1 P2P 提供最核心的硬件前提 —— 全量顯存靜態線性映射到 BAR1 空間。
3.2.2. 綁定 Hopper 的 BAR1 P2P HAL 實現
將 KernelBus 的幾個 BAR1 P2P HAL 函數指針,從默認的通用實現(直接返回 NOT_SUPPORTED / FALSE)替換為 Hopper(GH100)架構實現。
BAR1 P2P 更多依賴于 PCIe 本身的能力。Hopper 架構原生支持了 BAR1 P2P,推測 Blackwell 架構在硬件上和 Hopper 的 BAR1 P2P 邏輯完全兼容,所以可以沿用 Hopper 的檢測、映射創建 / 刪除、DMA 信息獲取等核心操作。
替換的 HAL 實現如下:
![]()
3.2.3. GPU 的 GMMU 頁表構建問題
這里的改動解決了一部分高性能 GPU 不支持 PEER 孔徑的問題,分兩個部分來看:
改動 1:替換 P2P 的 GMMU Aperture 類型
}專業級 GPU 的 GMMU 支持 GMMU_APERTURE_PEER,這個 Aperture 的作用是告訴 GMMU:這個虛擬地址對應的是對端 GPU 的顯存,需要直接發起 PCIe P2P 事務,而不是訪問本地顯存或系統內存。然而,一部分高性能 GPU 的 GMMU 硬件上限制了這個 Aperture。即使驅動設置了該孔徑,GMMU 也會觸發 MMU 頁錯誤,導致 P2P 映射直接失敗。這是原廠驅動在一些 GPU 上無法啟用 P2P 的核心原因之一。
這里把 PEER 孔徑替換為所有 GPU 都支持的 GMMU_APERTURE_SYS_NONCOH。這個孔徑原本用于 GPU 訪問系統內存,而對端 GPU 的 BAR1 空間在 PCIe 總線域中,本身就是一個標準的 64 位系統物理地址。使用這個孔徑完全可以正常訪問,從而繞過對 PEER 孔徑的限制。
改動 2:新增 BAR1 總線地址參數,修復物理地址翻譯
}fabricBaseAddress 是 GMMU 頁表中用于設置 PCIe 總線基地址的關鍵字段。在原廠驅動中,這個字段原本用于 NVLink 的 Fabric 地址。在 BAR1 P2P 場景下,需要將其設置成對端 GPU 的 BAR1 總線基地址。
這里給頁表構建函數新增了 bar1BusAddr 參數。在構建 P2P 頁表時,直接把對端 GPU 的 BAR1 總線基地址賦值給 fabricBaseAddress。當我們用 SYS_NONCOH 孔徑訪問時,GMMU 會自動把虛擬地址翻譯成 fabricBaseAddress + 頁內偏移,也就是對端 GPU 的 BAR1 總線物理地址。這正好完成了 P2P 訪問的地址翻譯,讓 GPU 能正確找到對端顯存的 PCIe 地址。
3.2.4. P2P Map 的入口分支
在 kbusCreateP2PMapping_GP100 和 kbusRemoveP2PMapping_GP100 兩個 P2P 映射的入口函數中,新增了 BAR1 P2P 類型的分支判斷:
}原廠驅動的 P2P 映射入口函數,原本只支持兩種連接類型:_PCIE(對應 Mailbox P2P)和 _NVLINK(對應 NVLink P2P),沒有 _PCIE_BAR1 的分支。所以即使我們前面強制指定了 BAR1 P2P 類型,驅動在調用映射函數時,也會因為找不到對應的分支,直接返回不支持,出現 mapping of buffer object failed 的報錯。
這里給 P2P 映射入口新增了 BAR1 類型的分支。當驅動收到 BAR1 P2P 的請求時,會正確調用之前綁定的 Hopper HAL 函數,完成 P2P 地址映射的創建與刪除。
3.2.5. 繞過 SKU 鎖,默認啟用 BAR1 P2P
改動 1:P2P 權限覆蓋
pKernelBif->p2pOverride = 0x11;p2pOverride 是驅動內部的 P2P 權限強制覆蓋開關。推測 0x11 是驅動內部定義的啟用值,可以忽略 GPU 的 SKU 類型限制。
原廠驅動會根據 GPU 的 SKU(消費級 / 專業級)標記,直接判定一部分 GPU 不支持 P2P。這個改動直接繞過了 SKU 鎖。
改動 2:強制指定 P2P 類型為 BAR1 P2P
原廠驅動默認 P2P 類型為 DEFAULT,這會優先使用 Mailbox P2P。但一些高性能 GPU 的 Mailbox 硬件會被鎖定,即使開啟了權限,也會因為 Mailbox 不可用而返回 P2P 不支持。所以這里選擇使用 BAR1 P2P 類型。
GPUDirect PCIe P2P 的分類與實現
從上面可以看到,本次解鎖 P2P 主要針對的是基于 PCIe 實現的 GPUDirect P2P,不涉及基于 NVLink 實現的 P2P。
在 NVIDIA 開源驅動中可以看到,P2P 有如下幾種不同的類型:
} P2P_CONNECTIVITY;NVIDIA 開源驅動中會對當前環境支持的 P2P 類型分別作出檢測(參考 p2p_caps.c::p2pGetCapsStatus),且檢測有先后順序區別:
C2C (Chip-to-Chip):GPU 芯片間互連,NVIDIA 的一種高速互連技術,用于連接 GPU 和其他芯片(如 Grace CPU);
NVLink 直連:GPU 之間通過 NVLink 直接連接;
NVLink Indirect:通過 NVSwitch 間接連接;
PCIe P2P:
PCIe PROPRIETARY P2P(Mailbox P2P);
PCIe BAR1 P2P。
NVLink 與 C2C 作為 NVIDIA 自研的專用互聯協議,其設計初衷便是為了實現 GPU 間的無縫內存共享,因此實現路徑相對統一且直觀。相比之下,基于標準 PCIe 總線的 GPUDirect P2P 則面臨著更多的底層約束,演化出了 Mailbox P2P 與 BAR1 P2P 兩種截然不同的實現方案。
4.1. 為什么基于 PCIe 的 GPUDirect P2P 會更復雜?
在深入解析這兩種方案的具體實現之前,我們需要先理解 PCIe P2P 面臨的核心挑戰。簡而言之,NVLink 的設計哲學是「連為一體」,從硬件層面抹平了設備界限;而 PCIe P2P 則更像是在主從架構下「為繞過 CPU 中轉而打的補丁」,這種先天性的架構差異導致了其實現過程的復雜性。
架構哲學不同
NVLink (互聯):架構設計就是扁平化的。GPU A 和 GPU B 通過 NVLink 連接后,它們之間的內存空間在硬件層面就被映射在一起了。
PCIe (主從):PCIe 的設計哲學是「以 CPU 為中心」的總線協議。它的原生邏輯是:所有設備都是外設,CPU 是主設備。數據流動默認是 設備 <-> CPU 內存,GPU 之間并沒有直接的總線連接。
拓撲結構限制
這是 PCIe P2P 最讓人頭疼的地方,也是它不如 NVLink 直觀的核心原因:PCIe P2P 非常依賴物理位置,需要區分是否在同 Switch 下、同 Root Port 下、同 NUMA 節點下等等。相比之下,NVLink 拓撲簡單粗暴:只要連了 NVLink,就是直連,無視 PCIe 的物理位置。
而得益于 NVSwtch 連接節點內多個 GPU ,將直連模式擴展為交換網絡,使任意 GPU 對之間都有等價的直連路徑,構建起一個無阻塞的全互聯拓撲,多級 NVLink/NVSwitch 從根本上擺脫了 PCIe 樹狀結構帶來的層級限制和跨 RC 的復雜路由問題。
地址映射與一致性
NVLink:支持完美的內存一致性。GPU A 可以直接讀寫 GPU B 的顯存,甚至支持原子操作。在程序員眼里,這幾乎就是一個全局顯存池。
PCIe P2P:運行時數據傳輸依賴 DMA 機制。GPU A 要訪問 GPU B,必須先通過 OS 獲取 GPU B 顯存的物理地址(IOVA),然后映射到自己的 PCIe BAR 空間。這個過程涉及 IOMMU、ATS 等復雜的系統層配置。一旦 OS 或 BIOS 禁用了某些特性,P2P 就會靜默失敗,回退到通過 CPU 內存拷貝。這對開發者來說行為不可預期,調試成本高。
正是由于上述復雜性,NVIDIA 在 PCIe 鏈路上進化出了兩種不同的實現方案:BAR1 P2P 和 Mailbox P2P。接下來我們將詳細解析這兩種方案。
4.2. PCIe P2P 支持情況
4.2.1. 如何判斷
首先的問題是如何判斷當前 GPU 設備是否支持 GPUDirect P2P。由于 P2P 連通性受到多方面的影響,最可靠的標準即為 NVIDIA 提供的 API cudaDeviceCanAccessPeer。返回 true 則兩 GPU 節點支持 P2P,否則不支持。
參考鏈接:https://forums.developer.nvidia.com/t/how-can-i-tell-which-nvidia-gpus-will-have-p2p-access-to-the-same-gpu-on-pcie/248378/4
注意:cudaDeviceEnablePeerAccess 是一個昂貴的操作(可能涉及 TLB 刷新和頁表修改),通常只需在應用程序啟動時執行一次。
Returns in *canAccessPeer a value of 1 if device device is capable of directly accessing memory from peerDevice and 0 otherwise. If direct access of peerDevice from device is possible, then access may be enabled by calling cudaDeviceEnablePeerAccess().4.2.2. 拓撲結構的影響
物理拓撲結構會直接對 GPUDirect PCIe P2P 的連通性與性能造成影響。通過執行 nvidia-smi topo -m 可以直觀了解到當前節點 GPU / NIC 的拓撲結構。
![]()
根據 GPU 在 PCIe 總線樹上的位置,可以將物理拓撲分為三個層級。
層級一:同一 PCIe Switch 下游
這是 PCIe 方案下性能最優的拓撲結構,對應于 nvidia-smi topo -m 輸出中的 PIX。
物理連接:兩張 GPU 連接在同一個 PCIe Switch 芯片的不同下游端口上,該 Switch 上行連接到 CPU 的 Root Complex;
聯通性:最佳狀態。PCIe Switch 具備路由 P2P TLP(Transaction Layer Packet)的能力,GPU 可以直接看到對方的 BAR 空間。
通信性能:
帶寬:接近線速。數據包僅在 Switch 內部轉發,不經過 CPU;
延遲:最低。路徑短,僅涉及 Switch 內部交換邏輯;
一致性:Switch 通常能較好地處理 Cache Coherency,減少 CPU 干預。
層級二:同一 CPU Socket,不同 PCIe Switch/Root Port
PCIe 下的次優路徑,對應于 nvidia-smi topo -m 輸出中的 PHB。
物理連接:GPU A 連接在 Switch 1(或 CPU Root Port A),GPU B 連接在 Switch 2(或 CPU Root Port B),但兩者都掛在同一個 CPU Socket 下。
聯通性:通常支持,但受固件配置影響。
數據路徑為:GPU A -> Switch A -> CPU Root Complex -> Switch B -> GPU B;
數據必須經過 CPU 內部的 PCIe Host Bridge。
一個關鍵阻礙 - ACS (Access Control Services):
為了安全性和虛擬化隔離,現代服務器 BIOS 通常默認開啟 ACS。ACS 策略會強制要求 P2P 流量必須先上行發送到 Root Complex 進行驗證,甚至可能禁止 P2P 事務轉發;
如果 ACS 開啟并配置了強制轉發到 RC,會導致性能退化。
性能:
帶寬:依然較高,但受限于 CPU 內部的互連總線帶寬。如果多個 GPU 對同時進行 P2P,會爭搶 CPU 內部的 PCIe 路由帶寬;
延遲:比同 Switch 場景高,因為增加了穿過 CPU Root Complex 的跳數。
層級三:跨 CPU Socket (Multi-Socket Topology)
這是 PCIe 方案下性能最差或最復雜的拓撲,對應于 nvidia-smi topo -m 輸出中的 SYS。
物理連接:GPU A 掛在 CPU Socket 0 上,GPU B 掛在 CPU Socket 1 上。
聯通性:較差,甚至不支持直接 P2P。
數據路徑為:GPU A -> CPU 0 -> QPI/UPI (Intel) 或 Infinity Fabric (AMD) -> CPU 1 -> GPU B。
通信性能
帶寬瓶頸:可能成為較嚴重的性能瓶頸。跨 Socket 通信依賴處理器間的互連鏈路(QPI / UPI /xGMI)。GPU 間的數據通路會與 CPU 間的內存一致性流量競爭,而 UPI 帶寬通常會低于 PCIe Gen4/Gen5 的帶寬;
延遲:顯著增加。這個過程中使用的基于目錄的緩存一致性協議,會比傳統的基于總線監聽的 MESI 協議邏輯更復雜,會加劇跨片互連延遲;
實際結果:在大多數 PCIe P2P 實現中(如 CUDA),驅動檢測到跨 Socket 拓撲時,往往會限制直接 P2P,強制使用 系統內存中轉 模式。即:GPU A -> CPU0 Mem -> CPU1 Mem -> GPU B(或者如果內存互聯足夠快,直接 DMA 到遠端內存)。這實際上已經失去了 P2P Zero Copy 的優勢。
![]()
4.3. BAR1 Mapping
BAR1 Mapping P2P 是基于標準 PCIe BAR 地址映射機制實現的 P2P 方案,利用 PCIe 規范與 ReBAR 技術實現。
4.3.1. 前置基礎:PCIe BAR 與 ReBAR
PCIe 設備在系統枚舉時,會通過 BAR(Base Address Register,基地址寄存器)向主機 BIOS/OS 報告自己需要的內存映射 IO(MMIO)空間。OS 會為其分配唯一的 PCIe 總線物理地址,CPU 或其他 PCIe 設備可通過該地址直接訪問設備的內部資源。
對于 NVIDIA GPU,有兩個核心 BAR 空間:
BAR0:固定小容量(通常 16MB),用于映射 GPU 的控制寄存器、固件接口,是 CPU 控制 GPU 的核心通道;
BAR1:原本為小容量顯存訪問窗口(通常 256MB)。開啟 Resizable BAR(ReBAR)后,可擴容至與 GPU 顯存總容量完全一致,讓 CPU 可以直接尋址 GPU 的全部顯存,無需窗口切換。
BAR1 P2P 的核心,就是利用 ReBAR 擴容后的 BAR1 空間,讓其他 GPU 通過 PCIe 總線地址,直接訪問對端 GPU 的顯存。
4.3.2. 核心思想
NVIDIA 的原廠驅動中仍然復用了 GMMU 的 PEER Aperture 機制,只是底層的 fabric base address 指向的是對端 GPU 的 BAR1 總線地址。GMMU 仍然通過 fldPeerIndex 和 HSHUB 路由到正確的 PCIe 端口。
而在 Patch 中,使用了 GMMU_APERTURE_SYS_NONCOH 的邏輯,將 PTE 地址從 fldAddrPeer 換到 fldAddrSysmem,基地址從 Fabric 地址換成 BAR1 物理地址,讓 GMMU 將對端顯存當作系統內存來訪問。
以下以 GPU1 訪問 GPU0 顯存的場景為例,大體描述一下數據通路:
環境準備與 BAR1 空間暴露
主板 BIOS 開啟 Above 4G Decoding、Resizable BAR,關閉 PCIe ACS。IOMMU 可配置為 Passthrough 模式或正確配置 P2P DMA 映射支持,保證 PCIe P2P 事務可達;
OS 啟動時,為每個 GPU 的 BAR1 空間分配連續的 64 位 PCIe 總線物理地址,地址范圍覆蓋 GPU 全部顯存;
NVIDIA 內核驅動暴露 BAR1 地址映射能力,封裝基礎的 P2P 接口。
地址映射與轉換
驅動獲取對端 GPU(GPU0)的 BAR1 總線物理基地址,以及顯存地址到 BAR1 地址的偏移關系(GPU0 顯存內的偏移 = BAR1 總線地址的偏移);
將 GPU0 的 BAR1 總線地址,映射到本地 GPU(GPU1)的可訪問地址空間。在 GPU1 的 GMMU 中,該地址被標記為 SYS Aperture,而非 GPU 原生的 VID Aperture;
訪問 GPU0 顯存內的數據,則需要將 GPU0 的顯存偏移轉換為對應的 BAR1 總線物理地址(BAR1 基地址 + 顯存偏移)。IOMMU 需正確配置以支持 Peer-to-Peer DMA 映射;在 IOMMU 不支持 P2P 映射的場景下,可能需要設置為 Passthrough 模式或禁用,則該地址可直接用于 PCIe 路由。
運行時數據訪問
GPU1 的 SM 發起對轉換后的 BAR1 總線地址的讀寫請求;
GPU1 的 GMMU 識別到該地址屬于 SYS Aperture,將其當作系統物理地址,轉發給 PCIe 控制器;
PCIe 控制器發起標準的 PCIe Memory Read/Write TLP 事務,目標地址為 GPU0 的 BAR1 總線地址,通過 PCIe 交換機路由到 GPU0;
GPU0 的 PCIe 控制器收到 TLP 事務,根據 BAR1 的基地址與偏移,轉換為本地顯存的物理地址,轉發給顯存控制器完成讀寫;
數據通過 PCIe Completion TLP 沿原路徑返回給 GPU1 的 SM;
整個過程繞過了 CPU 系統內存。
4.3.2.1. BAR1 Mapping 的核心特性與限制
不依賴 NVIDIA 原生 Peer 硬件機制,僅需 GPU 支持 ReBAR、PCIe 拓撲支持 P2P 即可實現;
完全基于標準 PCIe 協議,僅能通過 PCIe 鏈路傳輸,無法利用 NVLink;
關鍵的一點是,地址轉換發生在較低層的總線與 DMA 語義層面,對上層應用透明。開發者仍需經由 CUDA Peer Access API 取得合法的 UVA Pointer,而不能直接在用戶態 Mmap BAR1 做透明訪問。4.3.3. 代碼概覽接下來我們看下 NVIDIA 驅動代碼中有關 BAR1 P2P 的具體實現。
4.3.3.1. 能力檢測
驅動會首先在 p2p_caps.c::p2pGetCapsStatus() 這里對當前系統環境適配的 P2P 能力,按照 C2C -> NVLink -> PCIe BAR1 -> PCIe Mailbox 的順序依次檢測。
對于 BAR1 P2P 的檢測在 p2p_caps.c::_kp2pCapsGetStatusOverPcieBar1():
驅動配置檢查(是否允許 BAR1 P2P)
}檢查驅動的 P2P 類型配置,BAR1 P2P 需要滿足 Regkey 對應的值:
forceP2PType 是默認值或 PCIEP2P;
pcieP2PType 是 BAR1 或 AUTO。
硬件支持檢查(HAL 層)
}這里是 BAR1 P2P 的準入檢查,調用 HAL 函數 kbusIsPcieBar1P2PMappingSupported_HAL,檢查 2 張 GPU 之間是否滿足以下條件:
GPU 架構是否原生支持 BAR1 P2P(Hopper+);
ReBAR 是否啟用,BAR1 空間是否完整映射了全部顯存。值得一提的是,以下 2 種情況會被直接判斷不支持:
vGPU 虛擬化環境下不支持;
Confidential Computing 模式下不支持。
覆蓋配置檢查
}這里檢查 Regkey 是否有寫入的強制值。如果有覆蓋配置則直接按覆蓋值返回,無需進行后續的能力檢測。主要包含了以下能力:P2PReadCap、P2PWriteCap、P2PAtomicsCap。
主機系統狀態檢查
&readCapStatus, bCommonPciSwitchFound));調用 _p2pCapsGetHostSystemStatusOverPcieBar1 檢查主機系統,需至少滿足以下任一條件:
有 PCIe Switch;
CPU 為 Ryzen 或是 Xeon SPR。
對比 Mailbox:沒有 Intel IOH 的歷史包袱檢查。
PCIe 原子操作能力檢查
_p2pCapsGetPcieToplogySupportForBar1Atomics(gpuMask, &atomicsCapStatus);當前直接返回 NOT_SUPPORTED。
4.3.3.2. 建立映射
這里是從 kern_bus_gh100.c::kbusCreateP2PMapping_GH100() 分發到映射建立的入口 kbusCreateP2PMappingForBar1P2P_GH100():
}可以看到建立映射前同樣先檢查了當前的虛擬化環境與 BAR1 P2P 的能力;
busBar1PeerRefcount[] 數組記錄每個 GPU 對的 P2P 映射引用次數;
在首次執行 Mapping 時建立 IOMMU 映射。接下來看這里是怎么做的。
獲取對端 GPU 的 Static BAR1 DMA 內存描述符
NV_ASSERT_OR_RETURN(pPeerDmaMemDesc != NULL, NV_ERR_INVALID_STATE);Static BAR1 結構:每個 GPU 的 BAR1 區域都通過 staticBar1 結構管理;
這里的 pDmaMemDesc 是 DMA 內存描述符,描述了 BAR1 區域的物理地址和大小;
GFID 機制:這里通過 GPU Function ID 區分物理 GPU(GFID=0)和 vGPU 實例。
創建 IOVA 映射(內存描述符層)
}每個 GPU 有獨立的 I/O Virtual Address 空間,這里就是將 GPU1 的 BAR1 物理地址映射到 GPU0 的 IOVA 空間中;
這里的 MEMORY_DESCRIPTOR 是內存描述符的抽象,用來統一管理不同類型的內存映射。
OS 層創建 Peer DMA 映射
}進行 Peer 映射,將對端 GPU 的 BAR 區域映射為本地 GPU 可訪問的 DMA 地址;
進行地址轉換,將物理地址轉換為 GPU 可用的 DMA 地址。
內核 IOMMU API 建立硬件映射
}其中的 dma_map_resource() 是 Linux 內核 API,用于創建 IOMMU 頁表映射;
IOMMU 頁表:將對端 GPU 的 BAR1 物理地址映射到本地 GPU 的 IOVA 空間;
這里需要硬件支持,PCIe 控制器和 IOMMU 硬件需要支持 Peer-to-Peer DMA。
4.3.3.3. 數據傳輸
}映射建立完成后,數據傳輸就由 PCIe 硬件處理,通過標準的 DMA 操作,使用 IOMMU 映射后的對端 GPU BAR1 地址。
我們大體看一下讀寫操作對應的硬件流程:
P2P 寫操作(GPU0 -> GPU1)
GPU0 驅動準備 DMA 描述符,目標地址為 GPU1_BAR1_IOVA + offset;
GPU0 的 Copy Engine/DMA 發起 PCIe Posted Write 事務;
事務通過 PCIe 總線到達 GPU1 的 PCIe 接口;
GPU1 硬件識別到這是 BAR1 空間的訪問,通過靜態 BAR1 映射表,直接轉換為 FB 物理地址;
GPU1 將數據寫入對應的 FB 物理地址,同時更新 GPU1 的 L2 緩存;
GPU0 的 DMA 引擎確認所有 Posted Write 已被 PCIe Fabric 接收后,通過 Doorbell 或同步點通知上層完成。
P2P 讀操作(GPU0 <- GPU1)
GPU0 驅動準備 DMA 描述符,源地址為 GPU1_BAR1_IOVA + offset;
GPU0 的 Copy Engine/DMA 發起 PCIe Non-Posted Read Request 事務;
事務通過 PCIe 總線到達 GPU1 的 PCIe 端口;GPU1 硬件識別到這是 BAR1 空間的訪問,通過靜態 BAR1 映射表直接轉換為 FB 物理地址;
GPU1 從對應的 FB 物理地址讀取數據,這里優先從 L2 緩存讀取;
GPU1 構造 PCIe Read Completion TLP,攜帶讀取的數據返回給 GPU0;
GPU0 的 DMA 引擎收到所有 Completion TLP 并重組數據后,觸發本地 DMA 完成中斷;
數據已寫入 GPU0 的本地 FB,可被 GPU0 訪問。
4.3.3.4. 清理
調用到 kbusRemoveP2PMappingForBar1P2P_GH100() 會觸發釋放 P2P 映射,釋放資源,清除 BAR1 映射:
}4.3.4.5. 流程圖
BAR1 P2P 實現的流程如下圖所示:
![]()
4.4. Mailbox
Mailbox P2P(即驅動枚舉類型中的 P2P_CONNECTIVITY_PCIE_PROPRIETARY)是 NVIDIA 為 ReBAR 尚未普及的時代設計的一套私有 Peer-to-Peer 通信機制。該方案自 Fermi 延續至 Ampere,在專業級 Tesla / Quadro 產品線中長期作為默認 PCIe P2P 路徑存在。
4.4.1. 原理概述
所謂 Mailbox,是指 GPU 內部集成了一組專用于跨設備握手的 Mailbox Control Registers。真正的業務數據仍走標準 PCIe Transaction Layer Packet(TLP),不會在寬度僅為 32-bit 級別的 Mailbox Regs 上逐片搬運。
其核心思路可概括為:利用 GPU 內部的 Peer ID 空間與 HSHUB 片上互聯路由表,把遠端顯存翻譯成本地 GMMU 可直接識別的 PEER Aperture,從而允許 SM 或 Copy Engine 像訪問本地 VID Memory 一樣發起跨設備讀寫。 BAR1 在此過程中僅被借用做一小段 MMIO 控制區,用于暴露對方的 P2P 相關寄存器。
4.4.1.1. 依賴的硬件基礎
Peer ID & HSHUB Mask:每顆 GPU 維護最多 8 個 Peer Slot(0–7)。當某段 UVA 經 CUDA Runtime 注冊為 Remote Allocation 時,GMMU 會將對應的虛擬頁標記為 PEER Aperture,并在報文中加入 Peer Index;HSHUB 根據該索引選擇下游 PCIe Port;
WMB Tag:單向 32-bit 隨機令牌,由 CPU 驅動在初始化階段分別寫入兩端,用于過濾非法的設備間訪問,防止惡意或誤配的訪存操作穿透 FrameBuffer;
BAR1 小窗(僅作控制面):驅動在本地 BAR1 空間中切出一小塊區域來映射對端設備的寄存器,使得 A 卡可以直接通過 MMIO 來寫 B 卡的 Doorbell/Status 寄存器。注意這塊區域與顯存本身無關,不直接參與數據遷移;
標準 PCIe Link:與 BAR1 P2P 相同,底層仍需 Switch 支持 P2P Routing、ACS/IOMMU 正確放行。
4.4.1.2. 大體工作流程
整個生命周期分為三個階段:CPU 握手與路由建立 → GPU 數據傳輸階段 → CPU 析構階段。其中真正耗時的數據傳輸全部發生在第二階段,無需 Kernel Trap。
初始化階段
NVIDIA 內核驅動為成對的 GPU 分配專屬 Mailbox 硬件資源,建立雙向控制通道,配置中斷觸發條件和狀態寄存器,完成 GPU 間的握手認證。這個過程上層 CUDA 應用是透明的。
數據傳輸核心流程
GMMU 地址解析:當 CUDA Kernel 內核對已經注冊為 Peer Accessible 的 UVA 執行 Load/Store 時,本地 GPU 的 GMMU 會將該虛擬地址對應的頁表項命中為 PEER Aperture。此時 GMMU 會在生成的存儲器請求報文頭部嵌入預先配置好的 Peer ID,并將其遞交給片上的高速互聯樞紐 HSHUB;
HSHUB 路由:HSHUB 內部維護著一張以 Peer Index 為索引的路由掩碼表。HSHUB 解碼該請求的 Peer ID 后,直接將其導向本 GPU 連接至目標方向的 PCIe Root Port / Switch Port,形成一條數據通路。整個過程既不需要陷入 CPU 驅動,也不需要經由 Mailbox 寄存器參與調度;
PCIe Fabric 傳輸:PCIe 控制器將該請求封裝為標準 Memory Read/Write TLP,經 PCIe Switch 或直接 RC 路由抵達對端 GPU 的 PCIe Downstream Port。由于此前已完成靜態 BAR1 MMIO 控制區映射和對端 P2P Config/Doorbell 寄存器配置,因此這里的 TLP Payload 就是單純的業務數據流;
對端 FrameBuffer 完成讀寫:對端 GPU 收到 TLP 后,其 FrameBuffer Controller 首先校驗攜帶的 WMB Tag,匹配成功后直接將事務轉換為本地的 Frame Buffer Physical Address 并完成最終的 DRAM 讀寫。如果是 Non-Posted Write,則按標準 PCIe Ack 規則回復 Completion;若是 Posted Write,則無需等待回應即可完成流水線提交。
4.4.2. 代碼概覽
接下來我們結合 NVIDIA 驅動代碼,看一下 Mailbox P2P 的具體實現。
4.4.2.1. 初始化
該階段建立 P2P 連接。
Mailbox P2P 資源分配
}當用戶調用 cuCtxEnablePeerAccess() 時,RM 內核最終會調用到 kbusCreateP2PMappingForMailbox_GM200()
Peer ID 是 GPU 內部用于標識遠程 GPU 的唯一編號,范圍 0-7,每個 GPU 最多支持 8 個 Peer;
HSHUB 是 GPU 內部的高速 Hub,路由來自 SM、CE 等的 Peer 訪存請求;
kbusSetupMailboxes_GM200 調用會建立單方向的通信,需要調用 2 次。
單個方向的通道配置
}這里配置了本地 GPU 的各種訪問權限,其中 kbusSetupPeerBarAccess 是基礎,后續的寄存器操作都基于 BAR 映射;
WMB Tag 是一個 32 位的隨機數,用于防止惡意或錯誤的 P2P 請求,只有攜帶正確 Tag 的請求才會被處理。需要注意的是,WMB Tag 是單向的,每個方向都有自己獨立的 Tag。
經過上述初始化操作后,兩張 GPU 之間的握手完成,路由已建立。但此時還 沒有發生任何應用數據傳輸。
4.4.2.2. 數據傳輸
初始化階段完成后,數據傳輸就由 GPU 硬件處理,不需要 CPU 參與了。CUDA Runtime 會把 Peer Device 的顯存映射到同一套 Unified Virtual Address (UVA) 空間中。此后應用程序看到的就是普通的指針,內核態無需再次陷入。我們大體看一下讀寫操作對應的硬件流程和觸發時機。
P2P 寫操作(GPU0 → GPU1)
代碼觸發時機:CUDA Kernel 中對指向 GPU1 顯存的 UVA Pointer 執行賦值語句(如 *dst_remote = src_local_data;);編譯器會生成與普通全局內存 Store 相同的指令序列。
大致流程:
GPU0 的 SM 取指譯碼后將 Store 指令送入 LSU(Load/Store Unit),計算得到完整的 64 位虛擬地址;
GMMU 頁表時發現該頁屬性為 PEER Aperture 并且關聯到 GPU1,于是在發出的報文中填入對應的 Peer Index;
HSHUB 檢索本地 Peer-to-Port 映射表,將該請求轉發給通向 GPU1 的 PCIe 端口;
GPU0 的 PCIe 物理層封裝為標準 PCIe Posted Memory Write TLP,Payload 即為待寫的數據,目標地址為 UVA 翻譯后經系統拓撲決定的目標地址;
GPU1 的 PCIe Endpoint 接收到 TLP,校驗 WMB Tag 合法性,若合法則將 TLP 遞交至 FrameBuffer Controller,最終轉換為針對本地 GDDR 的物理地址;
由于是 Posted Write,無需立即返回 Completion。Store Instruction 在該層級被視為已 Retire,只要滿足 CUDA 內存一致性模型即可繼續向前推進。
P2P 讀操作(GPU0 ← GPU1)
代碼觸發時機:CUDA Kernel 中對指向 GPU1 顯存的 UVA Pointer 執行取值(如 val = *src_remote;);編譯器會生成與普通全局內存 Load 類似的指令序列。
硬件大致流程:
GPU0 的 SM 取指譯碼后將 Load 指令送入 LSU,計算出 64 位虛擬地址;
GMMU 頁表命中 PEER Aperture 及對應到 GPU1,于是在發出的報文中填入對應的 Peer Index;
HSHUB 查詢 Peer-to-Port 映射表并將請求導向下行 PCIe 端口;
GPU0 的 PCIe Controllers 構造 PCIe Non-Posted Memory Read Request TLP,發送至 Fabric;
GPU1 接收 TLP,驗證 WMB Tag,隨后從其本地 FrameBuffer 中提取所需的 Cacheline;
GPU1 打包多個 PCIe Read Completion TLP(CplD)沿反向鏈路送回 GPU0;
GPU0 的 PCIe RX Engine 重組 Completion Data,返回到最初發射該 Load 指令的 Warp/SM,最終將數據填充到目的 General Purpose 寄存器中。
4.4.2.3. 清理
釋放資源,關閉 P2P 通道
}當用戶調用 cuCtxDisablePeerAccess() 時就會執行清理工作。
4.4.2.4. 流程圖
Mailbox P2P 實現的流程如下圖所示:
![]()
4.5. 性能對比
我們以一臺支持 PCIe P2P 的 GPU 做 p2pBandwidthLatencyTest 測試,結果如下:
BAR1 mapping 性能數據
![]()
Mailbox 性能數據
![]()
可以看到,BAR1 Mapping 在帶寬性能上顯著優于 Mailbox P2P(實測約提升 20%~35%),這也是符合預期的。
Mailbox P2P 是小 BAR 時代的妥協性方案,其軟硬件設計都圍繞「如何用 256MB 的小 BAR 空間,實現對幾十 GB 顯存的跨 GPU 訪問」,不可避免地引入了信令協商、寄存器間接尋址等額外開銷,性能上限受限于架構。
而 BAR1 P2P 是 ReBAR 大 BAR 時代的原生架構,徹底消除了小 BAR 的歷史包袱。通過靜態的全量線性映射,相較于 Mailbox,運行時開銷低很多。同時它更符合 PCIe 標準協議,在帶寬、延遲、擴展性上實現了對 Mailbox P2P 的性能優勢。
結 語
GPUDirect P2P 技術路線的走向相對清晰:Mailbox P2P 是當年小 BAR 時代的補丁方案,天生有性能天花板。在 ReBAR 普及后,其應用場景將逐步收窄。
BAR1 P2P 才是更合理的方案。靠著大 BAR 一次性映射完所有顯存,跑起來低額外開銷,能把 PCIe 總線的帶寬幾乎榨干,應該成為 PCIe P2P 主推的通用方案。以后跨設備、跨廠商的互聯應該會越來越容易。
至于 NVLink,那是給超算、千億大模型訓練準備的頂級配置。BAR1 P2P 更多覆蓋從消費卡到入門專業卡的絕大多數 PCIe-only 的互聯場景,兩者互不沖突。
這次社區支持部分 GPU 的 GPUDirect P2P 能力是一次很好的實踐,最大程度地挖掘了 GPU 設備的能力,實現了技術普惠大眾。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.