Skip to content

Commit c725751

Browse files
committed
Add CuTe symmetric memory all-reduce example
1 parent 468b3d0 commit c725751

2 files changed

Lines changed: 242 additions & 0 deletions

File tree

transformer_nuggets/cute/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,20 @@
99
from transformer_nuggets.cute.element_wise import ElementwiseOp, elementwise_op
1010
from transformer_nuggets.cute.utils import visualize_tv_layout
1111
from transformer_nuggets.cute import profiler
12+
13+
14+
_SYMMETRIC_MEMORY_EXPORTS = {
15+
"compile_symmetric_memory_all_reduce",
16+
"init_torchrun_process_group",
17+
"run_symmetric_memory_all_reduce_example",
18+
"symmetric_memory_all_reduce",
19+
"symmetric_memory_peer_tensors",
20+
}
21+
22+
23+
def __getattr__(name):
24+
if name in _SYMMETRIC_MEMORY_EXPORTS:
25+
from transformer_nuggets.cute import symmetric_memory
26+
27+
return getattr(symmetric_memory, name)
28+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
import os
5+
import time
6+
7+
import torch
8+
import torch.distributed as dist
9+
10+
import cutlass.cute as cute
11+
from cutlass.cute.runtime import from_dlpack
12+
13+
14+
def _symm_mem():
15+
import torch.distributed._symmetric_memory as symm_mem
16+
17+
return symm_mem
18+
19+
20+
@cute.kernel
21+
def _all_reduce_simple_kernel(
22+
inputs: list[cute.Tensor],
23+
output: cute.Tensor,
24+
thr_layout: cute.Layout,
25+
val_layout: cute.Layout,
26+
):
27+
tidx, _, _ = cute.arch.thread_idx()
28+
bidx, _, _ = cute.arch.block_idx()
29+
30+
blk_coord = ((None, None), bidx)
31+
local_tile_out = output[blk_coord]
32+
local_tile_list = [tensor[blk_coord] for tensor in inputs]
33+
34+
assert all(tensor.element_type == inputs[0].element_type for tensor in inputs)
35+
36+
copy_atom_load = cute.make_copy_atom(
37+
cute.nvgpu.CopyUniversalOp(),
38+
inputs[0].element_type,
39+
)
40+
copy_atom_store = cute.make_copy_atom(
41+
cute.nvgpu.CopyUniversalOp(),
42+
inputs[0].element_type,
43+
)
44+
tiled_copy = cute.make_tiled_copy_tv(copy_atom_load, thr_layout, val_layout)
45+
thr_copy = tiled_copy.get_slice(tidx)
46+
47+
thr_tensor_list = [thr_copy.partition_S(tensor) for tensor in local_tile_list]
48+
thr_out = thr_copy.partition_D(local_tile_out)
49+
frg_tensor_list = [cute.make_fragment_like(tensor) for tensor in thr_tensor_list]
50+
frg_acc = cute.make_fragment_like(thr_out)
51+
frg_acc.fill(0.0)
52+
53+
for thr_tensor, frg_tensor in zip(thr_tensor_list, frg_tensor_list):
54+
cute.copy(copy_atom_load, thr_tensor, frg_tensor)
55+
frg_acc.store(frg_tensor.load() + frg_acc.load())
56+
57+
cute.copy(copy_atom_store, frg_acc, thr_out)
58+
59+
60+
@cute.jit
61+
def _all_reduce_simple(
62+
inputs: list[cute.Tensor],
63+
output: cute.Tensor,
64+
):
65+
thr_layout = cute.make_ordered_layout((4, 32), order=(1, 0))
66+
val_layout = cute.make_ordered_layout((4, 4), order=(1, 0))
67+
tiler_mn, tv_layout = cute.make_layout_tv(thr_layout, val_layout)
68+
69+
divided_inputs = [cute.zipped_divide(tensor, tiler_mn) for tensor in inputs]
70+
divided_output = cute.zipped_divide(output, tiler_mn)
71+
_all_reduce_simple_kernel(
72+
divided_inputs,
73+
divided_output,
74+
thr_layout,
75+
val_layout,
76+
).launch(
77+
grid=[cute.size(divided_output, mode=[1]), 1, 1],
78+
block=[cute.size(tv_layout, mode=[0]), 1, 1],
79+
)
80+
81+
82+
def symmetric_memory_peer_tensors(
83+
local_tensor: torch.Tensor,
84+
group: dist.ProcessGroup | str | None = None,
85+
) -> tuple[object, list[torch.Tensor]]:
86+
"""Rendezvous a PyTorch symmetric-memory tensor and return all peer views."""
87+
if group is None:
88+
group = dist.group.WORLD
89+
handle = _symm_mem().rendezvous(local_tensor, group=group)
90+
return handle, [
91+
handle.get_buffer(peer_rank, local_tensor.shape, local_tensor.dtype)
92+
for peer_rank in range(handle.world_size)
93+
]
94+
95+
96+
def compile_symmetric_memory_all_reduce(
97+
peer_tensors: list[torch.Tensor],
98+
output: torch.Tensor,
99+
):
100+
"""Compile the simple CuTeDSL peer-load all-reduce for the given tensor layouts."""
101+
if output.dtype != torch.float32 or any(
102+
tensor.dtype != torch.float32 for tensor in peer_tensors
103+
):
104+
raise TypeError("symmetric_memory_all_reduce currently expects float32 tensors")
105+
return cute.compile(
106+
_all_reduce_simple,
107+
[from_dlpack(tensor) for tensor in peer_tensors],
108+
from_dlpack(output),
109+
)
110+
111+
112+
def symmetric_memory_all_reduce(
113+
peer_tensors: list[torch.Tensor],
114+
output: torch.Tensor,
115+
compiled=None,
116+
):
117+
"""Run the simple CuTeDSL all-reduce over PyTorch symmetric-memory peer tensors."""
118+
if compiled is None:
119+
compiled = compile_symmetric_memory_all_reduce(peer_tensors, output)
120+
compiled(
121+
[from_dlpack(tensor) for tensor in peer_tensors],
122+
from_dlpack(output),
123+
)
124+
return output
125+
126+
127+
def init_torchrun_process_group() -> None:
128+
"""Initialize CUDA and torch.distributed for `torchrun` launched examples."""
129+
os.environ.setdefault("TORCH_SYMM_MEM_DISABLE_MULTICAST", "1")
130+
local_rank = int(os.environ["LOCAL_RANK"])
131+
torch.cuda.set_device(local_rank)
132+
dist.init_process_group(backend="cpu:gloo,cuda:nccl")
133+
134+
135+
def run_symmetric_memory_all_reduce_example(
136+
m: int,
137+
n: int,
138+
*,
139+
warmup_iterations: int = 2,
140+
iterations: int = 10,
141+
skip_ref_check: bool = False,
142+
benchmark: bool = False,
143+
) -> torch.Tensor:
144+
"""Run a torchrun-friendly CuTeDSL all-reduce example using PyTorch symmetric memory."""
145+
rank = dist.get_rank()
146+
world_size = dist.get_world_size()
147+
device = torch.device("cuda", torch.cuda.current_device())
148+
if rank == 0:
149+
print("\nRunning CuTeDSL symmetric-memory all-reduce with:")
150+
print(f"Tensor dimensions: [{m}, {n}]")
151+
print(f"GPU count: {world_size}")
152+
153+
local_tensor = _symm_mem().empty((m, n), dtype=torch.float32, device=device)
154+
local_tensor.random_(0, 100)
155+
_, peer_tensors = symmetric_memory_peer_tensors(local_tensor)
156+
output = torch.zeros((m, n), device=device)
157+
158+
if rank == 0:
159+
print("Compiling kernel with cute.compile ...")
160+
start_time = time.time()
161+
compiled = compile_symmetric_memory_all_reduce(peer_tensors, output)
162+
if rank == 0:
163+
print(f"Compilation time: {time.time() - start_time:.4f} seconds")
164+
165+
if not skip_ref_check:
166+
dist.barrier(device_ids=[device.index])
167+
symmetric_memory_all_reduce(peer_tensors, output, compiled)
168+
dist.barrier(device_ids=[device.index])
169+
torch.testing.assert_close(sum(tensor.cpu() for tensor in peer_tensors), output.cpu())
170+
if rank == 0:
171+
print("Results verified successfully!")
172+
173+
if not benchmark:
174+
return output
175+
176+
for _ in range(warmup_iterations):
177+
symmetric_memory_all_reduce(peer_tensors, output, compiled)
178+
torch.cuda.synchronize()
179+
180+
start = torch.cuda.Event(enable_timing=True)
181+
end = torch.cuda.Event(enable_timing=True)
182+
start.record()
183+
for _ in range(iterations):
184+
symmetric_memory_all_reduce(peer_tensors, output, compiled)
185+
end.record()
186+
end.synchronize()
187+
avg_time_us = start.elapsed_time(end) * 1000 / iterations
188+
189+
if rank == 0:
190+
bytes_moved = (world_size + 1) * output.numel() * output.element_size()
191+
print(f"Kernel execution time: {avg_time_us / 1e3:.4f} ms")
192+
print(f"Achieved memory throughput: {bytes_moved / (avg_time_us / 1e6) / 1e9:.2f} GB/s")
193+
print(f"First few elements of result:\n{output[:3, :3]}")
194+
195+
return output
196+
197+
198+
def main() -> None:
199+
parser = argparse.ArgumentParser(
200+
description="simple CuTeDSL all-reduce using PyTorch symmetric memory"
201+
)
202+
parser.add_argument("--M", default=1024, type=int)
203+
parser.add_argument("--N", default=1024, type=int)
204+
parser.add_argument("--warmup_iterations", default=2, type=int)
205+
parser.add_argument("--iterations", default=10, type=int)
206+
parser.add_argument("--skip_ref_check", action="store_true")
207+
parser.add_argument("--benchmark", action="store_true")
208+
args = parser.parse_args()
209+
210+
init_torchrun_process_group()
211+
try:
212+
run_symmetric_memory_all_reduce_example(
213+
args.M,
214+
args.N,
215+
warmup_iterations=args.warmup_iterations,
216+
iterations=args.iterations,
217+
skip_ref_check=args.skip_ref_check,
218+
benchmark=args.benchmark,
219+
)
220+
finally:
221+
dist.destroy_process_group()
222+
223+
224+
if __name__ == "__main__":
225+
main()

0 commit comments

Comments
 (0)