Cuda 多次调用cufft存储回调

Cuda 多次调用cufft存储回调,cuda,fft,nvidia,cufft,Cuda,Fft,Nvidia,Cufft,我在一个复杂到复杂、不合适的1D批处理FFT中使用cufft存储回调(即,我正在做许多相同大小的1D FFT)。从中,我希望对每个输出调用此回调一次。具体请参见以下链接中的逐字引用: cuFFT将为输入中的每个点调用load回调例程一次且仅一次。类似地,它将为输出中的每个点调用存储回调例程一次,并且仅调用一次 然而,我似乎有一个例子与此相矛盾。在下面的代码中,我希望看到每个数字0-19只出现一次,对应于为每个输出样本调用一次存储回调。但是,当我执行大小为32的504 1D FFT时,每次输出都会

我在一个复杂到复杂、不合适的1D批处理FFT中使用cufft存储回调(即,我正在做许多相同大小的1D FFT)。从中,我希望对每个输出调用此回调一次。具体请参见以下链接中的逐字引用:

cuFFT将为输入中的每个点调用load回调例程一次且仅一次。类似地,它将为输出中的每个点调用存储回调例程一次,并且仅调用一次

然而,我似乎有一个例子与此相矛盾。在下面的代码中,我希望看到每个数字0-19只出现一次,对应于为每个输出样本调用一次存储回调。但是,当我执行大小为32的504 1D FFT时,每次输出都会调用存储回调两次

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <cuda.h>    
#include <cuda_runtime.h>
#include <cufft.h>
#include <cufftXt.h>


// Very simple store callback: prints the index and does the store
static __device__ void stor_cb(void *a, size_t index, cufftComplex z,
                               void *cb_info, void *sharedmem) {

    // Print the index. Each index should appear exactly once.
    if (index < 20) printf("%8llu\n", index);

    // Do the store
    ((cufftComplex *)a)[index] = z;
}
__device__ cufftCallbackStoreC stor_cb_ptr_d = stor_cb;


int main() {
    size_t work_size;

    // With these parameters, the store callback is
    // called twice for each output
    int fft_sz = 32;            // Size of each FFT
    int num_ffts = 504;         // How many FFTs to do

    // With these parameters, the store callback is
    // called once for each output
//    int fft_sz = 1024;         // Size of each FFT
//    int num_ffts = 20;         // How many FFTs to do

    // Buffers
    cufftComplex *in_buf_h, *in_buf_d, *out_buf_d;

    // Allocate buffers on host and device
    in_buf_h = new cufftComplex[fft_sz*num_ffts];
    cudaMalloc(&in_buf_d, fft_sz*num_ffts*sizeof(cufftComplex));
    cudaMalloc(&out_buf_d, fft_sz*num_ffts*sizeof(cufftComplex));

    // Fill input buffer with zeros and copy to device
    memset(in_buf_h, 0, fft_sz*num_ffts*sizeof(cufftComplex));
    cudaMemcpy(in_buf_d, in_buf_h, fft_sz*num_ffts*sizeof(cufftComplex), cudaMemcpyHostToDevice);

    // Plan num_ffts of size fft_sz
    cufftHandle plan;
    cufftCreate(&plan);
    cufftMakePlan1d(plan, fft_sz, CUFFT_C2C, num_ffts, &work_size);

    // Associate save callback with plan
    cufftCallbackStoreC stor_cb_ptr_h;
    cudaMemcpyFromSymbol(&stor_cb_ptr_h, stor_cb_ptr_d, sizeof(stor_cb_ptr_h));
    cufftXtSetCallback(plan, (void **)&stor_cb_ptr_h, CUFFT_CB_ST_COMPLEX, 0);

    // Execute the plan. We don't actually care about values. The idea
    // is that the store callback should be called exactly once for
    // each of the fft_sz*num_ffts samples.
    cufftExecC2C(plan, in_buf_d, out_buf_d, -1);

    // Sync the device to flush the output
    cudaDeviceSynchronize();

    return 0;
}
相反,如果我执行大小为1024的20个FFT,那么我会得到预期的行为:对于每个输出,存储回调只调用一次。fft_sz=1024,num_ffts=20的输出示例:

