913ce47ef1
[ROCm/rocshmem commit: fd8dbc7fb6]
501 строка
16 KiB
C++
501 строка
16 KiB
C++
/******************************************************************************
|
|
* Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*****************************************************************************/
|
|
|
|
#include "network_policy.hpp"
|
|
|
|
#include <mpi.h>
|
|
|
|
#include "config.h" // NOLINT(build/include_subdir)
|
|
#include "../atomic_return.hpp"
|
|
#include "../context_incl.hpp"
|
|
#include "backend_ib.hpp"
|
|
#include "connection.hpp"
|
|
#include "dynamic_connection.hpp"
|
|
#include "queue_pair.hpp"
|
|
#include "reliable_connection.hpp"
|
|
|
|
namespace rocshmem {
|
|
|
|
void NetworkOnImpl::dump_backend_stats(ROCStats *globalStats) {
|
|
/*
|
|
* TODO(bpotter): Refactor this into the Stats class to remove the ifdef
|
|
*/
|
|
#ifdef PROFILE
|
|
int statblocks = connection->total_number_connections();
|
|
|
|
uint64_t cycles_ring_sq_db = 0;
|
|
uint64_t cycles_update_wqe = 0;
|
|
uint64_t cycles_poll_cq = 0;
|
|
uint64_t cycles_next_cq = 0;
|
|
uint64_t cycles_init = gpu_qps[statblocks - 1].profiler.getStat(INIT);
|
|
uint64_t cycles_finalize = gpu_qps[statblocks - 1].profiler.getStat(FINALIZE);
|
|
|
|
uint64_t total_quiet_count = 0;
|
|
uint64_t total_db_count = 0;
|
|
uint64_t total_wqe_count = 0;
|
|
|
|
for (int i = 0; i < statblocks; i++) {
|
|
cycles_ring_sq_db += gpu_qps[i].profiler.getStat(RING_SQ_DB);
|
|
cycles_update_wqe += gpu_qps[i].profiler.getStat(UPDATE_WQE);
|
|
cycles_poll_cq += gpu_qps[i].profiler.getStat(POLL_CQ);
|
|
cycles_next_cq += gpu_qps[i].profiler.getStat(NEXT_CQ);
|
|
total_quiet_count += gpu_qps[i].profiler.getStat(QUIET_COUNT);
|
|
total_db_count += gpu_qps[i].profiler.getStat(DB_COUNT);
|
|
total_wqe_count += gpu_qps[i].profiler.getStat(WQE_COUNT);
|
|
}
|
|
|
|
double us_ring_sq_db = cycles_ring_sq_db / gpu_clock_freq_mhz;
|
|
double us_update_wqe = cycles_update_wqe / gpu_clock_freq_mhz;
|
|
double us_poll_cq = cycles_poll_cq / gpu_clock_freq_mhz;
|
|
double us_next_cq = cycles_next_cq / gpu_clock_freq_mhz;
|
|
double us_init = cycles_init / gpu_clock_freq_mhz;
|
|
double us_finalize = cycles_finalize / gpu_clock_freq_mhz;
|
|
|
|
const int FIELD_WIDTH = 20;
|
|
const int FLOAT_PRECISION = 2;
|
|
|
|
printf("Counts: Internal Quiets %lu DB Rings %lu WQE Posts %lu\n",
|
|
total_quiet_count, total_db_count, total_wqe_count);
|
|
|
|
printf("\n%*s%*s%*s%*s%*s%*s\n", FIELD_WIDTH + 1, "Init (us)",
|
|
FIELD_WIDTH + 1, "Finalize (us)", FIELD_WIDTH + 1, "Ring SQ DB (us)",
|
|
FIELD_WIDTH + 1, "Update WQE (us)", FIELD_WIDTH + 1, "Poll CQ (us)",
|
|
FIELD_WIDTH + 1, "Next CQ (us)");
|
|
|
|
uint64_t totalFinalize = globalStats->getStat(NUM_FINALIZE);
|
|
printf("%*.*f %*.*f %*.*f %*.*f %*.*f %*.*f\n", FIELD_WIDTH, FLOAT_PRECISION,
|
|
us_init / totalFinalize, FIELD_WIDTH, FLOAT_PRECISION,
|
|
us_finalize / totalFinalize, FIELD_WIDTH, FLOAT_PRECISION,
|
|
us_ring_sq_db / total_db_count, FIELD_WIDTH, FLOAT_PRECISION,
|
|
us_update_wqe / total_wqe_count, FIELD_WIDTH, FLOAT_PRECISION,
|
|
us_poll_cq / total_quiet_count, FIELD_WIDTH, FLOAT_PRECISION,
|
|
us_next_cq / total_quiet_count);
|
|
#endif
|
|
}
|
|
|
|
void NetworkOnImpl::reset_backend_stats() {
|
|
int statblocks = connection->total_number_connections();
|
|
|
|
for (size_t i = 0; i < statblocks; i++) {
|
|
gpu_qps[i].profiler.resetStats();
|
|
}
|
|
}
|
|
|
|
void NetworkOnImpl::exchange_hdp_info(HdpPolicy *hdp_policy,
|
|
MPI_Comm thread_comm) {
|
|
/*
|
|
* Using Connection class, register the host-side hdp flush address
|
|
* with the InfiniBand network.
|
|
*/
|
|
connection->reg_mr(hdp_policy->get_hdp_flush_ptr(), 32, &hdp_mr, false);
|
|
|
|
/*
|
|
* Allocate device-side memory for the remote HDP keys.
|
|
*/
|
|
CHECK_HIP(hipMalloc(reinterpret_cast<void **>(&hdp_rkey),
|
|
num_pes * sizeof(uint32_t)));
|
|
|
|
/*
|
|
* Allocate device-side memory for the remote HDP addresses.
|
|
*/
|
|
CHECK_HIP(hipMalloc(reinterpret_cast<void **>(&hdp_address),
|
|
num_pes * sizeof(uintptr_t)));
|
|
|
|
/*
|
|
* Allocate host-side memory to exchange hdp keys using MPI_Allgather.
|
|
*/
|
|
uint32_t *host_hdp_cpy =
|
|
reinterpret_cast<uint32_t *>(malloc(num_pes * sizeof(uint32_t)));
|
|
if (host_hdp_cpy == nullptr) {
|
|
abort();
|
|
}
|
|
|
|
/*
|
|
* Allocate host-side memory to exchange hdp addresses using
|
|
* MPI_Allgather.
|
|
*/
|
|
uint32_t **host_hdp_address_cpy =
|
|
reinterpret_cast<uint32_t **>(malloc(num_pes * sizeof(uint32_t *)));
|
|
if (host_hdp_address_cpy == nullptr) {
|
|
free(host_hdp_cpy);
|
|
abort();
|
|
}
|
|
|
|
/*
|
|
* This processing element writes its personal HDP key and HDP address
|
|
* into the host-side arrays which were just allocated.
|
|
*/
|
|
int my_rank = my_pe;
|
|
host_hdp_cpy[my_rank] = htobe32(hdp_mr->rkey);
|
|
host_hdp_address_cpy[my_rank] = hdp_policy->get_hdp_flush_ptr();
|
|
|
|
/*
|
|
* Do all-to-all exchange of our HDP key with other processing elements.
|
|
*/
|
|
MPI_Allgather(MPI_IN_PLACE, sizeof(uint32_t), MPI_CHAR, host_hdp_cpy,
|
|
sizeof(uint32_t), MPI_CHAR, thread_comm);
|
|
|
|
/*
|
|
* Do all-to-all exchange of our HDP address with other processing
|
|
* elements.
|
|
*/
|
|
MPI_Allgather(MPI_IN_PLACE, sizeof(uintptr_t), MPI_CHAR, host_hdp_address_cpy,
|
|
sizeof(uint32_t *), MPI_CHAR, thread_comm);
|
|
|
|
/*
|
|
* Copy the recently exchanged HDP keys to device memory.
|
|
*/
|
|
hipStream_t stream;
|
|
CHECK_HIP(hipStreamCreateWithFlags(&stream, hipStreamNonBlocking));
|
|
CHECK_HIP(hipMemcpyAsync(hdp_rkey, host_hdp_cpy, num_pes * sizeof(uint32_t),
|
|
hipMemcpyHostToDevice, stream));
|
|
|
|
/*
|
|
* Copy the recently exchanged HDP addresses to device memory.
|
|
*/
|
|
CHECK_HIP(hipMemcpyAsync(hdp_address, host_hdp_address_cpy,
|
|
num_pes * sizeof(uint32_t *), hipMemcpyHostToDevice,
|
|
stream));
|
|
CHECK_HIP(hipStreamSynchronize(stream));
|
|
CHECK_HIP(hipStreamDestroy(stream));
|
|
|
|
/*
|
|
* Free the host-side resources used to exchange HDP resources
|
|
* between processing elements.
|
|
*/
|
|
free(host_hdp_cpy);
|
|
free(host_hdp_address_cpy);
|
|
}
|
|
|
|
void NetworkOnImpl::setup_atomic_region() {
|
|
/*
|
|
* Allocate fine-grained device-side memory for the atomic return
|
|
* region.
|
|
*/
|
|
allocate_atomic_region(&atomic_ret, num_blocks);
|
|
|
|
/*
|
|
* Register the atomic return region on the InfiniBand network.
|
|
*/
|
|
connection->reg_mr(atomic_ret->atomic_base_ptr,
|
|
sizeof(uint64_t) * max_nb_atomic * num_blocks, &mr, false);
|
|
|
|
/*
|
|
* Set member variable from class.
|
|
*/
|
|
atomic_ret->atomic_lkey = htobe32(mr->lkey);
|
|
}
|
|
|
|
void NetworkOnImpl::heap_memory_rkey(char *local_heap_base, size_t heap_size,
|
|
MPI_Comm thread_comm, bool is_managed) {
|
|
/*
|
|
* Allocate host-side memory to hold remote keys for all processing
|
|
* elements.
|
|
*/
|
|
const size_t rkeys_size = sizeof(uint32_t) * num_pes;
|
|
uint32_t *host_rkey_cpy = reinterpret_cast<uint32_t *>(malloc(rkeys_size));
|
|
if (host_rkey_cpy == nullptr) {
|
|
abort();
|
|
}
|
|
|
|
/*
|
|
* Using the Connection class, register the symmetric heap with the
|
|
* InfiniBand network.
|
|
*/
|
|
void *base_heap = local_heap_base;
|
|
connection->reg_mr(base_heap, heap_size, &heap_mr, is_managed);
|
|
|
|
/*
|
|
* Using the memory region from the prior heap memory registration,
|
|
* allocate and initialize some device-side memory to hold the remote
|
|
* keys for the symmetric heap base.
|
|
*
|
|
* Only the device-side memory entry for this processing element will be
|
|
* updated with the key for the heap memory region.
|
|
*/
|
|
connection->initialize_rkey_handle(&heap_rkey, heap_mr);
|
|
|
|
/*
|
|
* Copy the device-side heap base remote key array to the host-side
|
|
* heap base remote key array.
|
|
*/
|
|
hipStream_t stream;
|
|
CHECK_HIP(hipStreamCreateWithFlags(&stream, hipStreamNonBlocking));
|
|
CHECK_HIP(hipMemcpyAsync(host_rkey_cpy, heap_rkey, rkeys_size,
|
|
hipMemcpyDeviceToHost, stream));
|
|
CHECK_HIP(hipStreamSynchronize(stream));
|
|
|
|
/*
|
|
* Do all-to-all exchange of symmetric heap base remote key between the
|
|
* processing elements.
|
|
*/
|
|
MPI_Allgather(MPI_IN_PLACE, sizeof(uint32_t), MPI_CHAR, host_rkey_cpy,
|
|
sizeof(uint32_t), MPI_CHAR, thread_comm);
|
|
|
|
/*
|
|
* Copy the recently updated host-side heap base remote key array back
|
|
* to the device-side memory.
|
|
*/
|
|
CHECK_HIP(hipMemcpyAsync(heap_rkey, host_rkey_cpy, rkeys_size,
|
|
hipMemcpyHostToDevice, stream));
|
|
CHECK_HIP(hipStreamSynchronize(stream));
|
|
CHECK_HIP(hipStreamDestroy(stream));
|
|
|
|
/*
|
|
* Free the host-side resources used to do the processing element
|
|
* exchange of keys and addresses for the symmetric heap base.
|
|
*/
|
|
free(host_rkey_cpy);
|
|
|
|
/*
|
|
* Initialize this member variable to hold the InfiniBand memory
|
|
* region's local key.
|
|
*/
|
|
lkey = heap_mr->lkey;
|
|
}
|
|
|
|
void NetworkOnImpl::setup_gpu_qps(GPUIBBackend *B) {
|
|
/*
|
|
* Determine how many connections are needed.
|
|
* The number of connections depends on the connection type and the
|
|
* number of workgroups.
|
|
*/
|
|
int connections;
|
|
connection->get_remote_conn(&connections);
|
|
connections *= num_blocks;
|
|
|
|
/*
|
|
* Allocate device-side memory for the queue pairs.
|
|
*/
|
|
CHECK_HIP(hipMalloc(&gpu_qps, sizeof(QueuePair) * connections));
|
|
|
|
/*
|
|
* For every connection, initialize the QueuePair.
|
|
*/
|
|
for (int i = 0; i < connections; i++) {
|
|
new (&gpu_qps[i]) QueuePair(B);
|
|
connection->init_gpu_qp_from_connection(&gpu_qps[i], i);
|
|
}
|
|
}
|
|
|
|
void NetworkOnImpl::rocshmem_g_init(SymmetricHeap *heap_handle,
|
|
MPI_Comm thread_comm) {
|
|
init_g_ret(heap_handle, thread_comm, num_blocks, &g_ret);
|
|
}
|
|
|
|
__host__ void NetworkOnImpl::networkHostSetup(GPUIBBackend *B) {
|
|
num_pes = B->num_pes;
|
|
my_pe = B->my_pe;
|
|
num_blocks = B->num_blocks_;
|
|
|
|
#ifdef USE_DC
|
|
connection = new DynamicConnection(B);
|
|
#else
|
|
connection = new ReliableConnection(B);
|
|
#endif
|
|
|
|
connection->initialize(B->num_blocks_);
|
|
exchange_hdp_info(B->hdp_policy, B->thread_comm);
|
|
|
|
const auto &heap_bases{B->heap.get_heap_bases()};
|
|
heap_memory_rkey(heap_bases[my_pe], B->heap.get_size(), B->thread_comm,
|
|
B->heap.is_managed());
|
|
// The earliest we can allow the main thread to launch a kernel to
|
|
// avoid potential deadlock
|
|
network_init_done = true;
|
|
|
|
setup_atomic_region();
|
|
|
|
connection->initialize_gpu_policy(&connection_policy, heap_rkey);
|
|
|
|
rocshmem_g_init(&B->heap, B->thread_comm);
|
|
|
|
connection->post_wqes();
|
|
|
|
setup_gpu_qps(B);
|
|
}
|
|
|
|
__host__ void NetworkOnImpl::networkHostFinalize() {
|
|
CHECK_HIP(hipFree(hdp_rkey));
|
|
hdp_rkey = nullptr;
|
|
|
|
CHECK_HIP(hipFree(hdp_address));
|
|
hdp_address = nullptr;
|
|
|
|
CHECK_HIP(hipFree(atomic_ret));
|
|
atomic_ret = nullptr;
|
|
|
|
CHECK_HIP(hipFree(gpu_qps));
|
|
gpu_qps = nullptr;
|
|
|
|
CHECK_HIP(hipFree(connection_policy));
|
|
connection_policy = nullptr;
|
|
|
|
connection->free_rkey_handle(heap_rkey);
|
|
|
|
connection->finalize();
|
|
delete connection;
|
|
connection = nullptr;
|
|
}
|
|
|
|
__host__ void NetworkOnImpl::networkHostInit(GPUIBContext *ctx, int buffer_id) {
|
|
int remote_conn = getNumQueuePairs();
|
|
|
|
CHECK_HIP(hipMalloc(&ctx->device_qp_proxy, remote_conn * sizeof(QueuePair)));
|
|
|
|
for (int i = 0; i < getNumQueuePairs(); i++) {
|
|
/*
|
|
* RC gpu_qp is actually [NUM_PE][NUM_BLOCK] qps but is flattened.
|
|
* Each num_pe entry contains num_block QPs connected to that PE.
|
|
* For RC, we need to iterate gpu_qp[i][buffer_id] to collect a
|
|
* single QP for each connected PE in order to build context.
|
|
* For DC, NUM_PE = 1 so can just use buffer_id directly.
|
|
*/
|
|
int offset = num_blocks * i + buffer_id;
|
|
new (ctx->getQueuePair(i)) QueuePair(gpu_qps[offset]);
|
|
|
|
auto *qp = ctx->getQueuePair(i);
|
|
qp->global_qp = &gpu_qps[offset];
|
|
qp->num_cqs = getNumQueuePairs();
|
|
qp->atomic_ret.atomic_base_ptr =
|
|
&atomic_ret->atomic_base_ptr[max_nb_atomic * buffer_id];
|
|
qp->base_heap = ctx->base_heap;
|
|
}
|
|
ctx->g_ret = g_ret;
|
|
}
|
|
|
|
__device__ void NetworkOnImpl::networkGpuInit(GPUIBContext *ctx,
|
|
int buffer_id) {
|
|
for (int i = 0; i < getNumQueuePairs(); i++) {
|
|
int offset = num_blocks * i + buffer_id;
|
|
|
|
auto *qp = ctx->getQueuePair(i);
|
|
new (qp) QueuePair(gpu_qps[offset]);
|
|
|
|
qp->global_qp = &gpu_qps[offset];
|
|
qp->num_cqs = getNumQueuePairs();
|
|
qp->atomic_ret.atomic_base_ptr =
|
|
&atomic_ret->atomic_base_ptr[max_nb_atomic * buffer_id];
|
|
qp->base_heap = ctx->base_heap;
|
|
}
|
|
ctx->g_ret = g_ret;
|
|
}
|
|
|
|
__device__ __host__ QueuePair *NetworkOnImpl::getQueuePair(QueuePair *qp_handle,
|
|
int pe) {
|
|
#ifdef USE_DC
|
|
return qp_handle;
|
|
#else
|
|
return &qp_handle[pe];
|
|
#endif
|
|
}
|
|
|
|
__device__ __host__ int NetworkOnImpl::getNumQueuePairs() {
|
|
#ifdef USE_DC
|
|
return 1;
|
|
#else
|
|
return num_pes;
|
|
#endif
|
|
}
|
|
|
|
void NetworkOffImpl::networkHostSetup(GPUIBBackend *B) {
|
|
num_pes = B->num_pes;
|
|
my_pe = B->my_pe;
|
|
num_blocks = B->num_blocks_;
|
|
|
|
exchange_hdp_info(B->hdp_policy, B->thread_comm);
|
|
}
|
|
void NetworkOffImpl::exchange_hdp_info(HdpPolicy *hdp_policy,
|
|
MPI_Comm thread_comm) {
|
|
#ifdef USE_SINGLE_NODE
|
|
// We are using the symmetric heap for the HDP flush ptr
|
|
hdp_address = reinterpret_cast<uintptr_t *>(hdp_policy->get_hdp_flush_ptr());
|
|
#else
|
|
/*
|
|
* Allocate device-side memory for the remote HDP addresses.
|
|
*/
|
|
CHECK_HIP(hipMalloc(reinterpret_cast<void **>(&hdp_address),
|
|
num_pes * sizeof(uintptr_t)));
|
|
|
|
/*
|
|
* Allocate host-side memory to exchange hdp keys using MPI_Allgather.
|
|
*/
|
|
uint32_t *host_hdp_cpy =
|
|
reinterpret_cast<uint32_t *>(malloc(num_pes * sizeof(uint32_t)));
|
|
if (host_hdp_cpy == nullptr) {
|
|
abort();
|
|
}
|
|
|
|
/*
|
|
* Allocate host-side memory to exchange hdp addresses using
|
|
* MPI_Allgather.
|
|
*/
|
|
uint32_t **host_hdp_address_cpy =
|
|
reinterpret_cast<uint32_t **>(malloc(num_pes * sizeof(uint32_t *)));
|
|
if (host_hdp_address_cpy == nullptr) {
|
|
free(host_hdp_cpy);
|
|
abort();
|
|
}
|
|
|
|
/*
|
|
* This processing element writes its personal HDP address
|
|
* into the host-side array which were just allocated.
|
|
*/
|
|
int my_rank = my_pe;
|
|
host_hdp_address_cpy[my_rank] = hdp_policy->get_hdp_flush_ptr();
|
|
|
|
/*
|
|
* Do all-to-all exchange of our HDP address with other processing
|
|
* elements.
|
|
*/
|
|
MPI_Allgather(MPI_IN_PLACE, sizeof(uintptr_t), MPI_CHAR, host_hdp_address_cpy,
|
|
sizeof(uint32_t *), MPI_CHAR, thread_comm);
|
|
|
|
/*
|
|
* Copy the recently exchanged HDP addresses to device memory.
|
|
*/
|
|
hipStream_t stream;
|
|
CHECK_HIP(hipStreamCreateWithFlags(&stream, hipStreamNonBlocking));
|
|
CHECK_HIP(hipMemcpyAsync(hdp_address, host_hdp_address_cpy,
|
|
num_pes * sizeof(uint32_t *), hipMemcpyHostToDevice,
|
|
stream));
|
|
CHECK_HIP(hipStreamSynchronize(stream));
|
|
CHECK_HIP(hipStreamDestroy(stream));
|
|
|
|
/*
|
|
* Free the host-side resources used to exchange HDP resources
|
|
* between processing elements.
|
|
*/
|
|
free(host_hdp_cpy);
|
|
free(host_hdp_address_cpy);
|
|
#endif
|
|
}
|
|
|
|
void NetworkOffImpl::networkHostFinalize() {
|
|
#ifndef USE_SINGLE_NODE
|
|
CHECK_HIP(hipFree(hdp_address));
|
|
#endif
|
|
hdp_address = nullptr;
|
|
}
|
|
|
|
} // namespace rocshmem
|