我有以下设置,在顶级父内核上称为:
parent_kernel<<<a, 1>>>(...)
它唯一要做的就是调用一系列子内核:
child_kernel_1<<<c1, b1>>>(... + offset(blockIdx.x), blockIdx.x)
child_kernel_2<<<c2, b2>>>(... + offset(blockIdx.x), blockIdx.x)
...
我需要将child_kernel_1
的结果传递到child_kernel_2
。
所说的中间结果大于任何输入,因此我不能(至少直接)重用它们的内存。此外,在a
上组合时,它们足够大,无法容纳在GPU内存中,这意味着无法选择在parent_kernel
之前进行批量预分配。
这使我在malloc
中留有parent_kernel
,这也不是一件好事,因为内存分配非常耗时,并且a
可能会很大。
同时,只有有限数量的块同时在执行,并且在a
上将所有分配的块设置为相同大小并不会增加太多开销。
这让我想知道,是否有可能将索引绑定到SM /核心(或类似)而不是阻塞? (这样的想法是,当另一个块在同一SM /核心下进行调用时,先前的块必须已经完成并且可以安全地重用内存)。
答案 0 :(得分:1)
作为一种可能的方法,我们来掩盖this answer中已经覆盖的地面。
总体概念如下。
基于parent_kernel<<<a, 1>>>(...)
,我们有a
个子内核启动序列要执行。 a
中的每个项目均由child_kernel_1
后跟child_kernel_2
组成。有大量的临时数据需要从子1传递到子2,我们不希望为所有a
数量的此类临时数据进行预分配。
我们观察到,对于我们GPU中的每个SM,可能存在最大数量的驻留块X
;这是可在运行时查询的CUDA硬件限制(例如deviceQuery
示例代码)。
让我们假设我们的GPU中有W
个SM(也可以在运行时查询)。并假设对于每个SM,常驻块的硬件限制为X
。这意味着我们只需要提供W*X
临时分配,并且如果W*X
小于a
,我们可能会以减小的临时分配大小来解决此问题。 (要正确地执行此步骤,可能需要根据对相关内核的占用率分析来减少X
。)
为了使用此途径,我们将需要限制我们启动的块总数,以便每个SM仅具有X
,即我们必须启动W*X
个块。由于该值小于a
(大概),因此我们必须从以下项重新构建父内核设计:
child_kernel_1<<<c1, b1>>>(... + offset(blockIdx.x), blockIdx.x)
child_kernel_2<<<c2, b2>>>(... + offset(blockIdx.x), blockIdx.x)
收件人:
for (int i = 0; i < a/(W*X); i++){
child_kernel_1<<<c1, b1>>>(i, ... + offset(blockIdx.x), blockIdx.x)
child_kernel_2<<<c2, b2>>>(i, ... + offset(blockIdx.x), blockIdx.x)}
(为简单起见,假设a
是可以被W*X
整除的整数,但是可以通过限制检查很容易地解决)(还要注意,限制总块数的想法不是绝对必要,但是它大大简化了每个SM的分配方案。请参见下面的替代方法。)
GPU块分配器将块分配给每个SM,最终达到每个SM X
个块的满负荷。当每个块都驻留在SM上时,它开始执行代码并首先执行两件事:A.确定我所在的SM,将其称为w
,其中w
可以接受{{ 1}}至0
。 B.确定我在此SM上的块编号。这是通过简单的W-1
完成的,从我所知的任何意义上讲,它都不是锁。让我们将此操作atomicAdd
返回的数字称为atomicAdd
,其中x
的范围可以从0到x
。
每个块现在都有一个唯一的X-1
有序对。通过此有序对,它可以从一组(w,x)
个预先分配的临时存储区中进行选择。
每个块现在正在执行子项1和子项2启动的W*X
序列,并为每个1-2序列重用其已选择的临时存储分配。
实现上述功能所需的许多代码已经在另一个答案here(方法3)中。
我们还可以放宽对上述方法的限制,即将发射的块总数限制为可以同时共存的块数。相反,我们可以跟踪所有插槽,并在块启动和退出时使它们可用。对于这种情况,每个块必须在完成时发出信号。这是一个经过严格测试的示例:
a/(W*X)
在第一种方法中,我提到了启动#include <iostream>
#include <cassert>
const long long DELAY_T = 100000;
// this is used to get one of a set of unique slots on the SM
//const unsigned long long slots = 0xFFFFFFFFULL; // 0xFFFFFFFF assumes 32 unique slots per SM
const int max_num_slots = 32;
const unsigned long long busy = 0x1FFFFFFFFULL;
__device__ int get_slot(unsigned long long *sm_slots){
unsigned long long my_slots;
bool done = false;
int my_slot;
while (!done){
while ((my_slots=atomicExch(sm_slots, busy)) == busy); // wait until we get an available slot
my_slot = __ffsll(~my_slots) - 1;
if (my_slot < max_num_slots) done = true;
else atomicExch(sm_slots, my_slots);} // handle case where all slots busy, should not happen
unsigned long long my_slot_bit = 1ULL<<my_slot;
unsigned long long retval = my_slots|my_slot_bit;
assert(atomicExch(sm_slots, retval) == busy);
return my_slot;
}
__device__ void release_slot(unsigned long long *sm_slots, int slot){
unsigned long long my_slots;
while ((my_slots=atomicExch(sm_slots, busy)) == busy); // wait until slot access not busy
unsigned long long my_slot_bit = 1ULL<<slot;
unsigned long long retval = my_slots^my_slot_bit;
assert(atomicExch(sm_slots, retval) == busy);
}
__device__ int __mysmid(){
int smid;
asm volatile("mov.u32 %0, %%smid;" : "=r"(smid));
return smid;}
__global__ void k(unsigned long long *sm_slots, int *temp_data){
int my_sm = __mysmid();
int my_slot = get_slot(sm_slots+my_sm);
temp_data[my_sm*max_num_slots + my_slot] = blockIdx.x;
long long start = clock64();
while (clock64()<start+DELAY_T);
assert(temp_data[my_sm*max_num_slots + my_slot] == blockIdx.x);
release_slot(sm_slots+my_sm, my_slot);
}
int main(){
// hard coding constants for Tesla V100 for demonstration purposes.
// these should instead be queried at runtime to match your GPU
const int num_sms = 80;
const int blocks_per_sm = 32;
// slots must match the number of blocks per SM, constants at top may need to be modified
assert(blocks_per_sm <= max_num_slots);
unsigned long long *d_sm_slots;
int *d_data;
cudaMalloc(&d_sm_slots, num_sms*blocks_per_sm*sizeof(unsigned long long));
cudaMalloc(&d_data, num_sms*blocks_per_sm*sizeof(int));
cudaMemset(d_sm_slots, 0, num_sms*blocks_per_sm*sizeof(unsigned long long));
k<<<123456, 1>>>(d_sm_slots, d_data);
cudaDeviceSynchronize();
if (cudaGetLastError()!=cudaSuccess) {std::cout << "failure" << std::endl; return 0;}
std::cout << "success" << std::endl;
return 0;
}
块,但是要正确使用该方法,则有必要进行占用分析。由于占用率分析,可能需要减少数量W*X
。代码示例中指出的第二种方法对于启动的任意数量的块都可以正常工作。