Skip to content

Commit b2f15e6

Browse files
authored
Use GPU AllreduceV for distributed quantile sketch exchange (#12128)
1 parent a4c3dcc commit b2f15e6

15 files changed

Lines changed: 1135 additions & 252 deletions

src/collective/allreduce.h

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
*/
44
#pragma once
55
#include <cstdint> // for int8_t
6+
#include <cstring> // for memcpy
67
#include <functional> // for function
78
#include <type_traits> // for is_invocable_v, enable_if_t
89
#include <vector> // for vector
910

1011
#include "../common/type.h" // for EraseType, RestoreType
1112
#include "../data/array_interface.h" // for ToDType, ArrayInterfaceHandler
13+
#include "allgather.h" // for AllgatherV
1214
#include "broadcast.h" // for Broadcast
1315
#include "comm.h" // for Comm, RestoreType
1416
#include "comm_group.h" // for GlobalCommGroup
@@ -215,3 +217,197 @@ AllreduceV(Context const* ctx, std::vector<T>* data, Fn redop) {
215217
return AllreduceV(ctx, *GlobalCommGroup(), data, redop);
216218
}
217219
} // namespace xgboost::collective
220+
221+
#if defined(XGBOOST_USE_NCCL) && defined(__CUDACC__)
222+
#include "../common/cuda_context.cuh"
223+
#include "allreduce_v.cuh" // for gpu_impl::AllreduceV, AllreduceVScratch
224+
225+
namespace xgboost::collective {
226+
template <typename T>
227+
using AllreduceVScratch = gpu_impl::AllreduceVScratch<T>;
228+
229+
namespace gpu_detail {
230+
template <typename T>
231+
Result CopyDeviceVectorToHost(dh::device_vector<T> const& src, std::vector<T>* dst,
232+
cudaStream_t stream) {
233+
CHECK(dst);
234+
dst->resize(src.size());
235+
if (src.empty()) {
236+
return Success();
237+
}
238+
auto rc = GetCUDAResult(cudaMemcpyAsync(dst->data(), src.data().get(), src.size() * sizeof(T),
239+
cudaMemcpyDeviceToHost, stream));
240+
if (!rc.OK()) {
241+
return rc;
242+
}
243+
return GetCUDAResult(cudaStreamSynchronize(stream));
244+
}
245+
246+
template <typename T>
247+
Result CopyHostVectorToDevice(std::vector<T> const& src, dh::device_vector<T>* dst,
248+
cudaStream_t stream) {
249+
CHECK(dst);
250+
dst->resize(src.size());
251+
if (src.empty()) {
252+
return Success();
253+
}
254+
auto rc = GetCUDAResult(cudaMemcpyAsync(dst->data().get(), src.data(), src.size() * sizeof(T),
255+
cudaMemcpyHostToDevice, stream));
256+
if (!rc.OK()) {
257+
return rc;
258+
}
259+
return GetCUDAResult(cudaStreamSynchronize(stream));
260+
}
261+
262+
template <typename T>
263+
void CopyGatheredSegment(common::Span<std::int8_t const> gathered,
264+
std::vector<std::int64_t> const& recv_segments, std::int32_t rank,
265+
std::vector<T>* out) {
266+
CHECK(out);
267+
CHECK_GE(rank, 0);
268+
CHECK_LT(static_cast<std::size_t>(rank + 1), recv_segments.size());
269+
auto begin = recv_segments[rank];
270+
auto end = recv_segments[rank + 1];
271+
CHECK_LE(begin, end);
272+
auto n_bytes = static_cast<std::size_t>(end - begin);
273+
CHECK_EQ(n_bytes % sizeof(T), 0) << "Invalid gathered segment size.";
274+
out->resize(n_bytes / sizeof(T));
275+
if (n_bytes != 0) {
276+
std::memcpy(out->data(), gathered.data() + begin, n_bytes);
277+
}
278+
}
279+
280+
template <typename T, typename Fn>
281+
std::enable_if_t<std::is_invocable_v<Fn, dh::device_vector<T> const&, dh::device_vector<T> const&,
282+
dh::device_vector<T>*, cudaStream_t>,
283+
Result>
284+
AllreduceVHostFallback(Context const* ctx, CommGroup const& comm, dh::device_vector<T>* data,
285+
AllreduceVScratch<T>* scratch, Fn&& redop) {
286+
CHECK(ctx);
287+
CHECK(ctx->IsCUDA()) << "GPU AllreduceV requires a CUDA context.";
288+
CHECK(data);
289+
CHECK(scratch);
290+
291+
Context cpu_ctx;
292+
auto stream = ctx->CUDACtx()->Stream();
293+
294+
std::vector<T> h_local;
295+
auto rc = CopyDeviceVectorToHost(*data, &h_local, stream);
296+
if (!rc.OK()) {
297+
return Fail("GPU AllreduceV fallback failed to copy local payload to host.", std::move(rc));
298+
}
299+
300+
std::vector<std::int64_t> recv_segments;
301+
HostDeviceVector<std::int8_t> gathered;
302+
rc = AllgatherV(&cpu_ctx, comm, linalg::MakeVec(h_local.data(), h_local.size()), &recv_segments,
303+
&gathered);
304+
if (!rc.OK()) {
305+
return Fail("GPU AllreduceV fallback failed to allgather host payloads.", std::move(rc));
306+
}
307+
308+
constexpr std::int32_t kRoot = 0;
309+
std::vector<T> h_result;
310+
if (comm.Rank() == kRoot) {
311+
auto gathered_bytes = gathered.ConstHostSpan();
312+
CopyGatheredSegment(gathered_bytes, recv_segments, kRoot, &h_result);
313+
314+
rc = CopyHostVectorToDevice(h_result, data, stream);
315+
if (!rc.OK()) {
316+
return Fail("GPU AllreduceV fallback failed to stage root payload to device.", std::move(rc));
317+
}
318+
319+
std::vector<T> h_peer;
320+
for (std::int32_t peer = 1; peer < comm.World(); ++peer) {
321+
CopyGatheredSegment(gathered_bytes, recv_segments, peer, &h_peer);
322+
rc = CopyHostVectorToDevice(h_peer, &scratch->payload, stream);
323+
if (!rc.OK()) {
324+
return Fail("GPU AllreduceV fallback failed to stage peer payload to device.",
325+
std::move(rc));
326+
}
327+
redop(*data, scratch->payload, &scratch->next, stream);
328+
std::swap(*data, scratch->next);
329+
}
330+
331+
rc = CopyDeviceVectorToHost(*data, &h_result, stream);
332+
if (!rc.OK()) {
333+
return Fail("GPU AllreduceV fallback failed to copy reduced payload to host.", std::move(rc));
334+
}
335+
}
336+
337+
std::int64_t reduced_size = comm.Rank() == kRoot ? static_cast<std::int64_t>(h_result.size()) : 0;
338+
rc = Broadcast(&cpu_ctx, comm, linalg::MakeVec(&reduced_size, 1), kRoot);
339+
if (!rc.OK()) {
340+
return Fail("GPU AllreduceV fallback failed to broadcast reduced size.", std::move(rc));
341+
}
342+
343+
CHECK_GE(reduced_size, 0);
344+
if (comm.Rank() != kRoot) {
345+
h_result.resize(static_cast<std::size_t>(reduced_size));
346+
}
347+
if (reduced_size != 0) {
348+
rc = Broadcast(&cpu_ctx, comm, linalg::MakeVec(h_result.data(), h_result.size()), kRoot);
349+
if (!rc.OK()) {
350+
return Fail("GPU AllreduceV fallback failed to broadcast reduced payload.", std::move(rc));
351+
}
352+
}
353+
354+
if (comm.Rank() != kRoot) {
355+
rc = CopyHostVectorToDevice(h_result, data, stream);
356+
if (!rc.OK()) {
357+
return Fail("GPU AllreduceV fallback failed to copy broadcast payload to device.",
358+
std::move(rc));
359+
}
360+
}
361+
return Success();
362+
}
363+
} // namespace gpu_detail
364+
365+
template <typename T, typename Fn>
366+
std::enable_if_t<std::is_invocable_v<Fn, dh::device_vector<T> const&, dh::device_vector<T> const&,
367+
dh::device_vector<T>*, cudaStream_t>,
368+
Result>
369+
AllreduceV(Comm const& comm, dh::device_vector<T>* data, AllreduceVScratch<T>* scratch,
370+
Fn&& redop) {
371+
if (!comm.IsDistributed() || comm.World() == 1) {
372+
return Success();
373+
}
374+
375+
auto nccl = dynamic_cast<NCCLComm const*>(&comm);
376+
if (nccl == nullptr) {
377+
return Fail("Distributed GPU AllreduceV requires NCCL support.");
378+
}
379+
380+
return gpu_impl::AllreduceV(*nccl, data, scratch, std::forward<Fn>(redop));
381+
}
382+
383+
template <typename T, typename Fn>
384+
std::enable_if_t<std::is_invocable_v<Fn, dh::device_vector<T> const&, dh::device_vector<T> const&,
385+
dh::device_vector<T>*, cudaStream_t>,
386+
Result>
387+
AllreduceV(Context const* ctx, CommGroup const& comm, dh::device_vector<T>* data,
388+
AllreduceVScratch<T>* scratch, Fn&& redop) {
389+
CHECK(ctx);
390+
CHECK(ctx->IsCUDA()) << "GPU AllreduceV requires a CUDA context.";
391+
392+
if (!comm.IsDistributed()) {
393+
return Success();
394+
}
395+
396+
auto const& cctx = comm.Ctx(ctx, ctx->Device());
397+
auto nccl = dynamic_cast<NCCLComm const*>(&cctx);
398+
if (nccl != nullptr) {
399+
return gpu_impl::AllreduceV(*nccl, data, scratch, std::forward<Fn>(redop));
400+
}
401+
return gpu_detail::AllreduceVHostFallback(ctx, comm, data, scratch, std::forward<Fn>(redop));
402+
}
403+
404+
template <typename T, typename Fn>
405+
std::enable_if_t<std::is_invocable_v<Fn, dh::device_vector<T> const&, dh::device_vector<T> const&,
406+
dh::device_vector<T>*, cudaStream_t>,
407+
Result>
408+
AllreduceV(Context const* ctx, dh::device_vector<T>* data, AllreduceVScratch<T>* scratch,
409+
Fn&& redop) {
410+
return AllreduceV(ctx, *GlobalCommGroup(), data, scratch, std::forward<Fn>(redop));
411+
}
412+
} // namespace xgboost::collective
413+
#endif // defined(XGBOOST_USE_NCCL) && defined(__CUDACC__)

0 commit comments

Comments
 (0)