rocm_bandwidth_test-4: (RBT-NG) 测试原理

ROCm带宽测试原理与优化

前文介绍了rocm_bandwidth_test工具功能和安装方法,本文详细分析带宽测试的原理。

1. 整体架构

ROCm Bandwidth Test (RBT-NG) 是下一代 ROCm 带宽测试工具,采用插件化架构,核心基于 TransferBench (TB) 引擎。

设计理念

  • 模块化设计:通过插件系统实现功能扩展
  • 多执行器支持:支持 CPU、GPU GFX、GPU DMA、NIC 等多种执行单元
  • 灵活的内存类型:支持多种内存类型和设备组合
  • 精确测量:提供纳秒级时间精度和详细的性能指标

2. 核心测试原理

2.1 Transfer(传输)概念

每个 Transfer 定义了一次数据传输操作,是测试的基本单元。

Transfer 数据结构:

struct Transfer {
    size_t            numBytes;      // 传输的字节数
    vector<MemDevice> srcs;          // 源内存设备列表
    vector<MemDevice> dsts;          // 目标内存设备列表
    ExeDevice         exeDevice;     // 执行器设备
    int32_t           exeSubIndex;   // 执行器子索引(XCC/SDMA引擎)
    int               numSubExecs;   // 子执行器数量
};

核心算法:
将所有源数据求和后写入所有目标位置

sum = src[0] + src[1] + ... + src[N-1]
dst[0] = dst[1] = ... = dst[M-1] = sum

特殊情况:

  • 0 个源:对目标执行 memset 操作(填充固定值)
  • 1 个源:执行简单的内存复制
  • 多个源:执行累加操作后写入目标

2. 执行器类型 (Executor Types)

2.1 CPU 执行器 (EXE_CPU)

特点:

  • 使用多线程并行执行
  • 每个子执行器在独立的 CPU 线程上运行
  • 基于 NUMA 节点进行内存分配和线程绑定

核心实现:

static void CpuReduceKernel(SubExecParam const& p, int numSubIterations) {
    if (numSrcs == 0) {
        // Memset 操作
        memset(dst, MEMSET_CHAR, N * sizeof(float));
    } else if (numSrcs == 1) {
        // 单源复制
        memcpy(dst, src, N * sizeof(float));
    } else {
        // 多源累加
        for (int j = 0; j < N; j++) {
            sum = src[0][j];
            for (int i = 1; i < numSrcs; i++) 
                sum += src[i][j];
            for (int i = 0; i < numDsts; i++) 
                dst[i][j] = sum;
        }
    }
}

执行流程:

  1. 绑定到指定的 NUMA 节点
  2. 为每个传输创建异步线程
  3. 每个传输内部启动多个子执行器线程
  4. 使用 std::thread 实现并行
2.2 GPU GFX 执行器 (EXE_GPU_GFX)

特点:

  • 使用 GPU 计算内核(HIP/CUDA kernel)执行
  • 支持模板化设计,可配置 block size 和 unroll 因子
  • 支持多种 wavefront 排序模式
  • 支持 XCC(芯片组)指定和 CU 掩码

核心内核:

template <int BLOCKSIZE, int UNROLL>
__global__ void GpuReduceKernel(SubExecParam* params, 
                                int waveOrder, 
                                int numSubIterations) {
    // 1. 记录开始时间戳
    if (threadIdx.x == 0) 
        startCycle = GetTimestamp();
    
    // 2. 过滤指定的 XCC
    if (preferredXccId != -1 && xccId != preferredXccId) 
        return;
    
    // 3. 向量化读取和处理(使用 float4)
    float4 val[UNROLL];
    for (int u = 0; u < UNROLL; u++)
        val[u] = srcFloat4[0][idx + u * unrlStride * warpSize];
    
    // 4. 累加多个源
    for (int s = 1; s < numSrcs; s++)
        for (int u = 0; u < UNROLL; u++)
            val[u] += srcFloat4[s][idx + u * unrlStride * warpSize];
    
    // 5. 写入所有目标
    for (int d = 0; d < numDsts; d++)
        for (int u = 0; u < UNROLL; u++)
            dstFloat4[d][idx + u * unrlStride * warpSize] = val[u];
    
    // 6. 记录结束时间戳
    if (threadIdx.x == 0) {
        p.stopCycle = GetTimestamp();
        p.startCycle = startCycle;
    }
}

