vector addition in CUDA using streams - vector

I m a beginner in CUDA parallel programming. I tried a program for vector addition using CUDA streams. When i compile, i get the following error.
Solution is not correct.
The solution did not match the expected results at row 0. Expecting (1+0.5=1.5) but got 0.
I checked with cuda by examples book and similar questions online. Couldn't find a solution. Can anyone help me solve this error? Thanks in advance.
#include <wb.h>
#define wbCheck(stmt) do { \
cudaError_t err = stmt; \
if (err != cudaSuccess) { \
wbLog(ERROR, "Failed to run stmt ", #stmt); \
wbLog(ERROR, "Got CUDA error ... ", cudaGetErrorString(err)); \
return -1; \
} \
} while(0)
__global__ void vecAdd(float * in1, float * in2, float * out, int len)
{
int i = threadIdx.x + blockDim.x * blockIdx.x;
if (i < len)
out[i] = in1[i] + in2[i];
}
int main(int argc, char ** argv)
{
cudaStream_t stream0, stream1,stream2,stream3;
cudaStreamCreate(&stream0);
cudaStreamCreate(&stream1);
cudaStreamCreate(&stream2);
cudaStreamCreate(&stream3);
wbArg_t args;
int inputLength;
float *h_A, *h_B, *h_C;
float *d_A0, *d_B0, *d_C0;
float *d_A1, *d_B1, *d_C1;
float *d_A2, *d_B2, *d_C2;
float *d_A3, *d_B3, *d_C3;
args = wbArg_read(argc, argv);
wbTime_start(Generic, "Importing data and creating memory on host");
h_A = (float *) wbImport(wbArg_getInputFile(args, 0), &inputLength);
h_B = (float *) wbImport(wbArg_getInputFile(args, 1), &inputLength);
h_C = (float *) malloc(inputLength * sizeof(float));
wbTime_stop(Generic, "Importing data and creating memory on host");
wbLog(TRACE, "The input length is ", inputLength);
wbLog(TRACE, "h_A ", *h_A);
wbLog(TRACE, "h_B", *h_B);
int size = inputLength * sizeof(float);
int SegSize = inputLength/4;
wbCheck(cudaMalloc((void **) &d_A0, size));
wbCheck(cudaMalloc((void **) &d_B0, size));
wbCheck(cudaMalloc((void **) &d_C0, size));
wbCheck(cudaMalloc((void **) &d_A1, size));
wbCheck(cudaMalloc((void **) &d_B1, size));
wbCheck(cudaMalloc((void **) &d_C1, size));
wbCheck(cudaMalloc((void **) &d_A2, size));
wbCheck(cudaMalloc((void **) &d_B2, size));
wbCheck(cudaMalloc((void **) &d_C2, size));
wbCheck(cudaMalloc((void **) &d_A3, size));
wbCheck(cudaMalloc((void **) &d_B3, size));
wbCheck(cudaMalloc((void **) &d_C3, size));
cudaHostAlloc((void **) &h_A, size, cudaHostAllocDefault);
cudaHostAlloc((void **) &h_B, size, cudaHostAllocDefault);
cudaHostAlloc((void **) &h_C, size, cudaHostAllocDefault);
dim3 DimGrid((inputLength -1)/256 +1 , 1 , 1);
dim3 DimBlock(256 , 1, 1);
for (int i=0; i<size; i+=inputLength*4)
{
cudaMemcpyAsync(d_A0, h_A+i, SegSize*sizeof(float),cudaMemcpyHostToDevice, stream0);
cudaMemcpyAsync(d_B0, h_B+i, SegSize*sizeof(float),cudaMemcpyHostToDevice, stream0);
cudaMemcpyAsync(d_A1, h_A+i+SegSize, SegSize*sizeof(float),cudaMemcpyHostToDevice,stream1);
cudaMemcpyAsync(d_B1, h_B+i+SegSize, SegSize*sizeof(float),cudaMemcpyHostToDevice,stream1);
cudaMemcpyAsync(d_A2, h_A+i+SegSize+SegSize, SegSize*sizeof(float),cudaMemcpyHostToDevice, stream2);
cudaMemcpyAsync(d_B2, h_B+i+SegSize+SegSize, SegSize*sizeof(float),cudaMemcpyHostToDevice, stream2);
cudaMemcpyAsync(d_A3, h_A+i+SegSize+SegSize+SegSize, SegSize*sizeof(float),cudaMemcpyHostToDevice, stream3);
cudaMemcpyAsync(d_B3, h_B+i+SegSize+SegSize+SegSize, SegSize*sizeof(float),cudaMemcpyHostToDevice, stream3);
vecAdd<<<DimGrid, DimBlock, 0, stream0>>>(d_A0, d_B0, d_C0,inputLength);
vecAdd<<<DimGrid, DimBlock, 0, stream1>>>(d_A1, d_B1, d_C1,inputLength);
vecAdd<<<DimGrid, DimBlock, 0, stream2>>>(d_A2, d_B2, d_C2,inputLength);
vecAdd<<<DimGrid, DimBlock, 0, stream3>>>(d_A3, d_B3, d_C3,inputLength);
cudaDeviceSynchronize();
cudaMemcpyAsync(h_C+i, d_C0, SegSize*sizeof(float),cudaMemcpyDeviceToHost, stream0);
cudaMemcpyAsync(h_C+i+SegSize, d_C1, SegSize*sizeof(float),cudaMemcpyDeviceToHost,stream1);
cudaMemcpyAsync(h_C+i+SegSize+SegSize, d_C2, SegSize*sizeof(float),cudaMemcpyDeviceToHost,stream2);
cudaMemcpyAsync(h_C+i+SegSize+SegSize+SegSize, d_C3, SegSize*sizeof(float),cudaMemcpyDeviceToHost,stream3);
wbLog(TRACE, "on addition is ", *h_C);
}
cudaFree(d_A0);
cudaFree(d_B0);
cudaFree(d_C0);
cudaFree(d_A1);
cudaFree(d_B1);
cudaFree(d_C1);
cudaFree(d_A2);
cudaFree(d_B2);
cudaFree(d_C2);
cudaFree(d_A3);
cudaFree(d_B3);
cudaFree(d_C3);
wbSolution(args, h_C, inputLength);
cudaFreeHost(h_A);
cudaFreeHost(h_B);
cudaFreeHost(h_C);
return 0;
}

