📋 概述

TransferBench (TB) 是 ROCm Bandwidth Test 的核心带宽测试引擎,版本 1.60。它被封装为插件形式集成到 RBT 框架中。


🏗️ 整体架构

rocm_bandwidth_test
├── plugins/tb/                           # TB 插件封装层
│   ├── src/
│   │   ├── plugin_transferbench.cpp      # 插件入口点
│   │   └── view_transferbench.cpp        # 视图层(UI相关)
│   ├── include/
│   │   ├── plugin_transferbench.hpp
│   │   └── view_transferbench.hpp
│   └── transferbench/                    # TB 客户端封装
│       ├── src/Client.cpp                # 命令行解析和测试执行
│       └── include/
│           ├── Client.hpp
│           ├── Topology.hpp
│           ├── EnvVars.hpp
│           └── plugin_tb_link.hpp        # 插件链接桥梁
└── deps/external/TransferBench/          # TB 核心实现
    └── src/header/TransferBench.hpp      # 单头文件实现

🔌 插件集成机制

1. 插件注册(plugin_transferbench.cpp)

// 宏注册插件元数据
AMD_WORK_BENCH_PLUGIN_SETUP(
    "tb",                                    // 插件名称
    "Linux System Tools Team @AMD",          // 作者
    "Builtin: TransferBench",                // 描述
    "1.0.0"                                  // 版本
)
{
    using namespace wb_plugin_tb;
    register_plugin_view();                  // 注册视图组件
}

2. 插件入口点

// 插件主入口
int32_t plugin_main(int argc, char** argv)
{
    // 调用 TransferBench 原始 main() 函数(重命名为 plugin_main_entry)
    auto plugin_run_result = plugin_main_entry(argc, argv);
    return plugin_run_result;
}

// TransferBench 原始主函数
extern int plugin_main_entry(int argc, char** argv);

设计思路:

  • 将原有的独立可执行程序的 main() 改名为 plugin_main_entry()
  • 保持原有功能不变,便于从静态可执行文件转换为动态插件

🎯 核心执行流程

Client.cpp 主流程

int plugin_main_entry(int argc, char** argv)
{
    1. 收集环境变量
       ├── EnvVars ev;
       └── 读取各种配置选项
    
    2. 处理命令行参数
       ├── if (argc <= 1) → 显示拓扑和帮助
       ├── 解析传输字节数 (numBytesPerTransfer)
       └── 处理单位后缀 (K/M/G)
    
    3. 运行预设测试(可选)
       └── RunPreset(ev, numBytesPerTransfer, argc, argv)
    
    4. 读取配置
       ├── 从配置文件读取
       │   └── std::ifstream cfgFile(argv[1])
       └── 或从命令行读取
           └── if (!strcmp(argv[1], "cmdline"))
    
    5. 主测试循环
       for each line in config:
         ├── ParseTransfers(line, transfers)     # 解析传输配置
         ├── 处理可变子执行器
         └── for each byte size:
             ├── TransferBench::RunTransfers()   # 执行传输
             ├── PrintResults()                   # 打印结果
             └── PrintErrors()                    # 错误处理
}

🔑 核心 API 调用

1. TransferBench::ParseTransfers()

功能: 解析传输配置字符串

ErrResult ParseTransfers(
    std::string str,              // 配置字符串
    std::vector<Transfer>& transfers  // 输出:传输列表
);

配置格式示例:

# 语法: Src -> Executor:NumSubExecs -> Dst
G0 -> G0:8 -> G0        # GPU0 内部传输,8个子执行器
G0 -> C0:1 -> C0        # GPU0 -> CPU0

2. TransferBench::RunTransfers()

功能: 执行一组并行传输

bool RunTransfers(
    ConfigOptions const& config,      // 配置选项
    vector<Transfer> const& transfers,  // 传输列表
    TestResults& results              // 输出:测试结果
);

返回: true 表示成功,false 表示有致命错误

3. TransferBench::AllocateMemory()

功能: 分配指定类型的内存

