Проект «Вычислительный Кластер»
Содержание
Имеющееся оборудование
Схема работы кластера
Как происходит распараллеливание задачи
Формат приложения для запуска на кластере
Скрипт-загрузчик
Пример приложения «Сложение матриц»
Имеющееся оборудование
Количество узлов сети: 12 + main
Конфигурация узлов
OS: Ubuntu 10.04, Windows 7
Системная плата Asus M4A79XTD Evo
CPU: AMD Phenom II X4, 2800МГц, 4 ядра
29.9241 GFlops
RAM: DDR3 4 ГБ
Чтение из памяти 8116 Мб/с
Запись в память 6676 Мб/с
GPU: nVidia GT 240
467 Mbytes GDDR3
CUDA Capability: 1.2
(12) Multiprocessors x ( 8) CUDA Cores/MP: 96 CUDA Cores
CUDA 4.0
OpenMPI
Соединение между узлами: Ethernet 100 Мбит
Switch Allied Telesis AT-8000S
Дополнительные сведения по каждому узлу:
Host to Device Bandwidth 2373.8 MB/s
Device to Host Bandwidth 2000.6 MB/s
Device to Device Bandwidth 26709.6 MB/s
Настроена политика привилегий для пользователей:
stud имеет пароль stud
Настроена на машинах работа с NFS - доступна общая папка для хранения данных, которая автоматически монтируется при загрузке узлов. Физически папка находится на главной машине.
От главной машины настроены двусторонние беспарольные ssh-туннели к каждому узлу
Схема работы кластера
Используется топология «Звездочка»:
Суть схемы заключается в том, что явно существует головная машина, которая выполняет роль маршрутизатора для остальных машин кластера.
Как происходит распараллеливание задачи
Подразумевается, что исходную задачу можно единожды разбить на независимые в процессе вычисления части, каждая из которых может быть отправлена на свой узел для вычисления.
Если задача не может быть единожды разбита на независимые части (например, требуется последовательное применение параллельной обработки), то разбиение происходит на несколько частей, каждая из которых отдельно запускается на вычислительном кластере.
Если задача не может быть разбита на независимые части, то использование вычислительного кластера будет неэффективным. В этом случае понадобится разбить задачу на несколько этапов с промежуточными результатами.
Входные данные разбиваются на несколько частей (не превышающих по количеству число вычислительных узлов). Приложение состоит из двух программ: первая программа предназначена для локального запуска на каждом вычислительном узле на части входных данных; а вторая программа предназначена для запуска на главном узле после обработки данных – ей на вход подаются части-результаты от каждого узла, а она их собирает в единый ответ.
Формат приложения для запуска на кластере
Входной тест. Представляет собой набор файлов, названных номерами кластеров. Количество файлов должно быть не больше чем количество вычислительных узлов. Содержимое каждого такого файла является входными локальными данными для соответствующего вычислительного узла.
Программа обработки. Программа, которая рассылается на каждый узел и запускается на соответствующих входных данных, давая некоторый результат вычислений. Для возможной гибкости системы, перед входными данными на вход подается номер узла, на котором происходит вычисление.
Программа обработки результатов. Программа, которая принимает на вход последовательность текстовых данных:
Номер кластерного узла (нумерация начинается с нуля)
Данные-результат вычисления на соответствующем кластерном узле
Конец локальных данных: три пустых строки подряд
Особенность окончания локального блока данных накладывает ограничение на присутствие в результатах трех пустых строк.
Результатом работы программы является конечный результат вычислений.
Скрипт-загрузчик
CC = mpicc
GPUCC = nvcc
CFLAGS = -std=c99 -Wall -c
CXXFLAGS = $(CFLAGS) -lstdc++
LD = mpicc
LDFLAGS = -L/usr/local/cuda/lib64 -lcuda -lcudart
INCLUDE = -I/usr/local/cuda/include
target: prog.o main.o
$(CC) $(LDFLAGS) prog.o main.o
prog.o: prog.cu
$(GPUCC) -c *.cu
main.o: main.c
$(CC) $(CFLAGS) $(INCLUDE) *.c
.o.cc:
$(CC) $(CXXFLAGS) $(INCLUDE) *.cc
.o.cpp:
$(CC) $(CXXFLAGS) $(INCLUDE) *.cpp
clean:
rm -f a.out *.o
Пример приложения «Сложение матриц»
.c файл
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cuda.h"
#include "mpi.h"
#include "cuda_runtime_api.h"
int main(int argc, char **argv)
{
MPI_Status stat;
MPI_Init(&argc, &argv); /*START MPI */
//Инициализирует MPI - пишите первой строчкой мейна
int my_rank;
int size,t,i;
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*DETERMINE RANK OF THIS PROCESSOR*/
/*
Записывает в my_rank номер(id) текущего процесса, для "начального" процесса будет 0, из него будем рассылать данные для всех других
*/
MPI_Comm_size(MPI_COMM_WORLD, &size); /*DETERMINE TOTAL NUMBER OF PROCESSORS*/
/*
Записывает в size количество доступных узлов
*/
const int sz = 100;
int video_sz = sz/size;
float *a;
float *b;
float *c;
if (my_rank == 0)//Если процесс является начальным (главным), то он рассылает данные
{
a = malloc(sizeof(float)*sz);
b = malloc(sizeof(float)*sz);
c = malloc(sizeof(float)*sz);
memset(a, 0, sizeof(float)*sz);
memset(b, 0, sizeof(float)*sz);
memset(c, 0, sizeof(float)*sz);
a[0] = 0.1;
b[0] = 0.1;
for (i = 0; i < sz; i++)
{
a[i+1] = a[i]+0.1;
}
for (i = 0; i < sz; i++)
{
b[i+1] = b[i]+0.1;
}
for(i = 1; i<size;++i)
{
/*
Функция передачи сообщения MPI_Send
• MPI_SEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERROR)
• <type> BUF(*)
• INTEGER COUNT, DATATYPE, DEST, TAG, COMM, IERROR
IN buf
- адрес начала расположения пересылаемых данных;
IN count - число пересылаемых элементов;
IN datatype - тип посылаемых элементов;
IN dest
IN tag
- номер процесса-получателя в группе, связанной с коммуникатором comm;
- идентификатор сообщения (аналог типа сообщения функций nread и nwrite PSE
nCUBE2);
IN comm - коммуникатор области связи.
Функция выполняет посылку count элементов типа datatype сообщения с идентификатором
tag процессу dest в области связи коммуникатора comm. Переменная buf - это,
*/
MPI_Send(a+i*video_sz, video_sz*sizeof(float), MPI_BYTE, i, 123, MPI_COMM_WORLD);
MPI_Send(b+i*video_sz, video_sz*sizeof(float), MPI_BYTE, i, 123, MPI_COMM_WORLD);
}
//MPI_Send(a, video_sz*sizeof(float), MPI_BYTE, 1, 123, MPI_COMM_WORLD);
//MPI_Send(b, video_sz*sizeof(float), MPI_BYTE, 1, 123, MPI_COMM_WORLD);
}
else//не главный процесс, принимает данные посланные главным
{
a = malloc(sizeof(float)*video_sz);
b = malloc(sizeof(float)*video_sz);
c = malloc(sizeof(float)*video_sz);
memset(a, 0, sizeof(float)*video_sz);
memset(b, 0, sizeof(float)*video_sz);
memset(c, 0, sizeof(float)*video_sz);
/*
Функция приема сообщения MPI_Recv
MPI_RECV(BUF, COUNT, DATATYPE, SOURCE, TAG, COMM,
STATUS, IERROR)
<type> BUF(*)
INTEGER COUNT, DATATYPE, SOURCE, TAG, COMM,
STATUS(MPI_STATUS_SIZE), IERROR
OUT buf
- адрес начала расположения принимаемого сообщения;
IN count - максимальное число принимаемых элементов;
IN datatype - тип элементов принимаемого сообщения;
IN source - номер процесса-отправителя;
IN tag
- идентификатор сообщения;
IN comm - коммуникатор области связи;
OUT status - атрибуты принятого сообщения.
Функция выполняет прием count элементов типа datatype сообщения с идентификатором tag от процесса source в области связи коммуникатора comm.
*/
MPI_Recv(a, video_sz*sizeof(float) ,MPI_BYTE, 0, 123, MPI_COMM_WORLD, &stat);
MPI_Recv(b, video_sz*sizeof(float) ,MPI_BYTE, 0, 123, MPI_COMM_WORLD, &stat);
}
float *ad,*bd,*cd;
cudaMalloc((void**) &ad, video_sz*sizeof(float));
cudaMalloc((void**) &bd, video_sz*sizeof(float));
cudaMalloc((void**) &cd, video_sz*sizeof(float));
cudaMemcpy(ad,a,video_sz*sizeof(float),cudaMemcpyHostToDevice);
cudaMemcpy(bd,b,video_sz*sizeof(float),cudaMemcpyHostToDevice);
runcuda(ad,bd,cd,video_sz);
cudaMemcpy(c,cd,video_sz*sizeof(float),cudaMemcpyDeviceToHost);
if(my_rank!=0)
{
MPI_Send(c, video_sz*sizeof(float), MPI_BYTE, 0, 123, MPI_COMM_WORLD);//отсылаем результаты вычислений
}
else
{
for(i = 1; i<size;++i)
{
MPI_Recv(c+i*video_sz, video_sz*sizeof(float), MPI_BYTE, i, 123, MPI_COMM_WORLD, &stat);//принимаем результаты вычислений с других узлов
}
for (i = 0; i < sz; i++)
printf("%f+%f=%f\n",a[i],b[i],c[i]);
}
MPI_Finalize();
//Завершение работы с MPI - пишите перед каждой точкой выхода из программы
return 0;
}
.cu файл
#include <stdio.h>
#include <stdlib.h>
#include "cuda.h"
__global__ void vectorSum(float *a, float *b, float *c)
{
int index = blockIdx.x*blockDim.x + threadIdx.x;
c[index] = a[index] + b[index];
}
extern "C"
{
__host__ void runcuda(float *_in1, float *_in2, float *_out, int size)
{
int TreadPerBlock = size;
int BlocksPerGrid = 1;
vectorSum <<<BlocksPerGrid, TreadPerBlock>>>(_in1,_in2,_out);
}
}