$ stor_cb_tst 
   0
   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
我是不是误解了什么,我是不是有问题,还是这是cufft的问题

我在LinuxMint上运行这个,CUDAV8.0.61,g++5.4.0,GeForceGTX1080:

$ uname -a
Linux orpheus 4.4.0-53-generic #74-Ubuntu SMP Fri Dec 2 15:59:10 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

$ nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2016 NVIDIA Corporation
Built on Tue_Jan_10_13:22:03_CST_2017
Cuda compilation tools, release 8.0, V8.0.61

$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ ./deviceQuery 
./deviceQuery Starting...

 CUDA Device Query (Runtime API) version (CUDART static linking)

Detected 1 CUDA Capable device(s)

Device 0: "GeForce GTX 1080"
  CUDA Driver Version / Runtime Version          8.0 / 8.0
  CUDA Capability Major/Minor version number:    6.1
  Total amount of global memory:                 8114 MBytes (8507752448 bytes)
  (20) Multiprocessors, (128) CUDA Cores/MP:     2560 CUDA Cores
  GPU Max Clock rate:                            1848 MHz (1.85 GHz)
  Memory Clock rate:                             5005 Mhz
  Memory Bus Width:                              256-bit
  L2 Cache Size:                                 2097152 bytes
  Maximum Texture Dimension Size (x,y,z)         1D=(131072), 2D=(131072, 65536), 3D=(16384, 16384, 16384)
  Maximum Layered 1D Texture Size, (num) layers  1D=(32768), 2048 layers
  Maximum Layered 2D Texture Size, (num) layers  2D=(32768, 32768), 2048 layers
  Total amount of constant memory:               65536 bytes
  Total amount of shared memory per block:       49152 bytes
  Total number of registers available per block: 65536
  Warp size:                                     32
  Maximum number of threads per multiprocessor:  2048
  Maximum number of threads per block:           1024
  Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
  Max dimension size of a grid size    (x,y,z): (2147483647, 65535, 65535)
  Maximum memory pitch:                          2147483647 bytes
  Texture alignment:                             512 bytes
  Concurrent copy and kernel execution:          Yes with 2 copy engine(s)
  Run time limit on kernels:                     Yes
  Integrated GPU sharing Host Memory:            No
  Support host page-locked memory mapping:       Yes
  Alignment requirement for Surfaces:            Yes
  Device has ECC support:                        Disabled
  Device supports Unified Addressing (UVA):      Yes
  Device PCI Domain ID / Bus ID / location ID:   0 / 1 / 0
  Compute Mode:
     < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >

deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 8.0, CUDA Runtime Version = 8.0, NumDevs = 1, Device0 = GeForce GTX 1080
Result = PASS

我能够在CUDA8上重现观察结果,但在CUDA9上无法重现。然而,我不认为这里有任何问题,即使是CUDA 8。让我们从更仔细地查看文档开始:

发件人:

类似地,它将为输出中的每个点调用存储回调例程一次,并且仅调用一次

您假设输出中的每个点都有一个对应的唯一值
index
,传递给存储回调例程,但是我们很快就会看到情况并非如此

它将只调用最后阶段内核中的存储回调例程

因此,在转换的最后阶段,我们可以从多个独立的内核调用存储回调例程(注意使用内核)

对于某些配置,线程可能以任何顺序加载或存储输入或输出,而cuFFT不保证由给定线程处理的输入或输出是连续的。这些特性可能随变换大小、变换类型(例如C2C与C2R)、维数和GPU体系结构而变化。这些变体也可能随着库版本的变化而变化

这提供了一些额外的线索,我们不应该期望在任何情况下都对所有输出数据进行良好的连续处理。指示的可变性可能取决于精确的变换参数以及袖口库版本

