DeepEP Legacy 测试文件对比分析
1. 概述
本文档对 DeepEP 三个 legacy 测试文件进行深入的源码级对比分析,覆盖 Normal(Internode)、Intranode、Low-Latency 三种通信模式。这三个文件共同构成了 DeepEP V1(NVSHMEM-based)的测试体系,与 V2(Elastic)的 tests/elastic/test_ep.py 形成鲜明对比。
| 文件 |
行数 |
通信模式 |
底层传输 |
核心 API |
test_internode.py |
395 |
Normal (Internode) |
NVLink + RDMA |
dispatch() / combine() |
test_intranode.py |
311 |
Intranode |
NVLink only |
dispatch() / combine() |
test_low_latency.py |
332 |
Low-Latency |
IBGDA (RDMA) |
low_latency_dispatch() / low_latency_combine() |
2. 文件结构对比
2.1 整体结构
test_internode.py (395L) test_intranode.py (311L) test_low_latency.py (332L)
├── imports (1-14) ├── imports (1-13) ├── imports (1-11)
├── test_main() (18-314) ├── test_main() (17-265) ├── simulate_failure_and_skip() (14-30)
│ ├── Settings (28-34) │ ├── Settings (19-25) ├── query_mask_buffer_and_check() (33-36)
│ ├── Random data (36-56) │ ├── Random data (28-40) ├── test_main() (39-251)
│ ├── RDMA dispatch counts │ ├── Expert meta (42-47) │ ├── Settings (50-54)
│ │ (59-63) │ ├── Rank layout (49-62) │ ├── Data prep (57-77)
│ ├── Expert meta (65-70) │ ├── Layout validation(64-74) │ ├── Dispatch loop (89-149)
│ ├── Rank layout (72-88) │ ├── Config (77-78) │ ├── Combine loop (151-191)
│ ├── Layout validation(90-101) │ ├── Dispatch test (90-194) │ ├── Perf test (196-251)
│ ├── Config (104-105) │ ├── Tune dispatch(196-230) ├── test_loop() (255-312)
│ ├── Dispatch test (117-201) │ ├── Tune combine (242-264) └── main (315-332)
│ ├── Combine test (202-221) ├── test_loop() (268-297)
│ ├── Tune dispatch (237-275) └── main (300-311)
│ ├── Tune combine (287-313)
│ └── test_loop() (318-370)
└── main (373-395)
2.2 关键函数签名对比
# test_internode.py L18-27
def test_main(args, num_sms, local_rank, num_local_ranks, num_ranks, num_nodes, rank, buffer, group, skip_benchmark=False):
# test_intranode.py L17-18
def test_main(args, num_sms, local_rank, num_ranks, rank, buffer, group):
# test_low_latency.py L39-49
def test_main(num_tokens, hidden, num_experts, num_topk, rank, num_ranks, group, buffer,
use_logfmt=False, shrink_test=False, seed=0):
关键差异:
- internode 多了 num_nodes 和 num_local_ranks 参数(跨节点需要)
- low_latency 没有 args 命名空间,参数直接传递,多了 use_logfmt 和 shrink_test
3. 测试初始化差异
3.1 Buffer 创建
# test_internode.py L327-332 — Normal 模式,跨节点
buffer = deep_ep.Buffer(group,
int(2e9), # num_nvl_bytes = 2GB
int(1e9), # num_rdma_bytes = 1GB
low_latency_mode=False, # 显式关闭
num_qps_per_rank=num_qps_per_rank,
explicitly_destroy=True)
assert num_local_ranks == 8 and num_ranks > 8 # 必须跨节点
# test_intranode.py L275-281 — 纯节点内,无 RDMA
buffer = deep_ep.Buffer(group,
int(2e9), # num_nvl_bytes = 2GB
0, # num_rdma_bytes = 0
low_latency_mode=False,
num_qps_per_rank=1, # 无需 RDMA QP
explicitly_destroy=True,
allow_mnnvl=args.allow_mnnvl)
# test_low_latency.py L260-270 — 低延迟模式,IBGDA
num_rdma_bytes = deep_ep.Buffer.get_low_latency_rdma_size_hint(num_tokens, hidden, num_ranks, num_experts)
buffer = deep_ep.Buffer(group,
num_rdma_bytes=num_rdma_bytes, # 自动计算大小
low_latency_mode=True, # 开启低延迟
num_qps_per_rank=num_experts // num_ranks, # QP 数 = 本地 expert 数
allow_nvlink_for_low_latency_mode=not args.disable_nvlink,
explicitly_destroy=True,
allow_mnnvl=args.allow_mnnvl,
enable_shrink=args.shrink_test)
3.2 Buffer 创建参数对比表
| 参数 |
Internode |
Intranode |
Low-Latency |
num_nvl_bytes |
2GB |
2GB |
未指定(默认0) |
num_rdma_bytes |
1GB |
0 |
get_low_latency_rdma_size_hint() 计算 |
low_latency_mode |
False |
False |
True |
num_qps_per_rank |
max(num_sms, ll_num_experts//num_ranks) |
1 |
num_experts // num_ranks |
allow_nvlink_for_low_latency_mode |
— |
— |
not args.disable_nvlink |
enable_shrink |
— |
— |
args.shrink_test |
allow_mnnvl |
— |
args.allow_mnnvl |
args.allow_mnnvl |
3.3 Config 配置
# test_internode.py L104-105 — 5 参数 Config(含 RDMA)
rdma_buffer_size, nvl_buffer_size = 128, (720 if num_ranks in (24, 48, 96, 144, 160) else 512)
config = deep_ep.Config(num_sms, 8, nvl_buffer_size, 16, rdma_buffer_size)
# Config(SMs, nvl_chunk, nvl_buffer, rdma_chunk, rdma_buffer)
# test_intranode.py L77-78 — 3 参数 Config(仅 NVL)
nvl_buffer_size = 256
config = deep_ep.Config(num_sms, 8, nvl_buffer_size)
# Config(SMs, nvl_chunk, nvl_buffer)
# test_low_latency.py — 无 Config 对象
# low_latency_dispatch/combine 不接受 config 参数,配置由 buffer 内部管理
3.4 默认测试参数
| 参数 |
Internode |
Intranode |
Low-Latency |
num_tokens |
4096 |
4096 |
128 |
hidden |
7168 |
7168 |
7168 |
num_topk |
8 |
8 |
8 |
num_experts |
256 |
256 |
288 |
num_topk_groups |
min(num_nodes, 4) |
— |
— |
num_processes |
8 |
8 |
8 |
关键洞察:Low-Latency 模式默认 num_tokens=128(小 batch 低延迟场景),而 Normal/Intranode 为 4096(大 batch 高吞吐场景)。
4. API 使用模式
4.1 Dispatch API 对比
Internode/Intranode — High-Throughput API
# test_internode.py L127-141
dispatch_args = {
'x': current_x, # BF16 tensor 或 FP8 tuple
'num_tokens_per_rank': num_tokens_per_rank,
'num_tokens_per_rdma_rank': num_tokens_per_rdma_rank, # internode only
'is_token_in_rank': is_token_in_rank,
'num_tokens_per_expert': num_tokens_per_expert,
'config': config,
'async_finish': async_mode
}
if with_topk:
dispatch_args.update({'topk_idx': topk_idx, 'topk_weights': topk_weights})
if previous_mode:
dispatch_args.update({'previous_event': buffer.capture()})
recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event = buffer.dispatch(**dispatch_args)
Low-Latency — IBGDA API
# test_low_latency.py L99-104
packed_recv_x, packed_recv_count, handle, event, hook = \
buffer.low_latency_dispatch(current_x, topk_idx, num_tokens, num_experts,
use_fp8=dispatch_use_fp8, round_scale=round_scale, use_ue8m0=use_ue8m0,
cumulative_local_expert_recv_stats=cumulative_local_expert_recv_stats,
async_finish=not return_recv_hook, return_recv_hook=return_recv_hook)
4.2 Combine API 对比
Internode/Intranode
# test_internode.py L205-210
combine_args = {'x': recv_x, 'bias': (bias_0, bias_1), 'handle': handle, 'config': config, 'async_finish': async_mode}
if with_topk:
combine_args.update({'topk_weights': recv_topk_weights})
combined_x, combined_topk_weights, event = buffer.combine(**combine_args)
Low-Latency
# test_low_latency.py L158-166
combined_x, event, hook = buffer.low_latency_combine(simulated_gemm_x,
topk_idx,
topk_weights,
handle,
use_logfmt=use_logfmt,
async_finish=not return_recv_hook,
zero_copy=zero_copy,
return_recv_hook=return_recv_hook,
out=out)
4.3 API 参数对比表
| 特性 |
dispatch() / combine() |
low_latency_dispatch() / low_latency_combine() |
| 数据格式 |
BF16 或 FP8 tuple |
BF16,FP8 可选 |
| topk_idx/topk_weights |
可选参数 |
必须参数 |
| config |
deep_ep.Config 对象 |
无(内部管理) |
| previous_event |
支持 |
不支持 |
| async_finish |
支持 |
支持 |
| return_recv_hook |
不支持 |
支持(延迟接收) |
| num_worst_tokens |
支持(intranode only) |
不支持 |
| zero_copy |
不支持 |
支持(combine) |
| use_logfmt |
不支持 |
支持(combine) |
| round_scale/use_ue8m0 |
不支持 |
支持(dispatch) |
| cumulative_local_expert_recv_stats |
不支持 |
支持 |
5. 测试模式与参数组合
5.1 循环维度对比
# test_internode.py L117-120 — 4 维组合 = 2×2×4×2 = 32 cases
for previous_mode in (False, True): # 2
for async_mode in (False, True): # 2
for current_x in (x_pure_rand, x, x_pure_rand_e4m3, x_e4m3): # 4 (BF16/FP8 × rand/deterministic)
for with_topk in (False, True): # 2
# test_intranode.py L90-93 — 4 维组合 = 2×2×3×2 = 24 cases (FP8 可能为 None)
for previous_mode in (False, True):
for async_mode in (False, True):
for current_x in filter(lambda elem: elem is not None, (x_pure_rand, x, x_e4m3)): # 3
for with_topk in (False, True):
# test_low_latency.py L89-93 — 5 维组合 = 5×2×2×2×2 = 80 cases (最多)
for current_x in x_list: # 5 (1 deterministic + 4 logfmt/rand)
for return_recv_hook in (False, True): # 2
for dispatch_use_fp8 in (False, True): # 2
for round_scale in (False, True) if dispatch_use_fp8 else (False,): # 1-2
for use_ue8m0 in (False, True) if round_scale else (False,): # 1-2
5.2 测试模式特性表
| 测试模式 |
Internode |
Intranode |
Low-Latency |
previous_event (CUDA graph capture) |
✅ |
✅ |
❌ |
async_finish |
✅ |
✅ |
✅ |
return_recv_hook |
❌ |
❌ |
✅ |
| FP8 dispatch |
✅ |
✅ (SM90 only) |
✅ |
round_scale (Pow2 scaling) |
❌ |
❌ |
✅ |
use_ue8m0 |
❌ |
❌ |
✅ |
zero_copy combine |
❌ |
❌ |
✅ |
use_logfmt |
❌ |
❌ |
✅ |
num_worst_tokens |
✅ |
✅ |
❌ |
| Cached dispatch (handle reuse) |
✅ |
✅ |
❌ |
| Bias (0/1/2 tensors) |
✅ (固定2个) |
❌ |
❌ |
6. 验证逻辑
6.1 数据正确性验证
Internode/Intranode — check_data() 函数
# test_internode.py L109-115
def check_data(check_x, recv_gbl_rank_prefix_sum):
assert torch.allclose(check_x.amin(dim=1), check_x.amax(dim=1)) # 每行值相同(rank 编码)
check_start = 0
for i in range(num_ranks):
check_end = recv_gbl_rank_prefix_sum[i].item()
assert (check_x[check_start:check_end, :].int() - i).sum().item() == 0 # 验证 rank 编码
check_start = check_end
核心思路:用 x = torch.ones(...) * rank 构造确定性数据,接收端验证每个 token 的值等于发送端 rank。
Low-Latency — 逐 expert 验证
# test_low_latency.py L110-149
for i in range(num_local_experts if do_check else 0):
expert_id = rank * num_local_experts + i
recv_x = per_token_cast_back(packed_recv_x[0][i], packed_recv_x[1][i]) if dispatch_use_fp8 else packed_recv_x[i]
recv_count, recv_src_info, recv_layout_range = packed_recv_count[i], handle[0][i], handle[1][i]
# 验证 expert 索引
num_valid_tokens = recv_count.item()
assert num_valid_tokens == (all_topk_idx == expert_id).sum(dim=[1, 2])[mask_status == 0].sum().item()
# 验证接收数据
if current_x is x:
recv_x_amin = recv_x[:, :-128].amin(dim=-1)
assert torch.equal(recv_x_amin, recv_x[:, :-128].amax(dim=-1))
# 验证 rank 编码
assert (recv_x_amin == j - rank_offset).sum().item() == (all_topk_idx[j] == expert_id).sum().item()
6.2 Combine 验证
Internode/Intranode
# test_internode.py L212-214
check_x = (combined_x.float() - bias_0.float() - bias_1.float()) / is_token_in_rank.sum(dim=1).unsqueeze(1)
ref_x = x_pure_rand if is_rand else x
assert calc_diff(check_x, ref_x) < 5e-4 if current_x is x_pure_rand_e4m3 else 5e-6
Low-Latency
# test_low_latency.py L178-181
diff = calc_diff(current_x * topk_weights.masked_fill(topk_idx == -1, 0).sum(dim=1).view(-1, 1), combined_x)
assert torch.isnan(combined_x).sum().item() == 0
if not round_scale:
assert diff < (9e-4 if dispatch_use_fp8 else 1e-5), f'Error: {diff=}, {dispatch_use_fp8=}, {zero_copy=}'
6.3 验证策略对比
| 验证项 |
Internode |
Intranode |
Low-Latency |
| 数据编码 |
x = ones * rank |
x = ones * rank |
x = ones * (rank - 128) + 尾部 token ID |
| 行一致性 |
amin == amax |
amin == amax |
amin == amax (前 128 列) |
| rank 编码 |
直接比较 |
直接比较 |
rank - rank_offset (避免 BF16 精度问题) |
| token ID 追踪 |
❌ |
✅ (最后一列) |
✅ (最后 128 列) |
| topk_idx 范围 |
[0, num_experts//num_ranks) |
[0, num_experts//num_ranks) |
全局 expert ID |
| topk_weights 精度 |
< 1e-9 |
< 1e-9 |
不直接验证 |
| combine 精度 |
< 5e-6 (BF16) / < 5e-4 (FP8) |
< 5e-6 |
< 1e-5 (BF16) / < 9e-4 (FP8) |
| NaN 检查 |
❌ |
❌ |
✅ |
7. 性能调优
7.1 Dispatch 调优
Internode — 双维度网格搜索
# test_internode.py L244-259
for nvl_chunk_size in range(4, 45, 4): # NVL chunk: 4~40, step 4
for rdma_chunk_size in range(4, 33, 4): # RDMA chunk: 4~28, step 4
config = deep_ep.Config(num_sms, nvl_chunk_size, nvl_buffer_size, rdma_chunk_size, rdma_buffer_size)
tune_args = {'x': current_x, 'handle': handle, 'config': config}
t, notify_t = bench_kineto(lambda: buffer.dispatch(**tune_args), ...)
Intranode — 单维度 + 默认配置
# test_intranode.py L202-208
for nvl_chunk_size in tuple(range(4, 33, 2)) + (0, ): # 0 表示默认配置
if nvl_chunk_size > 0:
config = deep_ep.Config(num_sms, nvl_chunk_size, nvl_buffer_size)
else:
deep_ep.Buffer.set_num_sms(num_sms)
config = deep_ep.Buffer.get_dispatch_config(num_ranks) # 查表法
Low-Latency — 无调优,直接 benchmark
# test_low_latency.py L227-250
avg_t, min_t, max_t = bench(partial(test_func, return_recv_hook=False))
# 分离 profiling
dispatch_t, combine_t = bench_kineto(partial(test_func, return_recv_hook=...),
kernel_names=('dispatch', 'combine'), ...)
7.2 调优策略对比
| 调优项 |
Internode |
Intranode |
Low-Latency |
| NVL chunk 范围 |
4~40, step 4 |
4~30, step 2 |
不调优 |
| RDMA chunk 范围 |
4~28, step 4 |
— |
不调优 |
| 默认配置测试 |
❌ |
✅ (get_dispatch_config) |
— |
| benchmark 工具 |
bench_kineto |
bench |
bench + bench_kineto |
| SMs 调优 |
固定 24 |
固定 24 |
— |
| FP8 最佳配置收集 |
all_gather rank 0 |
all_gather rank 0 |
— |
| 分离 profiling |
❌ |
❌ |
✅ (dispatch/combine 分开) |
8. 特殊测试功能
8.1 压力测试 (Pressure Test)
# test_internode.py L335-360
for seed in range(int(1e9)):
torch.manual_seed(rank + seed)
ref_hash = test_main(...)
if args.pressure_test_mode == 0:
break # 只跑一次
for _ in range(20): # 重复 20 次验证确定性
torch.manual_seed(rank + seed)
current_hash = test_main(...)
assert current_hash == ref_hash
# test_low_latency.py L283-307
for seed in range(int(1e9) if do_pressure_test else 0):
ref_hash = test_main(..., seed=seed)
for _ in range(20):
assert test_main(..., seed=seed) == ref_hash
8.2 失败模拟与 Shrink 测试 (Low-Latency 独有)
# test_low_latency.py L14-30
def simulate_failure_and_skip(rank, api, expected_masked_ranks):
failed_api_ranks = {
'dispatch': 1, # rank 1 在 dispatch 时失败
'combine': 3, # rank 3 在 combine 时失败
'clean': 5 # rank 5 在 clean 时失败
}
if rank in expected_masked_ranks:
return True # 已失败,跳过
if api in failed_api_ranks.keys():
expected_masked_ranks.add(failed_api_ranks[api])
if failed_api_ranks[api] == rank:
print(f"Rank {rank} failed when first calling {api} communication API, exit...")
return True
return False
# test_low_latency.py L33-36 — 查询 mask buffer 并验证
def query_mask_buffer_and_check(api, buffer, mask_status, expected_masked_ranks):
buffer.low_latency_query_mask_buffer(mask_status)
assert set(mask_status.nonzero().squeeze(-1).tolist()) == expected_masked_ranks
8.3 Low-Latency 与 Normal 兼容性测试
# test_internode.py L362-365
if args.test_ll_compatibility:
buffer.clean_low_latency_buffer(ll_num_tokens, ll_hidden, ll_num_experts)
test_low_latency.test_main(ll_num_tokens, ll_hidden, ll_num_experts, ll_num_topk, rank, num_ranks, group, buffer, seed=1)
# test_intranode.py L289-292
if test_ll_compatibility:
buffer.clean_low_latency_buffer(ll_num_tokens, ll_hidden, ll_num_experts)
test_low_latency.test_main(ll_num_tokens, ll_hidden, ll_num_experts, ll_num_topk, rank, num_ranks, group, buffer, seed=1)
关键洞察:Internode 和 Intranode 都通过 import test_low_latency 显式测试与 Low-Latency 模式的兼容性,需要先调用 clean_low_latency_buffer 清理缓冲区。
8.4 特殊功能对比
| 功能 |
Internode |
Intranode |
Low-Latency |
| 压力测试 |
✅ (3 modes) |
❌ |
✅ |
| 失败模拟 (shrink) |
❌ |
❌ |
✅ |
| LL 兼容性 |
✅ (optional) |
✅ (optional) |
— |
| MNNVL 支持 |
❌ |
✅ |
✅ |
| NVLink 禁用 |
❌ |
❌ |
✅ |
| LogFMT |
❌ |
❌ |
✅ |
| hash 校验 |
✅ |
❌ |
✅ |
| num_worst_tokens |
✅ |
✅ |
❌ |
9. Handle 结构对比
9.1 Internode Handle (10 元素 tuple)
# legacy.py L500-502
handle = (is_token_in_rank, rdma_channel_prefix_matrix, gbl_channel_prefix_matrix,
recv_rdma_channel_prefix_matrix, recv_rdma_rank_prefix_sum,
recv_gbl_channel_prefix_matrix, recv_gbl_rank_prefix_sum,
recv_src_meta, send_rdma_head, send_nvl_head)
9.2 Intranode Handle (6 元素 tuple)
# legacy.py L401
handle = (rank_prefix_matrix, channel_prefix_matrix, recv_channel_prefix_matrix,
recv_src_idx, is_token_in_rank, send_head)
9.3 Low-Latency Handle (2 元素 tuple)
# test_low_latency.py L113
recv_src_info, recv_layout_range = handle[0][i], handle[1][i]
# handle = (recv_src_info, recv_layout_range) # 每个 expert 对应一个 slice
10. Legacy vs Elastic 对比
10.1 API 风格对比
| 特性 |
Legacy (V1) |
Elastic (V2) |
| Buffer 类 |
deep_ep.Buffer |
deep_ep.ElasticBuffer |
| Dispatch API |
buffer.dispatch(x, num_tokens_per_rank, ...) |
buffer.dispatch(x, topk_idx, topk_weights, num_sms, num_qps, ...) |
| Combine API |
buffer.combine(x, handle, ...) |
buffer.combine(x, handle, ...) |
| Config |
deep_ep.Config(SMs, nvl_chunk, nvl_buffer, rdma_chunk, rdma_buffer) |
无(num_sms + num_qps 直接传) |
| Handle |
原始 tuple |
EPHandle 对象(具名属性) |
| Layout 计算 |
buffer.get_dispatch_layout() |
内部自动完成 |
| Expand 模式 |
❌ |
✅ (do_expand=True) |
| Cached 模式 |
传 handle 复用 |
传 handle 复用 |
| zero_padding |
❌ |
✅ |
| allocate_on_comm_stream |
✅ |
✅ |
| async_with_compute_stream |
❌ |
✅ |
10.2 测试风格对比
| 特性 |
Legacy |
Elastic |
| 参考实现 |
手工计算 num_tokens_per_rank 等 |
ref_dispatch / ref_combine (NCCL-based) |
| 参数组合 |
嵌套 for 循环 |
enumerate_ep_modes() 生成器 |
| 验证方式 |
check_data() + calc_diff() |
与 NCCL 参考实现逐元素比较 |
| 默认参数 |
硬编码 |
argparse |
| 分布式启动 |
torch.multiprocessing.spawn |
torch.multiprocessing.spawn |
| 性能报告 |
打印 GB/s |
打印 GB/s + trace 导出 |
10.3 关键代码对比
Legacy Dispatch 调用
# test_internode.py L127-141
dispatch_args = {
'x': current_x,
'num_tokens_per_rank': num_tokens_per_rank,
'num_tokens_per_rdma_rank': num_tokens_per_rdma_rank,
'is_token_in_rank': is_token_in_rank,
'num_tokens_per_expert': num_tokens_per_expert,
'config': config,
'async_finish': async_mode
}
recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event = buffer.dispatch(**dispatch_args)
Elastic Dispatch 调用
# test_ep.py L144-153
dispatch_args = dict(
x=x, topk_idx=topk_idx, topk_weights=topk_weights,
num_sms=num_sms, num_qps=num_qps,
num_max_tokens_per_rank=num_max_tokens_per_rank, num_experts=num_experts,
expert_alignment=expert_alignment,
async_with_compute_stream=async_with_compute_stream,
allocate_on_comm_stream=allocate_on_comm_stream,
do_handle_copy=do_handle_copy, do_cpu_sync=args.do_cpu_sync)
recv_x, recv_topk_idx, recv_topk_weights, handle, dispatch_event = \
launch(buffer, 'dispatch', with_previous_event, async_with_compute_stream, dispatch_args)
11. 关键代码片段
11.1 数据构造模式
# test_internode.py L37-41 — 确定性数据 + FP8 转换
x = torch.ones((num_tokens, hidden), dtype=torch.bfloat16, device='cuda') * rank
x_pure_rand = torch.randn((num_tokens, hidden), dtype=torch.bfloat16, device='cuda')
x_e4m3 = per_token_cast_to_fp8(x)
x_pure_rand_e4m3 = per_token_cast_to_fp8(x_pure_rand)
x_e4m3 = (x_e4m3[0], x_e4m3[1].T.contiguous().T) # scale 转置为 column-major (TMA 兼容)
# test_low_latency.py L57-68 — 带 token ID 追踪的数据
rank_offset = 128
x = torch.ones((num_tokens, hidden), dtype=torch.bfloat16, device='cuda') * (rank - rank_offset)
x[:, -128:] = torch.arange(num_tokens, device='cuda').to(torch.bfloat16).view(-1, 1) # 尾部嵌入 token ID
11.2 Layout 计算与验证
# test_internode.py L90-98
ref_num_tokens_per_rank, ref_num_tokens_per_rdma_rank, ref_num_tokens_per_expert, ref_is_token_in_rank, _ = \
buffer.get_dispatch_layout(topk_idx, num_experts)
assert torch.allclose(ref_num_tokens_per_rank, num_tokens_per_rank)
assert torch.allclose(ref_num_tokens_per_rdma_rank, num_tokens_per_rdma_rank)
assert torch.allclose(ref_num_tokens_per_expert, num_tokens_per_expert)
assert torch.allclose(ref_is_token_in_rank, is_token_in_rank)
11.3 Cached Dispatch 测试
# test_internode.py L191-200
if not with_topk:
dispatch_args = {'x': current_x, 'handle': handle, 'config': config, 'async_finish': async_mode}
if previous_mode:
dispatch_args.update({'previous_event': buffer.capture()})
recv_x, _, _, _, _, event = buffer.dispatch(**dispatch_args)
event.current_stream_wait() if async_mode else ()
recv_x = per_token_cast_back(*recv_x) if isinstance(recv_x, tuple) else recv_x
if not is_rand:
check_data(recv_x, recv_gbl_rank_prefix_sum)
11.4 num_worst_tokens 测试
# test_internode.py L175-189
if with_topk:
num_worst_tokens = num_tokens * num_ranks
dispatch_args.update({'num_worst_tokens': num_worst_tokens})
recv_worst_x, recv_worst_topk_idx, recv_worst_topk_weights, empty_list, _, event = buffer.dispatch(**dispatch_args)
assert len(empty_list) == 0
assert num_worst_tokens == recv_worst_x.size(0)
assert torch.equal(recv_x, recv_worst_x[:recv_x.size(0)])
assert torch.all(recv_worst_topk_idx[recv_x.size(0):] == -1).item() # 超出部分填充 -1
11.5 Low-Latency Return Hook 模式
# test_low_latency.py L99-104
packed_recv_x, packed_recv_count, handle, event, hook = \
buffer.low_latency_dispatch(current_x, topk_idx, num_tokens, num_experts,
use_fp8=dispatch_use_fp8,
async_finish=not return_recv_hook,
return_recv_hook=return_recv_hook)
hook() if return_recv_hook else event.current_stream_wait()
11.6 Low-Latency Zero-Copy Combine
# test_low_latency.py L154-166
for zero_copy in (False, ) if use_logfmt else (False, True):
if zero_copy:
buffer.get_next_low_latency_combine_buffer(handle)[:, :, :] = simulated_gemm_x
out = torch.empty((num_tokens, hidden), dtype=torch.bfloat16, device='cuda')
combined_x, event, hook = buffer.low_latency_combine(simulated_gemm_x,
topk_idx, topk_weights, handle,
use_logfmt=use_logfmt,
zero_copy=zero_copy, out=out)
12. 共享模式
12.1 共同导入
# 三个文件共享
import argparse
import torch
import torch.distributed as dist
import deep_ep
from deep_ep.utils.envs import init_dist
from deep_ep.utils.math import calc_diff, per_token_cast_to_fp8, per_token_cast_back
12.2 共同测试模式
| 模式 |
Internode |
Intranode |
Low-Latency |
previous_event |
✅ |
✅ |
❌ |
async_finish |
✅ |
✅ |
✅ |
| BF16/FP8 切换 |
✅ |
✅ |
✅ |
| with/without topk |
✅ |
✅ |
❌ (必须 topk) |
check_data() 行一致性 |
✅ |
✅ |
✅ |
calc_diff() 精度验证 |
✅ |
✅ |
✅ |
12.3 共同验证策略
- 行一致性检查:
amin(dim=1) == amax(dim=1) — 确保每行值相同(rank 编码)
- rank 编码验证:接收数据按 rank 分段,每段值等于发送端 rank
- topk_idx 范围检查:
recv_topk_idx 在 [0, num_local_experts) 或 -1
- combine 精度:
calc_diff(combined_x, ref_x) < threshold
13. 架构洞察
13.1 三层 API 设计
DeepEP V1 的 API 分为三层:
- High-Throughput (
dispatch/combine):面向大 batch,支持 NVLink、RDMA、NVLink+RDMA
- Low-Latency (
low_latency_dispatch/low_latency_combine):面向小 batch,仅 IBGDA
- Elastic (V2):统一 API,支持 scale-up/scale-out 分离
13.2 Config 的演进
- V1:
Config 对象封装 5 个调优参数(SMs, nvl_chunk, nvl_buffer, rdma_chunk, rdma_buffer)
- V2:
Config 消失,改为 num_sms + num_qps 直接传递,更灵活
13.3 Handle 的演进
- V1:原始 tuple,元素含义需查文档
- V2:
EPHandle 对象,具名属性(psum_num_recv_tokens_per_scaleup_rank 等)
13.4 测试覆盖度
| 测试场景 |
Legacy 覆盖 |
Elastic 覆盖 |
| BF16/FP8 |
✅ |
✅ |
| with/without topk |
✅ |
✅ |
| async/previous_event |
✅ |
✅ |
| Expand 模式 |
❌ |
✅ |
| Cached 模式 |
✅ |
✅ |
| zero_copy |
✅ (LL only) |
✅ |
| LogFMT |
✅ (LL only) |
✅ |
| 失败恢复 (shrink) |
✅ (LL only) |
✅ |
| 压力测试 |
✅ |
✅ |
| MNNVL |
✅ |
✅ |
14. 总结
14.1 关键发现
- 三种模式共享测试框架:嵌套 for 循环 × 多维度组合,但循环维度不同(Internode 4D, Low-Latency 5D)
- Low-Latency 测试最复杂:80 个测试组合(含 round_scale/ue8m0/zero_copy/logfmt),且有独有的失败模拟功能
- 性能调优策略不同:Internode 双维度网格搜索,Intranode 单维度+查表,Low-Latency 无调优
- 兼容性测试:Internode/Intranode 通过
import test_low_latency 显式测试与 Low-Latency 的共存
- 数据编码技巧:
x = ones * rank 构造确定性数据,Low-Latency 额外嵌入 token ID 用于精确追踪
14.2 Legacy vs Elastic 核心差异
| 维度 |
Legacy (V1) |
Elastic (V2) |
| 传输层 |
NVSHMEM (IBGDA) |
NCCL + NVSHMEM |
| API 复杂度 |
高(3 种 API + Config) |
低(统一 API) |
| Handle |
原始 tuple |
具名对象 |
| Expand 模式 |
不支持 |
支持 |
| 测试参考 |
手工计算 |
NCCL 参考实现 |
| 调优方式 |
网格搜索 |
理论建模 (get_theoretical_num_sms) |
附录:文件行号索引
| 文件 |
关键函数/段落 |
行号 |
test_internode.py |
test_main() 定义 |
18-314 |
|
Buffer 创建 |
327-332 |
|
Config 创建 |
104-105 |
|
Dispatch 测试循环 |
117-201 |
|
Combine 测试循环 |
202-221 |
|
Dispatch 调优 |
237-275 |
|
Combine 调优 |
287-313 |
|
test_loop() |
318-370 |
|
main |
373-395 |
test_intranode.py |
test_main() 定义 |
17-265 |
|
Buffer 创建 |
275-281 |
|
Config 创建 |
77-78 |
|
Dispatch 测试循环 |
90-194 |
|
Combine 测试循环 |
169-194 |
|
Dispatch 调优 |
196-230 |
|
Combine 调优 |
242-264 |
|
test_loop() |
268-297 |
|
main |
300-311 |
test_low_latency.py |
simulate_failure_and_skip() |
14-30 |
|
query_mask_buffer_and_check() |
33-36 |
|
test_main() 定义 |
39-251 |
|
Buffer 创建 |
260-270 |
|
Dispatch 测试循环 |
89-149 |
|
Combine 测试循环 |
151-191 |
|
Perf test |
196-251 |
|
test_loop() |
255-312 |
|
main |
315-332 |