One problem is how you are handling h_A, h_B, and h_C:
h_A = (float *) wbImport(wbArg_getInputFile(args, 0), &inputLength);
h_B = (float *) wbImport(wbArg_getInputFile(args, 1), &inputLength);
The above lines of code are creating an allocation for h_A and h_B and importing some data (presumably).
These lines of code:
cudaHostAlloc((void **) &h_A, size, cudaHostAllocDefault);
cudaHostAlloc((void **) &h_B, size, cudaHostAllocDefault);
cudaHostAlloc((void **) &h_C, size, cudaHostAllocDefault);
Are not doing what you think. They are creating a new allocation for h_A, h_B and h_C. Whatever data those pointers previously referenced is no longer accessible from those pointers (i.e. for all intents and purposes, it is lost).
CUDA should be able to work just fine with the pointers and allocations being created here:
h_A = (float *) wbImport(wbArg_getInputFile(args, 0), &inputLength);
h_B = (float *) wbImport(wbArg_getInputFile(args, 1), &inputLength);
h_C = (float *) malloc(inputLength * sizeof(float));
So delete these lines of code:
cudaHostAlloc((void **) &h_A, size, cudaHostAllocDefault);
cudaHostAlloc((void **) &h_B, size, cudaHostAllocDefault);
cudaHostAlloc((void **) &h_C, size, cudaHostAllocDefault);
and delete these:
cudaFreeHost(h_A);
cudaFreeHost(h_B);
cudaFreeHost(h_C);
And you should be closer to a solution.

Related

OpenCL program works only for the multiple of itemsize