所以,让我们开始讨论具体问题。CUFFT是否在每个输出点多次调用存储回调?事实并非如此。为了证明这一点,让我们修改您的存储回调,如下所示:

static __device__ void stor_cb(void *a, size_t index, cufftComplex z,
                               void *cb_info, void *sharedmem) {

    // Print the index. Each index should appear exactly once.
    //if (index < 20) printf("%8llu, %p, \n", index, a);
    cufftComplex temp = ((cufftComplex *)a)[index];
    temp.x++;
    ((cufftComplex *)a)[index] = temp;
    // Do the store
    //((cufftComplex *)a)[index] = z;
    if (index < 20) printf("%8llu, %p, %f\n", index, a, temp.x);

}
当我在CUDA 8、linux和cc3.5设备(特斯拉K20x)上编译并运行修改后的代码时,输出如下:

$ nvcc -arch=sm_35 -o t20 t20.cu -rdc=true -lcufft_static -lcudadevrt -lculibos
$ ./t20
       0, 0x2305b5f800, 1.000000
       1, 0x2305b5f800, 1.000000
       2, 0x2305b5f800, 1.000000
       3, 0x2305b5f800, 1.000000
       4, 0x2305b5f800, 1.000000
       5, 0x2305b5f800, 1.000000
       6, 0x2305b5f800, 1.000000
       7, 0x2305b5f800, 1.000000
       8, 0x2305b5f800, 1.000000
       9, 0x2305b5f800, 1.000000
      10, 0x2305b5f800, 1.000000
      11, 0x2305b5f800, 1.000000
      12, 0x2305b5f800, 1.000000
      13, 0x2305b5f800, 1.000000
      14, 0x2305b5f800, 1.000000
      15, 0x2305b5f800, 1.000000
      16, 0x2305b5f800, 1.000000
      17, 0x2305b5f800, 1.000000
      18, 0x2305b5f800, 1.000000
      19, 0x2305b5f800, 1.000000
       0, 0x2305b7d800, 1.000000
       1, 0x2305b7d800, 1.000000
       2, 0x2305b7d800, 1.000000
       3, 0x2305b7d800, 1.000000
       4, 0x2305b7d800, 1.000000
       5, 0x2305b7d800, 1.000000
       6, 0x2305b7d800, 1.000000
       7, 0x2305b7d800, 1.000000
       8, 0x2305b7d800, 1.000000
       9, 0x2305b7d800, 1.000000
      10, 0x2305b7d800, 1.000000
      11, 0x2305b7d800, 1.000000
      12, 0x2305b7d800, 1.000000
      13, 0x2305b7d800, 1.000000
      14, 0x2305b7d800, 1.000000
      15, 0x2305b7d800, 1.000000
      16, 0x2305b7d800, 1.000000
      17, 0x2305b7d800, 1.000000
      18, 0x2305b7d800, 1.000000
      19, 0x2305b7d800, 1.000000