关键优化技术:

  1. 向量化访问:使用 float4 类型,一次读写 16 字节

  2. 循环展开:支持 1-8 的展开因子,减少循环开销

  3. Wavefront 排序:6 种排序模式优化内存访问

    • U,W,C: Unroll, Wave, CU
    • U,C,W: Unroll, CU, Wave
    • W,U,C: Wave, Unroll, CU
    • W,C,U: Wave, CU, Unroll
    • C,U,W: CU, Unroll, Wave
    • C,W,U: CU, Wave, Unroll
  4. 多流支持:可以为每个传输创建独立的 HIP 流

  5. XCC 亲和性:在多芯片 GPU 上指定执行的芯片组

内核启动配置:

dim3 gridSize(xccDim, numSubExecs, 1);
dim3 blockSize(cfg.gfx.blockSize, 1);

// 支持 64, 128, 192, 256, 320, 384, 448, 512 的 block size
// 支持 1-8 的 unroll 因子
GpuKernelTable[blockSize/64 - 1][unrollFactor - 1]
    <<<gridSize, blockSize, 0, stream>>>
    (params, waveOrder, numSubIterations);
2.3 GPU DMA 执行器 (EXE_GPU_DMA)

特点:

  • 使用 GPU 的 SDMA(System DMA)引擎
  • 不占用 GPU 计算资源
  • 支持指定特定的 SDMA 引擎

两种实现方式:

  1. HIP Copy(默认):
hipMemcpyAsync(dst, src, numBytes, hipMemcpyDefault, stream);
  1. HSA Copy(高级模式):
hsa_amd_memory_async_copy_on_engine(
    dst, dstAgent,
    src, srcAgent,
    numBytes, 0, NULL,
    signal, sdmaEngineId, true
);

SDMA 引擎选择:

  • 可以通过 exeSubIndex 指定特定的 SDMA 引擎
  • 支持 SDMA_ID_0, SDMA_ID_1 等
  • 引擎可用性通过 hsa_amd_memory_copy_engine_status 查询
2.4 NIC 执行器 (EXE_NIC)

特点:

  • 使用 InfiniBand Verbs 进行 RDMA 传输
  • 支持 RoCE (RDMA over Converged Ethernet)
  • 使用队列对(Queue Pairs)实现并行传输

核心实现:

// 1. 创建队列对
CreateQueuePair(cfg, protectDomain, completionQueue, queuePair);

// 2. 初始化队列对
InitQueuePair(queuePair, port, rdmaAccessFlags);

// 3. 转换到 RTR (Ready to Receive)
TransitionQpToRtr(queuePair, ...);

// 4. 转换到 RTS (Ready to Send)
TransitionQpToRts(queuePair);

// 5. 执行 RDMA Write
ibv_post_send(queuePair, &sendWorkRequest, &badWorkReq);

// 6. 轮询完成队列
while (numComplete < qpCount) {
    nc = ibv_poll_cq(completionQueue, 1, &wc);
    if (nc > 0) numComplete++;
}

支持特性:

  • RoCE v1 和 v2
  • IPv4 和 IPv6
  • Relaxed Ordering 优化
  • 多队列对并行传输

3. 内存类型 (Memory Types)

支持 7 种内存类型,适应不同的测试场景:

类型代码描述特点
MEM_CPUC粗粒度固定 CPU 内存高带宽,GPU 可访问
MEM_GPUG粗粒度 GPU 全局内存GPU 原生内存
MEM_CPU_FINEB细粒度固定 CPU 内存缓存一致性
MEM_GPU_FINEF细粒度 GPU 全局内存原子操作友好
MEM_CPU_UNPINNEDU非固定 CPU 内存可分页内存
MEM_MANAGEDM统一管理内存自动迁移
MEM_NULLN空内存只读测试

内存分配实现:

static ErrResult AllocateMemory(MemDevice memDevice, 
                                size_t numBytes, 
                                void** memPtr) {
    if (IsCpuMemType(memType)) {
        // 设置 NUMA 策略
        numa_set_preferred(memDevice.memIndex);
        
        if (memType == MEM_CPU_FINE) {
            hipHostMalloc(memPtr, numBytes, hipHostMallocNumaUser);
        } else if (memType == MEM_CPU) {
            hipHostMalloc(memPtr, numBytes, 
                         hipHostMallocNumaUser | hipHostMallocNonCoherent);
        } else if (memType == MEM_CPU_UNPINNED) {
            *memPtr = numa_alloc_onnode(numBytes, memDevice.memIndex);
        }
        
        // 验证页面分配在正确的 NUMA 节点
        CheckPages((char*)*memPtr, numBytes, memDevice.memIndex);
        
    } else if (IsGpuMemType(memType)) {
        hipSetDevice(memDevice.memIndex);
        
        if (memType == MEM_GPU) {
            hipMalloc(memPtr, numBytes);
        } else if (memType == MEM_GPU_FINE) {
            hipExtMallocWithFlags(memPtr, numBytes, hipDeviceMallocUncached);
        } else if (memType == MEM_MANAGED) {
            hipMallocManaged(memPtr, numBytes);
        }
        
        hipMemset(*memPtr, 0, numBytes);
    }
}

3. 测试执行流程

阶段 1:准备阶段 (Preparation)

1.1 内存分配
for each Transfer:
    for each source:
        AllocateMemory(srcMemDevice, numBytes)
        EnablePeerAccess(if needed)
    
    for each destination:
        AllocateMemory(dstMemDevice, numBytes)
        EnablePeerAccess(if needed)
1.2 数据准备
// 生成参考数据
PrepareReference(cfg, srcBuffer, srcBufferIdx);

// 填充公式(伪随机但可重现)
element[i] = ((i * 517) % 383 + 31) * (srcBufferIdx + 1)

// 计算预期输出
for (numSrcs sources):
    expectedOutput = sum(all sources)
1.3 子执行器配置
PrepareSubExecParams(cfg, transfer, resources);

// 数据分割策略
- 每个子执行器处理 blockBytes 的倍数
- 尽量均匀分配
- 最后一个子执行器处理剩余数据

// Single Team 模式
if (useSingleTeam):
    所有子执行器协同处理整个数组(交错访问)
else:
    每个子执行器处理独立的子数组

阶段 2:执行阶段 (Execution)

2.1 预热迭代
for i = 0 to numWarmups - 1:
    RunAllTransfers(iteration = -1)  // 不计时
    预热缓存和 GPU 内核
2.2 计时迭代
for iteration = 0 to numIterations - 1:
    startTime = now()
    
    // 并行执行所有 Executor
    for each Executor:
        async_execute:
            for each Transfer:
                if (useMultiStream):
                    LaunchInSeparateStream()
                else:
                    CombineInSingleKernel()
    
    WaitForAllExecutors()
    
    endTime = now()
    totalDuration += (endTime - startTime)
    
    if (alwaysValidate):
        ValidateResults()
2.3 时间测量机制

CPU 执行器:

auto start = std::chrono::high_resolution_clock::now();
ExecuteTransfer();
auto end = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);

GPU GFX 执行器(HIP Events):

hipEventRecord(startEvent, stream);
LaunchKernel<<<...>>>(params);
hipEventRecord(stopEvent, stream);
hipEventElapsedTime(&duration, startEvent, stopEvent);

GPU GFX 执行器(设备时间戳):

// 在内核内部
__global__ void Kernel() {
    if (threadIdx.x == 0) {
        startCycle = wall_clock64();
    }
    
    // 执行数据传输
    ...
    
    __syncthreads();
    if (threadIdx.x == 0) {
        stopCycle = wall_clock64();
    }
}

// 在主机端
duration_ms = (stopCycle - startCycle) / wallClockRate;

阶段 3:验证阶段 (Validation)

ValidateAllTransfers(cfg, transfers, resources, reference, outputBuffer);

for each Transfer:
    for each destination:
        if (validateDirect or IsCpuMemType):
            output = dstMemPtr
        else:
            hipMemcpy(outputBuffer, dstMemPtr, numBytes, hipMemcpyDefault)
            output = outputBuffer
        
        if (memcmp(output, expected, numBytes) != 0):
            // 找到第一个不匹配的位置
            for i in range(N):
                if (output[i] != expected[i]):
                    ReportError(transferIdx, i, expected[i], output[i])
                    return FATAL_ERROR

4. 带宽计算方法

4.1 单次传输带宽

TransferBandwidth (GB/s) = (numBytes / 1.0e6) / avgDurationMsec

示例:

  • 传输 1 GB 数据
  • 耗时 10 ms
  • 带宽 = (1,000,000,000 / 1,000,000) / 10 = 100 GB/s