I'm new to openCL program and this is the problem I'm facing while executing a simple vector addition.
I have the following kernel code
#include <CL/cl.hpp>
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
#define MAX_SOURCE_SIZE (0x100000)
int main() {
__kernel void vector_add(__global const int *A, __global const int *B, __global int *C) {
int i = get_global_id(0);
C[i] = A[i] + B[i];
}
I have integrated gpu and amd gpus on my system. I'm trying to perform vector addition on my intel gpu and for which I have installed the intel opencl drivers (i7 3rd gen processor with hd graphics).
I have the below openCL code
std::vector<cl::Platform> platforms;
cl::Platform::get(&platforms);
std::cout << "Total platforms including cpu: " << platforms.size() << std::endl;
if (platforms.size() == 0) {
std::cout << " No platforms found. Check OpenCL installation!\n";
exit(1);
}
int i;
const int LIST_SIZE = 50;
int *A = (int*)malloc(sizeof(int)*LIST_SIZE);
int *B = (int*)malloc(sizeof(int)*LIST_SIZE);
for(i = 0; i < LIST_SIZE; i++) {
A[i] = i;
B[i] = LIST_SIZE - i;
}
FILE *fp;
char *source_str;
size_t source_size;
fp = fopen("vector_add_kernel.cl", "r");
if (!fp) {
fprintf(stderr, "Failed to load kernel.\n");
exit(1);
}
source_str = (char*)malloc(MAX_SOURCE_SIZE);
source_size = fread( source_str, 1, MAX_SOURCE_SIZE, fp);
fclose( fp );
//std::cout<<source_str<<std::endl;
// Get platform and device information
cl_platform_id* platforms1 = NULL;
cl_device_id device_id = NULL;
cl_uint ret_num_devices;
cl_uint ret_num_platforms;
cl_int ret = clGetPlatformIDs(1, platforms1, &ret_num_platforms);
platforms1= (cl_platform_id*) malloc(sizeof(cl_platform_id) * ret_num_platforms);
clGetPlatformIDs(ret_num_platforms, platforms1, NULL);
/*
* Platform 0: Intel Graphics
* Platform 1 : AMD Graphics
*/
//CHANGE THE PLATFORM ACCORDING TO YOUR SYSTEM!!!!
ret = clGetDeviceIDs( platforms1[0], CL_DEVICE_TYPE_GPU, 1,
&device_id, &ret_num_devices);
// Create an OpenCL context
cl_context context = clCreateContext( NULL, 1, &device_id, NULL, NULL, &ret);
// Create a command queue
cl_command_queue command_queue = clCreateCommandQueue(context, device_id, 0, &ret);
// Create memory buffers on the device for each vector
cl_mem a_mem_obj = clCreateBuffer(context, CL_MEM_READ_ONLY,
LIST_SIZE * sizeof(int), NULL, &ret);
cl_mem b_mem_obj = clCreateBuffer(context, CL_MEM_READ_ONLY,
LIST_SIZE * sizeof(int), NULL, &ret);
cl_mem c_mem_obj = clCreateBuffer(context, CL_MEM_WRITE_ONLY,
LIST_SIZE * sizeof(int), NULL, &ret);
// Copy the lists A and B to their respective memory buffers
ret = clEnqueueWriteBuffer(command_queue, a_mem_obj, CL_TRUE, 0,
LIST_SIZE * sizeof(int), A, 0, NULL, NULL);
ret = clEnqueueWriteBuffer(command_queue, b_mem_obj, CL_TRUE, 0,
LIST_SIZE * sizeof(int), B, 0, NULL, NULL);
// Create a program from the kernel source
cl_program program = clCreateProgramWithSource(context, 1,
(const char **)&source_str, (const size_t *)&source_size, &ret);
// Build the program
ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);
// Create the OpenCL kernel
cl_kernel kernel = clCreateKernel(program, "vector_add", &ret);
// Set the arguments of the kernel
ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&a_mem_obj);
ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&b_mem_obj);
ret = clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&c_mem_obj);
// Execute the OpenCL kernel on the list
size_t global_item_size = LIST_SIZE; // Process the entire lists
size_t local_item_size = 16; // Divide work items into groups of 64
ret = clEnqueueNDRangeKernel(command_queue, kernel, 1, NULL,
&global_item_size, &local_item_size, 0, NULL, NULL);
// Read the memory buffer C on the device to the local variable C
int *C = (int*)malloc(sizeof(int)*LIST_SIZE);
ret = clEnqueueReadBuffer(command_queue, c_mem_obj, CL_TRUE, 0,
LIST_SIZE * sizeof(int), C, 0, NULL, NULL);
// Display the result to the screen
for(i = 0; i < LIST_SIZE; i++)
printf("%d + %d = %d\n", A[i], B[i], C[i]);
//FREE
return 0;
}
If the LISTSIZE is 50, it prints only till 48 that is 16*3. It prints only the multiple of LISTSIZE and I'm not able to figure out why?.
OpenCL kernels execute only for a multiple of the local thread block size (local Range, in your code local_item_size), which should not be smaller than 32 and must be a multiple of 2, (so it can be (32, 64, 128, 256, ...). If you set it to 16, half of the GPU will be idle at any time. global_item_size must be a multiple of local_item_size. You need at least 32 data items for the kernel to function and a lot more for it to yield good performance.
Also the part
#include <CL/cl.hpp>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#define MAX_SOURCE_SIZE (0x100000)
int main() {
is not OpenCL C code and does not belong in the .cl source file. If it is not too lengthy, you can write the OpenCL C code directly in the .cpp file as a raw string:
const string kernel_code = R"(
__kernel void vector_add(__global const int *A, __global const int *B, __global int *C) {
int i = get_global_id(0);
C[i] = A[i] + B[i];
}
)";
char* source_str = kernel_code.c_str();

Casting a `local int*` to `local long16*` causes the kernel give me wrong results

I have a __local int* pointer which I want to copy the data from a __global int* to it. To make the copy faster, I cast both to long16*, I know all the arrays (input, output and local memory) are of size 16 * 1024 bytes. The code is as follows:
__kernel void test_kernel(
__global int* a,
__global int* b,
__local int* localbuf
){
int thread_idx = get_global_id(0);
int local_idx = get_local_id(0);
__global long16* input = (__global long16*)a;
__global long16* output = (__global long16*)b;
__local long16* local_buf = (__local long16*)localbuf;
local_buf[local_idx * 4 + 0] = input[0];
local_buf[local_idx * 4 + 1] = input[1];
local_buf[local_idx * 4 + 2] = input[2];
local_buf[local_idx * 4 + 3] = input[3];
barrier(CLK_LOCAL_MEM_FENCE);
output[thread_idx] = local_buf[thread_idx];
}
The result is not as I expect and output is filled with zeros.
Now, if I simplly replace the local_buf in last line with input, still I will get all zeros in output:
__kernel void test_kernel(
__global int* a,
__global int* b,
__local int* localbuf
){
int thread_idx = get_global_id(0);
int local_idx = get_local_id(0);
__global long16* input = (__global long16*)a;
__global long16* output = (__global long16*)b;
__local long16* local_buf = (__local long16*)localbuf;
local_buf[local_idx * 4 + 0] = input[0];
local_buf[local_idx * 4 + 1] = input[1];
local_buf[local_idx * 4 + 2] = input[2];
local_buf[local_idx * 4 + 3] = input[3];
barrier(CLK_LOCAL_MEM_FENCE);
output[thread_idx] = input[thread_idx];
}
But if I remove the assignment lines to the local buffer as follows:
__kernel void test_kernel(
__global int* a,
__global int* b,
__local int* localbuf
){
int thread_idx = get_global_id(0);
int local_idx = get_local_id(0);
__global long16* input = (__global long16*)a;
__global long16* output = (__global long16*)b;
__local long16* local_buf = (__local long16*)localbuf;
barrier(CLK_LOCAL_MEM_FENCE);
output[thread_idx] = input[thread_idx];
}
I will get the values totally correct in the output array. Also, if I simply do not cast the localbuf to long16 and copy it as ints, everything will work fine.
I really do not know what can be the problem. I am using a nVIDIA GTX 560 Ti.
Update 1: I noticed that this problem does not exist on AMD R9 280X and nVIDIA GTX 280... So, it can be architecture dependent.
Update2: Source Code:
size_t buffer_size = 16 * 1024 / 4 ;
size_t global_ws = buffer_size;
size_t local_ws = 32;
std::vector<int> host_data (buffer_size);
std::vector<int> output_data(buffer_size);
for(size_t i = 0; i < buffer_size; i++){
host_data[i] = static_cast<int>(i);
output_data[i] = 0;
}
cl_mem input = clCreateBuffer(cl->devices[0].ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, buffer_size * sizeof(int), host_data.data(), &err); CL_ERROR(err);
cl_mem output = clCreateBuffer(cl->devices[0].ctx, CL_MEM_WRITE_ONLY, buffer_size * sizeof(int), nullptr, &err); CL_ERROR(err);
auto start_frame_time = hrc::now();
clSetKernelArg(kernel, 0, sizeof(cl_mem), &input);
clSetKernelArg(kernel, 1, sizeof(cl_mem), &output);
clSetKernelArg(kernel, 2, buffer_size*sizeof(cl_int), NULL);
err = clEnqueueNDRangeKernel(cl->devices[0].cmd_queue, kernel, 1, nullptr, &global_ws, &local_ws, 0, nullptr, nullptr); CL_ERROR(err);
clFinish(cl->devices[0].cmd_queue);
err = clEnqueueReadBuffer(cl->devices[0].cmd_queue, output, CL_TRUE, 0, buffer_size * sizeof(int), output_data.data(), 0, nullptr, nullptr); CL_ERROR(err);
for (size_t i = 0; i < buffer_size; i++){
std::cout << i << ". " << output_data[i] << std::endl;
if (i % 512 == 0) getchar();
}
std::cout << "Elapsed Time: " << hrc::now() - start_frame_time << std::endl;
long -> 8 bytes
int -> 4 bytes
long16 -> 128 bytes
long16 is 32 times bigger than int.
So if you have 16kB of input/local/output you can only have (according to your kernel code) 512 work items. How many are you launching? Can you put that code as well?
For nVIDIA platform, if you go over these limits, you will get an error not at the kernel launch time but at the enqueueRead(). And the output will not even be read (leaving the output array with zeros). Check the errors there.
Also, AMD/Others might look like it works, but then have half of the results will be wrong.

Parallel reduction using local memory in OpenCL

I implemented a reduce kernel in OpenCL to sum up all entries in the input vector of size N. For a easier testing I initialize the input vector with 1.0f. So the result should be N. But it is not!
Here is my reduce-kernel:
kernel void reduce(global float* input, global float* output, const unsigned int N, local float* cache)
{
const uint local_id = get_local_id(0);
const uint global_id = get_global_id(0);
const uint local_size = get_local_size(0);
cache[local_id] = (global_id < N) ? input[global_id] : 0.0f;
barrier(CLK_LOCAL_MEM_FENCE);
for (unsigned int s = local_size >> 1; s > 0; s >>= 1) {
if (local_id < s) {
cache[local_id] += cache[local_id + s];
}
barrier(CLK_LOCAL_MEM_FENCE);
}
if (local_id == 0) output[local_size] = cache[0];
}
And here is the setting for OpenCL:
const uint N = 8196;
cl_float a[N];
cl_float b[N];
for (uint i=0; i<N; i++) {
a[i] = 1.0f;
b[i] = 0.0f;
}
cl::Buffer inputBuffer(context, CL_MEM_WRITE_ONLY, sizeof(cl_float)*N);
cl::Buffer resultBuffer(context, CL_MEM_READ_ONLY, sizeof(cl_float)*N);
queue.enqueueWriteBuffer(inputBuffer, CL_TRUE, 0, sizeof(cl_float)*N, a);
queue.enqueueWriteBuffer(resultBuffer, CL_TRUE, 0, sizeof(cl_float)*N, b);
cl::Kernel addVectorKernel = cl::Kernel(program, "reduce");
size_t localSize = addVectorKernel.getWorkGroupInfo<CL_KERNEL_WORK_GROUP_SIZE>(device); // e.g. => 512
size_t globalSize = roundUp(localSize, N); // rounds up to a multiple of localSize
addVectorKernel.setArg(0, inputBuffer);
addVectorKernel.setArg(1, resultBuffer);
addVectorKernel.setArg(2, N);
addVectorKernel.setArg(3, (sizeof(cl_float) * localSize), NULL);
queue.enqueueNDRangeKernel(
addVectorKernel,
cl::NullRange,
cl::NDRange(globalSize),
cl::NDRange(localSize)
);
queue.finish(); // wait for ending
queue.enqueueReadBuffer(resultBuffer, CL_TRUE, 0, sizeof(cl_float)*N, b); // e.g. => 1024
The result depends on the workgroup size. What am I doing wrong? Is it the kernel itself or is it the settings for OpenCL?
You should be using the group's id when writing the sum back to global memory.
if (local_id == 0) output[local_size] = cache[0];
That line will write to output[512] repeatedly. You need each work group to write to a dedicated location in the output.
kernel void reduce(global float* input, global float* output, const unsigned int N, local float* cache)
{
const uint local_id = get_local_id(0);
const uint global_id = get_global_id(0);
const uint group_id = get_group_id(0);
const uint local_size = get_local_size(0);
cache[local_id] = (global_id < N) ? input[global_id] : 0.0f;
barrier(CLK_LOCAL_MEM_FENCE);
for (unsigned int s = local_size >> 1; s > 0; s >>= 1) {
if (local_id < s) {
cache[local_id] += cache[local_id + s];
}
barrier(CLK_LOCAL_MEM_FENCE);
}
if (local_id == 0) output[group_id] = cache[0];
}
Then you need to sum the values from the output on the host. Note that 'b' in the host code does not need to hold N elements. Only one element for each work group will be used.
//replace (globalSize/localSize) with the pre-calculated/known number of work groups
for (i=1; i<(globalSize/localSize); i++) {
b[0] += b[i];
}
Now b[0] is your grand total.
In the reduction for loop, you need this:
for(unsigned int s = localSize >> 1; s > 0; s >>= 1)
You are shifting one more bit than you should when initializing s.
After that's fixed, let's look at what your kernel is doing. The host code executes it with globalSize of 8192 and localSize of 512, which results in 16 work groups. Inside the kernel you first sum the data from the two consecutive memory locations at index 2*global_id. For work group with id 15, work item 0, that will be at index 15*512*2 = 15,360 and 15,361, which is outside the boundaries of your input array. I am surprised you don't get a crash. At the same time, this explains why you have double the values that you expect.
To fix it, you can do this:
cache[localID] = input[globalID];
Or specify a global size that's half of the number of the current one.

OpenCL: Can one kernel call the other kernel

Hi ,
I am trying to run the available convolution code in OpenCL.
I am having heterogeneous system with -
1) CPU
2) GPU
PFB my code base which is running in my system :
convolution.cl
// TODO: Add OpenCL kernel code here.
__kernel
void convolve(
const __global uint * const input,
__constant uint * const mask,
__global uint * const output,
const int inputWidth,
const int maskWidth){
const int x = get_global_id(0);
const int y = get_global_id(1);
uint sum = 0;
for (int r = 0; r < maskWidth; r++)
{
const int idxIntmp = (y + r) * inputWidth + x;
for (int c = 0; c < maskWidth; c++)
{
sum += mask[(r * maskWidth) + c] * input[idxIntmp + c];
}
}
output[y * get_global_size(0) + x] = sum;
}
and convolution.cpp -
//Convolution-Process of applying a 3×3 mask to an 8×8 input signal,resulting in a 6×6 output signal
#include "CL/cl.h"
#include "vector"
#include "iostream"
#include "time.h"
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
// Constants
const unsigned int inputSignalWidth = 8;
const unsigned int inputSignalHeight = 8;
cl_uint inputSignal[inputSignalWidth][inputSignalHeight] =
{
{3, 1, 1, 4, 8, 2, 1, 3},
{4, 2, 1, 1, 2, 1, 2, 3},
{4, 4, 4, 4, 3, 2, 2, 2},
{9, 8, 3, 8, 9, 0, 0, 0},
{9, 3, 3, 9, 0, 0, 0, 0},
{0, 9, 0, 8, 0, 0, 0, 0},
{3, 0, 8, 8, 9, 4, 4, 4},
{5, 9, 8, 1, 8, 1, 1, 1}
};
const unsigned int outputSignalWidth = 6;
const unsigned int outputSignalHeight = 6;
cl_uint outputSignal[outputSignalWidth][outputSignalHeight];
const unsigned int maskWidth = 3;
const unsigned int maskHeight = 3;
cl_uint mask[maskWidth][maskHeight] =
{
{1, 1, 1},
{1, 0, 1},
{1, 1, 1},
};
inline void checkErr(cl_int err, const char * name)
{
if (err != CL_SUCCESS)
{
std::cerr << "ERROR: " << name
<< " (" << err << ")" << std::endl;
exit(EXIT_FAILURE);
}
}
void CL_CALLBACK contextCallback(
const char * errInfo,
const void * private_info,
size_t cb,
void * user_data)
{
std::cout << "Error occurred during context use: "<< errInfo << std::endl;
exit(EXIT_FAILURE);
}
int main(int argc,char argv[]){
cl_int errNum;
cl_uint numPlatforms;
cl_uint numDevices;
cl_platform_id * platformIDs;
cl_device_id * deviceIDs;
cl_context context = NULL;
cl_command_queue queue;
cl_program program;
cl_kernel kernel;
cl_mem inputSignalBuffer;
cl_mem outputSignalBuffer;
cl_mem maskBuffer;
double start,end,Totaltime;//Timer variables
errNum = clGetPlatformIDs(0, NULL, &numPlatforms);
checkErr(
(errNum != CL_SUCCESS) ? errNum :
(numPlatforms <= 0 ? -1 : CL_SUCCESS),
"clGetPlatformIDs");
platformIDs = (cl_platform_id *)malloc(sizeof(cl_platform_id) * numPlatforms);
errNum = clGetPlatformIDs(numPlatforms, platformIDs, NULL);
checkErr(
(errNum != CL_SUCCESS) ? errNum :
(numPlatforms <= 0 ? -1 : CL_SUCCESS), "clGetPlatformIDs");
deviceIDs = NULL;
cl_uint i;
for (i = 0; i < numPlatforms; i++)
{
errNum = clGetDeviceIDs(
platformIDs[i],
CL_DEVICE_TYPE_GPU,
0,
NULL,
&numDevices);
if (errNum != CL_SUCCESS && errNum != CL_DEVICE_NOT_FOUND)
{
checkErr(errNum, "clGetDeviceIDs");
}
else if (numDevices > 0)
{
deviceIDs = (cl_device_id *)malloc(
sizeof(cl_device_id) * numDevices);
errNum = clGetDeviceIDs(
platformIDs[i],
CL_DEVICE_TYPE_GPU,
numDevices,
&deviceIDs[0],
NULL);
checkErr(errNum, "clGetDeviceIDs");
break;
}
}
if (deviceIDs == NULL) {
std::cout << "No CPU device found" << std::endl;
exit(-1);
}
cl_context_properties contextProperties[] =
{
CL_CONTEXT_PLATFORM,(cl_context_properties)platformIDs[i], 0
};
context = clCreateContext(
contextProperties, numDevices, deviceIDs,
&contextCallback, NULL, &errNum);
checkErr(errNum, "clCreateContext");
std::ifstream srcFile("convolution.cl");
checkErr(srcFile.is_open() ? CL_SUCCESS : -1,
"reading convolution.cl");
std::string srcProg(
std::istreambuf_iterator<char>(srcFile),
(std::istreambuf_iterator<char>()));
const char * src = srcProg.c_str();
size_t length = srcProg.length();
program = clCreateProgramWithSource(context, 1, &src, &length, &errNum);
checkErr(errNum, "clCreateProgramWithSource");
errNum = clBuildProgram(program, numDevices, deviceIDs, NULL, NULL, NULL);
checkErr(errNum, "clBuildProgram");
kernel = clCreateKernel(program, "convolve", &errNum);
checkErr(errNum, "clCreateKernel");
inputSignalBuffer = clCreateBuffer(
context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
sizeof(cl_uint) * inputSignalHeight * inputSignalWidth,
static_cast<void *>(inputSignal), &errNum);
checkErr(errNum, "clCreateBuffer(inputSignal)");
maskBuffer = clCreateBuffer(
context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
sizeof(cl_uint) * maskHeight * maskWidth,
static_cast<void *>(mask), &errNum);
checkErr(errNum, "clCreateBuffer(mask)");
outputSignalBuffer = clCreateBuffer(
context, CL_MEM_WRITE_ONLY,
sizeof(cl_uint) * outputSignalHeight * outputSignalWidth,
NULL, &errNum);
checkErr(errNum, "clCreateBuffer(outputSignal)");
queue = clCreateCommandQueue(
context, deviceIDs[0], 0, &errNum);
checkErr(errNum, "clCreateCommandQueue");
errNum = clSetKernelArg(
kernel, 0, sizeof(cl_mem), &inputSignalBuffer);
errNum |= clSetKernelArg(
kernel, 1, sizeof(cl_mem), &maskBuffer);
errNum |= clSetKernelArg(
kernel, 2, sizeof(cl_mem), &outputSignalBuffer);
errNum |= clSetKernelArg(
kernel, 3, sizeof(cl_uint), &inputSignalWidth);
errNum |= clSetKernelArg(
kernel, 4, sizeof(cl_uint), &maskWidth);
checkErr(errNum, "clSetKernelArg");
const size_t globalWorkSize[1] ={ outputSignalWidth * outputSignalHeight };
const size_t localWorkSize[1] = { 1 };
start = clock();
errNum = clEnqueueNDRangeKernel(
queue,
kernel,
1,
NULL,
globalWorkSize,
localWorkSize,
0,
NULL,
NULL
);
checkErr(errNum, "clEnqueueNDRangeKernel");
errNum = clEnqueueReadBuffer(
queue, outputSignalBuffer, CL_TRUE, 0,
sizeof(cl_uint) * outputSignalHeight * outputSignalHeight,
outputSignal, 0, NULL, NULL);
checkErr(errNum, "clEnqueueReadBuffer");
end= clock(); - start;
cout<<"Time in ms = "<<((end/CLOCKS_PER_SEC) * 1000) << endl;
for (int y = 0; y < outputSignalHeight; y++)
{
for (int x = 0; x < outputSignalWidth; x++)
{
std::cout << outputSignal[x][y] << " ";
}
std::cout << std::endl;
}
return 0;
}
Questions :
I am having below doubts-
1) When I am using device type as CL_DEVICE_TYPE_GPU,
am getting 267 ms performance .When I am using CL_DEVICE_TYPE_CPU,execution time changed to 467 ms.
I want to know that what is the difference between running a convolution code on a CPU without GPU and CPU with GPU (by selecting device type as CL_DEVICE_TYPE_CPU) .
2) As I can see the convolution.cl file where there is a for loop which is executing 3 times.Can I call other Kernel for doing this operation from available kernel file ??
I am asking this question as I am new to the OpenCL coding and want to know that thing.
Both CPU & GPU are OpenCL Devices. So, by choosing CL_DEVICE_TYPE_CPU, you are telling OpenCL runtime to compile kernel code to CPU assembler & run it on CPU. When you are choosing CL_DEVICE_TYPE_GPU, kernel code is compiled to GPU assembler & executed on your video card. Ability to change device type without re-writing source code is of the main OpenCL features. It doesn't matter, does your CPU have integrated GPU, and / or discrete GPU is installed, you just pick available Device & run kernel on it.
For OpenCL 1.2 & older you can't call kernel from kernel. Dynamic parallelism is implemented in OpenCL 2.0.
For the first question: you should vectorize the kernel so opencl can easily use SIMD feature of your CPU hence unlock 4x(or 8x) more compute units per core.
__kernel
void convolve(
const __global uint8 * const input, // uint8 fits AVX(AVX2?) and uint4 fits SSE(SSE3?)
__constant uint8 * const mask,
__global uint8 * const output,
const int inputWidth,
const int maskWidth){
const int x = get_global_id(0); // this is 1/8 size now
const int y = get_global_id(1); // this is 1/8 size now
uint8 sum = 0; // a vector of 8 unsigneds
for (int r = 0; r < maskWidth; r++)
{
const int idxIntmp = (y + r) * inputWidth + x;
for (int c = 0; c < maskWidth; c++)
{
sum += mask[(r * maskWidth) + c] * input[idxIntmp + c]; //8 issued per clock
// scalars get promoted when used in direct multiplication of addition.
}
}
output[y * get_global_size(0) + x] = sum;
}
dont forget to decrease total work threads by 7/8 ratio (example: from 8k threads to 1k threads).
Please increase work per thread such as 50 convolutions per thread to increase occupation ratio of work units, then work on some local memory optimizations(for GPU) to get even better results such as 5ms per kernel..
On my AVX capable CPU, a simple matrix multiplication got speed up ratio of 2.4X going for 8-element vectorizations like this.
Running a kernel 3 times is not an issue if you offload enough work on it. If not, you should concatenate multiple kernels into a single one using some tricky algorithm.
If a profiler is not available at the moment, you can check GPU/CPU temperatures to get some idea of how close you are to the limits of hardware.
Play with number of local threads per work group. This can change performance as it lets more or less registers to be used per thread.

