DeepEP Elastic AGRS 与 PP 测试源码分析
分析对象:
tests/elastic/test_agrs.py(202 行)与tests/elastic/test_pp.py(139 行)
定位:DeepEP 实验性 Elastic Buffer 功能的集成测试与性能基准
1. 文件结构总览
| 文件 | 行数 | 核心函数 | 测试目标 |
|---|---|---|---|
test_agrs.py |
202 | all_gather_ref, generate_stress_ops, do_all_gather, test |
All-Gather/Reduce-Scatter 正确性 + 带宽 |
test_pp.py |
139 | generate_stress_ops, test |
Pipeline Parallel send/recv 正确性 + 延迟 |
两个文件共享相同的测试范式:Stress Test(随机操作序列验证正确性)+ Profiling(性能基准测量)。
2. AGRS 测试 (test_agrs.py) 深度分析
2.1 函数角色
| 函数 | 行号 | 职责 |
|---|---|---|
all_gather_ref |
L12-17 | CPU 参考实现:生成各 rank 随机张量并 stack 为真值 |
generate_stress_ops |
L20-54 | 生成随机 AGRS 操作序列(create/destroy session、ag、fetch) |
do_all_gather |
L57-84: | 封装 buffer 的 all_gather 调用,支持 inplace/batched 模式 |
test |
L89-192 | 主测试逻辑:配置 → stress → profiling → 销毁 |
2.2 参考实现:确定性真值生成
# L12-17
def all_gather_ref(shape: tuple, rank_idx: int, num_ranks: int, round_idx: int = 0):
ref_list = []
for i in range(num_ranks):
torch.manual_seed(42 + round_idx * 43 + i) # 确定性种子
ref_list.append(torch.randn(shape, dtype=torch.bfloat16, device='cuda'))
return ref_list[rank_idx], torch.stack(ref_list, dim=0)
关键设计:
- 每个 rank 使用独立但确定性的种子 42 + round_idx * 43 + i,确保跨 rank 可复现
- 返回 (local_tensor, gathered_tensor) 对,local 用于输入,gathered 用于验证
2.3 压力测试:随机操作序列生成
# L20-54
def generate_stress_ops(num_ops, num_max_inflight_agrs, shape, rank_idx, num_ranks):
tensors, refs = zip(*(all_gather_ref(...) for i in range(num_ops)))
unprocessed = random.sample(range(num_ops), num_ops)
inflight, ops = [], [('create_session', (-1,))]
limit = num_max_inflight_agrs
while unprocessed or inflight:
# 随机选择:ag / fetch / destroy
op = random.choice(choices)
if op == 'ag':
b = tuple(unprocessed[-random.randint(1, max_g):]) # 随机批量
...
elif op == 'fetch':
ops.append(('fetch', inflight.pop(random.randrange(len(inflight)))))
else:
ops.extend([('destroy_session', (-1,)), ('create_session', (-1,))])
测试覆盖维度:
1. Session 生命周期:随机穿插 create_session / destroy_session
2. Inflight 并发:最多 num_max_inflight_agrs 个并发 AG 操作
3. 批量大小随机:每次 AG 操作合并 1~max_g 个张量
4. Fetch 顺序随机:非 FIFO 的完成等待
2.4 All-Gather 执行封装
# L57-84
def do_all_gather(buffer, is_inplace, is_batched, tensors, start_event=None):
if is_inplace:
ag_tensors = buffer.agrs_get_inplace_tensor(tuple(t.shape for t in tensors), torch.bfloat16)
for x, y in zip(ag_tensors, tensors, strict=True):
x.copy_(y) # 拷贝到弹性缓冲区
else:
ag_tensors = tensors
if start_event is not None:
torch.zeros(int(256e6 // 4), dtype=torch.int, device='cuda') # flush L2 cache
start_event.record()
if is_batched:
*out_tensors, handle = buffer.all_gather(ag_tensors)
return out_tensors, [handle]
else:
# 逐个调用,每个返回独立 handle
out_tensors, handles = [], []
for t in ag_tensors:
out_tensor, handle = buffer.all_gather(t)
...
关键模式:
- Inplace 模式:通过 agrs_get_inplace_tensor 获取 buffer 内零拷贝槽位
- L2 Cache Flush:torch.zeros(256e6 // 4) 分配 256MB 冲刷 L2,确保计时准确
- Batched vs 非 Batched:batched 返回单个 handle,非 batched 返回 handle 列表
2.5 主测试流程
# L93-110: 配置阶段
shape = (32, 64, 2048) # 固定形状
num_max_session_bytes = deep_ep.ElasticBuffer.get_agrs_num_max_session_bytes(
group, [shape for _ in range(num_max_inflight_agrs)], torch.bfloat16)
num_max_session_bytes = deep_ep.ElasticBuffer.get_agrs_buffer_size_hint(
group, num_max_session_bytes)
buffer = deep_ep.ElasticBuffer(group, explicitly_destroy=True, num_bytes=num_max_session_bytes)
buffer.agrs_set_config(num_max_session_bytes, num_max_inflight_agrs)
配置链:
1. get_agrs_num_max_session_bytes → 计算 session 所需总字节(含 32 字节对齐)
2. get_agrs_buffer_size_hint → 对齐到 2MB 的 buffer 大小
3. ElasticBuffer 构造 → 显式销毁模式
4. agrs_set_config → 设置运行时参数(含 barrier 刷新)
2.6 正确性验证
# L136-138
for i in range(num_ops):
assert results[i] is not None and torch.equal(results[i], refs[i]), \
f'Rank {rank_idx}: stress mismatch at seed={seed}, op={i}'
验证策略:
- torch.equal 精确比较(非 allclose),因为是无损 all-gather
- 每个 op 必须完成(is not None)
- 跨 seed 迭代(默认 4 次),每次 128 个 ops
2.7 Profiling:带宽测量
# L156-187
for num_bytes in (2 ** p for p in range(20, 27)): # 1MB 到 64MB
shape = (num_bytes // 2, ) # bfloat16 = 2 bytes
for is_inplace in (False, True):
for is_batched in (False, True):
# 50 次迭代,丢弃首次(warmup)
times = np.array([s.elapsed_time(e) / 1e3 ...])[1:]
bandwidth_info = f'{num_bytes * num_ranks * num_max_inflight_agrs / avg_t / 1e9:.3f} GB/s'
测量矩阵:
- 数据大小:1MB, 2MB, 4MB, 8MB, 16MB, 32MB, 64MB
- 模式组合:(inplace=False/True) × (batched=False/True) = 4 种
- 使用 CUDA Event 计时,丢弃首次 warmup
- 报告单位:微秒(per AG)+ 聚合带宽(GB/s)
3. PP 测试 (test_pp.py) 深度分析
3.1 函数角色
| 函数 | 行号 | 职责 |
|---|---|---|
generate_stress_ops |
L13-37 | 生成随机 send/recv 操作序列(含时间戳) |
test |
L42-121 | 主测试逻辑:配置 → stress → profiling → 销毁 |
3.2 压力测试:基于时间戳的 PP 操作序列
# L13-37
def generate_stress_ops(rank_idx, num_ranks, num_sends, shape):
send_times = {(s, d): [] for s in range(num_ranks) for d in range(num_ranks) if s != d}
recv_times = {(s, d): [] for s in range(num_ranks) for d in range(num_ranks) if s != d}
for _ in range(num_sends):
src_rank_idx = random.randint(0, num_ranks - 1)
dst_rank_idx = (src_rank_idx + (1 if random.randint(0, 1) else -1)) % num_ranks # 仅相邻 rank
st = random.randint(0, 10 ** 8)
rt = st + random.randint(1, 3 * 10 ** 6) # recv 在 send 之后
send_times[(src_rank_idx, dst_rank_idx)].append(st)
recv_times[(src_rank_idx, dst_rank_idx)].append(rt)
关键约束:
- 仅相邻 rank 通信:(src ± 1) % num_rings,符合 PP ring 拓扑
- 时间戳排序:send/recv 按时间戳排序后交错执行
- Recv 滞后:rt = st + random(1, 3e6) 模拟真实 PP 中 recv 在 send 之后
3.3 主测试流程
# L44-59: 配置阶段
shape = (args.num_tokens, args.hidden) # 默认 (4096, 7168)
num_max_tensor_bytes = math.prod(shape) * 2 # bfloat16
buffer = deep_ep.ElasticBuffer(
group, explicitly_destroy=True, allow_hybrid_mode=False, # 禁用 hybrid 模式
num_bytes=deep_ep.ElasticBuffer.get_pp_buffer_size_hint(
num_max_tensor_bytes, num_max_inflight_tensors))
buffer.pp_set_config(num_max_tensor_bytes, num_max_inflight_tensors)
与 AGRS 的区别:
- allow_hybrid_mode=False:PP 测试禁用 hybrid 模式(纯 RDMA 路径)
- get_pp_buffer_size_hint:buffer 大小 = num_max_tensor_bytes × num_max_inflight × 2(send/recv) × 2(prev/next)
3.4 Stress Test 执行与验证
# L69-80
prev = 0
for j, (op, timestamp, peer, _, tensor) in enumerate(ops):
if op == 'send':
buffer.pp_send(tensor, peer)
else:
result = torch.empty_like(tensor)
buffer.pp_recv(result, peer)
assert torch.equal(result, tensor), \ # 逐张量精确匹配
f'Rank {rank_idx}: mismatch at op {j}'
if timestamp > prev:
torch.cuda._sleep(int((timestamp - prev) / 10 ** 8 * args.num_sleep_cycles))
prev = timestamp
验证策略:
- 逐张量精确匹配:torch.equal(result, tensor) — 发送端 tensor 直接与接收端比较
- 时间戳驱动睡眠:torch.cuda._sleep 模拟计算间隔,按时间戳差值比例睡眠
- 隐式假设:send 和 recv 的 tensor 是同一个对象(生成时共享引用)
3.5 Profiling:延迟与带宽测量
# L86-116
num_approx_rdma_cycles = int(num_max_tensor_bytes * 2 / get_rdma_gbs() * 1.5)
for hide_rdma_latency in (True, False):
for num_concurrent in (1, 2, 3):
def loop(_hide_rdma_latency=hide_rdma_latency):
torch.zeros((131072, 32768), dtype=torch.int, device='cuda') # L2 flush
for t in send_tensors:
buffer.pp_send(t, (rank_idx + 1) % num_ranks)
if _hide_rdma_latency:
torch.cuda._sleep(num_approx_rdma_cycles * num_concurrent)
for t in recv_tensors:
buffer.pp_recv(t, (rank_idx - 1) % num_ranks)
send_t, recv_t = bench_kineto(
loop, kernel_names=('send_impl', 'recv_impl'),
barrier_comm_profiling=True, barrier=buffer.barrier, # 使用 buffer 的 barrier
trace_path=get_trace_path(...))
测量矩阵:
- hide_rdma_latency:True(隐藏 RDMA 延迟,测量纯计算重叠)/ False(暴露延迟)
- num_concurrent:1, 2, 3 并发张量
- 使用 bench_kineto + Kineto profiler 追踪
- 报告:send/recv 延迟(μs)+ 带宽(GB/s)
hide_rdma_latency 语义:
- True:send 后睡眠等待 RDMA 完成,recv 测量的是"数据已到达"后的纯读取延迟
- False:recv 测量的是包含 RDMA 传输的完整延迟
4. API 使用模式对比
4.1 AGRS API 调用链
ElasticBuffer(group, explicitly_destroy=True, num_bytes=...)
├── .agrs_set_config(num_max_session_bytes, num_max_inflight_agrs)
├── .create_agrs_session() / .destroy_agrs_session()
├── .agrs_new_session() [context manager]
├── .agrs_get_inplace_tensor(shapes, dtype) [inplace 模式]
└── .all_gather(tensors) → (gathered_tensors, handle)
4.2 PP API 调用链
ElasticBuffer(group, explicitly_destroy=True, allow_hybrid_mode=False, num_bytes=...)
├── .pp_set_config(num_max_tensor_bytes, num_max_inflight_tensors)
├── .pp_send(tensor, dst_rank_idx)
├── .pp_recv(tensor, src_rank_idx)
└── .barrier [用于 profiling 同步]
4.3 共同模式
| 模式 | AGRS | PP |
|---|---|---|
| Buffer 预计算 | get_agrs_num_max_session_bytes → get_agrs_buffer_size_hint |
get_pp_buffer_size_hint |
| 显式配置 | agrs_set_config |
pp_set_config |
| 显式销毁 | explicitly_destroy=True |
explicitly_destroy=True |
| 多进程启动 | torch.multiprocessing.spawn |
torch.multiprocessing.spawn |
5. 参数变化空间
5.1 AGRS 测试参数
| 参数 | 默认值 | 含义 |
|---|---|---|
--num-processes |
8 | 参与测试的 GPU 数 |
--num-max-inflight-agrs |
4 | 最大并发 AG 操作数 |
--num-stress-iterations |
4 | stress 轮数(每轮 128 ops) |
shape |
(32, 64, 2048) |
固定张量形状(约 8MB/张量) |
| Profiling 大小 | 2^20 ~ 2^27 bytes |
1MB ~ 64MB |
5.2 PP 测试参数
| 参数 | 默认值 | 含义 |
|---|---|---|
--num-processes |
4 | 参与测试的 GPU 数 |
--num-tokens |
4096 | token 数 |
--hidden |
7168 | hidden 维度 |
--num-max-inflight-tensors |
4 | 最大并发张量数 |
--num-stress-iterations |
4 | stress 轮数 |
--num-sends |
128 | 每轮 send 操作数 |
--num-sleep-cycles |
10^7 | 睡眠周期基数 |
--dump-profile-traces |
'' |
Kineto trace 输出目录 |
6. 验证逻辑深度分析
6.1 AGRS 正确性验证
# L136-138
assert results[i] is not None and torch.equal(results[i], refs[i])
验证链:
1. all_gather_ref 生成确定性真值(各 rank 独立种子)
2. generate_stress_ops 生成随机操作序列
3. do_all_gather 执行实际 AG 操作
4. handle() 等待完成
5. out.clone() 保存结果
6. torch.equal 精确比较
覆盖的边界条件:
- Session 随机销毁/重建
- Inflight 达到上限后的 fetch/destroy 选择
- Batched vs 非 batched 混合
- Inplace vs 非 inplace 混合
6.2 PP 正确性验证
# L76-77
assert torch.equal(result, tensor)
关键假设:
- 发送端和接收端共享同一个 tensor 对象引用(L31-35 生成时)
- 这意味着验证的是"数据无损传输",而非"数据一致性"
- 时间戳排序保证了 send 在 recv 之前执行
7. 关键代码片段与模式
7.1 L2 Cache Flush 模式
# AGRS: L71
torch.zeros(int(256e6 // 4), dtype=torch.int, device='cuda') # 256MB
# PP: L98
torch.zeros((131072, 32768), dtype=torch.int, device='cuda') # 131072*32768*4 = 16GB (!)
注意:PP 的 flush 量是 131072 × 32768 × 4 bytes = 16GB,远超典型 GPU L2 缓存(H100 为 50MB)。这可能是有意为之的"全局内存写入"模式,用于清空所有缓存层级。
7.2 CUDA Event 计时模式
# AGRS: L165-177
start_events = [torch.cuda.Event(enable_timing=True) for _ in range(num_tests)]
end_events = [torch.cuda.Event(enable_timing=True) for _ in range(num_tests)]
for i in range(num_tests):
with buffer.agrs_new_session():
_, wait_handles = do_all_gather(..., start_event=start_events[i])
for h in wait_handles:
h()
end_events[i].record()
times = np.array([s.elapsed_time(e) / 1e3 for s, e in zip(start_events, end_events)])[1:] # 丢弃 warmup
7.3 时间戳驱动睡眠
# PP: L78-80
if timestamp > prev:
torch.cuda._sleep(int((timestamp - prev) / 10 ** 8 * args.num_sleep_cycles))
prev = timestamp
语义:将抽象时间戳映射到 GPU sleep 周期,模拟计算与通信重叠。
8. 实验性状态评估
8.1 文档标注
所有相关 API 均标注为 (Experimental):
| API | 标注 |
|---|---|
pp_set_config |
✅ Experimental |
pp_send |
✅ Experimental |
pp_recv |
✅ Experimental |
create_agrs_session |
✅ Experimental |
destroy_agrs_session |
✅ Experimental |
agrs_new_session |
✅ Experimental |
agrs_set_config |
✅ Experimental |
agrs_get_inplace_tensor |
✅ Experimental |
all_gather |
✅ Experimental |
8.2 已知限制
| 限制 | 详情 |
|---|---|
| PP 仅相邻 rank | pp_send/pp_recv 文档明确"prev or next rank only" |
| AGRS 固定 shape | stress test 使用固定 (32, 64, 2048),未覆盖变长 |
| PP 禁用 hybrid | allow_hybrid_mode=False,不测试 NCCL 回退路径 |
| 无 Reduce-Scatter | 测试只覆盖 All-Gather,未测试 Reduce-Scatter(API 名称暗示应支持) |
| 无错误注入 | 测试仅覆盖 happy path,无超时/越界/并发溢出测试 |
| 进程数固定 | AGRS 默认 8 卡、PP 默认 4 卡,未自动化扩展 |
8.3 功能成熟度判断
| 维度 | 评估 |
|---|---|
| API 完整性 | 中高 — session 生命周期、配置、执行、查询齐全 |
| 测试覆盖 | 中 — stress + profiling 双轨,但边界条件不足 |
| 生产就绪 | 低 — 明确标注 Experimental,限制较多 |
| 性能可观测 | 高 — 集成 Kineto profiling、CUDA Event 计时、带宽计算 |
9. 与核心 EP 功能的关系
9.1 Elastic Buffer 的定位
DeepEP Core (Production)
├── Dispatch / Combine (MoE expert parallelism)
├── Intra-node (NVLink)
└── Inter-node (RDMA)
Elastic Buffer (Experimental)
├── AGRS (All-Gather / Reduce-Scatter)
├── PP (Pipeline Parallelism send/recv)
└── Hybrid Mode (NCCL fallback)
9.2 设计意图推测
- AGRS:为 MoE 层的 token distribution 提供比 NCCL 更灵活的 all-gather 语义(session 化、inplace、batched)
- PP:为 PP 通信提供与 EP 统一的 buffer 管理,可能用于 3D/4D 并行场景
- 共享 runtime:两者共用
ElasticBuffer.runtime,共享 symmetric memory 基础设施
10. 总结
| 维度 | AGRS 测试 | PP 测试 |
|---|---|---|
| 测试类型 | 正确性 + 带宽 | 正确性 + 延迟 |
| 核心 API | all_gather, agrs_get_inplace_tensor |
pp_send, pp_recv |
| 验证方式 | 与 CPU 参考实现精确比较 | 发送/接收张量精确比较 |
| 压力来源 | 随机 session 生命周期 + inflight 并发 | 时间戳排序的 send/recv 交错 |
| 性能度量 | 聚合带宽 (GB/s) | 延迟 (μs) + 带宽 (GB/s) |
| Profiler | CUDA Event | Kineto + barrier_comm_profiling |
| 成熟度 | Experimental | Experimental |
两个测试文件展示了 DeepEP 从核心 EP 功能向更通用通信原语扩展的实验方向,采用确定性随机测试 + 性能 profiling的双重验证范式,为后续生产化提供了可复现的基准。