4.2 执行器带宽

ExecutorBandwidth (GB/s) = totalBytes / avgDurationMsec

其中:
totalBytes = sum(所有该执行器处理的传输字节数)
avgDurationMsec = totalDuration / numIterations

4.3 系统总带宽

SystemBandwidth (GB/s) = totalBytesTransferred / avgTotalDurationMsec

其中:
totalBytesTransferred = sum(所有传输的字节数)
avgTotalDurationMsec = 墙上时钟时间 / numIterations

4.4 开销计算

Overhead (ms) = avgTotalDuration - min(所有执行器的持续时间)

开销表示系统同步、调度等非数据传输的时间消耗。

4.5 每迭代统计(可选)

当启用 recordPerIteration 时:

for each iteration:
    记录:
    - perIterMsec[i]: 该迭代的持续时间
    - perIterCUs[i]: 使用的计算单元 (XCC:CU)
    
计算:
    - 标准差:variance from avgBandwidth
    - 最小/最大带宽
    - 带宽稳定性

5. 性能优化技术

5.1 数据分块 (Data Blocking)

原理:

blockBytes = 256;  // 默认值,可配置
每个子执行器处理的数据量为 blockBytes 的倍数

优势:

  • 缓存行对齐(通常 64 或 128 字节)
  • 减少跨缓存行访问
  • 提高内存访问效率

5.2 向量化访问

GPU 实现:

// 使用 float4 代替 float
float4* srcFloat4 = (float4*)src;
float4* dstFloat4 = (float4*)dst;

float4 val = srcFloat4[idx];  // 一次读取 16 字节
dstFloat4[idx] = val;         // 一次写入 16 字节

性能提升:

  • 减少 4 倍的内存事务数量
  • 提高内存总线利用率
  • 现代 GPU 对向量访问优化良好

5.3 循环展开 (Loop Unrolling)

实现:

template <int UNROLL>
__global__ void Kernel() {
    float4 val[UNROLL];  // 寄存器数组
    
    #pragma unroll
    for (int u = 0; u < UNROLL; u++) {
        val[u] = src[idx + u * stride];
    }
    
    #pragma unroll
    for (int u = 0; u < UNROLL; u++) {
        dst[idx + u * stride] = val[u];
    }
}

优势:

  • 减少循环控制开销
  • 提高指令级并行 (ILP)
  • 隐藏内存延迟
  • 支持 1-8 的展开因子

5.4 Wavefront 排序优化

6 种排序模式:

// Mode 0: U,W,C - 适合连续访问
unroll, wave, cu
stride = 1, UNROLL, UNROLL * nWaves

// Mode 1: U,C,W - 适合跨 CU 分布
unroll, cu, wave
stride = 1, UNROLL, UNROLL * nTeams

// Mode 2: W,U,C - Wave 优先
wave, unroll, cu
stride = 1, nWaves, nWaves * UNROLL

// Mode 3: W,C,U - Wave 和 CU 优先
wave, cu, unroll
stride = 1, nWaves, nWaves * nTeams

// Mode 4: C,U,W - CU 优先
cu, unroll, wave
stride = 1, nTeams, nTeams * UNROLL

// Mode 5: C,W,U - CU 和 Wave 优先
cu, wave, unroll
stride = 1, nTeams, nTeams * nWaves

选择策略:

  • 连续访问:模式 0 或 2
  • 分散访问:模式 1 或 4
  • 负载均衡:模式 3 或 5

5.5 流并行 (Stream Parallelism)

单流模式(默认):

// 所有传输合并到一个内核启动
dim3 gridSize(xccDim, totalSubExecs, 1);
Kernel<<<gridSize, blockSize, 0, stream>>>(allParams);

多流模式:

// 每个传输使用独立的流
for each Transfer:
    Kernel<<<gridSize, blockSize, 0, stream[i]>>>(params[i]);

优势:

  • 提高 GPU 利用率
  • 允许并发执行
  • 更好的硬件队列利用
  • 需要设置 GPU_MAX_HW_QUEUES 环境变量

5.6 P2P 和 Large BAR 优化

P2P 启用:

EnablePeerAccess(int deviceId, int peerDeviceId) {
    hipDeviceCanAccessPeer(&canAccess, deviceId, peerDeviceId);
    if (canAccess) {
        hipDeviceEnablePeerAccess(peerDeviceId, 0);
    }
}

