Краткое описание шагов:
Получить количество локальных рангов (можно понимать как процессов) через MPI, localrank, который используется для привязки ранга к графическому процессору;
Rank0 получает идентификатор группы связи NCCL и передает его другим рангам через MPI_Bcast;
С помощью этой информации, полученной MPI, NCCL завершает инициализацию и осуществляет коллективную связь.
Основные шаги:
1. Инициализируйте и запустите связь MPI.
2. Вычислить хэш-значение имени хоста и связаться с MPI_allgather, чтобы каждый ранг (процесс) получил хеш-значение других рангов.
3. На основании полученного значения хеш-функции сравнить общее количество рангов localrank, с которыми участвует в общении хост, на котором расположен ранг (ранги с одинаковым значением хеш-функции находятся на одном хосте). (Хеш-значение — это имя хоста. Фактически, по имени хоста можно получить общее количество рангов, участвующих в общении на хосте. Однако имена хостов разнообразны, и хеш-значение легче сравнивать)
4. Получите уникальный идентификатор NCCL на ранге 0 и передайте его другим рангам с помощью MPI_Bcast. (Этот уникальный идентификатор используется для идентификации группы связи, поэтому все ранги в группе связи имеют одинаковый идентификатор)
5. Привяжите графический процессор на основе локального ранга, выделите буферы отправки и приема и создайте поток CUDA.
6. Инициализируйте коммуникатор NCCL.
7. nccl allreduce общение. Синхронизируйте потоки CUDA, чтобы обеспечить завершение связи.
8. Освободите буфер.
9. Уничтожить коммуникатор.
10. Завершите работу среды MPI
Ха-ха-ха,Я считаю, что нет необходимости снимать видео по этому вопросу.,Если необходимо продолжить, обновите его на станции B.
int main(int argc, char* argv[])
{
// Определите целочисленную переменную size, которая представляет размер буфера 32 МБ.
int size = 32*1024*1024;
// Определите переменные, связанные с MPI, включая рейтинг текущего процесса (myRank), общее количество процессов (nRanks) и локальный рейтинг (localRank).
int myRank, nRanks, localRank = 0;
//////// 1. Инициализируйте и запустите связь MPI ///////////////////
MPICHECK(MPI_Init(&argc, &argv));
// Получить рейтинг текущего процесса
MPICHECK(MPI_Comm_rank(MPI_COMM_WORLD, &myRank));
// Получить общее количество процессов
MPICHECK(MPI_Comm_size(MPI_COMM_WORLD, &nRanks));
///////// 2. Вычислить хэш-значение имени хоста и связаться с MPI_allgather, чтобы каждый ранг (процесс) получил хеш-значение других рангов.////////////////
// Вычисление localRank на основе хеша имени хоста для выбора графического процессора
uint64_t hostHashs[nRanks];
char hostname[1024];
getHostName(hostname, 1024); // Получить имя хоста
hostHashs[myRank] = getHostHash(hostname); // Вычислить хэш-значение имени хоста
// Используйте MPI_Allgather для сбора хешей всех процессов.
MPICHECK(MPI_Allgather(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, hostHashs, sizeof(uint64_t), MPI_BYTE, MPI_COMM_WORLD));
/////////////////3. На основании полученного значения хеш-функции подсчитать общее количество рангов, в общении которых участвует хост, на котором расположен ранг (ранги с одинаковым значением хеш-функции). находятся на одном хосте). (Хеш-значение — это имя хоста. Фактически, по имени хоста можно получить общее количество рангов, участвующих в общении на хосте. Однако имена хостов разнообразны и хэш-значения легче сравнивать) /////////////// ////////
// Вычислите localRank, который представляет собой количество процессов с одинаковым хешем хоста (исключая текущий процесс).
for (int p=0; p<nRanks; p++) {
if (p == myRank) break; // Если это текущий процесс, выйдите из цикла
if (hostHashs[p] == hostHashs[myRank]) localRank++; // Если хеши одинаковые, localRank увеличивается на 1.
}
// Переменные, связанные с NCCL, включая уникальный идентификатор (id) и коммуникатор (comm)
ncclUniqueId id;
ncclComm_t comm;
// Определите переменные, связанные с CUDA, включая буферы отправки и получения (sendbuff, Recvbuff) и поток(и) CUDA
float *sendbuff, *recvbuff;
cudaStream_t s;
///////////////////4. Получите уникальный идентификатор NCCL на ранге 0 и передайте его другим рангам с помощью MPI_Bcast. (Этот уникальный идентификатор используется для идентификации группы связи, поэтому все ранги в группе связи имеют одинаковый идентификатор)////////////
// в ранге Получите уникальный идентификатор NCCL со значением 0 и передайте его всем остальным процессам с помощью MPI_Bcast.
if (myRank == 0) ncclGetUniqueId(&id);
MPICHECK(MPI_Bcast((void *)&id, sizeof(id), MPI_BYTE, 0, MPI_COMM_WORLD));
////////////////5. Привяжите графический процессор на основе локального ранга, выделите буферы отправки и приема и создайте поток CUDA.////////////
// Выберите графический процессор на основе localRank и выделите буферы устройства.
// CUDACHECK — это макрос, используемый для проверки возвращаемого значения функции CUDA.
CUDACHECK(cudaSetDevice(localRank)); // Настройка устройства CUDA
CUDACHECK(cudaMalloc(&sendbuff, size * sizeof(float))); // Выделить буфер отправки
CUDACHECK(cudaMalloc(&recvbuff, size * sizeof(float))); // Выделить буфер приема
CUDACHECK(cudaStreamCreate(&s)); // Создать поток CUDA
/////////////6. Инициализация коммуникатора NCCL///////////////////////////
NCCLCHECK(ncclCommInitRank(&comm, nRanks, id, myRank));
//////////7. Используйте NCCL для операций AllReduce. //////////////////////
// Эта операция суммирует значения в sendbuff по всем процессам и помещает результат в Recvbuff.
NCCLCHECK(ncclAllReduce((const void*)sendbuff, (void*)recvbuff, size / sizeof(float), ncclFloat, ncclSum, comm, s));
// Примечание: размер / sizeof(float) Это потому, что ncclAllReduce требует количества элементов, а не количества байтов.
// Синхронизируйте потоки CUDA, чтобы обеспечить завершение операций NCCL.
CUDACHECK(cudaStreamSynchronize(s));
///////////////8. Освободите буфер устройства////////////////
CUDACHECK(cudaFree(sendbuff));
CUDACHECK(cudaFree(recvbuff));
////////////// 9. Уничтожить коммуникатор NCCL ///////////////////
ncclCommDestroy(comm);
///////////////10、 Завершить среду MPI /////////////////////
MPICHECK(MPI_Finalize());
printf("[MPI Rank %d] Success \n", myRank);
return 0;
}
Источник исходного кода Официальный документ NCCL Example 2: One Device per Process or Thread:Examples — NCCL 2.21.5 documentation (nvidia.com)
NCCLИнтерпретация исходного кода2:ncclGetUniqueId(&id)функция Интерпретация исходного кода