|
3 | 3 | */ |
4 | 4 | #pragma once |
5 | 5 | #include <cstdint> // for int8_t |
| 6 | +#include <cstring> // for memcpy |
6 | 7 | #include <functional> // for function |
7 | 8 | #include <type_traits> // for is_invocable_v, enable_if_t |
8 | 9 | #include <vector> // for vector |
9 | 10 |
|
10 | 11 | #include "../common/type.h" // for EraseType, RestoreType |
11 | 12 | #include "../data/array_interface.h" // for ToDType, ArrayInterfaceHandler |
| 13 | +#include "allgather.h" // for AllgatherV |
12 | 14 | #include "broadcast.h" // for Broadcast |
13 | 15 | #include "comm.h" // for Comm, RestoreType |
14 | 16 | #include "comm_group.h" // for GlobalCommGroup |
@@ -215,3 +217,197 @@ AllreduceV(Context const* ctx, std::vector<T>* data, Fn redop) { |
215 | 217 | return AllreduceV(ctx, *GlobalCommGroup(), data, redop); |
216 | 218 | } |
217 | 219 | } // 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