对于那些不知道那是什么的人,我会解释基本的想法.它是一个神经网络,其中一个层的每个神经元连接到下一层的每个神经元.每个神经元只有一个动作要执行:执行前一层所有神经元的总和,并按每个神经元的不同值加权.
这看起来很简单,在阅读了“使用OpenCL进行并行神经网络训练”之后,我按照以下方式实现了它
>每个图层都依赖于前一个图层,它们由主机按顺序运行
>为了计算一个图层,我运行我的内核,其中全局工作大小为图层内的神经元数量(例如,可能非常大,数万个).这使得所有神经元都能彼此独立地执行其总和.
>每个神经元(由其global_work_id标识)对前一层的所有神经元执行加权和.
这是我的全功能opencl内核:
/** * @brief Computes one layer of the perceptron given the previous one and the * weights * The kernel is run once for each layer. * The work items are each tasked with computing the output of a single neuron * of the out layer. * * @param out_layer_size * Size of the output layer (number of elements in the output array that will * contain the result for each neuron). * @param in_layer_size * Number of elements of the input layer * @param in_value * Values of the neuron in the previous layer * @param in_weights * Array containing the weights for each input neuron. It is organised as a * two dimensional matrix, written by concatenating each line in the array * [ w11, w12, w13, ... * w21, w22, w23, ... * ..., ..., ..., ... * ] * Where wij is the weight linking the neuron i of the input layer to the * neuron j of the output layer * @param out_values * Computed values for the current layer */ void kernel perceptron(global const int* in_layer_size, global const int* out_layer_size, global const float *in_value, global const float* in_weights, global float* out_values) { private const int global_id = get_global_id(0); private const int out_layer_s = *out_layer_size; private const int in_layer_s = *in_layer_size; private const int offset = out_layer_s * global_id; private float sum = 0.; for(int i=0; i < in_layer_s; i++) { sum += in_weights[i*out_layer_s+global_id] * in_value[i]; } //out_values[global_id] = sigma(sum); out_values[global_id] = sum; }
以下是我如何调用它:
queue.enqueueNDRangeKernel(kernel, cl::NullRange,cl::NDRange(number of neurons within layer),cl::NullRange);
我意识到这个内核的瓶颈是加权和的实现.如果有人可以解释我如何改进这一点以使其更快,那将是非常有帮助的.
我可能没有正确使用不同的内存区域,我基本上考虑的是我甚至不使用的本地内存.
只是为了让您了解性能(即在Nvidia GTX 660M上),我将向您展示我实现的一些时间.每个值是每层神经元的数量:
> 2500,10000,2500:0.018s~60FPS.它比我的处理器(运行在2.40GHz的Intel Core i7)快4到5倍
> 100 000,150 000,500:140s – >因为第二层中的每个神经元必须执行100 000个元素的加权和,所以我认为这并不令人惊讶.在我的处理器上运行它会产生大致相同的结果.
潜在地,可以通过将WI组织到本地工作组(工作组)来解决这个问题.因为每个WI都需要处理来自prev的所有数据.层,我想WG中的所有WI都可以将一些数据加载到本地内存中,处理它们而不是下一堆数据.这将使您的算法更加缓存友好.内核的伪代码如下:
void kernel Kernel( __global const int in_layer_size, __global const int out_layer_size, __global const float *in_value, __global const float *in_weights, __global float *out_values){ __local float buffer[SOME_SIZE]; __global const float* p_in = in_value; __global float* p_out = out_values; const int global_id = get_global_id(0), local_id = get_local_id(0), num_buffers = in_layer_size / SOME_SIZE, offset = out_layer_size * global_id; float sum = 0.0f; for(int i=0; i < num_buffers; i++){ buffer[local_id] = p_in[local_id]; barrier(CLK_LOCAL_MEM_FENCE); //Process all data inside buffer by every WI in WG //... p_in += SOME_SIZE; out_values += SOME_SIZE; } //... return;
}
所以,你正在用固定尺寸的窗户滑动.在&内计算数据然后进入下一个窗口. Al数据操作是独立完成的,工作项只能同时使用相同的数据.本地组的最佳大小取决于设备和内核.