- •ВВЕДЕНИЕ
- •1. ПРЕДПОСЫЛКИ ВОЗНИКНОВЕНИЯ ЯЗЫКА OPENCL
- •2. ДИЗАЙН OPENCL
- •2.1. Модель платформы
- •2.2. Модель вычислений
- •2.3. Модель памяти
- •2.4. Модель программирования
- •3. АППАРАТНЫЕ СРЕДСТВА, ПОДДЕРЖИВАЮЩИЕ ПАРАЛЛЕЛЬНЫЕ ВЫЧИСЛЕНИЯ
- •4. ЛАБОРАТОРНЫЕ РАБОТЫ
- •4.1. Поток проектирования при работе с языком OpenCL
- •4.1.1. Задание
- •4.1.2. Программное и аппаратное обеспечение
- •4.1.3. Последовательность выполнения работы
- •4.1.4. Заключение по практическому эксперименту
- •4.1.5. Содержание отчета
- •4.2. Создание аппаратно-программной системы с ОС Linux. Подключение к Ethernet. Работа с Web-сервером
- •4.2.1. Задание
- •4.2.2. Последовательность выполнения работы
- •4.2.3. Содержание отчета
- •4.3. Оптимизация умножения матриц в OpenCL
- •4.3.1. Базовый алгоритм умножения матриц
- •4.3.2. Использование локальной памяти
- •4.3.3. Увеличение числа одновременно исполняемых рабочих элементов
- •4.3.4. Содержание отчета
- •СПИСОК ЛИТЕРАТУРЫ
- •ИНТЕРНЕТ-РЕСУРСЫ
- •ПРИЛОЖЕНИЯ
- •П1. Код хост-программы для сложения двух векторов
- •П2. Исходный код хост-программы для умножения двух матриц
П2. Исходный код хост-программы для умножения двух матриц
#include <stdio.h> #include <stdlib.h> #include <math.h> #include "CL/opencl.h" #include "AOCL_Utils.h"
using namespace aocl_utils;
// Конфигурация среды исполнения OpenCL cl_platform_id platform = NULL;
unsigned num_devices = 0; // Число устройств scoped_array<cl_device_id> device; // num_devices элементов cl_context context = NULL; // Контекст исполнения scoped_array<cl_command_queue> queue;// num_devices элементов cl_program program = NULL; // Программа устройства scoped_array<cl_kernel> kernel; // num_devices элементов scoped_array<cl_mem> input_a_buf; // num_devices элементов scoped_array<cl_mem> input_b_buf; // num_devices элементов scoped_array<cl_mem> output_buf; // num_devices элементов long BLOCK_SIZE = 16; // Размер блока
long SIZE = 64; // Множитель размера матриц
//Размеры матриц unsigned A_height; unsigned A_width;
const unsigned &B_height = A_width; unsigned B_width;
const unsigned &C_height = A_height; const unsigned &C_width = B_width;
//Входные и выходные массивы
scoped_array<scoped_aligned_ptr<int> > input_a; // num_devices элементов
scoped_aligned_ptr<int> input_b; scoped_array<scoped_aligned_ptr<int> > output; // num_devices
элементов
scoped_array<int> ref_output;
scoped_array<unsigned> rows_per_device; // num_devices
элементов // Прототипы функций
int rand_int(); bool init_opencl(); void init_problem(); void run();
void compute_reference(); void verify();
void cleanup();
int main(int argc, char **argv) { if (argc > 1)
BLOCK_SIZE = strtol(argv[1], NULL, 0); if (argc > 2)
SIZE = strtol(argv[2], NULL, 0);
38
A_height = SIZE * BLOCK_SIZE;
A_width = SIZE * BLOCK_SIZE;
B_width = SIZE * BLOCK_SIZE;
printf("Matrix sizes:\n A: %d x %d\n B: %d x %d\n C: %d x %d\n",A_height, A_width, B_height, B_width, C_height, C_width);
//Инициализировать OpenCL if(!init_opencl()) { return -1;
}
//Инициализировать данные задачи
//На этом этапе необходимо знать число доступных устройств init_problem();
run();//Запустить ядро
cleanup();// Освободить выделенные ресурсы return 0;
}
/////// ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ///////
//Генерация целого числа в интервале от -10 до 10 int rand_int() {
return (rand() % 20) - 10;
}
//Инициализация OpenCL
bool init_opencl() { cl_int status;
printf("Initializing OpenCL\n"); if(!setCwdToExeDir()) {
return false;
}
// Найти платформу OpenCL platform = findPlatform("Altera"); if(platform == NULL) {
printf("ERROR: Unable to find Intel(R) FPGA OpenCL platform.\n");
return false;
}
// Запросить доступные устройства OpenCL
device.reset(getDevices(platform, CL_DEVICE_TYPE_ALL, &num_devices));
printf("Platform: %s\n", getPlatformName(platform).c_str()); printf("Using %d device(s)\n", num_devices);
for(unsigned i = 0; i < num_devices; ++i) {
printf(" %s\n", getDeviceName(device[i]).c_str());
}
context = clCreateContext(NULL, num_devices, device, NULL, NULL, &status);
checkError(status, "Failed to create context"); // Создать программу исполнения устройства
std::string binary_file = getBoardBinaryFile("mmul", device[0]);
printf("Using AOCX: %s\n", binary_file.c_str());
39
program = createProgramFromBinary(context, binary_file.c_str(), device, num_devices);
// Построить программу
status = clBuildProgram(program, 0, NULL, "", NULL, NULL); сheckError(status, "Failed to build program");
//Создать объекты OpenCL для каждого устройства queue.reset(num_devices); kernel.reset(num_devices); rows_per_device.reset(num_devices); input_a_buf.reset(num_devices); input_b_buf.reset(num_devices); output_buf.reset(num_devices);
//Число блоков строк для обработки
const unsigned num_block_rows = C_height / BLOCK_SIZE;
//Цикл по всем доступным устройствам OpenCL for(unsigned i = 0; i < num_devices; ++i) {
//Очередь команд
queue[i] = clCreateCommandQueue(context, device[i], CL_QUEUE_PROFILING_ENABLE, &status);
checkError(status, "Failed to create command queue"); const char *kernel_name = "mmul";//Ядро
kernel[i] = clCreateKernel(program, kernel_name, &status); checkError(status, "Failed to create kernel");
//Определить число строк, обрабатываемых устройством
//Сначала в блоках строк
rows_per_device[i] = num_block_rows / num_devices;
//Распределить блоки строк по устройствам if(i < (num_block_rows % num_devices)) { rows_per_device[i]++;
}
//Умножить на размер блока для получения числа строк rows_per_device[i] *= BLOCK_SIZE;
//Входные буферы
//Каждому устройству нужны только строки матрицы А,
//соответствующие строкам выходной матрицы
input_a_buf[i] = clCreateBuffer(context, CL_MEM_READ_ONLY, rows_per_device[i] * A_width * sizeof(int), NULL, &status); checkError(status, "Failed to create buffer for input A");
// Каждому устройству потребуется матрица В целиком input_b_buf[i] = clCreateBuffer(context, CL_MEM_READ_ONLY,
B_height * B_width * sizeof(int), NULL, &status); checkError(status, "Failed to create buffer for input B");
//Выходной буфер (только те строки матрицы С,
//которые будут вычисляться на данном устойстве)
output_buf[i] = clCreateBuffer(context, CL_MEM_WRITE_ONLY, rows_per_device[i] * C_width * sizeof(int), NULL, &status); checkError(status, "Failed to create buffer for output");
}
return true;
}
// Инициализация данных задачи
40
//Число устройств должно быть известным void init_problem() {
if(num_devices == 0) { checkError(-1, "No devices");
}
//Сгенерировать входные матрицы А и В
//Матрица А делится на все устройства
//Матрица В используется целиком каждым устройством,
//поэтому ее делить не надо
printf("Generating input matrices\n"); input_a.reset(num_devices); output.reset(num_devices);
for(unsigned i = 0; i < num_devices; ++i) { input_a[i].reset(rows_per_device[i] * A_width); output[i].reset(rows_per_device[i] * C_width); for(unsigned j = 0; j < rows_per_device[i] * A_width;++j)
{
input_a[i][j] = rand_int();
}
}
input_b.reset(B_height * B_width);
for(unsigned i = 0; i < B_height * B_width; ++i) { input_b[i] = rand_int();
}
}
void run() {// Запуск ядра cl_int status;
// Передать входные данные на устройства for(unsigned i = 0; i < num_devices; ++i) {
status = clEnqueueWriteBuffer(queue[i], input_a_buf[i], CL_FALSE,0, rows_per_device[i] * A_width * sizeof(int), input_a[i], 0, NULL, NULL);
checkError(status, "Failed to transfer input A"); status = clEnqueueWriteBuffer(queue[i], input_b_buf[i],
CL_FALSE,0, B_width * B_height * sizeof(int), input_b, 0, NULL, NULL);
checkError(status, "Failed to transfer input B");
}
//Подождать окончания передачи for(unsigned i = 0; i < num_devices; ++i) {
clFinish(queue[i]);
}
//Подготовка к запуску ядер
scoped_array<cl_event> kernel_event(num_devices); const double start_time = getCurrentTimestamp();
// Цикл по всем устройствам
for(unsigned i = 0; i < num_devices; ++i) {
// Установить аргументы ядра unsigned argi = 0;
status = clSetKernelArg(kernel[i], argi++, sizeof(cl_mem), &output_buf[i]);
41
checkError(status, "Failed to set argument %d", argi - 1); status = clSetKernelArg(kernel[i], argi++, sizeof(cl_mem),
&input_a_buf[i]);
checkError(status, "Failed to set argument %d", argi - 1); status = clSetKernelArg(kernel[i], argi++, sizeof(cl_mem),
&input_b_buf[i]);
checkError(status, "Failed to set argument %d", argi - 1); status = clSetKernelArg(kernel[i], argi++, sizeof(A_height),
&A_height);
checkError(status, "Failed to set argument %d", argi - 1); status = clSetKernelArg(kernel[i], argi++, sizeof(B_width),
&B_width);
checkError(status, "Failed to set argument %d", argi - 1); status = clSetKernelArg(kernel[i], argi++, sizeof(A_width),
&A_width);
checkError(status, "Failed to set argument %d", argi - 1);
//Поставить ядро в очередь на исполнение
//Глобальный размер работы равен размеру выходной матрицы
//Локальный размер работы - один блок,
//т. е. BLOCK_SIZE * BLOCK_SIZE
//Чтобы удостовериться, что ядра не начнут
//исполняться до того, как закончится запись во входные
//буферы, используется механизм событий
const size_t global_work_size[2] = {C_width, rows_per_device[i]};
const size_t local_work_size[2] = {BLOCK_SIZE, BLOCK_SIZE}; printf("Launching for device %d (global size: %zd, %zd)\n",
i, global_work_size[0], global_work_size[1]);
status = clEnqueueNDRangeKernel(queue[i], kernel[i], 2, NULL,global_work_size, local_work_size, 0, NULL, &kernel_event[i]);
checkError(status, "Failed to launch kernel");
}
//Дождаться окончания исполнения всех ядер clWaitForEvents(num_devices, kernel_event); const double end_time = getCurrentTimestamp(); const double total_time = end_time - start_time;
//Вывести общее время исполнения всех ядер printf("\nTime: %0.3f ms\n", total_time * 1e3);
//Получить время исполнения ядра с помощью
//профилировочного API OpenCL for(unsigned i = 0; i < num_devices; ++i) {
cl_ulong time_ns = getStartEndTime(kernel_event[i]); printf("Kernel time (device %d): %0.3f ms\n", i, dou-
ble(time_ns) * 1e-6);
}
//Вычислить производительность (в GFLOPS)
//Всего вычислено C_width × C_height значений,
//каждое из которых получено путем умножений и сложений const float flops = (float)(2.0f * C_width * C_height *
A_width / total_time);
42
printf("\nThroughput: %0.2f GFLOPS\n\n", flops * 1e-9); for(unsigned i = 0; i < num_devices; ++i) {
clReleaseEvent(kernel_event[i]);
}
// Прочитать результат
for(unsigned i = 0; i < num_devices; ++i) {
status = clEnqueueReadBuffer(queue[i], output_buf[i], CL_TRUE,0, rows_per_device[i] * C_width * sizeof(int), output[i], 0, NULL, NULL);
checkError(status, "Failed to read output matrix");
}
// Проверить результат compute_reference(); verify();
}
// Вычисление референсного результата void compute_reference() { printf("Computing reference output\n"); ref_output.reset(C_height * C_width);
for(unsigned y = 0, dev_index = 0; y < C_height; ++dev_index)
{
for(unsigned yy = 0; yy < rows_per_device[dev_index];++yy,++y)
{
for(unsigned x = 0; x < C_width; ++x) { int sum = 0;
for(unsigned k = 0; k < A_width; ++k) {
sum += input_a[dev_index][yy * A_width + k] * input_b[k * B_width + x];
}
ref_output[y * C_width + x] = sum;
}
}
}
}
// Сравнение референсного результата с полученным void verify() {
printf("Verifying\n"); bool pass = true;
for(unsigned i = 0; i < num_devices && pass; ++i) for(unsigned j = 0; j < rows_per_device[i] && pass; ++j)
for (unsigned k = 0; k < C_width; ++k) {
if (output[i][j*C_width + k] != ref_output[(i*num_devices+j)*C_width + k]) {
pass = false;
printf("Failed verification @ device %d, row %d, index %d, output = %d, ref = %d\n", i, j, k, output[i][j*C_width + k], ref_output[(i*num_devices+j)*C_width + k]);
break;
}
}
printf("Verification: %s\n", pass ? "PASS" : "FAIL");
43
}
void cleanup() {
for(unsigned i = 0; i < num_devices; ++i) { if(kernel && kernel[i]) {
clReleaseKernel(kernel[i]);
}
if(queue && queue[i]) { clReleaseCommandQueue(queue[i]);
}
if(input_a_buf && input_a_buf[i]) { clReleaseMemObject(input_a_buf[i]);
}
if(input_b_buf && input_b_buf[i]) { clReleaseMemObject(input_b_buf[i]);
}
if(output_buf && output_buf[i]) { clReleaseMemObject(output_buf[i]);
}
}
if(program) { clReleaseProgram(program);
}
if(context) { clReleaseContext(context);
}
}
44
|
СОДЕРЖАНИЕ |
|
ВВЕДЕНИЕ.............................................................................................................. |
3 |
|
1. ПРЕДПОСЫЛКИ ВОЗНИКНОВЕНИЯ ЯЗЫКА OPENCL.......................... |
4 |
|
2. ДИЗАЙН OPENCL............................................................................................ |
5 |
|
2.1. |
Модель платформы....................................................................................... |
6 |
2.2. |
Модель вычислений ..................................................................................... |
6 |
2.3. |
Модель памяти.............................................................................................. |
7 |
2.4. |
Модель программирования.......................................................................... |
8 |
3. АППАРАТНЫЕ СРЕДСТВА, ПОДДЕРЖИВАЮЩИЕ |
|
|
ПАРАЛЛЕЛЬНЫЕ ВЫЧИСЛЕНИЯ..................................................................... |
9 |
|
4. ЛАБОРАТОРНЫЕ РАБОТЫ......................................................................... |
11 |
|
4.1. Поток проектирования при работе с языком OpenCL............................ |
11 |
|
4.1.1.Задание..................................................................................................... |
11 |
|
4.1.2.Программное и аппаратное обеспечение............................................. |
11 |
|
4.1.3.Последовательность выполнения работы............................................ |
12 |
|
4.1.4.Заключение по практическому эксперименту..................................... |
18 |
|
4.1.5.Содержание отчета................................................................................. |
19 |
|
4.2. Создание аппаратно-программной системы с ОС Linux. |
|
|
Подключение к Ethernet. Работа с Web-сервером............................................ |
19 |
|
4.2.1.Задание..................................................................................................... |
20 |
|
4.2.2.Последовательность выполнения работы............................................ |
20 |
|
4.2.3.Содержание отчета................................................................................. |
25 |
|
4.3. Оптимизация умножения матриц в OpenCL............................................ |
25 |
|
4.3.1.Базовый алгоритм умножения матриц................................................. |
25 |
|
4.3.2.Использование локальной памяти........................................................ |
27 |
|
4.3.3.Увеличение числа одновременно исполняемых рабочих элементов30 |
||
4.3.4.Содержание отчета................................................................................. |
31 |
|
СПИСОК ЛИТЕРАТУРЫ..................................................................................... |
32 |
|
ИНТЕРНЕТ-РЕСУРСЫ ........................................................................................ |
32 |
|
ПРИЛОЖЕНИЯ..................................................................................................... |
33 |
|
П1. Код хост-программы для сложения двух векторов................................... |
33 |
|
П2. Исходный код хост-программы для умножения двух матриц................. |
38 |
45
Грушвицкий Ростислав Игоревич, Кондорский Ярослав Александрович, Перевертайло Ульяна Викторовна, Шарагина Наталья Сергеевна
Язык OpenCL. Практическое знакомство
Учебно-методическое пособие
Редактор Е. А. Ушакова
__________________________________________________________________
Подписано в печать 22.11.19. Формат 60×84 1/16. Бумага офсетная. Печать цифровая. Печ. л. 3,0.
Гарнитура «Times New Roman». Тираж 46 экз. Заказ 157.
__________________________________________________________________
Издательство СПбГЭТУ «ЛЭТИ» 197376, С.-Петербург, ул. Проф. Попова, 5