Я реализовал некоторые MPI_Scatterv
а также MPI_Gatherv
Процедуры для умножения матрицы на параллельную матрицу. Все отлично работает для небольших размеров матрицы до N = 180, если я превышаю этот размер, например N = 184 MPI выбрасывает некоторые ошибки при использовании MPI_Scatterv
,
Для 2D Scatter я использовал несколько конструкций с MPI_Type_create_subarray
а также MPI_TYPE_create_resized
, Объяснения этих конструкций можно найти в этот вопрос.
Минимальный пример кода, который я написал, заполняет матрицу A с некоторыми значениями, рассеивает ее в локальных процессах и записывает порядковый номер каждого процесса в локальную копию рассеянного A. После этого локальные копии будут собираться в главный процесс.
#include "mpi.h"
#define N 184 // grid size
#define procN 2 // size of process grid
int main(int argc, char **argv) {
double* gA = nullptr; // pointer to array
int rank, size; // rank of current process and no. of processes
// mpi initialization
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// force to use correct number of processes
if (size != procN * procN) {
if (rank == 0) fprintf(stderr,"%s: Only works with np = %d.\n", argv[0], procN * procN);
MPI_Abort(MPI_COMM_WORLD,1);
}
// allocate and print global A at master process
if (rank == 0) {
gA = new double[N * N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
gA[j * N + i] = j * N + i;
}
}
printf("A is:\n");
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%f ", gA[j * N + i]);
}
printf("\n");
}
}
// create local A on every process which we'll process
double* lA = new double[N / procN * N / procN];
// create a datatype to describe the subarrays of the gA array
int sizes[2] = {N, N}; // gA size
int subsizes[2] = {N / procN, N / procN}; // lA size
int starts[2] = {0,0}; // where this one starts
MPI_Datatype type, subarrtype;
MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, MPI_DOUBLE, &type);
MPI_Type_create_resized(type, 0, N / procN * sizeof(double), &subarrtype);
MPI_Type_commit(&subarrtype);
// compute number of send blocks
// compute distance between the send blocks
int sendcounts[procN * procN];
int displs[procN * procN];
if (rank == 0) {
for (int i = 0; i < procN * procN; i++) {
sendcounts[i] = 1;
}
int disp = 0;
for (int i = 0; i < procN; i++) {
for (int j = 0; j < procN; j++) {
displs[i * procN + j] = disp;
disp += 1;
}
disp += ((N / procN) - 1) * procN;
}
}
// scatter global A to all processes
MPI_Scatterv(gA, sendcounts, displs, subarrtype, lA,
N*N/(procN*procN), MPI_DOUBLE,
0, MPI_COMM_WORLD);
// print local A's on every process
for (int p = 0; p < size; p++) {
if (rank == p) {
printf("la on rank %d:\n", rank);
for (int i = 0; i < N / procN; i++) {
for (int j = 0; j < N / procN; j++) {
printf("%f ", lA[j * N / procN + i]);
}
printf("\n");
}
}
MPI_Barrier(MPI_COMM_WORLD);
}
MPI_Barrier(MPI_COMM_WORLD);
// write new values in local A's
for (int i = 0; i < N / procN; i++) {
for (int j = 0; j < N / procN; j++) {
lA[j * N / procN + i] = rank;
}
}
// gather all back to master process
MPI_Gatherv(lA, N*N/(procN*procN), MPI_DOUBLE,
gA, sendcounts, displs, subarrtype,
0, MPI_COMM_WORLD);
// print processed global A of process 0
if (rank == 0) {
printf("Processed gA is:\n");
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%f ", gA[j * N + i]);
}
printf("\n");
}
}
MPI_Type_free(&subarrtype);
if (rank == 0) {
delete gA;
}
delete lA;
MPI_Finalize();
return 0;
}
Его можно скомпилировать и запустить с помощью
mpicxx -std=c++11 -o test test.cpp
mpirun -np 4 ./test
Для малых N = 4, …, 180 все идет хорошо
A is:
0.000000 6.000000 12.000000 18.000000 24.000000 30.000000
1.000000 7.000000 13.000000 19.000000 25.000000 31.000000
2.000000 8.000000 14.000000 20.000000 26.000000 32.000000
3.000000 9.000000 15.000000 21.000000 27.000000 33.000000
4.000000 10.000000 16.000000 22.000000 28.000000 34.000000
5.000000 11.000000 17.000000 23.000000 29.000000 35.000000
la on rank 0:
0.000000 6.000000 12.000000
1.000000 7.000000 13.000000
2.000000 8.000000 14.000000
la on rank 1:
3.000000 9.000000 15.000000
4.000000 10.000000 16.000000
5.000000 11.000000 17.000000
la on rank 2:
18.000000 24.000000 30.000000
19.000000 25.000000 31.000000
20.000000 26.000000 32.000000
la on rank 3:
21.000000 27.000000 33.000000
22.000000 28.000000 34.000000
23.000000 29.000000 35.000000
Processed gA is:
0.000000 0.000000 0.000000 2.000000 2.000000 2.000000
0.000000 0.000000 0.000000 2.000000 2.000000 2.000000
0.000000 0.000000 0.000000 2.000000 2.000000 2.000000
1.000000 1.000000 1.000000 3.000000 3.000000 3.000000
1.000000 1.000000 1.000000 3.000000 3.000000 3.000000
1.000000 1.000000 1.000000 3.000000 3.000000 3.000000
Здесь вы видите ошибки, когда я использую N = 184:
Fatal error in PMPI_Scatterv: Other MPI error, error stack:
PMPI_Scatterv(655)..............: MPI_Scatterv(sbuf=(nil), scnts=0x7ffee066bad0, displs=0x7ffee066bae0, dtype=USER<resized>, rbuf=0xe9e590, rcount=8464, MPI_DOUBLE, root=0, MPI_COMM_WORLD) failed
MPIR_Scatterv_impl(205).........: fail failed
I_MPIR_Scatterv_intra(265)......: Failure during collective
I_MPIR_Scatterv_intra(259)......: fail failed
MPIR_Scatterv(141)..............: fail failed
MPIC_Recv(418)..................: fail failed
MPIC_Wait(269)..................: fail failed
PMPIDI_CH3I_Progress(623).......: fail failed
pkt_RTS_handler(317)............: fail failed
do_cts(662).....................: fail failed
MPID_nem_lmt_dcp_start_recv(288): fail failed
dcp_recv(154)...................: Internal MPI error! cannot read from remote process
Fatal error in PMPI_Scatterv: Other MPI error, error stack:
PMPI_Scatterv(655)..............: MPI_Scatterv(sbuf=(nil), scnts=0x7ffef0de9b50, displs=0x7ffef0de9b60, dtype=USER<resized>, rbuf=0x21a7610, rcount=8464, MPI_DOUBLE, root=0, MPI_COMM_WORLD) failed
MPIR_Scatterv_impl(205).........: fail failed
I_MPIR_Scatterv_intra(265)......: Failure during collective
I_MPIR_Scatterv_intra(259)......: fail failed
MPIR_Scatterv(141)..............: fail failed
MPIC_Recv(418)..................: fail failed
MPIC_Wait(269)..................: fail failed
PMPIDI_CH3I_Progress(623).......: fail failed
pkt_RTS_handler(317)............: fail failed
do_cts(662).....................: fail failed
MPID_nem_lmt_dcp_start_recv(288): fail failed
dcp_recv(154)...................: Internal MPI error! cannot read from remote process
Я думаю, что что-то пошло не так, используя подмассивы, но почему это работает для N = 4, …, 180? Другая возможность состоит в том, что данные моего массива не являются линейными для больших данных, и поэтому разброс больше не работает. Могут ли возникнуть проблемы с размером кэша? Я не могу поверить, что MPI не может рассеивать 2D-массивы N> 180 …
Я надеюсь, что кто-то из вас сможет мне помочь. Большое спасибо!
Во-первых, ваш код не работает для маленьких N. Если я установлю N = 6 и инициализирую матрицу так, чтобы все записи были уникальными, т.е.
gA[j * N + i] = j*N+i;
тогда вы можете увидеть, что есть ошибка:
mpiexec -n 4 ./gathervorig
A is:
0.000000 6.000000 12.000000 18.000000 24.000000 30.000000
1.000000 7.000000 13.000000 19.000000 25.000000 31.000000
2.000000 8.000000 14.000000 20.000000 26.000000 32.000000
3.000000 9.000000 15.000000 21.000000 27.000000 33.000000
4.000000 10.000000 16.000000 22.000000 28.000000 34.000000
5.000000 11.000000 17.000000 23.000000 29.000000 35.000000
la on rank 0:
0.000000 2.000000 7.000000
1.000000 6.000000 8.000000
2.000000 7.000000 12.000000
la on rank 1:
3.000000 5.000000 10.000000
4.000000 9.000000 11.000000
5.000000 10.000000 15.000000
la on rank 2:
18.000000 20.000000 25.000000
19.000000 24.000000 26.000000
20.000000 25.000000 30.000000
la on rank 3:
21.000000 23.000000 28.000000
22.000000 27.000000 29.000000
23.000000 28.000000 33.000000
Ошибка здесь не в коде, а в печати:
printf("%f ", lA[j * procN + i]);
должно быть
printf("%f ", lA[j * N/procN + i]);
Теперь это дает правильный ответ для разброса, по крайней мере:
mpiexec -n 4 ./gathervorig
A is:
0.000000 6.000000 12.000000 18.000000 24.000000 30.000000
1.000000 7.000000 13.000000 19.000000 25.000000 31.000000
2.000000 8.000000 14.000000 20.000000 26.000000 32.000000
3.000000 9.000000 15.000000 21.000000 27.000000 33.000000
4.000000 10.000000 16.000000 22.000000 28.000000 34.000000
5.000000 11.000000 17.000000 23.000000 29.000000 35.000000
la on rank 0:
0.000000 6.000000 12.000000
1.000000 7.000000 13.000000
2.000000 8.000000 14.000000
la on rank 1:
3.000000 9.000000 15.000000
4.000000 10.000000 16.000000
5.000000 11.000000 17.000000
la on rank 2:
18.000000 24.000000 30.000000
19.000000 25.000000 31.000000
20.000000 26.000000 32.000000
la on rank 3:
21.000000 27.000000 33.000000
22.000000 28.000000 34.000000
23.000000 29.000000 35.000000
Сбор не выполняется по той же причине — локальная инициализация:
lA[j * procN + i] = rank;
должно быть
lA[j * N/procN + i] = rank;
После этого изменения также работает сбор:
Processed gA is:
0.000000 0.000000 0.000000 2.000000 2.000000 2.000000
0.000000 0.000000 0.000000 2.000000 2.000000 2.000000
0.000000 0.000000 0.000000 2.000000 2.000000 2.000000
1.000000 1.000000 1.000000 3.000000 3.000000 3.000000
1.000000 1.000000 1.000000 3.000000 3.000000 3.000000
1.000000 1.000000 1.000000 3.000000 3.000000 3.000000
Я думаю, что урок здесь всегда использовать уникальный тестовые данные — инициализация в i * j затрудняет обнаружение начальной ошибки даже в небольших системах.
На самом деле реальная проблема заключалась в том, что вы установили N = 4, так что procN = N / procN = 2. Я всегда стараюсь использовать размеры, которые приводят к нечетным / необычным числам, например. N = 6 дает N / procN = 3, поэтому нет путаницы с procN = 2.
Других решений пока нет …