Large BAR 检查:

for each GPU:
    hipDeviceGetAttribute(&isLargeBar, 
                         hipDeviceAttributeIsLargeBar, 
                         gpuIndex);
    if (!isLargeBar) {
        ReportWarning("Large BAR not enabled for GPU %d", gpuIndex);
    }

优势:

  • 直接 GPU-to-GPU 传输
  • 无需 CPU 中转
  • 降低延迟,提高带宽

5.7 NUMA 优化

CPU 线程绑定:

numa_run_on_node(numaNode);  // 在指定 NUMA 节点运行

内存分配策略:

numa_set_preferred(numaNode);      // 设置首选 NUMA 节点
ptr = numa_alloc_onnode(size, node);  // 在指定节点分配

页面验证:

CheckPages(ptr, numBytes, targetNumaNode) {
    move_pages(0, numPages, pages, NULL, status, 0);
    for each page:
        if (status[i] != targetNumaNode) {
            ReportError("Page not on correct NUMA node");
        }
}

6. 测试场景支持

6.1 单向传输模式

Host-to-Device (H2D)
Transfer: C0 -> G0 (CPU内存 -> GPU内存)
Executor: CPU, GPU_GFX, 或 GPU_DMA

示例:
1 1 C0 G0 G0  // 使用 GPU_GFX
1 1 C0 D0 G0  // 使用 GPU_DMA
Device-to-Host (D2H)
Transfer: G0 -> C0 (GPU内存 -> CPU内存)
Executor: CPU, GPU_GFX, 或 GPU_DMA

示例:
1 1 G0 G0 C0  // 使用 GPU_GFX
1 1 G0 D0 C0  // 使用 GPU_DMA
Device-to-Device (D2D)
Transfer: G0 -> G1 (GPU0 -> GPU1)
需要:P2P 支持,Large BAR

示例:
1 1 G0 G0 G1  // GPU0 执行
1 1 G0 G1 G1  // GPU1 执行
RDMA Transfer
Transfer: G0 -> RemoteGPU (通过 NIC)
Executor: NIC

示例:
1 1 G0 N0.1 G1  // NIC0 到 NIC1

6.2 多源多目标模式

数据聚合(多源单目标)
Transfer: C0 + G0 -> G1
操作:dst = src_cpu + src_gpu

示例:
1 1 C0 G0 G1 G1  // 两个源,一个目标
数据广播(单源多目标)
Transfer: G0 -> C0 + G1
操作:dst_cpu = dst_gpu = src

示例:
1 1 G0 G0 C0 G1  // 一个源,两个目标
复杂模式(多源多目标)
Transfer: C0 + G0 + G1 -> C1 + G2 + G3
操作:sum = C0 + G0 + G1
     C1 = G2 = G3 = sum

示例:
1 1 C0 G0 G1 G0 C1 G2 G3  // 三源三目标

6.3 并发测试

并行传输:

# 4 个并行传输
4 1 C0 G0 G0  C1 G1 G1  G0 G2 G2  G1 G3 G3

执行:所有传输同时执行,测量总带宽

流水线测试:

# H2D + 计算 + D2H 流水线模拟
3 1 C0 D0 G0  N N G0  G0 D0 C0

6.4 细粒度控制

XCC 指定
# 在 GPU0 的 XCC1 上执行
1 1 G0 G0.1 G1

# XCC 亲和性表
gfx.prefXccTable = [[0, 1], [1, 0]]
SDMA 引擎指定
# 使用 GPU0 的 SDMA 引擎 0
1 1 G0 D0.0 C0

# 使用 GPU0 的 SDMA 引擎 1
1 1 G0 D0.1 C0
CU 掩码
// 只使用 CU 0, 2, 4, 6
cfg.gfx.cuMask = {0x55, 0x00, 0x00, 0x00};  // 二进制: 01010101

7. 配置选项详解

7.1 通用选项 (General Options)

struct GeneralOptions {
    int numIterations;       // 迭代次数(负数表示运行秒数)
    int numSubIterations;    // 每次迭代的子迭代次数
    int numWarmups;          // 预热迭代次数
    int recordPerIteration;  // 记录每次迭代的详细信息
    int useInteractive;      // 交互模式(暂停等待用户输入)
};

// 默认值
numIterations = 10;
numSubIterations = 1;
numWarmups = 3;

使用示例:

// 运行 100 次迭代
cfg.general.numIterations = 100;

