Skip to content

Commit 04215a5

Browse files
lizhangyu258i3wanna2zhzhcookie
authored
[KMCompiler][TLERaw] Add allreduce based on CUDA IPC (#884)
Co-authored-by: iwanna-lxy <37344393+i3wanna2@users.noreply.github.com> Co-authored-by: zhzhcookie <zhengyang@baai.ac.cn>
1 parent 82a18e3 commit 04215a5

5 files changed

Lines changed: 1377 additions & 2 deletions

File tree

python/triton/experimental/tle/raw/nvshmem/utils.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ def _compile_cuda_host_to_cache(
222222
f"-L{lib_dir}",
223223
f"-l:{host_lib.name}",
224224
"-lnvshmem_device",
225+
"-lcuda",
225226
"-Xlinker",
226227
"-rpath",
227228
"-Xlinker",
@@ -313,12 +314,23 @@ def init_nvshmem_by_torch_pg(common, group):
313314
torch.distributed.barrier(group=group)
314315

315316

316-
def tensor_from_pointer(pointer, shape, dtype, device):
317+
def tensor_from_pointer(
318+
pointer: int | ctypes.c_void_p,
319+
shape: tuple[int, ...],
320+
dtype: torch.dtype,
321+
device: torch.device,
322+
) -> torch.Tensor:
323+
"""Create a non-owning Torch tensor view over a CUDA allocation."""
324+
address = pointer.value if isinstance(pointer, ctypes.c_void_p) else pointer
325+
if address is not None and not isinstance(address, int):
326+
raise TypeError(f"pointer must be int or ctypes.c_void_p, got {type(pointer).__name__}")
327+
if not address:
328+
raise ValueError("pointer cannot be null; CUDA memory must be allocated")
317329
elements = 1
318330
for extent in shape:
319331
elements *= extent
320332
storage = torch._C._construct_storage_from_data_pointer(
321-
pointer.value,
333+
address,
322334
device,
323335
elements * dtype.itemsize,
324336
)
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
#include <cuda_bf16.h>
2+
#include <cuda_fp16.h>
3+
#include <stdint.h>
4+
#include <type_traits>
5+
6+
namespace {
7+
8+
constexpr int kMaxBlocks = 36;
9+
constexpr int kMaxRanks = 8;
10+
11+
using Flag = uint32_t;
12+
13+
struct Signal {
14+
alignas(128) Flag start[kMaxBlocks][kMaxRanks];
15+
alignas(128) Flag end[kMaxBlocks][kMaxRanks];
16+
alignas(128) Flag epoch[kMaxBlocks];
17+
};
18+
19+
struct __align__(16) RankData {
20+
const void *ptrs[kMaxRanks];
21+
};
22+
23+
struct __align__(16) RankSignals {
24+
Signal *signals[kMaxRanks];
25+
};
26+
27+
template <typename T, int Size> struct __align__(alignof(T) * Size) Array {
28+
T data[Size];
29+
using type = T;
30+
static constexpr int size = Size;
31+
};
32+
33+
template <typename T> struct Packed {
34+
using Value = Array<T, 16 / sizeof(T)>;
35+
using Accumulator = Array<float, 16 / sizeof(T)>;
36+
};
37+
38+
__device__ __forceinline__ void store_flag_volatile(Flag *address, Flag value) {
39+
asm volatile("st.volatile.global.u32 [%1], %0;" : : "r"(value), "l"(address));
40+
}
41+
42+
__device__ __forceinline__ Flag load_flag_volatile(Flag *address) {
43+
Flag value;
44+
asm volatile("ld.volatile.global.u32 %0, [%1];" : "=r"(value) : "l"(address));
45+
return value;
46+
}
47+
48+
__device__ __forceinline__ void store_flag_release(Flag *address, Flag value) {
49+
asm volatile("st.release.sys.global.u32 [%1], %0;"
50+
:
51+
: "r"(value), "l"(address));
52+
}
53+
54+
__device__ __forceinline__ Flag load_flag_acquire(Flag *address) {
55+
Flag value;
56+
asm volatile("ld.acquire.sys.global.u32 %0, [%1];"
57+
: "=r"(value)
58+
: "l"(address));
59+
return value;
60+
}
61+
62+
template <int WorldSize>
63+
__device__ __forceinline__ void barrier_start(const RankSignals &signals,
64+
Signal *self_signal, int rank) {
65+
const Flag flag = self_signal->epoch[blockIdx.x] + 1;
66+
if (threadIdx.x < WorldSize) {
67+
Flag *remote = &signals.signals[threadIdx.x]->start[blockIdx.x][rank];
68+
Flag *local = &self_signal->start[blockIdx.x][threadIdx.x];
69+
store_flag_volatile(remote, flag);
70+
while (load_flag_volatile(local) != flag) {
71+
}
72+
}
73+
__syncthreads();
74+
if (threadIdx.x == 0)
75+
self_signal->epoch[blockIdx.x] = flag;
76+
}
77+
78+
template <int WorldSize, bool FinalSync = false>
79+
__device__ __forceinline__ void barrier_end(const RankSignals &signals,
80+
Signal *self_signal, int rank) {
81+
__syncthreads();
82+
const Flag flag = self_signal->epoch[blockIdx.x] + 1;
83+
if (threadIdx.x < WorldSize) {
84+
Flag *remote = &signals.signals[threadIdx.x]->end[blockIdx.x][rank];
85+
Flag *local = &self_signal->end[blockIdx.x][threadIdx.x];
86+
if constexpr (FinalSync) {
87+
store_flag_volatile(remote, flag);
88+
while (load_flag_volatile(local) != flag) {
89+
}
90+
} else {
91+
store_flag_release(remote, flag);
92+
while (load_flag_acquire(local) != flag) {
93+
}
94+
}
95+
}
96+
if constexpr (!FinalSync)
97+
__syncthreads();
98+
if (threadIdx.x == 0)
99+
self_signal->epoch[blockIdx.x] = flag;
100+
}
101+
102+
__device__ __forceinline__ float scalar_to_float(half value) {
103+
return __half2float(value);
104+
}
105+
106+
__device__ __forceinline__ float scalar_to_float(__nv_bfloat16 value) {
107+
return __bfloat162float(value);
108+
}
109+
110+
template <typename T> __device__ __forceinline__ T scalar_from_float(float);
111+
112+
template <> __device__ __forceinline__ half scalar_from_float(float value) {
113+
return __float2half(value);
114+
}
115+
116+
template <>
117+
__device__ __forceinline__ __nv_bfloat16 scalar_from_float(float value) {
118+
return __float2bfloat16(value);
119+
}
120+
121+
template <typename T, int Size>
122+
__device__ __forceinline__ Array<float, Size> upcast(Array<T, Size> value) {
123+
if constexpr (std::is_same<T, float>::value) {
124+
return value;
125+
} else {
126+
Array<float, Size> result;
127+
#pragma unroll
128+
for (int i = 0; i < Size; ++i)
129+
result.data[i] = scalar_to_float(value.data[i]);
130+
return result;
131+
}
132+
}
133+
134+
template <typename Output>
135+
__device__ __forceinline__ Output downcast(Array<float, Output::size> value) {
136+
if constexpr (std::is_same<typename Output::type, float>::value) {
137+
return value;
138+
} else {
139+
Output result;
140+
#pragma unroll
141+
for (int i = 0; i < Output::size; ++i)
142+
result.data[i] = scalar_from_float<typename Output::type>(value.data[i]);
143+
return result;
144+
}
145+
}
146+
147+
template <int WorldSize, typename Value, typename Accumulator>
148+
__device__ __forceinline__ Value packed_reduce(const Value *const *pointers,
149+
int index) {
150+
Accumulator sum = upcast(pointers[0][index]);
151+
#pragma unroll
152+
for (int peer = 1; peer < WorldSize; ++peer) {
153+
const Accumulator value = upcast(pointers[peer][index]);
154+
#pragma unroll
155+
for (int element = 0; element < Accumulator::size; ++element)
156+
sum.data[element] += value.data[element];
157+
}
158+
return downcast<Value>(sum);
159+
}
160+
161+
template <typename T, int WorldSize>
162+
__device__ __forceinline__ void
163+
ipc_allreduce_oneshot_impl(T *output, const int64_t *input_pointer_table,
164+
const int64_t *signal_pointer_table, int rank,
165+
int numel) {
166+
using Value = typename Packed<T>::Value;
167+
using Accumulator = typename Packed<T>::Accumulator;
168+
169+
RankData data;
170+
RankSignals signals;
171+
#pragma unroll
172+
for (int peer = 0; peer < WorldSize; ++peer) {
173+
data.ptrs[peer] = reinterpret_cast<const void *>(input_pointer_table[peer]);
174+
signals.signals[peer] =
175+
reinterpret_cast<Signal *>(signal_pointer_table[peer]);
176+
}
177+
178+
Signal *self_signal = signals.signals[rank];
179+
barrier_start<WorldSize>(signals, self_signal, rank);
180+
181+
const int packed_count = numel / Value::size;
182+
const int thread = blockIdx.x * blockDim.x + threadIdx.x;
183+
const int stride = gridDim.x * blockDim.x;
184+
const Value *inputs[WorldSize];
185+
#pragma unroll
186+
for (int peer = 0; peer < WorldSize; ++peer)
187+
inputs[peer] = reinterpret_cast<const Value *>(data.ptrs[peer]);
188+
189+
Value *packed_output = reinterpret_cast<Value *>(output);
190+
for (int index = thread; index < packed_count; index += stride)
191+
packed_output[index] =
192+
packed_reduce<WorldSize, Value, Accumulator>(inputs, index);
193+
194+
barrier_end<WorldSize, true>(signals, self_signal, rank);
195+
}
196+
197+
template <typename Value>
198+
__device__ __forceinline__ Value *temporary_buffer(Signal *signal) {
199+
return reinterpret_cast<Value *>(signal + 1);
200+
}
201+
202+
template <typename T, int WorldSize>
203+
__device__ __forceinline__ void
204+
ipc_allreduce_twoshot_impl(T *output, const int64_t *input_pointer_table,
205+
const int64_t *signal_pointer_table, int rank,
206+
int numel) {
207+
using Value = typename Packed<T>::Value;
208+
using Accumulator = typename Packed<T>::Accumulator;
209+
210+
RankData data;
211+
RankSignals signals;
212+
#pragma unroll
213+
for (int peer = 0; peer < WorldSize; ++peer) {
214+
data.ptrs[peer] = reinterpret_cast<const void *>(input_pointer_table[peer]);
215+
signals.signals[peer] =
216+
reinterpret_cast<Signal *>(signal_pointer_table[peer]);
217+
}
218+
219+
const int packed_count = numel / Value::size;
220+
const int thread = blockIdx.x * blockDim.x + threadIdx.x;
221+
const int stride = gridDim.x * blockDim.x;
222+
const int part = packed_count / WorldSize;
223+
const int start = rank * part;
224+
const int end = rank == WorldSize - 1 ? packed_count : start + part;
225+
const int largest_part = part + packed_count % WorldSize;
226+
227+
const Value *inputs[WorldSize];
228+
Value *temporaries[WorldSize];
229+
#pragma unroll
230+
for (int i = 0; i < WorldSize; ++i) {
231+
const int target = (rank + i) % WorldSize;
232+
inputs[i] = reinterpret_cast<const Value *>(data.ptrs[target]);
233+
temporaries[i] = temporary_buffer<Value>(signals.signals[target]);
234+
}
235+
236+
Signal *self_signal = signals.signals[rank];
237+
Value *temporary_output = temporaries[0];
238+
barrier_start<WorldSize>(signals, self_signal, rank);
239+
240+
for (int index = start + thread; index < end; index += stride)
241+
temporary_output[index - start] =
242+
packed_reduce<WorldSize, Value, Accumulator>(inputs, index);
243+
244+
barrier_end<WorldSize>(signals, self_signal, rank);
245+
246+
Value *packed_output = reinterpret_cast<Value *>(output);
247+
for (int index = thread; index < largest_part; index += stride) {
248+
#pragma unroll
249+
for (int i = 0; i < WorldSize; ++i) {
250+
const int source_rank = (rank + i) % WorldSize;
251+
if (source_rank == WorldSize - 1 || index < part)
252+
packed_output[source_rank * part + index] = temporaries[i][index];
253+
}
254+
}
255+
}
256+
257+
} // namespace
258+
259+
#define DEFINE_IPC_ALLREDUCE(ALGORITHM, NAME, TYPE, WORLD_SIZE) \
260+
extern "C" __device__ __attribute__((always_inline)) void \
261+
ipc_allreduce_##ALGORITHM##_##NAME##_##WORLD_SIZE( \
262+
__attribute__((address_space(1))) TYPE *output, \
263+
__attribute__((address_space(1))) \
264+
const int64_t *input_pointer_table, \
265+
__attribute__((address_space(1))) \
266+
const int64_t *signal_pointer_table, \
267+
int rank, int numel) { \
268+
ipc_allreduce_##ALGORITHM##_impl<TYPE, WORLD_SIZE>( \
269+
output, input_pointer_table, signal_pointer_table, rank, numel); \
270+
}
271+
272+
#define DEFINE_FOR_WORLD_SIZE(WORLD_SIZE) \
273+
DEFINE_IPC_ALLREDUCE(oneshot, fp16, half, WORLD_SIZE) \
274+
DEFINE_IPC_ALLREDUCE(twoshot, fp16, half, WORLD_SIZE) \
275+
DEFINE_IPC_ALLREDUCE(oneshot, bf16, __nv_bfloat16, WORLD_SIZE) \
276+
DEFINE_IPC_ALLREDUCE(twoshot, bf16, __nv_bfloat16, WORLD_SIZE) \
277+
DEFINE_IPC_ALLREDUCE(oneshot, fp32, float, WORLD_SIZE) \
278+
DEFINE_IPC_ALLREDUCE(twoshot, fp32, float, WORLD_SIZE)
279+
280+
DEFINE_FOR_WORLD_SIZE(2)
281+
DEFINE_FOR_WORLD_SIZE(4)
282+
DEFINE_FOR_WORLD_SIZE(6)
283+
DEFINE_FOR_WORLD_SIZE(8)
284+
285+
#undef DEFINE_FOR_WORLD_SIZE
286+
#undef DEFINE_IPC_ALLREDUCE

0 commit comments

Comments
 (0)