我能够在CUDA 8上重现观察结果,但在CUDA 9上无法重现。然而,我不认为这里有任何问题,即使是CUDA 8。让我们从更仔细地查看文档开始:
从…起
CUFFT doc section 2.9.4
:
类似地,它将为输出中的每个点调用存储回调例程一次,并且仅调用一次。
假设输出中的每个点都有相应的唯一值
index
传递给存储回调例程,但是我们很快就会看到情况并非如此。
它将只调用最后阶段内核中的存储回调例程。
因此,我们可以从多个独立的内核调用存储回调例程(注意使用
内核
),在转换的最后阶段。
对于某些配置,线程可能以任何顺序加载或存储输入或输出,而cuFFT并不保证由给定线程处理的输入或输出是连续的。这些特征可能随变换大小、变换类型(例如C2C与C2R)、维数和GPU架构而变化。这些变体也可能会随着库版本的变化而变化。
这提供了一些额外的线索,我们不应该期望在每种情况下都对所有输出数据进行良好的连续处理。所示的可变性可能取决于精确的变换参数以及袖套库版本。
那么,让我们开始讨论具体问题。CUFFT是否在每个输出点多次调用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, %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);
}
该存储回调不会写入预期的输出,只会将给定的输出点增加1。此外,不只是打印
指数
我们将打印出
指数
,加上基址
a
,加上我们增加的实际值。为了使这一切正常工作,我们需要将整个输出数组预初始化为零:
cudaMalloc(&out_buf_d, fft_sz*num_ffts*sizeof(cufftComplex));
cudaMemset(out_buf_d, 0, fft_sz*num_ffts*sizeof(cufftComplex));
当我在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的输出索引)然而,如前所述,文档中也考虑了这种可能性(从库版本到库版本的行为变化)。
预期后续问题:
但是如果
指数
值不是唯一的,我想对输出应用一些转换,这些转换根据
指数
,我该怎么办?
我认为这里的假设是,您打算应用于
成批的
变换应仅取决于索引位置
批次内
. 在这种假设下,我的期望是:
-
索引的多核复制总是在批边界上进行。
-
可以通过对传递的数据执行模批量操作来应用适当的转换
指数
回调例程的值。
我在没有证据的情况下提出这一点,也没有试图用文档来证实这一点,但鉴于已经涵盖的观察结果,这是唯一对我有意义的实现。一个优点是,如果您希望应用的转换因批次而异,那么这可能不是实现转换的方法(即通过回调)。然而,正如我已经提到的,CUDA 9中的情况似乎发生了变化。如果您对此有任何顾虑,请随时在以下位置提交带有所需/预期行为(和/或文档更新请求)的RFE(错误报告)
http://developer.nvidia.com
,请记住,您的预期行为可能已经在CUDA 9中实现。