// 运行 60 秒
cfg.general.numIterations = -60;

// 每次迭代重复 10 次
cfg.general.numSubIterations = 10;

7.2 数据选项 (Data Options)

struct DataOptions {
    int           alwaysValidate;   // 每次迭代后验证
    int           blockBytes;       // 数据块大小(字节)
    int           byteOffset;       // 内存分配偏移
    vector<float> fillPattern;      // 源数据填充模式
    int           validateDirect;   // 直接在 GPU 上验证
    int           validateSource;   // 验证源数据
};

// 默认值
blockBytes = 256;
byteOffset = 0;

7.3 GFX 选项 (GFX Options)

struct GfxOptions {
    int                 blockSize;       // 线程块大小
    vector<uint32_t>    cuMask;          // CU 掩码
    vector<vector<int>> prefXccTable;    // XCC 亲和性表
    int                 unrollFactor;    // 展开因子
    int                 useHipEvents;    // 使用 HIP Events 计时
    int                 useMultiStream;  // 使用多流
    int                 useSingleTeam;   // 单团队模式
    int                 waveOrder;       // Wavefront 排序
};

// 默认值
blockSize = 256;
unrollFactor = 4;
useHipEvents = 1;
useMultiStream = 0;
useSingleTeam = 0;
waveOrder = 0;

// 约束
blockSize ∈ {64, 128, 192, 256, 320, 384, 448, 512}
unrollFactor ∈ {1, 2, 3, 4, 5, 6, 7, 8}
waveOrder ∈ {0, 1, 2, 3, 4, 5}

7.4 DMA 选项 (DMA Options)

struct DmaOptions {
    int useHipEvents;   // 使用 HIP Events 计时
    int useHsaCopy;     // 使用 HSA copy(而非 HIP copy)
};

// 默认值
useHipEvents = 1;
useHsaCopy = 0;

7.5 NIC 选项 (NIC Options)

struct NicOptions {
    vector<int> closestNics;     // 每个 GPU 的最近 NIC 列表
    int         ibGidIndex;      // GID 索引(-1 为自动)
    uint8_t     ibPort;          // IB 端口号
    int         ipAddressFamily; // IP 地址族(4 或 6)
    int         maxRecvWorkReq;  // 最大接收工作请求数
    int         maxSendWorkReq;  // 最大发送工作请求数
    int         queueSize;       // 完成队列大小
    int         roceVersion;     // RoCE 版本(1 或 2)
    int         useRelaxedOrder; // 使用松弛排序
    int         useNuma;         // 切换到最近的 NUMA 节点
};

// 默认值
ibPort = 1;
ipAddressFamily = 4;
maxRecvWorkReq = 16;
maxSendWorkReq = 16;
queueSize = 100;
roceVersion = 2;
useRelaxedOrder = 1;
useNuma = 0;

八、结果输出格式

8.1 测试结果结构

struct TestResults {
    int    numTimedIterations;           // 计时迭代次数
    size_t totalBytesTransferred;        // 总传输字节数
    double avgTotalDurationMsec;         // 平均总持续时间
    double avgTotalBandwidthGbPerSec;    // 平均总带宽
    double overheadMsec;                 // 开销时间
    
    map<ExeDevice, ExeResult> exeResults;  // 每执行器结果
    vector<TransferResult>    tfrResults;  // 每传输结果
    vector<ErrResult>         errResults;  // 错误列表
};

8.2 执行器结果

struct ExeResult {
    size_t      numBytes;                // 总字节数
    double      avgDurationMsec;         // 平均持续时间
    double      avgBandwidthGbPerSec;    // 平均带宽
    double      sumBandwidthGbPerSec;    // 传输带宽总和
    vector<int> transferIdx;             // 传输索引列表
};

输出示例:

Executor: GPU 0 (GFX)
  Bandwidth:     125.50 GB/s
  Duration:      79.68 ms
  Total Bytes:   10000000000
  Sum Bandwidth: 130.20 GB/s
  Transfers:     [0, 1, 2]

8.3 传输结果

struct TransferResult {
    size_t numBytes;                     // 字节数
    double avgDurationMsec;              // 平均持续时间
    double avgBandwidthGbPerSec;         // 平均带宽
    
    vector<double> perIterMsec;          // 每次迭代时间
    vector<set<pair<int,int>>> perIterCUs;  // 每次迭代使用的 CU
    