$
我们看到的是:

  • 是的,
    索引
    值是重复的,但是每个重复情况的基址(指针)是不同的。因此,即使重复
    索引
    值,输出点也只写入一次
  • 作为进一步的确认,如果我们不止一次写入一个输出点,对于我们的特定回调,我们希望看到输出增加到2.000000。但我们只看到了1.000000的产出。因此,没有一个输出点被多次写入
  • 我认为这种特殊的输出模式很可能是在转换的最后阶段由两个单独的内核调用产生的。这方面的一些进一步证据可能会从探查器中获得

    正如我在开始时提到的,我看到在这个测试用例中使用CUDA 9而不是CUDA 8时有不同的行为(只打印出一组从0到19的输出索引)。但是,正如前面提到的,文档中也考虑了这种可能性(从库版本到库版本的行为变化)

    预期后续问题:

    但是如果
    索引
    值不是唯一的,并且我想对基于
    索引
    变化的输出应用一些转换,我应该怎么做

    我认为这里的假设是,您打算应用于批处理转换输出的任何转换都应该只依赖于批处理中的索引位置。在这种假设下,我的期望是:

  • 索引的多核复制将始终在批边界上完成

  • 通过对传递给回调例程的
    索引
    值执行模批量大小操作,可以应用适当的转换


  • 我在没有证据的情况下提出这一点,也没有试图用文档来证实这一点,但鉴于已经涵盖的观察结果,这是唯一对我有意义的实现。一个要点是,如果您希望应用的转换因批次而异,那么这可能不是实现转换的方法(即通过回调)。然而,正如我已经提到的,CUDA9中的情况似乎发生了变化。如果您对此有任何顾虑,请随时在提交带有所需/预期行为(和/或文档更新请求)的RFE(错误报告),记住您的预期行为可能已经在CUDA 9中实现。

    我能够在CUDA 8上重现观察结果,但在CUDA 9上没有。然而,我不认为这里有任何问题,即使是CUDA 8。让我们从更仔细地查看文档开始:

    发件人:

    类似地,它将为输出中的每个点调用存储回调例程一次,并且仅调用一次

    您假设输出中的每个点都有一个对应的唯一值
    index
    ,传递给存储回调例程,但是我们很快就会看到情况并非如此

    它只会叫t
    static __device__ void stor_cb(void *a, size_t index, cufftComplex z,
                                   void *cb_info, void *sharedmem) {
    
        // Print the index. Each index should appear exactly once.
        //if (index < 20) printf("%8llu, %p, \n", index, a);
        cufftComplex temp = ((cufftComplex *)a)[index];
        temp.x++;
        ((cufftComplex *)a)[index] = temp;
        // Do the store
        //((cufftComplex *)a)[index] = z;
        if (index < 20) printf("%8llu, %p, %f\n", index, a, temp.x);
    
    }
    
    cudaMalloc(&out_buf_d, fft_sz*num_ffts*sizeof(cufftComplex));
    cudaMemset(out_buf_d, 0, fft_sz*num_ffts*sizeof(cufftComplex));  // add this
    
    $ nvcc -arch=sm_35 -o t20 t20.cu -rdc=true -lcufft_static -lcudadevrt -lculibos
    $ ./t20
           0, 0x2305b5f800, 1.000000
           1, 0x2305b5f800, 1.000000
           2, 0x2305b5f800, 1.000000
           3, 0x2305b5f800, 1.000000
           4, 0x2305b5f800, 1.000000
           5, 0x2305b5f800, 1.000000
           6, 0x2305b5f800, 1.000000
           7, 0x2305b5f800, 1.000000
           8, 0x2305b5f800, 1.000000
           9, 0x2305b5f800, 1.000000
          10, 0x2305b5f800, 1.000000
          11, 0x2305b5f800, 1.000000
          12, 0x2305b5f800, 1.000000
          13, 0x2305b5f800, 1.000000
          14, 0x2305b5f800, 1.000000
          15, 0x2305b5f800, 1.000000
          16, 0x2305b5f800, 1.000000
          17, 0x2305b5f800, 1.000000
          18, 0x2305b5f800, 1.000000
          19, 0x2305b5f800, 1.000000
           0, 0x2305b7d800, 1.000000
           1, 0x2305b7d800, 1.000000
           2, 0x2305b7d800, 1.000000
           3, 0x2305b7d800, 1.000000
           4, 0x2305b7d800, 1.000000
           5, 0x2305b7d800, 1.000000
           6, 0x2305b7d800, 1.000000
           7, 0x2305b7d800, 1.000000
           8, 0x2305b7d800, 1.000000
           9, 0x2305b7d800, 1.000000
          10, 0x2305b7d800, 1.000000
          11, 0x2305b7d800, 1.000000
          12, 0x2305b7d800, 1.000000
          13, 0x2305b7d800, 1.000000
          14, 0x2305b7d800, 1.000000
          15, 0x2305b7d800, 1.000000
          16, 0x2305b7d800, 1.000000
          17, 0x2305b7d800, 1.000000
          18, 0x2305b7d800, 1.000000
          19, 0x2305b7d800, 1.000000
    $