static ErrResult AllocateMemory(
    MemDevice memDevice,    // 内存设备(类型+索引)
    size_t numBytes,        // 字节数
    void** memPtr           // 输出:内存指针
);

支持的内存类型:

  • MEM_CPU - 粗粒度固定 CPU 内存
  • MEM_GPU - 粗粒度 GPU 全局内存
  • MEM_CPU_FINE - 细粒度固定 CPU 内存
  • MEM_GPU_FINE - 细粒度 GPU 全局内存
  • MEM_CPU_UNPINNED - 未固定 CPU 内存
  • MEM_MANAGED - 统一内存

4. TransferBench::GetNumExecutors()

功能: 获取可用执行器数量

int GetNumExecutors(ExeType exeType);

执行器类型:

  • EXE_CPU © - CPU 执行器(子执行器 = CPU 线程)
  • EXE_GPU_GFX (G) - GPU 内核执行器(子执行器 = CU/线程块)
  • EXE_GPU_DMA (D) - GPU SDMA 执行器
  • EXE_NIC (I) - NIC RDMA 执行器
  • EXE_NIC_NEAREST (N) - 最近的 NIC RDMA 执行器

5. TransferBench::GetNumSubExecutors()

功能: 获取子执行器数量

int GetNumSubExecutors(ExeDevice exeDevice);

示例:

  • {EXE_GPU_GFX, 0} → 返回 GPU 0 的 CU 数量(如 40)
  • {EXE_CPU, 0} → 返回 CPU 线程数

📊 关键数据结构

Transfer(传输定义)

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

TestResults(测试结果)

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

ExeDevice(执行器设备)

struct ExeDevice
{
    ExeType exeType;     // 执行器类型(C/G/D/I/N)
    int32_t exeIndex;    // 执行器索引(设备编号)
};

MemDevice(内存设备)

struct MemDevice
{
    MemType memType;     // 内存类型(C/G/B/F/U/M)
    int32_t memIndex;    // 设备索引
};

⚙️ 配置选项

ConfigOptions(完整配置)

struct ConfigOptions
{
    GeneralOptions general;  // 通用选项
    DataOptions    data;     // 数据选项
    GfxOptions     gfx;      // GPU 图形执行器选项
    DmaOptions     dma;      // DMA 执行器选项
    NicOptions     nic;      // NIC 执行器选项
};

GeneralOptions(通用选项)

struct GeneralOptions
{
    int numIterations      = 10;   // 计时迭代次数(负数表示按秒运行)
    int numSubIterations   = 1;    // 每次迭代的子迭代次数
    int numWarmups         = 3;    // 预热迭代次数
    int recordPerIteration = 0;    // 记录每次迭代的计时信息
    int useInteractive     = 0;    // 在传输循环前暂停等待用户输入
};

GfxOptions(GPU 执行器选项)

struct GfxOptions
{
    int                 blockSize      = 256;   // 线程块大小(必须是 64 的倍数)
    vector<uint32_t>    cuMask         = {};    // CU 掩码位向量
    vector<vector<int>> prefXccTable   = {};    // [src][dst] 首选 XCD 表
    int                 unrollFactor   = 4;     // 内核展开因子
    int                 useHipEvents   = 1;     // 使用 HIP 事件计时
    int                 useMultiStream = 0;     // 使用多流
    int                 useSingleTeam  = 0;     // 所有子执行器组成单个团队
    int                 waveOrder      = 0;     // 波前排序
};

🔄 执行流程详解

1. 配置解析阶段

配置字符串: "G0 -> G0:8 -> G0 1048576"
    ↓
ParseTransfers()
    ↓
Transfer {
    numBytes = 1048576,         // 1 MB
    srcs = [{MEM_GPU, 0}],     // GPU 0 内存
    dsts = [{MEM_GPU, 0}],     // GPU 0 内存
    exeDevice = {EXE_GPU_GFX, 0},  // GPU 0 执行器
    numSubExecs = 8            // 8 个 CU
}

2. 内存分配阶段