    ExeDevice exeDevice;                 // 执行器
    ExeDevice exeDstDevice;              // 目标执行器(NIC)
};

输出示例:

Transfer 0: C0 -> G0 (GPU_GFX 0)
  Bandwidth:   42.50 GB/s
  Duration:    23.53 ms
  Bytes:       1000000000
  
  Per Iteration:
    Iter 000: 42.30 GB/s, 23.64 ms, CUs: (0:0,0:1,0:2,0:3)
    Iter 001: 42.70 GB/s, 23.42 ms, CUs: (0:0,0:1,0:2,0:3)
    ...

8.4 错误结果

enum ErrType {
    ERR_NONE  = 0,   // 无错误
    ERR_WARN  = 1,   // 警告
    ERR_FATAL = 2,   // 致命错误
};

struct ErrResult {
    ErrType     errType;  // 错误类型
    std::string errMsg;   // 错误消息
};

示例:

WARN: Large BAR not enabled for GPU 1. Multi-GPU access may be limited.
WARN: GPU 0 requests 128 total CUs however only 110 available. Serialization will occur.
FATAL: Transfer 3: Unexpected mismatch at index 1024: Expected 31.00000 Actual: 0.00000

九、高级特性

9.1 硬件拓扑检测

PCIe 树构建:

// 自动检测系统 PCIe 拓扑
GetPCIeTreeRoot();

// 示例拓扑输出
pci0000:00
├── 0000:00:01.0 (GPU 0)
├── 0000:00:02.0 (GPU 1)
├── 0000:00:03.0 (NIC 0)
└── 0000:00:04.0
    └── 0000:01:00.0 (GPU 2)

最近设备检测:

// 查找离 GPU 最近的 NUMA 节点
numaNode = GetClosestCpuNumaToGpu(gpuIndex);

// 查找离 GPU 最近的 NIC
nicIndex = GetClosestNicToGpu(gpuIndex);

// 基于 PCIe 树的最低公共祖先 (LCA) 算法
// 考虑总线号距离

9.2 环境变量支持

# GPU 最大硬件队列数
export GPU_MAX_HW_QUEUES=8

# 禁用 SDMA
export HSA_ENABLE_SDMA=0

# NUMA 策略
# (通过 libnuma 自动处理)

9.3 参数验证

配置验证:

ConfigOptionsHaveErrors(cfg, errors);

检查:
- numWarmups >= 0
- blockBytes 是 4 的倍数
- blockSize 是 64 的倍数且 <= MAX_BLOCKSIZE
- unrollFactor <= MAX_UNROLL
- waveOrder < 6
- XCC 表维度正确
- NIC 列表与 GPU 数量匹配
- Large BAR 启用状态

传输验证:

TransfersHaveErrors(cfg, transfers, errors);

检查:
- numBytes > 0
- 至少一个源或目标
- 执行器索引有效
- 内存设备索引有效
- 子执行器数量合理
- SDMA 引擎可用性
- P2P 访问可用性

9.4 交互模式

if (useInteractive) {
    printf("Memory prepared:\n");
    // 显示所有内存地址
    
    printf("Hit <Enter> to continue: ");
    scanf("%*c");
    
    // 执行传输
    
    printf("Transfers complete. Hit <Enter> to continue: ");
    scanf("%*c");
}

用途:

  • 调试内存分配
  • 使用外部工具(如 rocprof)
  • 手动验证硬件状态

十、实际应用示例

10.1 基本带宽测试

H2D 带宽:

# 命令行格式
rocm_bandwidth_test "1 16 C0 G0 G0"

解释:
- 1 个传输
- 16 个子执行器(线程块)
- C0: CPU NUMA 节点 0
- G0 (第一个): GPU 0 执行
- G0 (第二个): 写入 GPU 0

D2H 带宽:

rocm_bandwidth_test "1 16 G0 G0 C0"

D2D 带宽:

rocm_bandwidth_test "1 16 G0 G0 G1"

10.2 多 GPU 测试

所有 GPU 对之间的带宽:

# 4 GPU 系统:16 个传输(4x4)
rocm_bandwidth_test "16 8 \
  G0 G0 G0  G0 G0 G1  G0 G0 G2  G0 G0 G3 \
  G1 G1 G0  G1 G1 G1  G1 G1 G2  G1 G1 G3 \
  G2 G2 G0  G2 G2 G1  G2 G2 G2  G2 G2 G3 \
  G3 G3 G0  G3 G3 G1  G3 G3 G2  G3 G3 G3"

