| |
| |
| |
|
|
| #ifndef LLMC_ZERO_CUH |
| #define LLMC_ZERO_CUH |
|
|
| #include <cuda_runtime_api.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <stddef.h> |
|
|
| #ifdef MULTI_GPU |
| #include <nccl.h> |
| #ifdef USE_MPI |
| #include <mpi.h> |
| #endif |
| #endif |
|
|
| |
| #include "utils.h" |
|
|
| |
| |
| #ifdef MULTI_GPU |
|
|
| #if defined(ENABLE_FP32) |
| const ncclDataType_t ncclFloatX = ncclFloat; |
| #elif defined(ENABLE_FP16) |
| const ncclDataType_t ncclFloatX = ncclHalf; |
| #else |
| const ncclDataType_t ncclFloatX = ncclBfloat16; |
| #endif |
|
|
| void nccl_check(ncclResult_t status, const char *file, int line) { |
| if (status != ncclSuccess) { |
| printf("[NCCL ERROR] at file %s:%d:\n%s\n", file, line, ncclGetErrorString(status)); |
| exit(EXIT_FAILURE); |
| } |
| } |
| #define ncclCheck(err) (nccl_check(err, __FILE__, __LINE__)) |
|
|
| #ifdef USE_MPI |
| void mpi_check(int status, const char *file, int line) { |
| if (status != MPI_SUCCESS) { |
| char mpi_error[4096]; |
| int mpi_error_len = 0; |
| assert(MPI_Error_string(status, &mpi_error[0], &mpi_error_len) == MPI_SUCCESS); |
| printf("[MPI ERROR] at file %s:%d:\n%.*s\n", file, line, mpi_error_len, mpi_error); |
| exit(EXIT_FAILURE); |
| } |
| } |
| #define mpiCheck(err) (mpi_check(err, __FILE__, __LINE__)) |
| #endif |
|
|
| #endif |
|
|
| |
| |
| typedef struct { |
| int process_rank; |
| int num_processes; |
| int local_device_idx; |
|
|
| |
| |
| |
| |
| |
| int zero_stage; |
| size_t shard_num_parameters; |
| #ifdef MULTI_GPU |
| ncclComm_t nccl_comm; |
| cudaStream_t nccl_stream; |
| cudaEvent_t compute_nccl_sync; |
| float* unified_buffer; |
| #endif |
| } MultiGpuConfig; |
|
|
| |
| |
| inline MultiGpuConfig multi_gpu_config; |
|
|
| #ifdef MULTI_GPU |
|
|
| #ifdef _WIN32 |
| void send_nccl_id_to_clients_windows(ncclUniqueId *nccl_id, SOCKET client_sockets[], int num_clients) { |
| for (int i = 0; i < num_clients; ++i) { |
| if (send(client_sockets[i], (const char *)nccl_id, sizeof(*nccl_id), 0) == SOCKET_ERROR) { |
| printf("Failed to send nccl_id"); |
| WSACleanup(); |
| exit(EXIT_FAILURE); |
| } |
| closesocketCheck(client_sockets[i]); |
| } |
| } |
| #else |
| void send_nccl_id_to_clients(ncclUniqueId *nccl_id, int client_sockets[], int num_clients) { |
| for (int i = 0; i < num_clients; ++i) { |
| if (send(client_sockets[i], nccl_id, sizeof(*nccl_id), 0) == -1) { |
| printf("Failed to send nccl_id"); |
| exit(EXIT_FAILURE); |
| } |
| scloseCheck(client_sockets[i]); |
| } |
| } |
| #endif |
|
|
| #ifdef _WIN32 |
| |
| ncclUniqueId get_nccl_id_via_tcp_windows(MultiGpuConfig* result, const char* server_ip) { |
| ncclUniqueId nccl_id; |
|
|
| int SERVER_PORT = 12345; |
| WSADATA wsaData; |
| if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { |
| printf("WSAStartup failed"); |
| exit(EXIT_FAILURE); |
| } |
|
|
| if (result->process_rank == 0) { |
| ncclCheck(ncclGetUniqueId(&nccl_id)); |
|
|
| int MAX_CLIENTS = result->num_processes - 1; |
| SOCKET client_sockets[MAX_CLIENTS]; |
| int num_clients = 0; |
| SOCKET server_socket, new_socket; |
| struct sockaddr_in address; |
| int addrlen = sizeof(address); |
|
|
| |
| if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { |
| printf("Socket failed"); |
| WSACleanup(); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| address.sin_family = AF_INET; |
| address.sin_addr.s_addr = inet_addr(server_ip); |
| address.sin_port = htons(SERVER_PORT); |
|
|
| |
| if (bind(server_socket, (struct sockaddr *)&address, sizeof(address)) == SOCKET_ERROR) { |
| printf("Bind failed"); |
| closesocketCheck(server_socket); |
| WSACleanup(); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| if (listen(server_socket, MAX_CLIENTS) == SOCKET_ERROR) { |
| printf("Listen failed"); |
| closesocketCheck(server_socket); |
| WSACleanup(); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| printf("Waiting for clients to connect...\n"); |
| while (num_clients < MAX_CLIENTS) { |
| if ((new_socket = accept(server_socket, (struct sockaddr *)&address, &addrlen)) == INVALID_SOCKET) { |
| printf("Accept failed"); |
| closesocketCheck(server_socket); |
| WSACleanup(); |
| exit(EXIT_FAILURE); |
| } |
| client_sockets[num_clients++] = new_socket; |
| printf("Client %d connected\n", num_clients); |
| } |
|
|
| |
| send_nccl_id_to_clients_windows(&nccl_id, client_sockets, num_clients); |
| printf("NCCL ID sent to all clients\n"); |
|
|
| closesocketCheck(server_socket); |
| } else { |
| int num_connection_attempts = 5; |
| int time_to_sleep = 2; |
| SOCKET client_socket; |
| struct sockaddr_in serv_addr; |
|
|
| |
| if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { |
| printf("Socket creation error"); |
| WSACleanup(); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| serv_addr.sin_family = AF_INET; |
| serv_addr.sin_port = htons(SERVER_PORT); |
| if (inet_pton(AF_INET, server_ip, &serv_addr.sin_addr) <= 0) { |
| printf("Invalid address or address not supported"); |
| closesocketCheck(client_socket); |
| WSACleanup(); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| while (connect(client_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) { |
| printf("%d Connection failed, retrying in %d seconds\n", result->process_rank, time_to_sleep); |
| if (--num_connection_attempts == 0) { |
| printf("Failed to connect to the server\n"); |
| closesocketCheck(client_socket); |
| WSACleanup(); |
| exit(EXIT_FAILURE); |
| } |
| Sleep(time_to_sleep * 1000); |
| } |
|
|
| |
| if (recv(client_socket, (char *)&nccl_id, sizeof(nccl_id), 0) <= 0) { |
| printf("Failed to receive nccl_id"); |
| closesocketCheck(client_socket); |
| WSACleanup(); |
| exit(EXIT_FAILURE); |
| } |
|
|
| printf("Received NCCL ID\n"); |
| closesocketCheck(client_socket); |
| } |
|
|
| WSACleanup(); |
| return nccl_id; |
| } |
| #else |
| ncclUniqueId get_nccl_id_via_tcp(MultiGpuConfig* result, const char* server_ip) { |
| ncclUniqueId nccl_id; |
|
|
| int SERVER_PORT = 12345; |
| if (result->process_rank == 0) { |
| ncclCheck(ncclGetUniqueId(&nccl_id)); |
|
|
| int MAX_CLIENTS = result->num_processes - 1; |
| int client_sockets[MAX_CLIENTS]; |
| int num_clients = 0; |
| int server_socket, new_socket; |
| struct sockaddr_in address; |
| int addrlen = sizeof(address); |
| int opt = 1; |
|
|
| |
| if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { |
| printf("Socket failed"); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| |
| |
| |
| if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) < 0) { |
| printf("Setsockopt failed"); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| address.sin_family = AF_INET; |
| address.sin_addr.s_addr = inet_addr(server_ip); |
| address.sin_port = htons(SERVER_PORT); |
|
|
| |
| if (bind(server_socket, (struct sockaddr *)&address, sizeof(address)) < 0) { |
| printf("Bind failed"); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| if (listen(server_socket, MAX_CLIENTS) < 0) { |
| printf("Listen failed"); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| printf("Waiting for clients to connect...\n"); |
| while (num_clients < MAX_CLIENTS) { |
| if ((new_socket = accept(server_socket, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { |
| printf("Accept failed"); |
| exit(EXIT_FAILURE); |
| } |
| client_sockets[num_clients++] = new_socket; |
| printf("Client %d connected\n", num_clients); |
| } |
|
|
| |
| send_nccl_id_to_clients(&nccl_id, client_sockets, num_clients); |
| printf("NCCL ID sent to all clients\n"); |
|
|
| scloseCheck(server_socket); |
| } else { |
| int num_connection_attempts = 5; |
| int time_to_sleep = 2; |
| int client_socket; |
| struct sockaddr_in serv_addr; |
|
|
| |
| if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { |
| printf("Socket creation error"); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| serv_addr.sin_family = AF_INET; |
| serv_addr.sin_port = htons(SERVER_PORT); |
| if (inet_pton(AF_INET, server_ip, &serv_addr.sin_addr) <= 0) { |
| printf("Invalid address or address not supported"); |
| exit(EXIT_FAILURE); |
| } |
|
|
| |
| while (connect(client_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { |
| printf("%d Connection failed, retrying in %d seconds\n", result->process_rank, time_to_sleep); |
| if (--num_connection_attempts == 0) { |
| printf("Failed to connect to the server\n"); |
| exit(EXIT_FAILURE); |
| } |
| sleep(time_to_sleep); |
| } |
|
|
| |
| if (recv(client_socket, &nccl_id, sizeof(nccl_id), 0) <= 0) { |
| printf("Failed to receive nccl_id"); |
| exit(EXIT_FAILURE); |
| } |
|
|
| printf("Received NCCL ID\n"); |
| scloseCheck(client_socket); |
| } |
|
|
| return nccl_id; |
| } |
| #endif |
|
|
| ncclUniqueId get_nccl_id_via_fs(MultiGpuConfig* result, char* fs_path) { |
| |
| ncclUniqueId nccl_id; |
| FILE* idFile; |
| static char filename[1024]; |
| snprintf(filename, sizeof(filename), "%s/ncclUniqueId.sync", fs_path); |
|
|
| if (result->process_rank != 0) { |
| |
| sleep(2); |
| } |
|
|
| if (result->process_rank == 0) { |
| ncclCheck(ncclGetUniqueId(&nccl_id)); |
| idFile = fopen(filename, "wb"); |
| assert(idFile != NULL); |
| fwriteCheck(&nccl_id, sizeof(nccl_id), 1, idFile); |
| fcloseCheck(idFile); |
| } else { |
| |
| do { |
| sleep(1); |
| idFile = fopen(filename, "rb"); |
| if (idFile != NULL) break; |
| } while (idFile == NULL); |
| freadCheck(&nccl_id, sizeof(nccl_id), 1, idFile); |
| fcloseCheck(idFile); |
| } |
|
|
| return nccl_id; |
| } |
|
|
| #ifdef USE_MPI |
| |
| |
| |
| int multi_gpu_get_local_device_idx(int process_rank, int num_processes) { |
| char hostname[1024]; |
| hostname[1023] = '\0'; |
| |
| gethostname(hostname, 1023); |
| for (int i=0; i < 1024; i++) { |
| if (hostname[i] == '.') { |
| hostname[i] = '\0'; |
| break; |
| } |
| } |
| uint64_t hostname_hash = 5381u; |
| for (int c = 0; hostname[c] != '\0'; c++){ hostname_hash = ((hostname_hash << 5u) + hostname_hash) ^ hostname[c]; } |
|
|
| |
| uint64_t* all_hostsname_hashes = (uint64_t*)malloc(num_processes * sizeof(uint64_t)); |
| all_hostsname_hashes[process_rank] = hostname_hash; |
| mpiCheck(MPI_Allgather(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, all_hostsname_hashes, sizeof(uint64_t), MPI_BYTE, MPI_COMM_WORLD)); |
|
|
| |
| int local_device_idx = 0; |
| for (int current_process = 0; current_process < num_processes; ++current_process) { |
| if (current_process == process_rank) { |
| |
| break; |
| } |
| if (all_hostsname_hashes[current_process] == all_hostsname_hashes[process_rank]) { |
| |
| local_device_idx++; |
| } |
| } |
|
|
| free(all_hostsname_hashes); |
| return local_device_idx; |
| } |
| #endif |
|
|
| #endif |
|
|
| MultiGpuConfig multi_gpu_config_init(int num_processes, int process_rank, int gpus_per_node, char* server_ip, char* fs_path, char* init_method) { |
| #ifdef MULTI_GPU |
| MultiGpuConfig result; |
| ncclUniqueId nccl_id; |
| |
| |
| if (strcmp(init_method, "mpi") == 0) { |
| #ifdef USE_MPI |
| mpiCheck(MPI_Init(NULL, NULL)); |
| mpiCheck(MPI_Comm_rank(MPI_COMM_WORLD, &result.process_rank)); |
| mpiCheck(MPI_Comm_size(MPI_COMM_WORLD, &result.num_processes)); |
| result.local_device_idx = multi_gpu_get_local_device_idx(result.process_rank, result.num_processes); |
| if (result.process_rank == 0) { |
| ncclCheck(ncclGetUniqueId(&nccl_id)); |
| } |
| mpiCheck(MPI_Bcast(&nccl_id, sizeof(nccl_id), MPI_BYTE, 0, MPI_COMM_WORLD)); |
| #else |
| printf("MPI support is disabled. Please enable MPI support to use MPI-based NCCL-init method.\n"); |
| exit(EXIT_FAILURE); |
| #endif |
| } else { |
| result.process_rank = process_rank; |
| result.num_processes = num_processes; |
| result.local_device_idx = process_rank % gpus_per_node; |
| if (strcmp(init_method, "tcp") == 0) { |
| #ifdef _WIN32 |
| nccl_id = get_nccl_id_via_tcp_windows(&result, server_ip); |
| #else |
| nccl_id = get_nccl_id_via_tcp(&result, server_ip); |
| #endif |
| } else if (strcmp(init_method, "fs") == 0) { |
| nccl_id = get_nccl_id_via_fs(&result, fs_path); |
| } else { |
| printf("Invalid NCCL-init method\n"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| cudaCheck(cudaSetDevice(result.local_device_idx)); |
| ncclCheck(ncclCommInitRank(&result.nccl_comm, result.num_processes, nccl_id, result.process_rank)); |
| cudaCheck(cudaStreamCreate(&result.nccl_stream)); |
| |
| cudaCheck(cudaEventCreate(&result.compute_nccl_sync, cudaEventDisableTiming)); |
| nvtxNameCudaStreamA(result.nccl_stream, "nccl stream"); |
| nvtxNameCudaEventA(result.compute_nccl_sync, "nccl compute sync"); |
| cudaCheck(cudaMallocManaged(&result.unified_buffer, sizeof(float))); |
| return result; |
| #else |
| printf("Multi-GPU support is disabled. Using a single GPU.\n"); |
| cudaCheck(cudaSetDevice(0)); |
| MultiGpuConfig result; |
| result.process_rank = 0; |
| result.num_processes = 1; |
| result.local_device_idx = 0; |
| return result; |
| #endif |
| } |
|
|
| void multi_gpu_config_free(MultiGpuConfig* config) { |
| #ifdef MULTI_GPU |
| ncclCheck(ncclCommDestroy(config->nccl_comm)); |
| cudaCheck(cudaStreamDestroy(config->nccl_stream)); |
| cudaCheck(cudaEventDestroy(config->compute_nccl_sync)); |
| cudaCheck(cudaFree(config->unified_buffer)); |
| #ifdef USE_MPI |
| mpiCheck(MPI_Finalize()); |
| #endif |
| #endif |
| } |
|
|
| void multi_gpu_barrier(const MultiGpuConfig* config) { |
| #ifdef MULTI_GPU |
| if (config->num_processes > 1) { |
| ncclCheck(ncclAllReduce(config->unified_buffer, config->unified_buffer, sizeof(float), ncclFloat, ncclSum, config->nccl_comm, config->nccl_stream)); |
| } |
| cudaCheck(cudaDeviceSynchronize()); |
| #endif |
| } |
|
|
| |
| typedef struct { |
| ptrdiff_t offset; |
| size_t size; |
| } ShardInfo; |
|
|
| |
| ShardInfo multi_gpu_get_shard_offset(size_t elements, const MultiGpuConfig* config, int shard_at_stage) { |
| const int nproc = config->num_processes; |
| if(config->zero_stage >= shard_at_stage) { |
| if (elements % nproc != 0) { |
| fprintf(stderr, "Number of elements %zu must be a multiple of the number of processes %d\n", elements, nproc); |
| exit(EXIT_FAILURE); |
| } |
| return {(ptrdiff_t) (config->process_rank * (elements / nproc)), elements / nproc}; |
| } else { |
| return {0, elements}; |
| } |
| } |
|
|
| |
| |
| |
| |
| template<int N> |
| void multi_gpu_async_reduce_gradient( |
| floatX* const (&pointers)[N], const size_t (&pointers_sizes)[N], |
| MultiGpuConfig* config, cudaStream_t compute_stream) { |
| if (config->num_processes == 1) { |
| return; |
| } |
|
|
| #ifdef MULTI_GPU |
| NVTX_RANGE_FN(); |
| |
| |
| |
| |
| |
| cudaCheck(cudaEventRecord(config->compute_nccl_sync, compute_stream)); |
| cudaCheck(cudaStreamWaitEvent(config->nccl_stream, config->compute_nccl_sync)); |
| ncclCheck(ncclGroupStart()); |
| for (int i = 0; i < N; ++i) { |
| if(config->zero_stage == 0) { |
| ncclCheck(ncclAllReduce( |
| pointers[i], pointers[i], |
| pointers_sizes[i], |
| ncclFloatX, ncclAvg, |
| config->nccl_comm, config->nccl_stream |
| )); |
| } else if(config->zero_stage == 1) { |
| assert(pointers_sizes[i] % config->num_processes == 0); |
| size_t shard_size = pointers_sizes[i] / config->num_processes; |
| ptrdiff_t shard_offset = (ptrdiff_t)shard_size * config->process_rank; |
| ncclCheck(ncclReduceScatter( |
| pointers[i], pointers[i] + shard_offset, |
| shard_size, |
| ncclFloatX, ncclAvg, |
| config->nccl_comm, config->nccl_stream |
| )); |
| } |
| } |
| ncclCheck(ncclGroupEnd()); |
| #endif |
| } |
|
|
| |
| #define printf0(...) if (::multi_gpu_config.process_rank == 0) { printf(__VA_ARGS__); } |
|
|
| void set_zero_configs(MultiGpuConfig* config, int zero_stage, size_t total_parameters) { |
| config->zero_stage = 0; |
| config->shard_num_parameters = total_parameters; |
| |
| if (zero_stage == 0) { |
| printf0("| Zero Optimization is disabled |\n"); |
| } |
| else if (zero_stage == 1) { |
| if (total_parameters % config->num_processes != 0) { |
| printf0("| Zero Optimization is disabled, Can't equally partition parameters |\n"); |
| config->zero_stage = 0; |
| } |
| else { |
| config->zero_stage = 1; |
| config->shard_num_parameters = total_parameters / config->num_processes; |
| } |
| } |
| else{ |
| printf0("| Disabling Zero Optimization, Zero Stage2 and Stage3 are not yet supported |\n"); |
| config->zero_stage = 0; |
| } |
| } |
|
|
| |
| float multi_gpu_cpu_float_sum(float value, MultiGpuConfig* config) { |
| #ifdef MULTI_GPU |
| if (config->num_processes == 1) return value; |
|
|
| float* unified_buffer = config->unified_buffer; |
| *unified_buffer = value; |
| ncclCheck(ncclAllReduce(unified_buffer, unified_buffer, sizeof(float), ncclFloat, ncclSum, config->nccl_comm, config->nccl_stream)); |
| cudaCheck(cudaDeviceSynchronize()); |
| return *unified_buffer; |
| #else |
| return value; |
| #endif |
| } |
|
|
| #endif |
|
|
|
|