CUDA中与SM/core相关的内存分配和索引

CUDA中与SM/core相关的内存分配和索引,cuda,Cuda,我有以下设置,顶级父内核称为: parent_kernel<<<a, 1>>>(...) parent_内核(…) 它所做的唯一事情就是调用一系列子内核: child_kernel_1<<<c1, b1>>>(... + offset(blockIdx.x), blockIdx.x) child_kernel_2<<<c2, b2>>>(... + offset(blockIdx.x)

我有以下设置,顶级父内核称为:

parent_kernel<<<a, 1>>>(...)
parent_内核(…)
它所做的唯一事情就是调用一系列子内核:

child_kernel_1<<<c1, b1>>>(... + offset(blockIdx.x), blockIdx.x)
child_kernel_2<<<c2, b2>>>(... + offset(blockIdx.x), blockIdx.x)
...
child_kernel_1(…+偏移量(blockIdx.x),blockIdx.x)
子线程内核线程2(…+偏移量(blockIdx.x),blockIdx.x)
...
我需要将
child\u kernel\u 1
的结果传递给
child\u kernel\u 2
。 说中间结果比任何输入都大,所以我不能重用它们的内存(至少是直接重用)。此外,当在
a
上组合时,它们足够大,无法装入GPU内存,这意味着在
父内核
之前进行批量预分配不是一个选项

这给我留下了
malloc
parent\u内核中,这也不是一件好事,因为内存分配非常耗时,
a
可能相当大

同时,同一时间只执行有限数量的块,并且在
a
中将所有分配的块设置为相同大小不会增加太多开销


这让我想知道,是否有可能将索引绑定到SM/core(或类似),而不是块?(当另一个块在同一个SM/core下调用时,上一个必须已经完成,并且可以安全地重用内存)

让我们以一种可能的方式来讨论已经讨论过的问题

总体概念如下

  • 基于
    父内核(…)
    ,我们有
    a
    子内核启动序列要执行。
    a
    中的每个项目由
    子内核1
    子内核2
    组成。有大量临时数据需要从子1传递到子2,我们不希望预先分配所有
    a
    数量的此类临时数据

  • 我们观察到,对于GPU中的每个SM,可能存在最大数量的驻留块
    X
    ;这是运行时可查询的CUDA硬件限制(例如,
    deviceQuery
    sample code)

  • 假设我们的GPU中有
    W
    条短信(在运行时也可以查询)。让我们假设对于每个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)
    

    在第一种方法中,我提到启动
    W*X
    块,但要正确使用该方法,必须进行占用率分析。由于占用率分析,可能需要减少数量
    W*X
    。代码示例中指出的第二种方法可以对启动的任意数量的块正确工作。

    当然可以通过SM/block管理分配,请参阅。@RobertCrovella它忽略了我所指的粒度级别(核心),这很重要,因为SM太过强制,线程可以在锁步中执行,从而导致不必要的内存访问模式。不,根本没有办法将任何内容绑定到CUDA内核。CUDA核心大约是一个浮点ALU。无法保证它将用于什么用途,或者它将涉及哪些线程或指令。假设每个块有1个线程,那么就不可能在lockstep中执行线程。如果您了解最大可能占用率,您应该能够管理每个SM的分配,正如链接的答案所描述的。也许您不了解CUDA核心是什么。最接近CPU核心的是CUDA SM。CUDA SM不是由多个CUDA内核组成的,就像CPU由多个内核组成一样。@RobertCrovella对于链接的答案,您的意思是通过特殊寄存器确定SM,然后通过原子锁管理对与该特定SM相关联的内存块的访问?
    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)}
    
    #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;
    }