Files
paddlepaddle--paddle/test/collective/test_m2n.py
T
2026-07-13 12:40:42 +08:00

529 lines
15 KiB
Python

# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import contextlib
import random
import numpy as np
import paddle
import paddle.distributed as dist
from paddle.distributed import fleet
from paddle.distributed.communication import deep_ep
num_max_tokens = 512
def bench_split(
fn1,
fn2,
fn1_wait: bool = True,
fn2_wait: bool = True,
num_warmups: int = 50,
num_tests: int = 50,
):
# clear
cache = paddle.empty((int(256e6 // 4),), dtype="int32")
cache.zero_()
# Warmup
for _ in range(num_warmups):
dist.barrier()
req = fn1()
if fn1_wait:
req.wait()
dist.barrier()
req = fn2()
if fn2_wait:
req.wait()
dist.barrier()
# Flush L2
cache.zero_()
del cache
# Testing
start_events_fn1 = [
paddle.device.Event(enable_timing=True) for _ in range(num_tests)
]
end_events_fn1 = [
paddle.device.Event(enable_timing=True) for _ in range(num_tests)
]
start_events_fn2 = [
paddle.device.Event(enable_timing=True) for _ in range(num_tests)
]
end_events_fn2 = [
paddle.device.Event(enable_timing=True) for _ in range(num_tests)
]
for i in range(num_tests):
# Record
dist.barrier()
start_events_fn1[i].record()
req = fn1()
end_events_fn1[i].record()
if fn1_wait:
req.wait()
dist.barrier()
start_events_fn2[i].record()
req = fn2()
end_events_fn2[i].record()
if fn2_wait:
req.wait()
dist.barrier()
paddle.device.synchronize()
times_fn1 = np.array(
[
s.elapsed_time(e) / 1e3
for s, e in zip(start_events_fn1, end_events_fn1)
]
)[1:]
times_fn2 = np.array(
[
s.elapsed_time(e) / 1e3
for s, e in zip(start_events_fn2, end_events_fn2)
]
)[1:]
return (
np.average(times_fn1),
np.min(times_fn1),
np.max(times_fn1),
np.average(times_fn2),
np.min(times_fn2),
np.max(times_fn2),
)
def bench_m2n(fn, num_warmups: int = 50, num_tests: int = 50):
# clear
cache = paddle.empty((int(256e6 // 4),), dtype="int32")
cache.zero_()
# Warmup
for _ in range(num_warmups):
dist.barrier()
fn()
dist.barrier()
# Flush L2
cache.zero_()
del cache
# Testing
start_events_fn = [
paddle.device.Event(enable_timing=True) for _ in range(num_tests)
]
end_events_fn = [
paddle.device.Event(enable_timing=True) for _ in range(num_tests)
]
for i in range(num_tests):
dist.barrier()
start_events_fn[i].record()
fn()
end_events_fn[i].record()
dist.barrier()
paddle.device.synchronize()
times_fn = np.array(
[
s.elapsed_time(e) / 1e3
for s, e in zip(start_events_fn, end_events_fn)
]
)[1:]
return (
np.average(times_fn),
np.min(times_fn),
np.max(times_fn),
)
def per_token_cast_back(x_fp8: paddle.Tensor, x_scales: paddle.Tensor):
x_fp32 = x_fp8.to("float32").view((x_fp8.shape[0], -1, 128))
x_scales = x_scales.view((x_fp8.shape[0], -1, 1))
return (x_fp32 * x_scales).view(x_fp8.shape).to("bfloat16")
def test_main(
num_tokens: int,
hidden: int,
num_experts: int,
num_topk: int,
use_fp8: bool,
rank: int,
num_ranks: int,
a_start_rank: int,
a_num_ranks: int,
e_start_rank: int,
e_num_ranks: int,
group: dist.communication.group,
buffer: deep_ep.Buffer,
seed: int = 0,
):
paddle.seed(seed + rank)
random.seed(seed + rank)
assert num_experts % e_num_ranks == 0
num_local_experts = num_experts // e_num_ranks
num_rdma_ranks = num_ranks / 8
# NOTES: the integers greater than 256 exceeds the BF16 precision limit
rank_offset = 128
assert num_ranks - rank_offset < 257, (
'Too many ranks (exceeding test precision limit)'
)
x = paddle.ones((num_tokens, hidden), dtype="bfloat16") * (
rank - rank_offset
)
# x[:, -128:] = paddle.arange(0, num_tokens, dtype="bfloat16").view((-1, 1))
# x = paddle.randn((num_tokens, hidden), dtype="bfloat16")
# x = paddle.ones((num_tokens, hidden), dtype="bfloat16") * 3
topk_idx = paddle.randint(
0, num_experts, shape=[num_tokens, num_topk], dtype="int64"
)
print(f"rank: {rank}, num_local_experts: {num_local_experts}")
topk_weights = paddle.randn((num_tokens, num_topk), dtype="float32").abs_()
# topk_weights = paddle.ones((num_tokens, num_topk), dtype="float32") * 5
print("x: ", x, flush=True)
print("topk_idx: ", topk_idx, flush=True)
print("topk_weights: ", topk_weights, flush=True)
# Calculate bandwidth
num_fp8_bytes, num_bf16_bytes = (hidden + hidden / 128 * 4 + 16), hidden * 2
num_dispatch_comm_bytes, num_combine_comm_bytes = 0, 0
for i in range(num_tokens):
num_selections = (topk_idx[i] != -1).sum().item()
num_dispatch_comm_bytes += num_fp8_bytes * num_selections
num_combine_comm_bytes += num_bf16_bytes * num_selections
paddle.device.synchronize()
dist.barrier()
run_time = 1
print("run_time: ", run_time)
print("num_experts: ", num_experts)
ref_recv_x = paddle.zeros(
(e_num_ranks, num_local_experts, hidden), dtype=paddle.float32
) # [8, 3, 128]
gbl_recv_x = paddle.zeros(
(e_num_ranks, num_local_experts, hidden), dtype=paddle.float32
) # [8, 3, 128]
ref_combin_x = paddle.zeros(
(num_tokens, hidden), dtype=paddle.float32
) # [96, 8192]
gbl_combin_x = paddle.zeros(
(num_tokens, hidden), dtype=paddle.float32
) # [96, 8192]
if rank >= a_start_rank and rank < a_start_rank + a_num_ranks:
if not use_fp8:
ref_recv_x.zero_()
gbl_recv_x.zero_()
ref_combin_x.zero_()
gbl_combin_x.zero_()
for i in range(num_tokens):
for k, expert_id in enumerate(topk_idx[i]):
if expert_id == -1:
continue
erank_id = expert_id // num_local_experts # 0-7
local_expert_id = expert_id % num_local_experts # 0-2
ref_recv_x[erank_id, local_expert_id] += x[i].to(
paddle.float32
)
ref_combin_x[i] += (
x[i].to(paddle.float32) * topk_weights[i][k]
)
packed_recv_x, handle, event, req = buffer.a2e_isend_two_stage(
x,
topk_idx,
topk_weights,
num_max_tokens,
num_experts,
use_fp8=use_fp8,
)
req.wait()
dist.barrier()
e2a_x, event, req = buffer.e2a_irecv_two_stage(
topk_idx,
topk_weights,
handle,
dispatch_use_fp8=use_fp8,
out=None,
)
req.wait()
dist.barrier()
gbl_combin_x = e2a_x.to(paddle.float32)
def a2e_isend_func():
packed_recv_x, handle, event, req = buffer.a2e_isend_two_stage(
x,
topk_idx,
topk_weights,
num_max_tokens,
num_experts,
use_fp8=use_fp8,
)
return req
def e2a_irecv_func():
e2a_x, event, req = buffer.e2a_irecv_two_stage(
topk_idx,
topk_weights,
handle,
dispatch_use_fp8=use_fp8,
out=None,
)
req.wait()
return req
avg_t_fn1, min_t_fn1, max_t_fn1, avg_t_fn2, min_t_fn2, max_t_fn2 = (
bench_split(
a2e_isend_func, e2a_irecv_func, fn1_wait=True, fn2_wait=False
)
)
print(
f'[rank: {rank}][a2e_isend_two_stage] '
f'avg_t: {avg_t_fn1 * 1e6:.2f} us, min_t: {min_t_fn1 * 1e6:.2f} us, max_t: {max_t_fn1 * 1e6:.2f} us',
flush=True,
)
print(
f'[rank: {rank}][e2a_irecv_two_stage] '
f'avg_t: {avg_t_fn2 * 1e6:.2f} us, min_t: {min_t_fn2 * 1e6:.2f} us, max_t: {max_t_fn2 * 1e6:.2f} us',
flush=True,
)
if rank >= e_start_rank and rank < e_start_rank + e_num_ranks:
(
packed_recv_x,
packed_recv_count,
rdma_send_flags,
handle,
event,
req,
) = buffer.a2e_irecv_two_stage(
hidden,
num_topk,
num_max_tokens,
num_experts,
use_fp8=use_fp8,
)
req.wait()
print(
f'[rank: {rank}, packed_recv_count: {packed_recv_count}], packed_recv_x[1]: {packed_recv_x[1]}',
flush=True,
)
dist.barrier()
if not use_fp8:
for local_expert_id in range(num_local_experts):
gbl_recv_x[rank - e_start_rank, local_expert_id] = (
packed_recv_x[
local_expert_id, : packed_recv_count[local_expert_id]
]
.to(paddle.float32)
.sum(0)
)
# e2a isend
if use_fp8:
simulated_gemm_x = per_token_cast_back(
packed_recv_x[0].view((-1, hidden)),
packed_recv_x[1].contiguous().view((-1, hidden // 128)),
).view(packed_recv_x[0].shape)
else:
simulated_gemm_x = packed_recv_x.clone()
event, req = buffer.e2a_isend_two_stage(
simulated_gemm_x,
num_topk,
handle,
dispatch_use_fp8=use_fp8,
out=None,
)
req.wait()
dist.barrier()
def a2e_irecv_func():
(
packed_recv_x,
packed_recv_count,
rdma_send_flags,
handle,
event,
req,
) = buffer.a2e_irecv_two_stage(
hidden,
num_topk,
num_max_tokens,
num_experts,
use_fp8=use_fp8,
)
# event.current_stream_wait()
req.wait()
return req
def e2a_isend_func():
event, req = buffer.e2a_isend_two_stage(
simulated_gemm_x,
num_topk,
handle,
dispatch_use_fp8=use_fp8,
out=None,
)
return req
avg_t_fn1, min_t_fn1, max_t_fn1, avg_t_fn2, min_t_fn2, max_t_fn2 = (
bench_split(
a2e_irecv_func, e2a_isend_func, fn1_wait=False, fn2_wait=True
)
)
print(
f'[rank: {rank}][a2e_irecv_two_stage] '
f'avg_t: {avg_t_fn1 * 1e6:.2f} us, min_t: {min_t_fn1 * 1e6:.2f} us, max_t: {max_t_fn1 * 1e6:.2f} us',
flush=True,
)
print(
f'[rank: {rank}][e2a_isend_two_stage] '
f'avg_t: {avg_t_fn2 * 1e6:.2f} us, min_t: {min_t_fn2 * 1e6:.2f} us, max_t: {max_t_fn2 * 1e6:.2f} us',
flush=True,
)
if not use_fp8:
dist.all_reduce(ref_recv_x, group=group)
dist.all_reduce(gbl_recv_x, group=group)
assert paddle.allclose(ref_recv_x, gbl_recv_x, rtol=1e-3, atol=1e-3), (
f"[rank: {rank}], ref_recv_x: {ref_recv_x}, gbl_recv_x: {gbl_recv_x}"
)
print(
f"[rank: {rank}], ref_recv_x: {ref_recv_x}, gbl_recv_x: {gbl_recv_x}"
)
assert paddle.allclose(
ref_combin_x, gbl_combin_x, rtol=1.0, atol=1.0
), (
f"[rank: {rank}], ref_combin_x: {ref_combin_x}, gbl_combin_x: {gbl_combin_x}"
)
print(
f"[rank: {rank}], ref_combin_x: {ref_combin_x}, gbl_combin_x: {gbl_combin_x}"
)
print(f"rank: {rank} passed the check")
dist.barrier()
def test_loop():
rank = dist.get_rank()
num_ranks = dist.get_world_size()
group = paddle.distributed.new_group(range(num_ranks))
print("rank: ", rank, flush=True)
print("num_ranks: ", num_ranks, flush=True)
a_start_rank = 0
a_num_ranks = 16
e_start_rank = a_start_rank + a_num_ranks
e_num_ranks = num_ranks - a_num_ranks
# 64 * 3 / 48 = 4
# 64 * 3 / 32 = 6
# 64 * 3 / 24 = 8
# 64 * 3 / 12 = 16
num_tokens, hidden, num_topk, num_experts = 96, 8192, 8, 64
assert num_tokens <= num_max_tokens, (
"num_tokens must be less equal to num_max_tokens"
)
num_rdma_ranks = num_ranks / 8
num_local_experts = num_experts / num_ranks
num_rdma_bytes = deep_ep.M2NBuffer.get_low_latency_rdma_size_hint_two_stage(
num_max_tokens,
hidden,
num_ranks,
a_num_ranks,
e_num_ranks,
num_experts,
num_topk,
)
use_fp8 = True
num_nvl_bytes = deep_ep.M2NBuffer.get_low_latency_nvl_size_hint_two_stage(
num_max_tokens,
hidden,
num_ranks,
a_num_ranks,
e_num_ranks,
num_experts,
num_topk,
use_fp8,
)
print(
f'Allocating rdma buffer size: {num_rdma_bytes / 1e6} MB, nvl buffer size: {num_nvl_bytes / 1e6} MB...',
flush=True,
)
buffer = deep_ep.M2NBuffer(
group,
a_start_rank,
a_num_ranks,
e_start_rank,
e_num_ranks,
num_nvl_bytes=num_nvl_bytes,
num_rdma_bytes=num_rdma_bytes,
low_latency_mode=True,
num_qps_per_rank=num_rdma_ranks,
)
test_main(
num_tokens,
hidden,
num_experts,
num_topk,
use_fp8,
rank,
num_ranks,
a_start_rank,
a_num_ranks,
e_start_rank,
e_num_ranks,
group,
buffer,
seed=1,
)
def init_dist_env(world_size, seed=20):
context = contextlib.nullcontext()
with context:
# start to init distributed env
strategy = fleet.DistributedStrategy()
strategy.hybrid_configs = {
"dp_degree": 1,
"mp_degree": world_size,
"pp_degree": 1,
"sharding_degree": 1,
}
# Set control in tensor parallel
strategy.tensor_parallel_configs = {"tensor_init_seed": seed}
fleet.init(is_collective=True, strategy=strategy)
if __name__ == '__main__':
if dist.get_world_size() > 1:
init_dist_env(dist.get_world_size())
test_loop()