Skip to content

Commit b4dcdb7

Browse files
vmoenscursoragent
andcommitted
[Distributed] Add distributed benchmark script
Benchmark comparing leaf-by-leaf vs consolidated send/recv, broadcast, all_reduce, and init_remote across various TD sizes. Reports latency and speedup for each configuration. Co-authored-by: Cursor <cursoragent@cursor.com> ghstack-source-id: 45de218 Pull-Request: #1592 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent fa4131c commit b4dcdb7

1 file changed

Lines changed: 192 additions & 0 deletions

File tree

benchmarks/bench_distributed.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""Distributed TensorDict benchmark: leaf-by-leaf vs consolidated transport.
2+
3+
Benchmarks use NCCL backend with CUDA tensors, so requires GPU nodes.
4+
5+
Usage (2-node cluster via torchrun):
6+
# On node 0:
7+
torchrun --nproc_per_node=1 --nnodes=2 --node_rank=0 \
8+
--master_addr=$MASTER_ADDR --master_port=29500 bench_distributed.py
9+
# On node 1:
10+
torchrun --nproc_per_node=1 --nnodes=2 --node_rank=1 \
11+
--master_addr=$MASTER_ADDR --master_port=29500 bench_distributed.py
12+
"""
13+
14+
import os
15+
import time
16+
17+
import torch
18+
import torch.distributed as dist
19+
20+
from tensordict import TensorDict
21+
22+
23+
def make_td(num_tensors, tensor_size=1024, dtype=torch.float32, device="cuda"):
24+
"""Create a TensorDict with `num_tensors` tensors of `tensor_size` elements."""
25+
d = {f"t{i}": torch.randn(tensor_size, dtype=dtype, device=device) for i in range(num_tensors)}
26+
return TensorDict(d, batch_size=[], device=device)
27+
28+
29+
def bench_leaf_send_recv(td, n_iters, rank, warmup=3):
30+
"""Benchmark leaf-by-leaf send/recv."""
31+
for _ in range(warmup):
32+
if rank == 0:
33+
td.send(dst=1)
34+
else:
35+
td.recv(src=0)
36+
37+
dist.barrier()
38+
torch.cuda.synchronize()
39+
t0 = time.perf_counter()
40+
for _ in range(n_iters):
41+
if rank == 0:
42+
td.send(dst=1)
43+
else:
44+
td.recv(src=0)
45+
torch.cuda.synchronize()
46+
dist.barrier()
47+
return (time.perf_counter() - t0) / n_iters
48+
49+
50+
def bench_consolidated_send_recv(td_sender, td_receiver, n_iters, rank, warmup=3):
51+
"""Benchmark consolidated send/recv (steady-state)."""
52+
for _ in range(warmup):
53+
if rank == 0:
54+
td_sender.send(dst=1, consolidated=True)
55+
else:
56+
td_receiver.recv(src=0, consolidated=True)
57+
58+
dist.barrier()
59+
torch.cuda.synchronize()
60+
t0 = time.perf_counter()
61+
for _ in range(n_iters):
62+
if rank == 0:
63+
td_sender.send(dst=1, consolidated=True)
64+
else:
65+
td_receiver.recv(src=0, consolidated=True)
66+
torch.cuda.synchronize()
67+
dist.barrier()
68+
return (time.perf_counter() - t0) / n_iters
69+
70+
71+
def bench_broadcast(td, n_iters, rank, warmup=3):
72+
"""Benchmark broadcast from rank 0."""
73+
for _ in range(warmup):
74+
td.broadcast(src=0)
75+
76+
dist.barrier()
77+
torch.cuda.synchronize()
78+
t0 = time.perf_counter()
79+
for _ in range(n_iters):
80+
td.broadcast(src=0)
81+
torch.cuda.synchronize()
82+
dist.barrier()
83+
return (time.perf_counter() - t0) / n_iters
84+
85+
86+
def bench_all_reduce(td, n_iters, rank, warmup=3):
87+
"""Benchmark all_reduce."""
88+
for _ in range(warmup):
89+
td.all_reduce()
90+
91+
dist.barrier()
92+
torch.cuda.synchronize()
93+
t0 = time.perf_counter()
94+
for _ in range(n_iters):
95+
td.all_reduce()
96+
torch.cuda.synchronize()
97+
dist.barrier()
98+
return (time.perf_counter() - t0) / n_iters
99+
100+
101+
def bench_init_remote(td, n_iters, rank, warmup=3):
102+
"""Benchmark init_remote / from_remote_init (uses broadcast internally)."""
103+
for _ in range(warmup):
104+
if rank == 0:
105+
td.init_remote(dst=1)
106+
else:
107+
TensorDict.from_remote_init(src=0, device=td.device)
108+
109+
dist.barrier()
110+
torch.cuda.synchronize()
111+
t0 = time.perf_counter()
112+
for _ in range(n_iters):
113+
if rank == 0:
114+
td.init_remote(dst=1)
115+
else:
116+
TensorDict.from_remote_init(src=0, device=td.device)
117+
torch.cuda.synchronize()
118+
dist.barrier()
119+
return (time.perf_counter() - t0) / n_iters
120+
121+
122+
def total_bytes(td):
123+
"""Total bytes in all leaf tensors."""
124+
total = 0
125+
for v in td.values(True, True):
126+
if isinstance(v, torch.Tensor):
127+
total += v.numel() * v.element_size()
128+
return total
129+
130+
131+
def main():
132+
dist.init_process_group(backend="nccl")
133+
rank = dist.get_rank()
134+
local_rank = int(os.environ.get("LOCAL_RANK", 0))
135+
torch.cuda.set_device(local_rank)
136+
137+
configs = [
138+
(10, 1024),
139+
(50, 1024),
140+
(100, 1024),
141+
(500, 1024),
142+
(10, 1024 * 1024),
143+
(50, 1024 * 1024),
144+
]
145+
n_iters = 20
146+
147+
if rank == 0:
148+
print(f"Backend: nccl | Device: cuda:{local_rank}")
149+
print(f"{'num_tensors':>12} {'tensor_size':>12} {'total_MB':>10} "
150+
f"{'leaf_ms':>10} {'consol_ms':>10} {'speedup':>8} "
151+
f"{'bcast_ms':>10} {'allred_ms':>10} {'initrem_ms':>10}")
152+
print("-" * 112)
153+
154+
for num_tensors, tensor_size in configs:
155+
td = make_td(num_tensors, tensor_size, device=f"cuda:{local_rank}")
156+
nbytes = total_bytes(td)
157+
mb = nbytes / 1e6
158+
159+
td_recv_leaf = make_td(num_tensors, tensor_size, device=f"cuda:{local_rank}")
160+
td_recv_leaf.zero_()
161+
leaf_time = bench_leaf_send_recv(
162+
td if rank == 0 else td_recv_leaf, n_iters, rank
163+
)
164+
165+
# Consolidated send/recv: setup phase via broadcast
166+
if rank == 0:
167+
td.init_remote(dst=1)
168+
td_c = td.consolidate(metadata=True)
169+
else:
170+
td_c = TensorDict.from_remote_init(src=0, device=f"cuda:{local_rank}")
171+
172+
consol_time = bench_consolidated_send_recv(
173+
td_c if rank == 0 else None,
174+
td_c if rank == 1 else None,
175+
n_iters, rank,
176+
)
177+
178+
bcast_time = bench_broadcast(td if rank == 0 else TensorDict({}, device=f"cuda:{local_rank}"), n_iters, rank)
179+
allred_time = bench_all_reduce(td.clone(), n_iters, rank)
180+
initrem_time = bench_init_remote(td, n_iters, rank)
181+
182+
if rank == 0:
183+
speedup = leaf_time / consol_time if consol_time > 0 else float("inf")
184+
print(f"{num_tensors:>12} {tensor_size:>12} {mb:>10.2f} "
185+
f"{leaf_time * 1000:>10.2f} {consol_time * 1000:>10.2f} {speedup:>8.1f}x "
186+
f"{bcast_time * 1000:>10.2f} {allred_time * 1000:>10.2f} {initrem_time * 1000:>10.2f}")
187+
188+
dist.destroy_process_group()
189+
190+
191+
if __name__ == "__main__":
192+
main()

0 commit comments

Comments
 (0)