Skip to content

Commit fcbfab3

Browse files
committed
[TLE]Add two test cases for tle.remote's intro_node_reduce_scatter
1 parent 4910084 commit fcbfab3

4 files changed

Lines changed: 697 additions & 0 deletions
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
import os
2+
import sys
3+
from typing import Optional
4+
5+
import torch
6+
import torch.distributed as dist
7+
8+
import triton
9+
import triton.language as tl
10+
import triton.runtime
11+
import triton.experimental.tle.language as tle
12+
13+
14+
# Kernel 1: scatter (optimized)
15+
@triton.jit
16+
def scatter_kernel_opt(
17+
input_ptr,
18+
local_scatter_ptr,
19+
dev_mem_ptr,
20+
M_per_rank,
21+
N,
22+
LOCAL_RANK: tl.constexpr,
23+
WORLD_SIZE: tl.constexpr,
24+
BLOCK_M: tl.constexpr,
25+
BLOCK_N: tl.constexpr,
26+
):
27+
pid = tl.program_id(0)
28+
num_pid = tl.num_programs(0)
29+
30+
num_tiles_m = tl.cdiv(M_per_rank, BLOCK_M)
31+
num_tiles_n = tl.cdiv(N, BLOCK_N)
32+
tiles_per_peer = num_tiles_m * num_tiles_n
33+
34+
row_offs = tl.arange(0, BLOCK_M)
35+
col_offs = tl.arange(0, BLOCK_N)
36+
37+
# Every peer reserves a [M_per_rank, N] slot for this rank.
38+
slot_offset_elems = LOCAL_RANK * M_per_rank * N
39+
40+
for step in range(WORLD_SIZE):
41+
peer = (LOCAL_RANK + step + 1) % WORLD_SIZE
42+
43+
# Resolve remote base pointer once per peer.
44+
if peer == LOCAL_RANK:
45+
remote_base = local_scatter_ptr + slot_offset_elems
46+
else:
47+
remote_base = tle.remote(
48+
dev_mem_ptr,
49+
space="device",
50+
dtype=input_ptr.dtype.element_ty,
51+
shard_id=peer,
52+
offset=slot_offset_elems,
53+
)
54+
55+
for local_tile in range(pid, tiles_per_peer, num_pid):
56+
tile_m = local_tile // num_tiles_n
57+
tile_n = local_tile % num_tiles_n
58+
59+
in_row = peer * M_per_rank + tile_m * BLOCK_M
60+
in_col = tile_n * BLOCK_N
61+
in_ptrs = (input_ptr
62+
+ (in_row + row_offs[:, None]) * N
63+
+ (in_col + col_offs[None, :]))
64+
# Mask out rows/cols that are beyond the actual tensor boundary.
65+
in_row_mask = (in_row + row_offs[:, None]) < (peer + 1) * M_per_rank
66+
in_col_mask = (in_col + col_offs[None, :]) < N
67+
data = tl.load(in_ptrs, mask=in_row_mask & in_col_mask, other=0.0)
68+
69+
out_row_in_peer = tile_m * BLOCK_M
70+
out_col = tile_n * BLOCK_N
71+
out_ptrs = (remote_base
72+
+ (out_row_in_peer + row_offs[:, None]) * N
73+
+ (out_col + col_offs[None, :]))
74+
out_row_mask = (out_row_in_peer + row_offs[:, None]) < M_per_rank
75+
tl.store(out_ptrs, data, mask=out_row_mask & in_col_mask)
76+
77+
78+
# Kernel 2: ring reduce with TMA descriptors
79+
@triton.jit
80+
def ring_reduce_kernel_tma(
81+
local_scatter_ptr,
82+
output_ptr,
83+
M_per_rank,
84+
N,
85+
LOCAL_RANK: tl.constexpr,
86+
WORLD_SIZE: tl.constexpr,
87+
BLOCK_M: tl.constexpr,
88+
BLOCK_N: tl.constexpr,
89+
):
90+
pid = tl.program_id(0)
91+
num_pid = tl.num_programs(0)
92+
93+
num_tiles_m = tl.cdiv(M_per_rank, BLOCK_M)
94+
num_tiles_n = tl.cdiv(N, BLOCK_N)
95+
total_tiles = num_tiles_m * num_tiles_n
96+
97+
c_desc = tl.make_tensor_descriptor(
98+
local_scatter_ptr,
99+
shape=[M_per_rank * WORLD_SIZE, N],
100+
strides=[N, 1],
101+
block_shape=[BLOCK_M, BLOCK_N],
102+
)
103+
104+
output_desc = tl.make_tensor_descriptor(
105+
output_ptr,
106+
shape=[M_per_rank, N],
107+
strides=[N, 1],
108+
block_shape=[BLOCK_M, BLOCK_N],
109+
)
110+
111+
begin_idx = LOCAL_RANK
112+
113+
for tile_id in range(pid, total_tiles, num_pid):
114+
tile_m = tile_id // num_tiles_n
115+
tile_n = tile_id % num_tiles_n
116+
117+
row_in_shard = tile_m * BLOCK_M
118+
col = tile_n * BLOCK_N
119+
120+
src_rank = (begin_idx + 1) % WORLD_SIZE
121+
accum = c_desc.load([
122+
row_in_shard + src_rank * M_per_rank,
123+
col,
124+
])
125+
126+
for i in range(1, WORLD_SIZE):
127+
src_rank = (i + begin_idx + 1) % WORLD_SIZE
128+
data = c_desc.load([
129+
row_in_shard + src_rank * M_per_rank,
130+
col,
131+
])
132+
accum += data
133+
134+
output_desc.store([row_in_shard, col], accum)
135+
136+
137+
# Host-side reference using PyTorch/NCCL
138+
def torch_reduce_scatter(input_tensor, group):
139+
M, N = input_tensor.shape
140+
world_size = dist.get_world_size(group)
141+
output = torch.empty((M // world_size, N),
142+
dtype=input_tensor.dtype,
143+
device=input_tensor.device)
144+
dist.reduce_scatter_tensor(output, input_tensor, group=group)
145+
return output
146+
147+
148+
# TLE reduce-scatter host wrapper (optimized scatter -> barrier -> TMA reduce)
149+
def tle_reduce_scatter(
150+
input_tensor,
151+
scatter_buf,
152+
dev_mem_ptr,
153+
output,
154+
M_per_rank,
155+
N,
156+
local_rank,
157+
world_size,
158+
stream,
159+
num_sms: int = -1,
160+
):
161+
# Scatter launch config mirrors the reduce two-tier design.
162+
if num_sms == -1:
163+
grid_scatter = lambda META: (
164+
triton.cdiv(M_per_rank, META["BLOCK_M"])
165+
* triton.cdiv(N, META["BLOCK_N"]),
166+
)
167+
scatter_num_warps = 4
168+
else:
169+
grid_scatter = lambda META: (
170+
min(
171+
triton.cdiv(M_per_rank, META["BLOCK_M"])
172+
* triton.cdiv(N, META["BLOCK_N"]),
173+
128,
174+
),
175+
)
176+
scatter_num_warps = 8
177+
178+
with torch.cuda.stream(stream):
179+
scatter_kernel_opt[grid_scatter](
180+
input_tensor,
181+
scatter_buf,
182+
dev_mem_ptr,
183+
M_per_rank,
184+
N,
185+
LOCAL_RANK=local_rank,
186+
WORLD_SIZE=world_size,
187+
BLOCK_M=256,
188+
BLOCK_N=128,
189+
num_warps=scatter_num_warps,
190+
)
191+
192+
torch.cuda.synchronize()
193+
dist.barrier()
194+
195+
def alloc_fn(size: int, alignment: int, stream: Optional[int]):
196+
return torch.empty(size, device="cuda", dtype=torch.int8)
197+
198+
triton.set_allocator(alloc_fn)
199+
200+
# Reduce launch config aligned with 05-intra-node-reduce-scatter.py
201+
if num_sms == -1:
202+
grid_reduce = lambda META: (
203+
triton.cdiv(M_per_rank, META["BLOCK_M"])
204+
* triton.cdiv(N, META["BLOCK_N"]),
205+
)
206+
with torch.cuda.stream(stream):
207+
ring_reduce_kernel_tma[grid_reduce](
208+
scatter_buf,
209+
output,
210+
M_per_rank,
211+
N,
212+
LOCAL_RANK=local_rank,
213+
WORLD_SIZE=world_size,
214+
BLOCK_M=256,
215+
BLOCK_N=64,
216+
num_warps=4,
217+
)
218+
else:
219+
grid_reduce = lambda META: (
220+
min(
221+
triton.cdiv(M_per_rank, META["BLOCK_M"])
222+
* triton.cdiv(N, META["BLOCK_N"]),
223+
num_sms,
224+
),
225+
)
226+
with torch.cuda.stream(stream):
227+
ring_reduce_kernel_tma[grid_reduce](
228+
scatter_buf,
229+
output,
230+
M_per_rank,
231+
N,
232+
LOCAL_RANK=local_rank,
233+
WORLD_SIZE=world_size,
234+
BLOCK_M=256,
235+
BLOCK_N=128,
236+
num_warps=8,
237+
)
238+
239+
240+
# Main
241+
def main():
242+
mem_pool = tle.get_mem_pool()
243+
244+
rank = dist.get_rank()
245+
world_size = dist.get_world_size()
246+
local_rank = int(os.environ.get("LOCAL_RANK", rank))
247+
torch.cuda.set_device(local_rank)
248+
249+
print(f"[Rank {rank}/{world_size}] Starting TLE reduce-scatter (4096, 2048)")
250+
251+
if world_size < 2:
252+
print("This example needs at least 2 GPUs", file=sys.stderr)
253+
sys.exit(1)
254+
255+
dtype = torch.bfloat16
256+
M, N = 4096, 2048
257+
M_per_rank = M // world_size
258+
259+
if M_per_rank < 256:
260+
print(f"M // world_size = {M_per_rank} < 256, skipping", file=sys.stderr)
261+
sys.exit(1)
262+
263+
with torch.cuda.use_mem_pool(mem_pool):
264+
scatter_buf = torch.empty((M * N,), dtype=dtype, device="cuda")
265+
_, scatter_dev_mem_ptr = tle.create_comm_tensor(scatter_buf)
266+
267+
input_tensor = torch.rand((M, N), dtype=dtype, device="cuda")
268+
scatter_buf = scatter_buf.view(M, N)
269+
output = torch.empty((M_per_rank, N), dtype=dtype, device="cuda")
270+
stream = torch.cuda.current_stream()
271+
num_sms = torch.cuda.get_device_properties(local_rank).multi_processor_count
272+
273+
torch_output = torch_reduce_scatter(input_tensor, group=None)
274+
torch.cuda.synchronize()
275+
276+
# Correctness check.
277+
tle_reduce_scatter(
278+
input_tensor,
279+
scatter_buf,
280+
scatter_dev_mem_ptr,
281+
output,
282+
M_per_rank,
283+
N,
284+
local_rank,
285+
world_size,
286+
stream,
287+
num_sms=num_sms,
288+
)
289+
torch.cuda.synchronize()
290+
291+
atol, rtol = 6e-2, 6e-2
292+
if torch.allclose(torch_output, output, atol=atol, rtol=rtol):
293+
print(f"[Rank {rank}] shape={(M, N)} PASSED")
294+
else:
295+
print(f"[Rank {rank}] shape={(M, N)} FAILED")
296+
print(f"[Rank {rank}] torch_output[:2,:4] = {torch_output[:2,:4]}")
297+
print(f"[Rank {rank}] tle_output[:2,:4] = {output[:2,:4]}")
298+
sys.exit(1)
299+
300+
tle.cleanup_communicator()
301+
302+
303+
if __name__ == "__main__":
304+
main()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
3+
rm -rf ~/.triton/cache
4+
5+
# FlagCX environment variables (tune for your machine if needed)
6+
export FLAGCX_IB_HCA=mlx5_0,mlx5_1,mlx5_2,mlx5_3,mlx5_6,mlx5_7,mlx5_8,mlx5_9
7+
export FLAGCX_USE_HETERO_COMM=1
8+
export FLAGCX_MEM_ENABLE=1
9+
export FLAGCX_VMM_ENABLE=0
10+
export FLAGCX_P2P_DISABLE=0
11+
export CUDA_VISIBLE_DEVICES=0,1,2,3
12+
13+
run_test() {
14+
local script_dir
15+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16+
17+
torchrun \
18+
--nproc_per_node=4 \
19+
--nnodes=1 \
20+
--node_rank=0 \
21+
--master_addr=localhost \
22+
--master_port=8333 \
23+
"${script_dir}/test_tle_intra_node_reduce_scatter_tma.py"
24+
}
25+
26+
run_test
27+
28+
if [ $? -ne 0 ]; then
29+
echo "ERROR: test_tle_intra_node_reduce_scatter_tma failed"
30+
exit 1
31+
fi

0 commit comments

Comments
 (0)