PCIe 链路状态与主机-设备带宽实测
基于 RTX 5090 (PCIe Gen 5 x16) 和 A100-SXM4-80GB (PCIe Gen 4 x16) 双平台。本文提供一个零依赖 CUDA 程序,测量 Host↔Device 传输带宽,并分析 PCIe 链路状态在空闲/负载下的切换。
1. 为什么 PCIe 带宽测试不同于 nvbandwidth
nvbandwidth 功能强大但需要编译安装(依赖 Boost、CMake、GDS 等)。以下情况可能更适合本文的零依赖方案:
- 快速验证 GPU 是否插在正确的 PCIe 槽上
- 没有 root 权限编译 nvbandwidth
- 只需要 H2D/D2H 基础数据
nvbandwidth 的详细用法见 nvbandwidth 深度解析。
2. PCIe 链路状态:为什么空闲时是 Gen 1
# 空闲时查询
nvidia-smi --query-gpu=pcie.link.gen.current,pcie.link.width.current --format=csv,noheader
# 输出: 1, 16
RTX 5090 最大支持 PCIe Gen 5 x16,但空闲时运行在 Gen 1。这是 ASPM (Active State Power Management) 机制:GPU 无负载时降级链路省电,一旦有数据传输,自动恢复到最高速率。
通过 sysfs 查看
# 当前速度
cat /sys/bus/pci/devices/0000:98:00.0/current_link_speed
# 输出: 2.5 GT/s PCIe (Gen 1)
# 最大能力
cat /sys/bus/pci/devices/0000:98:00.0/max_link_speed
# 输出: 32.0 GT/s PCIe (Gen 5)
负载下的链路恢复
运行带宽测试后,链路自动恢复:
# 运行测试后查询
nvidia-smi --query-gpu=pcie.link.gen.current,pcie.link.width.current --format=csv,noheader
# 输出: 5, 16
结论:看到 Gen 1 不要惊慌,这是正常行为。nvidia-smi 中的 pcie.link.gen.max 才是真实能力上限。
3. 零依赖带宽测试程序
cat > pcie_bw_test.cu << 'EOF'
#include <cuda_runtime.h>
#include <stdio.h>
#include <sys/time.h>
#define CHECK(cmd) do { \
cudaError_t e = cmd; \
if (e != cudaSuccess) { \
printf("CUDA error: %s\n", cudaGetErrorString(e)); \
exit(1); \
} \
} while(0)
double get_time_ms() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
}
int main() {
const size_t sizes[] = {
1 * 1024 * 1024, // 1 MB
16 * 1024 * 1024, // 16 MB
64 * 1024 * 1024, // 64 MB
256 * 1024 * 1024, // 256 MB
1024 * 1024 * 1024 // 1 GB
};
const int num_sizes = sizeof(sizes) / sizeof(sizes[0]);
float *h_buf, *d_buf;
CHECK(cudaMallocHost(&h_buf, sizes[num_sizes - 1]));
CHECK(cudaMalloc(&d_buf, sizes[num_sizes - 1]));
printf("%-12s | %-15s | %-15s\n", "Size", "H2D (GB/s)", "D2H (GB/s)");
printf("-------------|------------------|------------------\n");
for (int i = 0; i < num_sizes; i++) {
size_t n = sizes[i];
cudaEvent_t start, stop;
float ms;
cudaEventCreate(&start);
cudaEventCreate(&stop);
// Host -> Device
cudaEventRecord(start, 0);
CHECK(cudaMemcpy(d_buf, h_buf, n, cudaMemcpyHostToDevice));
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&ms, start, stop);
float h2d = (n / (ms / 1000.0)) / (1024.0 * 1024.0 * 1024.0);
// Device -> Host
cudaEventRecord(start, 0);
CHECK(cudaMemcpy(h_buf, d_buf, n, cudaMemcpyDeviceToHost));
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&ms, start, stop);
float d2h = (n / (ms / 1000.0)) / (1024.0 * 1024.0 * 1024.0);
char size_str[16];
if (n >= 1024 * 1024 * 1024)
snprintf(size_str, 16, "%lu GB", n / (1024 * 1024 * 1024));
else
snprintf(size_str, 16, "%lu MB", n / (1024 * 1024));
printf("%-12s | %-15.2f | %-15.2f\n", size_str, h2d, d2h);
cudaEventDestroy(start);
cudaEventDestroy(stop);
}
CHECK(cudaFreeHost(h_buf));
CHECK(cudaFree(d_buf));
return 0;
}
EOF
nvcc -o pcie_bw_test pcie_bw_test.cu
./pcie_bw_test
4. 实测结果 (RTX 5090)
Size | H2D (GB/s) | D2H (GB/s)
-------------|------------------|------------------
1 MB | 18.28 | 32.43
16 MB | 50.70 | 52.09
64 MB | 52.09 | 52.92
256 MB | 52.44 | 53.27
1 GB | 52.50 | 53.34
4.1 趋势分析
| 传输大小 | 现象 | 原因 |
|---|---|---|
| 1 MB | 带宽低 (18-32 GB/s) | CUDA kernel launch 开销 + 链路 ramp-up 延迟主导 |
| 16 MB+ | 带宽稳定 (~52-53 GB/s) | 传输时间主导,链路已恢复到 Gen 5 |
4.2 与理论值对比
PCIe Gen 5 x16 理论单向带宽:32.0 GT/s × 16 lanes × 128b/130b 编码 = ~63.0 GB/s。
实测 ~52.5 GB/s,效率约 83%。损耗来自:
- 128b/130b 编码开销(已计入理论值)
- PCIe TLP header 开销
- CUDA driver 和 runtime 开销
- Host 端内存控制器带宽限制
4.3 D2H vs H2D
D2H 略快于 H2D (~53.3 vs ~52.5 GB/s)。这是因为 GPU 是 DMA 发起方,D2H 时 GPU 直接 push 数据,而 H2D 时需要 GPU 主动 pull。
4.4 A100-SXM4-80GB 对比
A100-SXM4-80GB 使用 PCIe Gen 4 x16,与 RTX 5090 的 Gen 5 x16 形成代差:
| 指标 | A100 (Gen 4 x16) | RTX 5090 (Gen 5 x16) |
|---|---|---|
| 信号速率 | 16.0 GT/s | 32.0 GT/s |
| 编码 | 128b/130b | 128b/130b |
| 理论单向带宽 | ~31.5 GB/s | ~63.0 GB/s |
| 实测 H2D 期望 | ~25-28 GB/s | ~52.5 GB/s |
| 效率 | ~80-89% | ~83% |
nvidia-smi |
pcie.link.gen.max = 4 |
pcie.link.gen.max = 5 |
| ASPM 空闲状态 | Gen 1 (2.5 GT/s) | Gen 1 (2.5 GT/s) |
注意:A100-SXM4 的 GPU 间互联通过 NVLink(NV12, 600 GB/s),但 Host↔Device 仍走 PCIe Gen 4。数据加载路径中 PCIe 是瓶颈:显存带宽 2039 GB/s vs PCIe ~28 GB/s,差距约 73 倍(RTX 5090 约 32 倍,因 GDDR7 带宽更低但 PCIe 更快)。
5. 诊断 PCIe 链路问题
5.1 确认是否跑在预期速率
# 方法 1: 最大能力查询
nvidia-smi --query-gpu=pcie.link.gen.max,pcie.link.width.max --format=csv,noheader
# 期望: 5, 16 (RTX 5090 Gen 5) / 4, 16 (A100 Gen 4)
# 方法 2: sysfs
cat /sys/bus/pci/devices/0000:98:00.0/max_link_speed
# 期望: 32.0 GT/s PCIe (Gen 5) / 16.0 GT/s PCIe (Gen 4)
5.2 常见问题
| 问题 | 排查命令 | 可能原因 |
|---|---|---|
| max 显示 Gen 3 | nvidia-smi --query-gpu=pcie.link.gen.max |
主板/CPU 不支持更高,或插在低代槽位 |
| width 显示 x8 | nvidia-smi --query-gpu=pcie.link.width.max |
插槽物理宽度不足,或 lane 被其他设备共享 |
| 带宽远低于预期 (> Gen 4) | 运行本文测试 + 检查 Gen | 若 ~25 GB/s 可能是 Gen 4 正常值 |
| 带宽异常低 (< 5 GB/s) | 检查 Gen+Width + 测小包 | 可能在 Gen 1 x1 或其他异常状态 |
5.3 PCIe 错误计数器
nvidia-smi --query-gpu=pcie.replay_counter,pcie.replay_rollover_counter --format=csv
# replay 计数器持续增长 = 链路信号质量问题
6. 进阶:GPU-GPU 带宽(多卡环境)
单卡环境不支持 P2P 带宽测试。如有双卡以上环境,参考 GPUDirect P2P 技术详解 或安装 nvbandwidth 测试 Device to Device 带宽。