MPI RDMA Burst 流量测试方案
架构概述
之前使用过chatgpt和claude协助写过一些测试脚本,比如早期的:RDMA测试脚本 by ChatGPT?!,后来优化的就没更新在blog中了。以至于后来研发的同事帮忙优化的测试脚本,虽然目前可以达到一定需求,但python精度太差,都是ms级精度控制,效率不是很高。因此需要一套高性能RDMA测试脚本,满足不同场景下针对交换机的测试需求。目前看上去使用openmpi来控制perftest的思路比较合理,而且有的用户也在使用类似的方式,所以就尝试走这条路。以下是五一期间跟claude合作开发的测试系统,里面很多细节还有待完善中。
设计思想
本方案采用 MPI 指挥 + perftest 执行的分离架构:
- MPI(mpi4py):只做”指挥官”——进程启动、全局同步(Barrier)、决策广播(Bcast)
- perftest(ib_write_bw):做”士兵”——真正发送 RDMA Write 流量,由 HCA 硬件直接执行
- 流量控制:通过 SIGSTOP/SIGCONT 信号(training 模式,毫秒级突发)、perftest 内置 rate_limit/burst_size(storage 模式,微秒级突发,但并行打流时机会错开)、或两者结合(sync_burst 模式,微秒级突发,同步并行打流)
文件结构
/root/rdma_burst/
├── config.py # 服务器/网卡配置(扩展时只改这个文件)
├── run.py # 部署+启动入口(生成 hostfile、nic_map,rsync,调 mpirun)
├── perftest_burst.py # MPI 主程序(每个 rank 启动/控制 perftest 子进程)
├── nic_wrapper.sh # MPI rank 的 NIC 绑定 + CPU 亲和性
├── libsigburst.c # C 共享库源码(sync_burst 模式用,μs 精度信号控制)
├── libsigburst.so # 自动编译:deploy 时在每台服务器上 gcc 编译
├── hostfile # 自动生成:MPI hostfile
└── nic_map.txt # 自动生成:rank → NIC/IP/CPU 映射
执行流程
run.py
├── 1. 读取 config.py → 生成 hostfile + nic_map.txt
├── 2. rsync 所有文件到每台服务器
├── 3. gcc 编译 libsigburst.so(每台服务器)
├── 4. pkill 残留 perftest 进程
└── 5. exec mpirun → 每台服务器启动 N 个 rank
│
└── 每个 rank (perftest_burst.py)
├── 读取 nic_map.txt 确定自己的 NIC/IP
├── 根据 mode 计算流量对 (我要跟谁通信)
├── Phase 1: 启动 perftest server 进程 (接收端)
├── Barrier 同步
├── Phase 2: 启动 perftest client 进程 (发送端)
│ └── TCP 握手用管理网 IP (10.26.x.x)
│ RDMA 数据由 -d mlx5_X 决定走哪张卡
├── Phase 3: 根据模式初始化
└── Phase 4: burst 循环或等待 duration
通信路径
系统中存在三条独立的通信路径:
- SSH 路径(启动用):run.py 通过 SSH 管理网(10.26.4.x)把文件 rsync 到远程服务器,mpirun 通过 SSH 启动远程 rank 进程。仅用于部署和进程启动。
- MPI 控制路径(同步用):MPI 的 Barrier/Bcast 走 RDMA DC transport(UCX),通过 RDMA 网卡(mlx5_X)通信。hostfile 中的管理网 IP 仅用于 SSH 启动进程,MPI 运行时通过 nic_wrapper.sh 的 UCX_NET_DEVICES 自动发现 RDMA 路径。MPI 走 RDMA 而不走管理网 TCP,是因为 RDMA Barrier 延迟 ~3μs,TCP 要 50-100μs,同步精度差距大
- perftest 数据路径(流量用):perftest 的 TCP 握手(交换 QP 元数据)走管理网 10.26.4.x(避免环境中使用VRF隔离的复杂场景),RDMA Write 数据传输由
-d mlx5_X决定走哪张卡
队列隔离机制
不管是RC亦或是UC模式,MPI 控制报文始终走 queue 0(tclass=0),perftest 数据走 queue 1(tclass=32)。这解决了如下场景中遇到的问题:
- UC/RC 模式 MPI 也在 queue 1(tclass=32),无 WRED 时 tail drop 无差别丢弃 MPI 和 perftest 包,导致MPI 崩溃
ucx_info -c 显示 UCX_DC_MLX5_TRAFFIC_CLASS=auto 是编译时默认值,运行时由 mpirun -x UCX_IB_TRAFFIC_CLASS=0 覆盖,已在run.py中实现:
root@tme92:~/rdma_burst# ucx_info -c | grep -i TRAFFIC_CLASS
UCX_DC_MLX5_TRAFFIC_CLASS=auto
UCX_RC_VERBS_TRAFFIC_CLASS=auto
UCX_RC_MLX5_TRAFFIC_CLASS=auto
UCX_UD_VERBS_TRAFFIC_CLASS=auto
UCX_UD_MLX5_TRAFFIC_CLASS=auto
RC vs UC 传输模式(–transport)
区别
RC(Reliable Connection,默认):有 ACK、有重传。丢包后 QP 检测到 NAK/超时 → 重传 → 有效速率下降。即使关闭服务器 DCQCN(roce_rp/roce_np),RC 本身的重传机制仍然会降速,CX7 仍然响应 ECN CE bit 生成 CNP。这意味着前几轮 burst 是 400G,后续 burst 因重传导致速率逐渐降低。
UC(Unreliable Connection):支持 RDMA Write,但不做 ACK、不做重传——发送端”发了就忘”。丢了就丢了,速率不受任何影响。每轮 burst 都是真正的全新 400G 全速。
使用场景
| 目标 | 用 RC 还是 UC |
| 测交换机 Buffer/OQ 水线(不受端侧干扰) | UC |
| 测 ECN+DCQCN 端到端收敛效果 | RC |
| 模拟真实训练/存储业务 | RC |
三种流量模式
all2all(全互联)
每个端点向所有其他端点发送。N 个端点 → N×(N-1) 条单向流,每个 egress port 压力被分散到多个目的端口
4 端点:12 条流,每卡:3 出 + 3 入
16 端点:240 条流,每卡:15 出 + 15 入
allreduce(双向 Ring)
端点排成环,正反两个方向同时通信。N 个端点 → 2×N 条单向流。每卡只跟环上两个邻居通信。
incast(N 打 1)
所有端点向同一个目标发送,N-1 条流全部汇聚到1个 egress port。此场景中目标端口 egress buffer 承受的压力最大。
各规模下的流数速查
| 配置 | 端点数 | all2all 每卡出流 | allreduce 每卡出流 | incast 入流(目标卡) |
| 2台4卡 | 4 | 3 | 2 | 3 |
| 8台16卡 | 16 | 15 | 2 | 15 |
| 9台18卡 | 18 | 17 | 2 | 17 |
突发模型 – Training 模式(默认)
控制机制
MPI 用 SIGSTOP/SIGCONT 信号冻结/解冻 perftest 进程
时间精度
毫秒级。瓶颈在于 SIGCONT 后 perftest 不知道何时停——它会不停地发,直到被 SIGSTOP。而 SIGSTOP 从 Python 调 os.kill() 到内核投递信号到进程被挂起,延迟和抖动在几十到几百 μs,无法精确控制 5μs 级的 burst。所以 training 模式最小可靠 burst ~1-2ms。
Burst 期间行为
所有 perftest 进程被 SIGCONT 唤醒后以 400G 线速连续发送。所有端口通过 MPI Barrier全局同步 burst——burst 开始瞬间所有端口同时从 0 跳到 400G。Grafana 上看到的带宽 = 400G × 占空比,假设burst=100ms,pause=0-500ms
python3 run.py all2all --burst-ms 100 --pause-min 0 --pause-max 0 --duration 60
rank bw = 400G x burst_ms / (burst_ms + 平均 pause_ms)
= 400G x 100 / (100+250)
= 400G x 28.6%
= ~114 Gbps/卡
时间轴
|===100ms burst===|---273ms pause---|===100ms burst===|
400G 线速 0 流量 400G 线速
(不停 post WQE) (SIGSTOP) (不停 post WQE)
(Python全程sleep,除了负责触发定时信号:SIGCOUNT和SIGSTOP)
模式流程
整体过程概述:
1. Barrier → 所有 rank 同步
2. SIGCONT → 唤醒 perftest(开始发数据)
3. sleep(burst_ms) → Python sleep 100ms,这期间 perftest 在全速 400G 发包
4. SIGSTOP → Python 醒来后冻结 perftest(停止发数据)
5. Bcast(pause-min,pause-max) → rank0 广播 pause 时长,广播时长3-5us
6. sleep(pause-min,pause-max) → Python 睡,这期间 perftest 已被冻结,无流量
7. 回到 1
启动阶段:
1. 启动所有 perftest server (--run_infinitely)
2. Barrier → 所有 rank 同步, 确保 server 就绪
3. 启动所有 perftest client (--run_infinitely, 无 rate_limit)
4. 等待连接建立 (sleep 4s)
5. SIGSTOP 所有 perftest → 冻结, 准备受控 burst
6. Barrier → 所有 rank 确认就绪
Burst 循环 (每轮 MPI 都参与):
7. Barrier → 所有 rank 同步
8. SIGCONT → 唤醒所有 perftest (开始发数据)
9. sleep(burst_ms) → Python sleep xxxms, perftest 全速 400G 发包
10. SIGSTOP → Python 醒来后冻结 perftest (停止发数据)
11. Bcast(pause-min,pause-max) → rank0 广播 pause 时长,广播时长 ~3-5μs
12. sleep(pause-min,pause-max) → Python 睡, perftest 冻结, 无流量
13. elapsed < duration → 回到 7; 否则跳出
结束阶段:
14. kill 所有 perftest 进程
15. MPI Finalize
突发模型 – Storage 模式
控制机制
perftest 内置的 –rate_limit + –burst_size。MPI 不做 SIGSTOP/SIGCONT。
时间精度
纳秒-微秒级。perftest 的 C 代码在用户态直接向 HCA post WQE,全程不经过内核。
Burst 期间行为
每条流的 perftest 进程独立运行,各自以 burst_size 为单位发送——发完一批后等到 rate_limit 计时器到期再发下一批。各流没有 MPI Barrier 同步,启动后自然漂移,burst 时刻互相错开(多条流由独立 perftest 进程驱动,各进程 CPU 调度、HCA 仲裁有微小差异,burst 时自然散开)。单条流是 bursty 的,但多条流叠加后端口持续有流量,微观上是 bursty。Grafana 上看到的带宽 = rate_limit × 每卡出流数(上限 400G),假设msg_size=4096B,burst_size=61,rate_limit=25Gbps:
python3 run.py all2all --burst-profile storage --msg-size 4096 --burst-size 61 --rate-limit 25 --duration 60
burst 时长 = burst_size × msg_size × 8 / 400Gbps
cycle 时长 = burst_size × msg_size × 8 / rate_limit
pause 时长 = cycle 时长 - burst 时长
burst time1 = burst_size × msg_size × 8 / 400Gbps
= 61 x 4096 x 8 / 400Gbps
= 0.000005 s
= 5us
burst time2 = burst_size × msg_size × 8 / 25Gbps
= 61 x 4096 x 8 / 25Gbps
= 0.00007995 s
= 79us
pause time = burst time2 - burst time1 = 79-5 = 74us
时间轴
|===5us burst===|---74us pause---|===5us burst===|---74us pause---|
400G 线速 0 流量 400G 线速 0 流量
(HCA满速61个WQE) (不 post WQE) (HCA满速61个WQE) (不 post WQE)
| 场景 | 每卡出流 | rate_limit | Grafana 每端口 |
| 4 卡 all2all | 3 | 120 | ~360 Gbps |
| 16 卡 all2all | 15 | 25 | ~375 Gbps |
| 16 卡 allreduce | 2 | 200 | ~400 Gbps |
注意事项
- rate_limit 必须是整数(10.0 不行,必须是 10)
- rate_limit 和 –run_infinitely 不兼容,storage 模式 client 自动改用 -D
- rate_limit/burst_size 是 client 端参数,server 端不能传
- rate_units 用字符串:g(Gbps)、M(Mbps)、p(Mpps)
- CX7 HW rate limit 支持离散值:2.5, 5, 10, 14, 20, 25, 30, 40, 56, 60, 80, 100, 112, 120, 168, 200, 300 Gbps。不匹配时自动 fallback 到 SW rate limiter(对测试无影响)
- rate_limit 是单条流平均速率,Grafana = rate_limit × 每卡出流数(上限 400G),打满 400G 时 16 卡 all2all 用 25,allreduce 用 200
- 此模式下各 perftest 进程独立运行,burst 时刻自然漂移,统计复用把压力平滑化
模式流程
启动阶段:
1. 启动所有 perftest server (--run_infinitely, 等待 client 连接)
2. Barrier → 所有 rank 同步, 确保 server 就绪
3. 启动所有 perftest client (带 --rate_limit, --burst_size, -D duration+10s)
│
└── client 启动后内部自动执行:
a. TCP 握手 (连接管理网 IP, 交换 QP 元数据, ~1-2s)
b. QP ready → 进入 burst 循环 ← 流量从这里开始,持续 -D 自行退出或被 kill
├── post burst_size 61 个 WQE, 每个4096B → HCA 400G 线速 burst 5us
├── 61×4096 = 249,856 B, 由于rate_limit 25Gbps 应分摊79us
├── 但实际只用了 5μs,pending 74us后才能post 下一批
├── 轮询 clock_gettime(), 空等 74μs, 不 post 任何 WQE, 无流量
└── 74μs 到了 → 回到步骤 1, post 下一批 61 个 WQE
4. sleep(4s) → Python 等连接建立, 此时 perftest 可能已经在发数据了
5. 检查进程存活
MPI 监控 (与步骤 6-8 并行):
6. 每 10s 检查 perftest 进程是否存活
7. elapsed ≥ duration → 跳出
结束阶段:
8. kill 所有 perftest 进程
9. MPI Finalize
突发模型 – sync_burst 模式
控制机制
MPI Barrier 跨节点同步 + C 库 killpg(SIGCONT) 并行唤醒同一服务器所有 client + perftest burst_size 硬件控制 burst 时长。SIGCONT 只负责”开始”,burst_size 负责”结束”。
时间精度
training 模式 burst 时长精度 ±1ms(不够细),storage 模式各流不同步(不产生同步冲击)。sync_burst 结合两者优点。Burst 时长由 HCA 硬件保证(纳秒级),跨节点同步窗口 ~10-15μs(MPI Barrier ~3μs + killpg 信号投递 ~5-10μs)。最小可用 burst ~20μs(需 > 同步窗口才能保证所有流重叠)。pause 精度 ~1ms(受 Python sleep + MPI Barrier 限制,但 pause 不需要精确)。
Burst 期间行为
所有端口通过 MPI Barrier 同步后,C 库一次 killpg 唤醒本节点所有 perftest client。各 client 恢复后发现 rate_limit 计时器已到期,立即 post burst_size 个 WQE,HCA 以 400G 线速发出。发完后进入 rate_limit 内部 pause,随即被 C 库的 killpg(SIGSTOP) 冻结,等待下一轮 MPI Barrier。假设同 msg_size=65536B,burst_size=61,sync-pause-ms 2 默认值:
python3 run.py incast --burst-profile sync_burst --msg-size 65536 --burst-size 61 --transport UC --sync-pause-ms 2.0 --duration 120
burst 时长 = burst_size × msg_size × 8 / 400Gbps
cycle 时长 = burst_size × msg_size × 8 / rate_limit
pause 时长 = cycle 时长 - burst 时长
burst time1 = burst_size × msg_size × 8 / 400Gbps
= 61 x 65536 x 8 / 400Gbps
= 80us
wait_us time= 80 + 100 = 180μs
rate_limit < burst_data / (burst_time1 + 100μs)
< (61 × 65536 × 8)/ wait_us time
< 31,981,568 / 180us
< 177Gbps
→ HW 向下对齐 → 168 Gbps
burst time2 = burst_size × msg_size × 8 / rate_limit
= 61 x 65536 x 8 / 168Gbps
= 190us
rate_limit:sync_burst 模式内部自动计算 rate_limit,使 perftest 的内部 pause 远短于 sync_pause_ms。计算值会向上对齐到 CX7 HW rate limiter 支持的离散值,避免 fallback 到 SW rate limiter(UC + SW rate limiter 有兼容问题会导致进程崩溃)。CX7 HW 支持的 rate_limit 值同training:2.5, 5, 10, 14, 20, 25, 30, 40, 56, 60, 80, 100, 112, 120, 168, 200, 300 Gbps
wait_us:burst_then_stop() 的 busy_wait 参数设为 burst_us + 100μs(perftest_burst.py 里的硬编码)。100μs 是给 SIGSTOP 信号投递留的余量窗口,跟 burst 时长无关,不需要调整。cycle 输出中包含这个余量:cycle = burst + 100μs + 信号开销 ~10μs
重叠问题
重叠指的是”所有流同时在发包的时间段”。只有在重叠窗口内,所有流同时打向交换机同一个 egress port,才能制造 buffer 冲击,比如:同步窗口 = 15μs (最早的流 t=0 开始, 最晚的流 t=15μs 开始),burst 时长必须 > 同步窗口才能保证所有流在时间上重叠:
5μs burst (不重叠):
flow1: t=0 |=5μs=| 发完了
flow15: t=15 |=5μs=| 才开始
→ 流1 和流15 从来没有同时在发 → 不制造同步冲击
50μs burst (重叠 35μs):
flow1: t=0 |==========50μs==========|
flow15: t=15 |==========50μs==========|
|====35μs====|
↑ 这 35μs 内所有流同时在打 → buffer 被冲击
其中跨节点同步窗口10~15us是根据信号链路估算的:
MPI Barrier 完成 (各节点时钟对齐, 抖动 ~3μs)
→ Python 调用 C 库 (~1μs)
→ C 库 killpg() 系统调用 (~3μs)
→ 内核遍历进程组, 发 IPI 中断 (~3-5μs)
→ 各 CPU 唤醒进程, post WQE (~3-5μs)
最早的流(本节点第一个被唤醒的)到最晚的流(另一节点最后一个被唤醒的)之间的总延迟 ≈ 10-15μs。实际值可以从输出中间接验证——如果 burst_size=20μs 时开始看到丢包效果,说明重叠存在,同步窗口 < 20μs。
不过要注意,这里所谓的“丢包效果”,都是指进入burst循环。在程序刚启动时,为了保障所有节点都正常建立(从第一个node开启client,到最后一个node开启client),会预留一些时间,保障不会出现burst开始循环时,还有client还没开始建立。因此在incast场景中的这个时刻,很容易把buffer打超而产生丢包,过了这段时间后,就可以观察“丢包效果”了。
时间轴
|===50μs burst===|---------~2ms pause---------|===50μs burst===|
400G 线速 perftest 被 SIGSTOP 冻结 400G 线速
(61 个 WQE) Python: sleep + Barrier (61 个 WQE)
(C 库控制) (Python 控制) (C 库控制)
一轮完整时间线,如下红色flag是可以控制的:
|←Barrier→|←── C 库 burst_then_stop() ──→|←── Python sleep ──→|
~3μs SIGCONT burst wait SIGSTOP sync_pause_ms
~5-15μs ~80μs ~100μs ~5μs ~2ms
| | |
| HCA 400G send |
| (burst_size 61 WQE) |
| |
└────── key path ───────┘
全程 C 代码, 零 Python
busy_wait 总计 = 80+100 = 180μs
关于中间的burst和wait的时间,还有后面2ms 的关系:2ms sync_pause 发生在 SIGSTOP 之后——perftest 已经被冻结了。问题出在 SIGCONT 到 SIGSTOP 之间的 180μs 窗口内,perftest 是自由运行的,没有任何外部控制。如果它的内部 cycle < 180μs,它会在这个窗口内自行启动第二轮 burst。
| t=0μs | MPI Barrier | 不可控, RDMA 固有 ~3μs |
| t=3μs | C 库: killpg(SIGCONT) | 不可控, 内核信号 ~5-10μs |
| t=8-18μs | 各 client 陆续恢复 (同步窗口) 【不可控, ~10-15μs】 检查 rate_limit 计时器 → 已到期 立即 post 61 个 WQE | 不可控, ~10-15μs |
| t=8-98μs | HCA 400G 线速发出 61 个 WQE 15 条流同时打向 rank 0 端口 重叠窗口 ≈ 80 – 15 = 65μs | 可控: –burst-size × –msg-size |
| t=98μs | burst 结束, 进入 rate_limit 内部 pause (perftest 轮询等 240μs 计时器到期,按照100Gbps算的) (但不会等完, 因为 SIGSTOP 会提前打断) | 不可控, 由 rate_limit 自动决定 |
| t=183μs | C 库: killpg(SIGSTOP) 冻结所有 client (此时内部 pause 才过了 85μs, 还剩 155μs) (perftest 不知道自己被冻了, 下次醒来继续看计时器) | 不可控, wait_us=burst+100 硬编码 |
| t=183μs+ | Python: time.sleep(2ms) perftest 冻结, 无流量 | 可控: –sync-pause-ms |
| t=2183μs | Python 醒来, MPI Barrier rank0 Bcast: 继续? | 不可控 |
| t=2190μs | 下一轮 SIGCONT client 恢复, 被冻结了 2000μs 内部 pause 只需 240μs, 早已到期 → 立即 post 下一批 61 个 WQE |
其中240μs 的来源就是 burst 数据量 / rate_limit - burst 时长。rate_limit=100G 意思是”这批数据按 100G 的速率应该分摊到 320μs”,但实际 80μs 就发完了,所以 perftest 空等 240μs 凑够 320μs。但这个 240μs 的等待会被 SIGSTOP 在 85μs 处打断,永远等不完——所以 240μs 这个值对实际行为没有任何影响,只要它 > 85μs(保证 SIGSTOP 落在 pause 里)就行
模式流程
启动阶段:
1. 启动所有 perftest server (--run_infinitely)
2. Barrier → 所有 rank 同步, 确保 server 就绪
3. 启动所有 perftest client (带 --rate_limit, --burst_size, -D duration+10s)
│
└── client 启动时加入同一个进程组 (第一个 client 创建, 后续加入)
QP 建立后立刻开始发数据 → 立刻被步骤 4 的 SIGSTOP 冻结
4. sleep(4s) → 等连接建立
5. SIGSTOP 所有 perftest → 冻结, 准备受控 burst
6. Barrier → 所有 rank 确认就绪
7. 加载 C 库 libsigburst.so
Burst 循环:
8. rank0 判断是否继续 → Bcast 给所有 rank (避免各 rank 独立判断导致 MPI 死锁)
9. Barrier → 所有 rank 同步 (~3μs)
10. C 库 burst_then_stop(pgid, wait_us):
├── killpg(SIGCONT) 一次 syscall 并行唤醒本节点所有 client (~5-10μs)
│ └── perftest 恢复, 检查 rate_limit 计时器 → 已到期 (因为被 SIGSTOP 了很久)
│ └── post xxx 个 WQE → HCA 400G 发出 (197μs) ← burst
│ └── HCA 以 400G 线速发出 (burst, 设置时间~50μs)
├── busy_wait(burst+100μs) C 层 μs 精确等待, 确保 burst 完成
└── killpg(SIGSTOP) 一次 syscall 并行冻结所有 client (此时在 rate_limit 内部 pause 中)
11. sleep(sync_pause_ms) → Python 睡 ~2ms, perftest 已冻结, 无流量
12. 回到 8
结束阶段:
13. kill 所有 perftest 进程
14. MPI Finalize
三种突发模式对比
| 维度 | Training | Storage | sync_burst |
| burst 期间 | perftest 不停 post,无数量限制 | post 恰好 burst_size 个 | post 恰好 burst_size 个 |
| burst 结束触发 | Python 醒来发 SIGSTOP | perftest 发完自动停 | perftest 发完自动停,C 库随后 SIGSTOP |
| 发送和结束方式 | 串行,一个一个发 | 无外部控制,perftest 内部计时器自行循环 | 一次 syscall,内核并行投递 |
| 发送和结束速度 | 15 个 client ≈ 750μs | 不适用(无 SIGSTOP/SIGCONT) | 15 个 client ≈ 10μs |
| burst 时长控制 | Python sleep(±1ms) | burst_size(纳秒级) | burst_size(纳秒级) |
| pause 触发 | SIGSTOP 冻结 | rate_limit 计时器等待 | C 库 SIGSTOP 冻结 |
| pause 时长控制 | rank0 随机选(0-500ms) | rate_limit 自动计算(μs 级) | sync_pause_ms 固定(~2ms) |
| 跨节点同步 | MPI Barrier(每轮) | 无(各流独立) | MPI Barrier + C killpg(每轮) |
| 下一轮如何开始 | Barrier → SIGCONT | rate_limit 计时器到期 | Barrier → C 库 SIGCONT |
| Python 角色 | 定时发信号 | 旁观监控 | 管理 pause 期间的同步 |
| C 库角色 | 无 | 无 | burst 关键路径(SIGCONT→wait→SIGSTOP) |
| 重叠情况 | 重叠问题几乎不存在。burst 100ms,同步窗口 ~15μs,重叠 99.985ms ≈ 99.99%。ms 级 burst 下同步窗口可以忽略 | 不存在重叠的概念。各流独立运行,没有同步机制,burst 时刻随机分布。偶尔碰巧 2-3 条流同时 burst,但不是设计保证 | 重叠是核心关注点。μs 级 burst 下同步窗口占比大,burst 必须 > 同步窗口才有效 |
| 最小可靠 burst | ~1-2ms | ~2μs | ~20μs |
测试场景
场景 1:sync_burst incast – UC
用 UC + sync_burst 让所有端口同步微妙级 burst,不受重传/DCQCN 降速影响,观察纯丢包行为。有效 buffer 计算(Alpha=50%)
理论数据:
- 配置: msg_size=65536, 16 卡 incast, UC, sync_pause=2ms
- 有效 buffer = 总 buffer × Alpha = 101MB × 50% = 50.5MB
- 16卡incast净流入= 15 × 400G – 400g 出流量 = 5.6T (15入 – 1出排空)
- 填满 50.5MB 需要 = 50.5MB × 8 / 5.6T = 75.6μs
- 理论速度恒定下,1us 填充 0.7MB Buffer
测试中每轮 burst 都有 ~15μs 的 ramp-up 阶段(流逐个加入):
- 实际 buffer 峰值 < 完美同步公式的计算值
- Grafana 带宽也略低(ramp-up 期间不是满功率)
| burst_size | burst 时长 | buffer 净积累 | vs 50.5MB | rate_limit (HW) | 内部 pause | 预期丢包 | Grafana/发送卡 |
| 30 | ~39μs | 26.3MB | 52% | 112 Gbps | 101μs ✓ | 无丢包 | ~7 Gbps |
| 45 | ~59μs | 39.4MB | 78% | 120 Gbps | 138μs ✓ | 无丢包 | ~11 Gbps |
| 58 | ~76μs | 50.7MB | 100% | 168 Gbps | 105μs ✓ | 拐点 | ~14 Gbps |
| 70 | ~92μs | 61.3MB | 121% | 168 Gbps | 127μs ✓ | 轻丢包 | ~17 Gbps |
| 100 | ~131μs | 87.5MB | 173% | 200 Gbps | 131μs ✓ | 中等丢包 | ~24 Gbps |
| 150 | ~197μs | 131.3MB | 260% | 200 Gbps | 197μs ✓ | 重丢包 | ~34 Gbps |
| 300 | ~393μs | 262.5MB | 520% | 300 Gbps | 131μs ✓ | 严重丢包 | ~63 Gbps |
# 无丢包基线(~26.3MB)
python3 run.py incast --burst-profile sync_burst --msg-size 65536 --burst-size 30 --transport UC --duration 300
# 拐点 (~39.4MB)
python3 run.py incast --burst-profile sync_burst --msg-size 65536 --burst-size 45 --transport UC --duration 300
# 轻丢包 (~50.7MB)
python3 run.py incast --burst-profile sync_burst --msg-size 65536 --burst-size 58 --transport UC --duration 300
# 中等丢包 (~50.7MB)
python3 run.py incast --burst-profile sync_burst --msg-size 65536 --burst-size 100 --transport UC --duration 300
# 严重丢包 (~50.7MB)
python3 run.py incast --burst-profile sync_burst --msg-size 65536 --burst-size 300 --transport UC --duration 300
场景 2:Training incast – RC
适合DCQCN 收敛测试,每端口 burst 期间净流入 = 14 × 400G = 5.6 Tbps,即使 1ms burst 也积累 700MB >> 50MB → 必然丢包。但在RC 下 DCQCN 和 RC 重传会逐渐降速,丢包从初始大量 → 收敛后减少。观察收敛时间和稳态丢包率
| burst_ms | buffer净积累 | 预期丢包 | Grafana/端口 |
| 1 | 700MB | 多 – > 少 | ~4G |
| 5 | 3.5GB | 多 – > 少 | ~19G |
| 50 | 35GB | 多 – > 少 | ~133G |
| 100 | 70GB | 多 – > 少 | ~200G |
python3 run.py incast --burst-ms 1 --pause-min 100 --pause-max 100 --duration 120
python3 run.py incast --burst-ms 5 --pause-min 100 --pause-max 100 --duration 120
python3 run.py incast --burst-ms 50 --pause-min 100 --pause-max 100 --duration 120
python3 run.py incast --burst-ms 100 --pause-min 100 --pause-max 100 --duration 120
场景 3:Storage incast – RC
15 流汇聚 1 端口,聚合 = rate_limit × 15
| rate_limit | burst_size | burst 时长 | 聚合带宽 | 预期丢包 | Grafana/端口 |
| 10 | 61 | ~5μs | 150G | 无 | ~10G |
| 25 | 61 | ~5μs | 375G | 偶尔(叠加超 400G) | ~25G |
| 30 | 61 | ~5μs | 450G | 持续(超出口) | ~30G |
python3 run.py incast --burst-profile storage --msg-size 4096 --burst-size 61 --rate-limit 10 --duration 120
python3 run.py incast --burst-profile storage --msg-size 4096 --burst-size 61 --rate-limit 25 --duration 120
python3 run.py incast --burst-profile storage --msg-size 4096 --burst-size 61 --rate-limit 30 --duration 120
场景 4:Training all2all – RC
每端口:15 条入流各 ~26.7G = 400G ≈ 出口线速,约平衡
| burst_ms | pause_ms | Buffer 压力 | 预期丢包 | Grafana/端口 |
| 100 | 0-500均250 | 低(入≈出) | 轻微(burst 启停瞬间) | ~114G |
| 100 | 100 | 低 | 轻微 | ~200G |
| 100 | 0 | 低 | 轻微 | ~400G |
python3 run.py all2all --burst-ms 100 --pause-min 0 --pause-max 500 --duration 120
python3 run.py all2all --burst-ms 100 --pause-min 100 --pause-max 100 --duration 120
python3 run.py all2all --pause-min 0 --pause-max 0 --duration 120
场景 5:Training allreduce – RC
每端口:2 条入流各 ~200G = 400G ≈ 出口线速,buffer 压力最低
python3 run.py allreduce --burst-ms 100 --pause-min 0 --pause-max 500 --duration 120
python3 run.py allreduce --pause-min 0 --pause-max 0 --duration 120
场景 6:Storage all2all – RC
每端口 15 条出流,聚合 = rate_limit × 15。Grafana 预期:~375 Gbps/卡。各流独立,burst 自然错开,丢包少于 training 模式。Grafana 显示几乎相同(都 ~375 Gbps),差异在交换机 queue 微观层面。
| rate_limit | burst_size | burst 时长 | 聚合带宽 | 预期丢包 | Grafana/端口 |
| 10 | 61 | ~5μs | 150G | 无 | ~150G |
| 25 | 61 | ~5μs | 375G | 级轻 | ~375G |
| 30 | 61 | ~5μs | 450G | 轻 | ~400G |
python3 run.py all2all --burst-profile storage --msg-size 4096 --burst-size 61 --rate-limit 10 --duration 120
python3 run.py all2all --burst-profile storage --msg-size 4096 --burst-size 61 --rate-limit 25 --duration 120
python3 run.py all2all --burst-profile storage --msg-size 4096 --burst-size 61 --rate-limit 30 --duration 120
场景对比总结
| 场景 | 单端口最大 buffer 压力 | 丢包风险 | 适合测试 |
| sync_burst incast UC | 可精控 26-262MB | 可控 | OQ 水线精度 |
| training incast RC | 极大 700MB-70GB | 必然重丢 | DCQCN 收敛 |
| storage incast RC | 取决于 rate_limit | 可调 | 存储热点 + DCQCN |
| training all2all RC | 低(入≈出) | 轻微 | 训练业务端到端 |
| storage all2all RC | 分散 | 极轻 | 存储正常业务 |
| training allreduce RC | 最低 | 极轻 | ring 通信 |
Messages size与场景的关系
| 场景 | msg-size | 原因 |
| AI 训练 | 65536 (64KB) | NCCL 典型分块 |
| 存储随机 IO | 4096 (4KB) | NVMe 标准块 |
| 存储顺序 IO | 65536-1048576 | 大块 IO |
| OQ 水线测试 | 65536 | 大包更高效,单次 burst 数据量更大 |
扩展部署
只改 config.py
SERVERS = [
("tme92", "10.26.4.92", [("mlx5_0","192.168.100.2","0-20,64-84"), ("mlx5_1","172.16.0.2","32-52,96-116")]),
# ...
]
新服务器准备
# OpenMPI 4.1.6
./configure --prefix=/opt/openmpi-4.1.6 --with-ucx=/usr --without-cuda --without-ucc --with-verbs
make -j$(nproc) && make install
# mpi4py 3.1.6
pip3 install --no-binary :all: mpi4py==3.1.6 -i https://pypi.tuna.tsinghua.edu.cn/simple
验证
python3 run.py show
python3 run.py deploy
python3 run.py all2all --duration 30
python3 run.py kill
大规模注意
16-18 卡时可能需要增加等待时间(perftest_burst.py 中 sleep(2)→5, sleep(4)→10)。mpirun 内置自动重试(最多 3 次),处理 DC 建连偶发超时。建议测试环境在同一局域网中。
常用命令速查
# 环境
export PATH=/opt/openmpi-4.1.6/bin:$PATH
export LD_LIBRARY_PATH=/opt/openmpi-4.1.6/lib
cd /root/rdma_burst
# 管理
python3 run.py show / deploy / kill
# 持续满载
python3 run.py all2all --pause-min 0 --pause-max 0 --duration 60
# 训练突发
python3 run.py all2all --duration 60
python3 run.py allreduce --duration 60
python3 run.py incast --duration 60
# 存储微突发
python3 run.py all2all --burst-profile storage --msg-size 4096 --burst-size 61 --rate-limit 25 --duration 60
# OQ 水线测试
python3 run.py incast --burst-profile sync_burst --msg-size 65536 --burst-size 58 --transport UC --duration 120
# 调试
python3 run.py all2all --duration 30 --log
ssh 10.26.4.93 "cat /tmp/perftest_r2_cli_to*.log"