[英]OpenCL float sum reduction
我想在我的內核代碼(1維數據)上應用reduce:
__local float sum = 0;
int i;
for(i = 0; i < length; i++)
sum += //some operation depending on i here;
我想要有n個線程(n =長度),最后有1個線程來計算總和,而不是只有1個線程執行此操作。
在偽代碼中,我希望能夠寫出這樣的東西:
int i = get_global_id(0);
__local float sum = 0;
sum += //some operation depending on i here;
barrier(CLK_LOCAL_MEM_FENCE);
if(i == 0)
res = sum;
有辦法嗎?
我總和有競爭條件。
為了幫助您入門,您可以執行以下示例( 請參閱Scarpino )。 在這里,我們還通過使用OpenCL float4數據類型來利用向量處理。
請記住,下面的內核會返回一些部分總和:每個本地工作組一個,返回主機。 這意味着您必須通過將所有部分金額加回主機來執行最終總和。 這是因為(至少在OpenCL 1.2中)沒有屏障功能可以同步不同工作組中的工作項。
如果不希望對主機上的部分和求和,可以通過啟動多個內核來解決這個問題。 這引入了一些內核調用開銷,但在某些應用程序中,額外的懲罰是可接受的或無關緊要的。 要使用下面的示例執行此操作,您需要修改主機代碼以重復調用內核,然后包含邏輯以在輸出向量的數量低於本地大小后停止執行內核(詳細信息留給您或查看Scarpino參考 )。
編輯:為輸出添加了額外的內核參數。 添加了點積乘以浮點4向量。
__kernel void reduction_vector(__global float4* data,__local float4* partial_sums, __global float* output)
{
int lid = get_local_id(0);
int group_size = get_local_size(0);
partial_sums[lid] = data[get_global_id(0)];
barrier(CLK_LOCAL_MEM_FENCE);
for(int i = group_size/2; i>0; i >>= 1) {
if(lid < i) {
partial_sums[lid] += partial_sums[lid + i];
}
barrier(CLK_LOCAL_MEM_FENCE);
}
if(lid == 0) {
output[get_group_id(0)] = dot(partial_sums[0], (float4)(1.0f));
}
}
我知道這是一個非常古老的帖子,但是從我嘗試過的所有內容來看,Bruce的答案都不起作用,而且由於全局內存使用和內核執行開銷,來自Adam的答案效率低下。
喬丹對布魯斯答案的評論是正確的,這個算法在元素數量不均勻的每次迭代中都會崩潰。 但它與幾個搜索結果中的代碼基本相同。
我對此感到頭疼了好幾天,部分受到我選擇的語言不是基於C / C ++的事實的阻礙,而且如果不是不可能在GPU上進行調試也很棘手。 最后,我找到了一個有效的答案。
這是布魯斯和亞當的答案的結合。 它將源從全局內存復制到本地,但是通過將上半部分重復折疊到底部來減少,直到沒有數據為止。
結果是一個緩沖區包含與使用的工作組相同數量的項目(因此可以分解非常大的減少量),這必須由CPU求和,或者從另一個內核調用並執行此操作的最后一步GPU。
這部分有點過頭,但我相信,這段代碼也可以通過從本地內存中按順序讀取來避免銀行切換問題。 **會對任何知道的人表示喜愛。
注意:如果數據從偏移零開始,則可以從源中省略全局“AOffset”參數。 只需將其從內核原型和第四行代碼中刪除,它將其用作數組索引的一部分......
__kernel void Sum(__global float * A, __global float *output, ulong AOffset, __local float * target ) {
const size_t globalId = get_global_id(0);
const size_t localId = get_local_id(0);
target[localId] = A[globalId+AOffset];
barrier(CLK_LOCAL_MEM_FENCE);
size_t blockSize = get_local_size(0);
size_t halfBlockSize = blockSize / 2;
while (halfBlockSize>0) {
if (localId<halfBlockSize) {
target[localId] += target[localId + halfBlockSize];
if ((halfBlockSize*2)<blockSize) { // uneven block division
if (localId==0) { // when localID==0
target[localId] += target[localId + (blockSize-1)];
}
}
}
barrier(CLK_LOCAL_MEM_FENCE);
blockSize = halfBlockSize;
halfBlockSize = blockSize / 2;
}
if (localId==0) {
output[get_group_id(0)] = target[0];
}
}
減少數據的一種簡單快捷的方法是將數據的上半部分重復折疊到下半部分。
例如,請使用以下非常簡單的CL代碼:
__kernel void foldKernel(__global float *arVal, int offset) {
int gid = get_global_id(0);
arVal[gid] = arVal[gid]+arVal[gid+offset];
}
使用以下Java / JOCL主機代碼(或將其移植到C ++等):
int t = totalDataSize;
while (t > 1) {
int m = t / 2;
int n = (t + 1) / 2;
clSetKernelArg(kernelFold, 0, Sizeof.cl_mem, Pointer.to(arVal));
clSetKernelArg(kernelFold, 1, Sizeof.cl_int, Pointer.to(new int[]{n}));
cl_event evFold = new cl_event();
clEnqueueNDRangeKernel(commandQueue, kernelFold, 1, null, new long[]{m}, null, 0, null, evFold);
clWaitForEvents(1, new cl_event[]{evFold});
t = n;
}
主機代碼循環log2(n)次,因此即使使用大型數組也能快速完成。 “m”和“n”的小提琴是處理非二次冪陣列。
如果您支持OpenCL C 2.0功能,則可以使用新的work_group_reduce_add()
函數在單個工作組內減少總和
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.