RunTransfers() 内部:
    for each Transfer:
        for each src in Transfer.srcs:
            AllocateMemory(src, numBytes, &srcMem)
        for each dst in Transfer.dsts:
            AllocateMemory(dst, numBytes, &dstMem)

3. 传输执行阶段

循环流程:
    Warmup iterations (不计时):
        for i = 0 to numWarmups:
            ExecuteTransfer()
    
    Timed iterations (计时):
        StartTimer()
        for i = 0 to numIterations:
            ExecuteTransfer()
            if recordPerIteration:
                RecordIterationTime()
        StopTimer()

4. 结果计算阶段

计算带宽:
    avgDurationMsec = totalTime / numIterations
    avgBandwidthGbPerSec = (numBytes / 1.0E9) / avgDurationMsec * 1000.0

🎨 执行器类型详解

GPU GFX 执行器(EXE_GPU_GFX)

特点:

  • 使用 GPU 内核(kernel)执行传输
  • 子执行器 = CU(计算单元)或线程块
  • 支持 CU 掩码精细控制
  • 支持多 XCD(多芯片设计)

内核参数:

- blockSize: 线程块大小(256- unrollFactor: 循环展开因子(4- waveOrder: 波前调度顺序
- cuMask: 位掩码指定使用哪些 CU

GPU DMA 执行器(EXE_GPU_DMA)

特点:

  • 使用 SDMA 引擎执行传输
  • 不涉及计算单元,纯硬件 DMA
  • 支持 HIP 或 HSA 拷贝

选项:

- useHipEvents: 使用 HIP 事件计时
- useHsaCopy: 使用 HSA 拷贝(而非 HIP)

CPU 执行器(EXE_CPU)

特点:

  • 使用 CPU 线程执行 memcpy
  • 子执行器 = CPU 线程
  • 支持 NUMA 绑定

📈 结果输出格式

标准输出格式

Test 1:
 Executor: GFX 00 |   230.456 GB/s |    4.532 ms |      1048576 bytes |  230.456 GB/s (sum)
     Transfer 00  |   230.456 GB/s |    4.532 ms |      1048576 bytes | G0 -> G00:8 -> G0
 Aggregate (CPU)  |   230.456 GB/s |    4.532 ms |      1048576 bytes | Overhead: 0.123 ms

CSV 输出格式

Test#,Transfer#,NumBytes,Src,Exe,Dst,CUs,BW(GB/s),Time(ms),SrcAddr,DstAddr
1,0,1048576,G0,G0,G0,8,230.456,4.532,0x7f1234,0x7f5678

🛠️ 重要辅助函数

1. GetClosestCpuNumaToGpu()

int GetClosestCpuNumaToGpu(int gpuIndex);

返回离 GPU 最近的 NUMA 节点索引。

2. GetClosestNicToGpu()

int GetClosestNicToGpu(int gpuIndex);

返回离 GPU 最近的 NIC 索引(用于 RDMA)。

3. GetNumExecutorSubIndices()

int GetNumExecutorSubIndices(ExeDevice exeDevice);

返回执行器子索引数量(如 XCD 数量、DMA 引擎数量)。


🔍 环境变量控制

TransferBench 通过 EnvVars 类读取环境变量:

struct EnvVars
{
    ConfigOptions ToConfigOptions();  // 转换为配置选项
    void DisplayEnvVars();            // 显示环境变量
    static void DisplayUsage();       // 显示使用说明
    
    // 成员变量(部分)
    int outputToCsv;                  // CSV 输出模式
    int showIterations;               // 显示每次迭代
    int samplingFactor;               // 采样因子
    int minNumVarSubExec;             // 最小可变子执行器数
    int maxNumVarSubExec;             // 最大可变子执行器数
};

🧪 测试流程示例

示例 1: GPU 内部带宽测试

# 配置
G0 -> G0:40 -> G0 1048576

# 执行流程
1. ParseTransfers() 解析配置
2. AllocateMemory({MEM_GPU, 0}, 1MB, &src)
3. AllocateMemory({MEM_GPU, 0}, 1MB, &dst)
4. 预热 3 次迭代
5. 计时执行 10 次迭代
6. 计算平均带宽
7. 打印结果: ~230 GB/s
8. DeallocateMemory()

示例 2: Host-to-Device 带宽测试

# 配置
C0 -> G0:8 -> G0 1048576

# 执行流程
1. AllocateMemory({MEM_CPU, 0}, 1MB, &src)   # 固定 CPU 内存
2. AllocateMemory({MEM_GPU, 0}, 1MB, &dst)   # GPU 内存
3. GPU 08 个 CU 执行传输
4. 结果: ~25 GB/s (PCIe 4.0 x16 理论值)

🚨 错误处理

ErrResult 结构

enum ErrType {
    ERR_NONE = 0,   // 无错误
    ERR_WARN = 1,   // 警告(结果可能不准确)
    ERR_FATAL = 2   // 致命错误(结果无效)
};

struct ErrResult {
    ErrType errType;
    std::string errMsg;
};

错误检查宏

#define ERR_CHECK(expression)                     \
  do {                                            \
    ErrResult err = (expression);                 \
    if (err.errType == ERR_FATAL) return err;     \
  } while(0)

使用示例:

ERR_CHECK(AllocateMemory(device, size, &ptr));
ERR_CHECK(RunTransfers(config, transfers, results));

📚 典型使用场景

1. 基准测试

./rocm_bandwidth_test test_simple.cfg

2. P2P 测试(如果支持)

./rocm_bandwidth_test test_p2p.cfg

3. 数据大小扫描

./rocm_bandwidth_test test.cfg 0  # 0 表示扫描多种大小

4. 预设测试

./rocm_bandwidth_test <preset_name> [bytes]

🎯 设计亮点

1. 单头文件实现

  • 整个 TransferBench 核心在单个 .hpp 文件中
  • 便于集成和分发
  • 降低编译依赖

2. 插件化架构

  • 原本独立的工具转换为插件
  • 保持向后兼容
  • 易于集成到更大的测试框架

3. 灵活的执行器模型

  • 支持多种执行器类型(CPU/GPU/DMA/NIC)
  • 子执行器概念支持精细控制
  • 可变子执行器数量自动优化

4. 全面的内存类型支持

  • 粗粒度/细粒度
  • 固定/未固定
  • 统一内存
  • 支持各种组合测试

5. 详细的性能分析

  • 每次迭代计时
  • 每个执行器带宽
  • 每个传输带宽
  • 聚合带宽和开销分析

🔧 关键实现技术

HIP/ROCm API

// 设备管理
hipGetDeviceCount(&numGpus);
hipSetDevice(gpuIndex);

// 内存管理
hipMalloc(&ptr, size);
hipHostMalloc(&ptr, size, hipHostMallocDefault);
hipMemcpy(dst, src, size, hipMemcpyDeviceToDevice);

// P2P 访问
hipDeviceCanAccessPeer(&canAccess, gpu0, gpu1);
hipDeviceEnablePeerAccess(gpu1, 0);

// 计时
hipEventCreate(&start);
hipEventRecord(start);
hipEventElapsedTime(&ms, start, stop);

HSA API

// 用于更底层的控制
hsa_agent_t agent;
hsa_amd_memory_pool_t pool;
hsa_amd_memory_copy(dst, src, size);

NUMA 库

// NUMA 节点管理
numa_available();
numa_bind(node);
numa_alloc_onnode(size, node);

📝 总结

TransferBench 是一个功能强大、设计精良的带宽测试工具:

核心优势:

  1. ✅ 支持多种执行器和内存类型
  2. ✅ 灵活的配置系统
  3. ✅ 详细的性能分析
  4. ✅ 插件化集成
  5. ✅ 全面的错误处理

关键 API:

  • ParseTransfers() - 配置解析
  • RunTransfers() - 执行传输
  • AllocateMemory() - 内存分配
  • GetNumExecutors() - 查询执行器

适用场景:

  • GPU 内部带宽测试
  • Host-Device 传输测试
  • P2P 带宽测试(专业卡)
  • 多 GPU 拓扑分析
  • 性能基准测试

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