# 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()