10.3 高级模式测试

指定传输大小和子执行器:

# -N 表示高级模式
rocm_bandwidth_test "-4 \
  C0 G0 G0 16 1G \
  G0 G0 C0 16 1G \
  G0 G0 G1 8  2G \
  G1 G1 G0 8  2G"

解释:
- 4 个传输(高级模式)
- 每个传输指定:src exe dst numCUs size

10.4 配置文件示例

JSON 配置:

{
  "general": {
    "numIterations": 100,
    "numWarmups": 5,
    "recordPerIteration": 1
  },
  "data": {
    "blockBytes": 256,
    "validateDirect": 1
  },
  "gfx": {
    "blockSize": 256,
    "unrollFactor": 4,
    "useMultiStream": 1,
    "waveOrder": 0
  }
}

十一、性能调优指南

11.1 参数选择建议

blockSize 选择:

  • 小数据量:128 或 192
  • 中等数据量:256(推荐)
  • 大数据量:384 或 512
  • 考虑寄存器压力

unrollFactor 选择:

  • 顺序访问:4-8
  • 随机访问:1-2
  • 平衡选择:4(默认)

waveOrder 选择:

  • 连续内存:0 (U,W,C)
  • 分散内存:1 (U,C,W)
  • 负载均衡:3 (W,C,U)

numSubExecs 选择:

  • CPU: = NUMA 节点上的核心数
  • GPU: = CU 数量(或更少以避免序列化)
  • 避免过度订阅

11.2 常见性能问题

问题 1:带宽低于预期

可能原因:
1. Large BAR 未启用
2. P2P 不可用
3. 内存未在正确的 NUMA 节点
4. SDMA 被禁用

解决方案:
- 检查 BIOS 设置
- 验证 lspci 输出
- 使用 numactl 验证
- 检查 HSA_ENABLE_SDMA

问题 2:GPU 利用率低

可能原因:
1. 子执行器太少
2. 数据量太小
3. 未使用多流

解决方案:
- 增加 numSubExecs
- 增加传输大小
- 启用 useMultiStream

问题 3:验证失败

可能原因:
1. 内存损坏
2. P2P 配置错误
3. 竞态条件

解决方案:
- 运行内存测试
- 检查 P2P 拓扑
- 增加同步点

11.3 性能分析工具集成

ROCProfiler:

# 启用交互模式
rocm_bandwidth_test --interactive "..."

# 在另一个终端
rocprof --hip-trace ./rocm_bandwidth_test ...

ROCTracer:

export HSA_TOOLS_LIB=/opt/rocm/lib/libroctracer64.so
rocm_bandwidth_test "..."

十二、总结

核心测试原理

ROCm Bandwidth Test (RBT-NG) 通过以下核心机制实现全面的带宽性能测试:

  1. Transfer 抽象:统一的数据传输模型,支持多源多目标
  2. 多执行器架构:CPU、GPU GFX、GPU DMA、NIC 四种执行方式
  3. 精确计时:GPU 硬件时间戳和 HIP Events 双模式
  4. 内存灵活性:7 种内存类型覆盖所有使用场景
  5. 并行优化:向量化、循环展开、流并行等技术
  6. 拓扑感知:PCIe 树分析,最优设备选择

技术特点

  • 高精度:纳秒级 GPU 时间戳,准确测量带宽
  • 高灵活性:丰富的配置选项适应各种场景
  • 高可靠性:完善的验证机制确保数据正确性
  • 高性能:多种优化技术最大化硬件利用率
  • 可扩展性:插件架构易于添加新功能

适用场景

  1. 系统性能评估:CPU-GPU 互连带宽基准测试
  2. 硬件验证:P2P、Large BAR、NUMA 配置验证
  3. 应用优化:找出数据传输瓶颈
  4. 多 GPU 调优:评估 GPU 间通信性能
  5. 网络性能:RDMA 和 RoCE 带宽测试

最佳实践

  1. 始终启用 Large BAR
  2. 验证 P2P 拓扑
  3. 使用多次迭代获得稳定结果
  4. 根据数据特征选择参数
  5. 监控 GPU 利用率和温度
  6. 使用 NUMA 绑定优化 CPU 性能
  7. 结合 profiling 工具深入分析
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值