Skip to content

Commit 2297b88

Browse files
author
wangjie
committed
Support multiple QPs for PP send/recv
1 parent d4f41e4 commit 2297b88

5 files changed

Lines changed: 106 additions & 32 deletions

File tree

csrc/elastic/buffer.hpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class ElasticBuffer {
6767
int prev_rank_idx = 0, next_rank_idx = 0;
6868
int64_t num_max_pp_tensor_bytes = 0;
6969
int num_max_pp_inflight_tensors = 0;
70+
int num_pp_qps = 0;
7071

7172
// AGRS session settings
7273
int64_t num_max_agrs_session_bytes = 0;
@@ -294,20 +295,23 @@ class ElasticBuffer {
294295
};
295296
}
296297

297-
void pp_set_config(const int64_t& num_max_tensor_bytes, const int& num_max_inflight_tensors) {
298+
void pp_set_config(const int64_t& num_max_tensor_bytes, const int& num_max_inflight_tensors, const int& num_qps) {
298299
// Flush previous operations
299300
barrier(false, true);
300301

301302
EP_HOST_ASSERT(num_max_tensor_bytes > 0 and num_max_inflight_tensors > 0);
302303
EP_HOST_ASSERT(num_max_tensor_bytes * num_max_inflight_tensors * 2 * 2 <= num_buffer_bytes);
304+
const auto actual_num_qps = num_qps == 0 ? nccl_context->num_allocated_qps : num_qps;
305+
EP_HOST_ASSERT(actual_num_qps > 0 and actual_num_qps <= nccl_context->num_allocated_qps);
303306
this->prev_rank_idx = (nccl_context->rank_idx + nccl_context->num_ranks - 1) % nccl_context->num_ranks;
304307
this->next_rank_idx = (nccl_context->rank_idx + 1) % nccl_context->num_ranks;
305308
this->num_max_pp_tensor_bytes = math::align<int64_t>(num_max_tensor_bytes, 32);
306309
this->num_max_pp_inflight_tensors = num_max_inflight_tensors;
310+
this->num_pp_qps = actual_num_qps;
307311
}
308312

309313
void pp_send(const torch::Tensor& x, const int& dst_rank_idx, const int& num_sms) const {
310-
EP_HOST_ASSERT(num_max_pp_tensor_bytes > 0 and num_max_pp_inflight_tensors > 0);
314+
EP_HOST_ASSERT(num_max_pp_tensor_bytes > 0 and num_max_pp_inflight_tensors > 0 and num_pp_qps > 0);
311315
EP_HOST_ASSERT(x.is_cuda() and x.is_contiguous() and x.nbytes() <= num_max_pp_tensor_bytes);
312316
EP_HOST_ASSERT(dst_rank_idx == prev_rank_idx or dst_rank_idx == next_rank_idx);
313317

@@ -319,14 +323,15 @@ class ElasticBuffer {
319323
num_max_pp_tensor_bytes,
320324
num_max_pp_inflight_tensors,
321325
num_sms == 0 ? jit::device_runtime->get_num_sms() : num_sms,
326+
num_pp_qps,
322327
num_gpu_timeout_cycles,
323328
jit::device_runtime->get_num_smem_bytes(),
324329
at::cuda::getCurrentCUDAStream()
325330
);
326331
}
327332

328333
void pp_recv(const torch::Tensor& x, const int& src_rank_idx, const int& num_sms) const {
329-
EP_HOST_ASSERT(num_max_pp_tensor_bytes > 0 and num_max_pp_inflight_tensors > 0);
334+
EP_HOST_ASSERT(num_max_pp_tensor_bytes > 0 and num_max_pp_inflight_tensors > 0 and num_pp_qps > 0);
330335
EP_HOST_ASSERT(x.is_cuda() and x.is_contiguous() and x.nbytes() <= num_max_pp_tensor_bytes);
331336
EP_HOST_ASSERT(src_rank_idx == prev_rank_idx or src_rank_idx == next_rank_idx);
332337

@@ -338,6 +343,7 @@ class ElasticBuffer {
338343
num_max_pp_tensor_bytes,
339344
num_max_pp_inflight_tensors,
340345
num_sms == 0 ? jit::device_runtime->get_num_sms() : num_sms,
346+
num_pp_qps,
341347
num_gpu_timeout_cycles,
342348
jit::device_runtime->get_num_smem_bytes(),
343349
at::cuda::getCurrentCUDAStream()
@@ -1314,9 +1320,12 @@ static void register_apis(pybind11::module_& m) {
13141320
.def("barrier", &ElasticBuffer::barrier)
13151321
.def("engram_write", &ElasticBuffer::engram_write)
13161322
.def("engram_fetch", &ElasticBuffer::engram_fetch)
1317-
.def("pp_set_config", &ElasticBuffer::pp_set_config)
1318-
.def("pp_send", &ElasticBuffer::pp_send)
1319-
.def("pp_recv", &ElasticBuffer::pp_recv)
1323+
.def("pp_set_config", &ElasticBuffer::pp_set_config,
1324+
pybind11::arg("num_max_tensor_bytes"), pybind11::arg("num_max_inflight_tensors"), pybind11::arg("num_qps") = 0)
1325+
.def("pp_send", &ElasticBuffer::pp_send,
1326+
pybind11::arg("x"), pybind11::arg("dst_rank_idx"), pybind11::arg("num_sms") = 0)
1327+
.def("pp_recv", &ElasticBuffer::pp_recv,
1328+
pybind11::arg("x"), pybind11::arg("src_rank_idx"), pybind11::arg("num_sms") = 0)
13201329
.def("create_agrs_session", &ElasticBuffer::create_agrs_session)
13211330
.def("destroy_agrs_session", &ElasticBuffer::destroy_agrs_session)
13221331
.def("agrs_set_config", &ElasticBuffer::agrs_set_config)

csrc/kernels/elastic/pp_send_recv.hpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class PPSendRuntime final : public jit::LaunchRuntime<PPSendRuntime> {
1818
// Templated arguments
1919
int num_ranks;
2020
int num_smem_bytes;
21+
int num_qps;
2122
int64_t num_timeout_cycles;
2223

2324
// Parameters
@@ -41,11 +42,12 @@ class PPSendRuntime final : public jit::LaunchRuntime<PPSendRuntime> {
4142
using namespace deep_ep::elastic;
4243
4344
static void __instantiate_kernel() {{
44-
auto ptr = reinterpret_cast<void*>(&pp_send_impl<{}, {}, {}, {}>);
45+
auto ptr = reinterpret_cast<void*>(&pp_send_impl<{}, {}, {}, {}, {}>);
4546
}}
4647
)", args.launch_args.grid_dim.first,
4748
args.num_ranks,
4849
args.num_smem_bytes,
50+
args.num_qps,
4951
args.num_timeout_cycles);
5052
}
5153

@@ -69,13 +71,15 @@ static void launch_pp_send(const ncclDevComm_t& nccl_dev_comm,
6971
const int64_t& num_max_tensor_bytes,
7072
const int num_max_inflight_tensors,
7173
const int& num_sms,
74+
const int& num_qps,
7275
const int64_t& num_timeout_cycles,
7376
const int& num_smem_bytes,
7477
const at::cuda::CUDAStream& stream) {
7578
// Generate, build and launch
7679
const PPSendRuntime::Args args = {
7780
.num_ranks = num_ranks,
7881
.num_smem_bytes = num_smem_bytes,
82+
.num_qps = num_qps,
7983
.num_timeout_cycles = num_timeout_cycles,
8084
.nccl_dev_comm = nccl_dev_comm,
8185
.nccl_window = nccl_window,
@@ -100,6 +104,7 @@ class PPRecvRuntime final : public jit::LaunchRuntime<PPRecvRuntime> {
100104
// Templated arguments
101105
int num_ranks;
102106
int num_smem_bytes;
107+
int num_qps;
103108
int64_t num_timeout_cycles;
104109

105110
// Parameters
@@ -124,11 +129,12 @@ class PPRecvRuntime final : public jit::LaunchRuntime<PPRecvRuntime> {
124129
using namespace deep_ep::elastic;
125130
126131
static void __instantiate_kernel() {{
127-
auto ptr = reinterpret_cast<void*>(&pp_recv_impl<{}, {}, {}, {}>);
132+
auto ptr = reinterpret_cast<void*>(&pp_recv_impl<{}, {}, {}, {}, {}>);
128133
}}
129134
)", args.launch_args.grid_dim.first,
130135
args.num_ranks,
131136
args.num_smem_bytes,
137+
args.num_qps,
132138
args.num_timeout_cycles);
133139
}
134140

@@ -154,13 +160,15 @@ static void launch_pp_recv(const ncclDevComm_t& nccl_dev_comm,
154160
const int64_t& num_max_tensor_bytes,
155161
const int& num_max_inflight_tensors,
156162
const int& num_sms,
163+
const int& num_qps,
157164
const int64_t& num_timeout_cycles,
158165
const int& num_smem_bytes,
159166
const at::cuda::CUDAStream& stream) {
160167
// Generate, build and launch
161168
const PPRecvRuntime::Args args = {
162169
.num_ranks = num_ranks,
163170
.num_smem_bytes = num_smem_bytes,
171+
.num_qps = num_qps,
164172
.num_timeout_cycles = num_timeout_cycles,
165173
.nccl_dev_comm = nccl_dev_comm,
166174
.nccl_window = nccl_window,

deep_ep/buffers/elastic.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ def __init__(self,
204204
else:
205205
num_allocated_qps = 17
206206
self.num_allocated_qps = num_allocated_qps
207+
self.num_pp_qps = 0
207208

208209
# Create CPU communicator (exchange POSIX FD handles for CPU segments)
209210
cpu_comm = []
@@ -456,15 +457,26 @@ def engram_fetch(self, indices: torch.Tensor, num_qps: int = 0) -> Callable:
456457
"""
457458
return self.runtime.engram_fetch(indices, num_qps)
458459

459-
def pp_set_config(self, num_max_tensor_bytes: int, num_max_inflight_tensors: int):
460+
def _normalize_pp_num_qps(self, num_qps: int) -> int:
461+
assert num_qps >= 0, 'Number of PP QPs must be non-negative'
462+
if num_qps == 0:
463+
return self.num_allocated_qps
464+
assert num_qps <= self.num_allocated_qps, 'Allocated QPs are not enough'
465+
return num_qps
466+
467+
def pp_set_config(self, num_max_tensor_bytes: int, num_max_inflight_tensors: int, num_qps: int = 0):
460468
"""
461469
(Experimental) Configure pipeline-parallel (PP) send/recv parameters. Includes a barrier to flush previous operations.
462470
463471
Arguments:
464472
num_max_tensor_bytes: the maximum tensor size in bytes per send/recv operation.
465473
num_max_inflight_tensors: the maximum number of in-flight tensors at once.
474+
num_qps: the number of RDMA QPs to use for PP (0 for all allocated QPs).
475+
This is fixed until the next `pp_set_config` call and must match on paired ranks.
466476
"""
467-
self.runtime.pp_set_config(num_max_tensor_bytes, num_max_inflight_tensors)
477+
actual_num_qps = self._normalize_pp_num_qps(num_qps)
478+
self.runtime.pp_set_config(num_max_tensor_bytes, num_max_inflight_tensors, num_qps)
479+
self.num_pp_qps = actual_num_qps
468480

469481
def pp_send(self, t: torch.Tensor, dst_rank_idx: int, num_sms: int = 0) -> None:
470482
"""

deep_ep/include/deep_ep/impls/pp_send_recv.cuh

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@ __device__ __forceinline__ std::pair<int, int> get_buffer_offset(
1515
return dst_rank_idx == next_rank_idx ? std::make_pair(0, 1) : std::make_pair(1, 0);
1616
}
1717

18+
template <int kNumQPs>
19+
__device__ __forceinline__ std::pair<int64_t, int> get_pp_send_chunk(
20+
const int& qp_idx, const int64_t& num_bytes) {
21+
EP_STATIC_ASSERT(kNumQPs > 0, "Invalid number of QPs");
22+
23+
const auto num_tma_blocks = num_bytes / ptx::kNumTMAAlignBytes;
24+
const auto num_tma_blocks_per_qp = math::ceil_div<int64_t>(num_tma_blocks, static_cast<int64_t>(kNumQPs));
25+
const auto start_block_idx = static_cast<int64_t>(qp_idx) * num_tma_blocks_per_qp;
26+
if (start_block_idx >= num_tma_blocks)
27+
return {0, 0};
28+
29+
const auto end_block_idx = std::min(start_block_idx + num_tma_blocks_per_qp, num_tma_blocks);
30+
const auto offset = start_block_idx * ptx::kNumTMAAlignBytes;
31+
const auto chunk_bytes = static_cast<int>((end_block_idx - start_block_idx) * ptx::kNumTMAAlignBytes);
32+
return {offset, chunk_bytes};
33+
}
34+
1835
template <int64_t kNumTimeoutCycles, typename timeout_print_t>
1936
__device__ __forceinline__ void check_signal(
2037
const handle::NCCLGin& gin,
@@ -113,6 +130,7 @@ __device__ __forceinline__ void tma_copy(
113130
template <int kNumSMs,
114131
int kNumRanks,
115132
int kNumSmemBytes,
133+
int kNumQPs,
116134
int64_t kNumTimeoutCycles>
117135
__global__ void __launch_bounds__(32, 1)
118136
pp_send_impl(const ncclDevComm_t nccl_dev_comm, const ncclWindow_t nccl_window,
@@ -126,7 +144,7 @@ pp_send_impl(const ncclDevComm_t nccl_dev_comm, const ncclWindow_t nccl_window,
126144
const auto [local_idx_in_dst, dst_idx_in_local] = get_buffer_offset<kNumRanks>(rank_idx, dst_rank_idx);
127145

128146
// Gin handle
129-
const auto gin = handle::NCCLGin(nccl_dev_comm, nccl_window, 0, NCCL_GIN_RESOURCE_SHARING_CTA);
147+
const auto signal_gin = handle::NCCLGin(nccl_dev_comm, nccl_window, 0, NCCL_GIN_RESOURCE_SHARING_CTA);
130148

131149
// Buffer offsets
132150
const auto send_count_ptr = workspace_layout.get_pp_send_count_ptr(dst_idx_in_local);
@@ -140,7 +158,7 @@ pp_send_impl(const ncclDevComm_t nccl_dev_comm, const ncclWindow_t nccl_window,
140158
// Wait buffer slot release and do TMA
141159
if (ptx::elect_one_sync()) {
142160
check_signal<kNumTimeoutCycles>(
143-
gin,
161+
signal_gin,
144162
static_cast<ncclGinSignal_t>(kNumRanks + dst_idx_in_local + 2),
145163
send_count - num_max_inflight_tensors + 1,
146164
// TODO: print more info, and control the SM who prints it
@@ -150,22 +168,36 @@ pp_send_impl(const ncclDevComm_t nccl_dev_comm, const ncclWindow_t nccl_window,
150168
}
151169
cooperative_groups::this_grid().sync();
152170

153-
// Issue RDMA put
171+
// Issue RDMA puts across all requested QPs. Each QP signals completion independently,
172+
// because there is no cross-QP ordering guarantee.
173+
if (ptx::elect_one_sync()) {
174+
for (int qp_idx = sm_idx; qp_idx < kNumQPs; qp_idx += kNumSMs) {
175+
const auto [offset, chunk_bytes] = get_pp_send_chunk<kNumQPs>(qp_idx, num_x_bytes);
176+
const auto data_gin = handle::NCCLGin(nccl_dev_comm, nccl_window, qp_idx, NCCL_GIN_RESOURCE_SHARING_CTA);
177+
const auto recv_signal = static_cast<ncclGinSignal_t>(kNumRanks + local_idx_in_dst);
178+
179+
if (chunk_bytes == 0) {
180+
data_gin.signal<ncclTeamTagWorld>(dst_rank_idx, ncclGin_SignalInc{recv_signal});
181+
} else {
182+
data_gin.put<ncclTeamTagWorld>(
183+
math::advance_ptr(recv_buffer_ptr, offset),
184+
math::advance_ptr(send_buffer_ptr, offset),
185+
chunk_bytes, dst_rank_idx,
186+
0,
187+
ncclGin_SignalInc{recv_signal});
188+
}
189+
}
190+
}
191+
154192
if (sm_idx == 0 and ptx::elect_one_sync()) {
155-
gin.put<ncclTeamTagWorld>(
156-
recv_buffer_ptr,
157-
send_buffer_ptr,
158-
num_x_bytes, dst_rank_idx,
159-
0,
160-
// TODO: is this signal highly optimized?
161-
ncclGin_SignalInc(static_cast<ncclGinSignal_t>(local_idx_in_dst + kNumRanks)));
162193
*send_count_ptr += 1;
163194
}
164195
}
165196

166197
template <int kNumSMs,
167198
int kNumRanks,
168199
int kNumSmemBytes,
200+
int kNumQPs,
169201
int64_t kNumTimeoutCycles>
170202
__global__ void __launch_bounds__(32, 1)
171203
pp_recv_impl(const ncclDevComm_t nccl_dev_comm, const ncclWindow_t nccl_window,
@@ -178,8 +210,9 @@ pp_recv_impl(const ncclDevComm_t nccl_dev_comm, const ncclWindow_t nccl_window,
178210
const auto workspace_layout = layout::WorkspaceLayout(workspace, 1, kNumRanks, 0);
179211
const auto [src_idx_in_local, local_idx_in_src] = get_buffer_offset<kNumRanks>(src_rank_idx, rank_idx);
180212

181-
// Gin handle
182-
const auto gin = handle::NCCLGin(nccl_dev_comm, nccl_window, 0, NCCL_GIN_RESOURCE_SHARING_CTA);
213+
// QP 0 is used for release notification. Payload completion signals are
214+
// polled on the same QP/context that issued each RDMA put.
215+
const auto signal_gin = handle::NCCLGin(nccl_dev_comm, nccl_window, 0, NCCL_GIN_RESOURCE_SHARING_CTA);
183216

184217
// Buffer offsets
185218
const auto recv_count_ptr = workspace_layout.get_pp_recv_count_ptr(src_idx_in_local);
@@ -190,21 +223,29 @@ pp_recv_impl(const ncclDevComm_t nccl_dev_comm, const ncclWindow_t nccl_window,
190223

191224
// Copy from the buffer into a new tensor
192225
if (ptx::elect_one_sync()) {
193-
check_signal<kNumTimeoutCycles>(
194-
gin,
195-
static_cast<ncclGinSignal_t>(src_idx_in_local + kNumRanks),
196-
recv_count + 1,
197-
// TODO: print more info, and control the SM who prints it
198-
[]() { printf("DeepEP PP recv timeout, recv buffer is empty\n"); }
199-
);
226+
for (int qp_idx = sm_idx; qp_idx < kNumQPs; qp_idx += kNumSMs) {
227+
const auto data_gin = handle::NCCLGin(nccl_dev_comm, nccl_window, qp_idx, NCCL_GIN_RESOURCE_SHARING_CTA);
228+
check_signal<kNumTimeoutCycles>(
229+
data_gin,
230+
static_cast<ncclGinSignal_t>(src_idx_in_local + kNumRanks),
231+
recv_count + 1,
232+
// TODO: print more info, and control the SM who prints it
233+
[]() { printf("DeepEP PP recv timeout, recv buffer is empty\n"); }
234+
);
235+
}
236+
}
237+
cooperative_groups::this_grid().sync();
238+
239+
if (ptx::elect_one_sync()) {
200240
tma_copy<kNumSMs, kNumSmemBytes>(recv_buffer_ptr, x, num_x_bytes, sm_idx);
201241
}
202242
cooperative_groups::this_grid().sync();
203243

204244
// TODO: add a comment
205245
if (sm_idx == 0 and ptx::elect_one_sync()) {
206-
gin.signal<ncclTeamTagWorld>(
207-
src_rank_idx, ncclGin_SignalInc(static_cast<ncclGinSignal_t>(kNumRanks + local_idx_in_src + 2))
246+
signal_gin.signal<ncclTeamTagWorld>(
247+
src_rank_idx,
248+
ncclGin_SignalInc(static_cast<ncclGinSignal_t>(kNumRanks + 2 + local_idx_in_src))
208249
);
209250
*recv_count_ptr += 1;
210251
}

tests/elastic/test_pp.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,16 @@ def test(local_rank: int, num_local_ranks: int, args: argparse.Namespace):
4848
group, explicitly_destroy=True, allow_hybrid_mode=False,
4949
num_bytes=deep_ep.ElasticBuffer.get_pp_buffer_size_hint(
5050
num_max_tensor_bytes, num_max_inflight_tensors))
51-
buffer.pp_set_config(num_max_tensor_bytes, num_max_inflight_tensors)
51+
requested_num_qps = args.num_qps
52+
buffer.pp_set_config(num_max_tensor_bytes, num_max_inflight_tensors, num_qps=requested_num_qps)
53+
num_qps = buffer.num_pp_qps
5254

5355
# Print configs
5456
assert num_ranks > 1
5557
dist_print(f'Config:\n'
5658
f' > Ranks: {num_ranks}\n'
5759
f' > Shape: {shape}\n'
60+
f' > QPs: {num_qps}/{buffer.num_allocated_qps}\n'
5861
f' > Max inflight tensors: {num_max_inflight_tensors}\n',
5962
once_in_node=True)
6063

@@ -127,6 +130,7 @@ def loop(_hide_rdma_latency=hide_rdma_latency):
127130
parser.add_argument('--num-tokens', type=int, default=4096)
128131
parser.add_argument('--hidden', type=int, default=7168)
129132
parser.add_argument('--num-max-inflight-tensors', type=int, default=4)
133+
parser.add_argument('--num_qps', type=int, default=0)
130134
parser.add_argument('--num-stress-iterations', type=int, default=4)
131135
parser.add_argument('--num-sends', type=int, default=128)
132136
parser.add_argument('--num-sleep-cycles', type=int, default=10 ** 7)

0 commit comments

Comments
 (0)