Sum array of vectors with cuda

I need to find average for thousands (20,000+) images represented by unsigned short arrays. Could you please check me, it looks for me that this code is not optimal:
my kernel:
__global__ void VecAdd(unsigned short *A, float *B, unsigned int Size, float div){
register float divider = div;
register int idx = threadIdx.x + blockIdx.x * blockDim.x;
if ( idx < Size) {
B[ idx ] = (float) A[idx] / divider + B[idx];
}
//__syncthreads();
}
kernel wrapper:
void kernel_wrapper(unsigned short* pixels1, float* pixels2, unsigned int length, float div)
{
unsigned short* deviceData1;
float* deviceData2;
cudaMalloc((void**)&deviceData1, length * sizeof(unsigned short));
cudaMalloc((void**)&deviceData2, length * sizeof(float));
cudaMemcpy(deviceData1, pixels1, length * sizeof(unsigned short), cudaMemcpyHostToDevice);
cudaMemcpy(deviceData2, pixels2, length * sizeof(float), cudaMemcpyHostToDevice);
int threads = 1024; //my maximum
int blocks = (length / threads); // lenght=1280*960 -> blocks=1200
VecAdd<<< blocks, threads >>>( deviceData1, deviceData2, length, div );
cudaMemcpy(pixels2, deviceData2, length * sizeof(float), cudaMemcpyDeviceToHost);
cudaFree( deviceData1 );
cudaFree( deviceData2 );
}`
and I do
float* avrg2f = (float*)malloc( width * height * sizeof(float));
memset( avrg2f, 0.0, sizeof(float) * width * height);
for (int k = 0; k < count; k++) {
imageObjectList.at( curObj )->getImage( k );
kernel_wrapper( avrg1, avrg2f, height * width, (float)count);
}
as result may averaged image will be in avrg2f;
Thank you.
If the images are all the same size, then your wrapper function need not do cudaMalloc and cudaFree operations on every call.
Pre-allocate that storage needed, and don't allocated and free it on every call to the wrapper.
In addition you may see something like a ~2x speedup (for the cudaMemcpy operations) if you use pinned allocations (cudaHostAlloc) on the host side for your image storage.
Finally, for the duration of your loop, there's no need to copy the results back to the host. Do this after you're done computing the average. This will save 2 out of the 3 cudaMemcpy operations you are doing in the wrapper.
While we're at it, in my opinion using memset to initialize a float array is questionable. It works for a zero value, but essentially no other. Furthermore, I would expect passing 0.0 as the second parameter to memset to at least throw a compiler warning.
The following code shows the above optimizations, and demonstrates about an 8x speedup over your code in my test case:
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
__global__ void VecAdd(unsigned short *A, float *B, unsigned int Size, float div){
register float divider = div;
register int idx = threadIdx.x + blockIdx.x * blockDim.x;
if ( idx < Size) {
B[ idx ] = (float) A[idx] / divider + B[idx];
}
//__syncthreads();
}
__global__ void VecAdd2(unsigned short *A, float *B, unsigned int Size, float mult){
register int idx = threadIdx.x + blockIdx.x * blockDim.x;
if ( idx < Size) {
B[ idx ] = (float) A[idx] * mult + B[idx];
}
}
void kernel_wrapper(unsigned short* pixels1, float* pixels2, unsigned int length, float div)
{
unsigned short* deviceData1;
float* deviceData2;
cudaMalloc((void**)&deviceData1, length * sizeof(unsigned short));
cudaMalloc((void**)&deviceData2, length * sizeof(float));
cudaMemcpy(deviceData1, pixels1, length * sizeof(unsigned short), cudaMemcpyHostToDevice);
cudaMemcpy(deviceData2, pixels2, length * sizeof(float), cudaMemcpyHostToDevice);
int threads = 1024; //my maximum
int blocks = (length / threads); // lenght=1280*960 -> blocks=1200
VecAdd<<< blocks, threads >>>( deviceData1, deviceData2, length, div );
cudaMemcpy(pixels2, deviceData2, length * sizeof(float), cudaMemcpyDeviceToHost);
cudaFree( deviceData1 );
cudaFree( deviceData2 );
}
void kernel_wrapper2(unsigned short* h_pixels1, unsigned short* d_pixels1, float* d_pixels2, unsigned int length, float my_mult)
{
cudaMemcpy(d_pixels1, h_pixels1, length * sizeof(unsigned short), cudaMemcpyHostToDevice);
int threads = 1024; //my maximum
int blocks = (length / threads); // lenght=1280*960 -> blocks=1200
VecAdd2<<< blocks, threads >>>( d_pixels1, d_pixels2, length, my_mult );
}
int main(){
const int count = 2000;
const int width = 1280;
const int height = 960;
timeval t1, t2;
unsigned long et;
unsigned short *h1_image;
h1_image = (unsigned short *)malloc(height*width*sizeof(unsigned short));
float* avrg2f = (float*)malloc( width * height * sizeof(float));
for (int i = 0; i<height*width; i++){
h1_image[i] = (i%256);
avrg2f[i] = 0.0f;
}
gettimeofday(&t1,NULL);
for (int k = 0; k < count; k++) {
kernel_wrapper( h1_image, avrg2f, height * width, (float)count);
}
gettimeofday(&t2,NULL);
et = ((t2.tv_sec * 1000000)+t2.tv_usec) - ((t1.tv_sec * 1000000) + t1.tv_usec);
printf("time 1 = %ld us\n", et);
unsigned short *h2_image;
float* avrg3f = (float*)malloc( width * height * sizeof(float));
cudaHostAlloc((void **)&h2_image, height*width*sizeof(unsigned short), cudaHostAllocDefault);
for (int i = 0; i<height*width; i++){
h2_image[i] = (i%256);
avrg3f[i] = 0.0f;
}
gettimeofday(&t1,NULL);
unsigned short *d_image;
float *d_result;
cudaMalloc((void **)&d_image, height*width*sizeof(unsigned short));
cudaMalloc((void **)&d_result, height*width*sizeof(float));
cudaMemcpy(d_result, avrg3f, height*width*sizeof(float), cudaMemcpyHostToDevice);
for (int k = 0; k < count; k++) {
kernel_wrapper2( h2_image, d_image, d_result, height * width, (float)(1/(float)count));
}
cudaMemcpy(avrg3f, d_result, height*width*sizeof(float), cudaMemcpyDeviceToHost);
gettimeofday(&t2,NULL);
et = ((t2.tv_sec * 1000000)+t2.tv_usec) - ((t1.tv_sec * 1000000) + t1.tv_usec);
printf("time 2 = %ld us\n", et);
for (int i = 0; i < (height*width); i++)
if (fabs(avrg2f[i] - avrg3f[i]) > 0.0001) {printf("mismatch at %d, 1 = %f, 2 = %f\n", i, avrg2f[i], avrg3f[i]); return 1;}
return 0;
}

Resources