Files
wehub-resource-sync b5ecf06f65
Code Format Check / format-check (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:24:32 +08:00

140 lines
6.1 KiB
Python

import argparse
import math
import os
import random
import torch
import torch.distributed as dist
import deep_ep
from deep_ep.utils.envs import init_dist, dist_print, get_rdma_gbs
from deep_ep.utils.testing import bench_kineto
def generate_stress_ops(rank_idx: int, num_ranks: int, num_sends: int, shape: tuple):
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
st = random.randint(0, 10 ** 8)
rt = st + random.randint(1, 3 * 10 ** 6)
send_times[(src_rank_idx, dst_rank_idx)].append(st)
recv_times[(src_rank_idx, dst_rank_idx)].append(rt)
ops = []
for (src_rank_idx, dst_rank_idx) in send_times:
n = len(send_times[(src_rank_idx, dst_rank_idx)])
sorted_send = sorted(send_times[(src_rank_idx, dst_rank_idx)])
sorted_recv = sorted(recv_times[(src_rank_idx, dst_rank_idx)])
for i in range(n):
tensor = torch.randn(shape, dtype=torch.bfloat16, device='cuda')
if src_rank_idx == rank_idx:
ops.append(('send', sorted_send[i], dst_rank_idx, i, tensor))
if dst_rank_idx == rank_idx:
ops.append(('recv', sorted_recv[i], src_rank_idx, i, tensor))
ops.sort(key=lambda x: (x[1], x[3]))
return ops
# noinspection PyShadowingNames
@torch.inference_mode()
def test(local_rank: int, num_local_ranks: int, args: argparse.Namespace):
rank_idx, num_ranks, group = init_dist(local_rank, num_local_ranks)
shape = (args.num_tokens, args.hidden)
num_max_tensor_bytes = math.prod(shape) * 2
num_max_inflight_tensors = args.num_max_inflight_tensors
buffer = deep_ep.ElasticBuffer(
group, explicitly_destroy=True, allow_hybrid_mode=False,
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)
# Print configs
assert num_ranks > 1
dist_print(f'Config:\n'
f' > Ranks: {num_ranks}\n'
f' > Shape: {shape}\n'
f' > Max inflight tensors: {num_max_inflight_tensors}\n',
once_in_node=True)
# Run stress tests
dist_print('Running stress tests:', once_in_node=True)
for seed in range(args.num_stress_iterations):
dist_print(f' > Testing with {seed=} ...', once_in_node=True)
torch.manual_seed(42 + seed)
random.seed(42 + seed)
ops = generate_stress_ops(rank_idx, num_ranks, args.num_sends, shape)
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
dist_print(' > All stress tests passed', once_in_node=True)
dist_print(once_in_node=True)
# Profiling
dist_print('Profiling PP send and recv:', once_in_node=True)
num_approx_rdma_cycles = int(num_max_tensor_bytes * 2 / get_rdma_gbs() * 1.5)
def get_trace_path(prefix: str):
return (None if not args.dump_profile_traces
else f'{args.dump_profile_traces}/{prefix}_rank{rank_idx}.json')
for hide_rdma_latency in (True, False):
for num_concurrent in (1, 2, 3):
send_tensors = [torch.randn(shape, dtype=torch.bfloat16, device='cuda') for _ in range(num_concurrent)]
recv_tensors = [torch.empty(shape, dtype=torch.bfloat16, device='cuda') for _ in range(num_concurrent)]
def loop(_hide_rdma_latency=hide_rdma_latency):
torch.zeros((131072, 32768), dtype=torch.int, device='cuda')
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,
trace_path=get_trace_path(f'pp_{num_concurrent}_{hide_rdma_latency}'))
dist_print(
f' > EP: {rank_idx:3}/{num_ranks:3} | '
f'hide={int(hide_rdma_latency)}, concurrent={num_concurrent} | '
f'send: {send_t * 1e6:.3f} us, '
f'{2 * num_max_tensor_bytes / send_t / 1e9:.3f} GB/s | '
f'recv: {recv_t * 1e6:.3f} us, '
f'{(2 if hide_rdma_latency else 1) * num_max_tensor_bytes / recv_t / 1e9:.3f} GB/s')
dist_print(once_in_node=True)
# Destroy the runtime and communication group
buffer.destroy()
dist.destroy_process_group()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Test PP send/recv kernels')
parser.add_argument('--num-processes', type=int, default=4)
parser.add_argument('--num-tokens', type=int, default=4096)
parser.add_argument('--hidden', type=int, default=7168)
parser.add_argument('--num-max-inflight-tensors', type=int, default=4)
parser.add_argument('--num-stress-iterations', type=int, default=4)
parser.add_argument('--num-sends', type=int, default=128)
parser.add_argument('--num-sleep-cycles', type=int, default=10 ** 7)
parser.add_argument('--dump-profile-traces', type=str, default='')
args = parser.parse_args()
if args.dump_profile_traces:
os.makedirs(args.dump_profile_traces, exist_ok=True)
torch.multiprocessing.spawn(test, args=(args.num_processes, args), nprocs=args.num_processes)