PAYATHON 2026

CUDA streams 分块计算 Mandelbrot 时为何出现像素误差?

数据小陈

结论

仅凭这段代码和现象,无法确定唯一原因。建议先检查分块是否覆盖了全部 xres 列。当前代码用 chunk = xres / nstr 做整数除法,余数会被丢掉。若 xres 不能被 nstr 整除,最后几列不会由任何 stream 计算,而且这段代码也没有将这些列复制回主机。

若 xres 能被 nstr 整除,代码中的分块范围与数组偏移是对应的。此时单个像素出现差异,也可能是分块后坐标计算的浮点运算顺序发生了变化,尤其当该像素靠近 Mandelbrot 集边界时。还需要结合 xres、nstr、ThpBlk 和像素索引进一步判断。

先检查分块是否完整

代码设置:

int chunk = xres / nstr;
int stride = chunk * yres;

每个 stream 处理 chunk 列,所有 stream 合计处理 nstr * chunk 列。若 xres % nstr != 0,就会有余列未处理。例如,xres 为 10、nstr 为 8 时,chunk 为 1,8 个 stream 只会处理 8 列。

可以用整除分块,让前几个 stream 各多处理一列:

int base = xres / nstr;
int remainder = xres % nstr;

int first_col = 0;
for (int s = 0; s < nstr; ++s) {
    int local_chunk = base + (s < remainder ? 1 : 0);
    int offset = first_col * yres;

    // 用 local_chunk 计算本 stream 的网格大小,
    // 并将对应的设备内存片段传给 kernel。

    first_col += local_chunk;
}

实现时,主机到设备、kernel 和设备到主机这三处都要使用同一个 local_chunk 和相应的列偏移;传给 kernel 的 xres 参数也应为 local_chunk。同时要确保 nstr <= xres,避免分块列数为零。

也可以只启用 xres / nstr 个整块,再单独处理余数列,但不能让余数列被遗漏。

核对数组布局和偏移

kernel 用 A[j*yres+i] 写入像素,也就是每列包含连续的 yres 个元素。主机代码用 offset = first_col * yres 定位分块起点,并复制 local_chunk * yres 个元素。按这种列优先布局,偏移与 kernel 索引一致。

如果主机侧其他代码按行优先布局访问数组,通常会使用类似 A[i*xres+j] 的索引。这时需要统一布局约定。不过,布局差异本身不能解释 streams 执行与非 streams 执行之间的结果差异,除非两条路径采用了不同的布局。

检查浮点坐标是否一致

分块 kernel 中的横坐标按以下方式计算:

double local_xmin = xmin + (s * chunk) * step_x_global;
double x = local_xmin + j * step_x;

未分块计算中的对应坐标通常是:

double x = xmin + (s * chunk + j) * step_x_global;

这两种写法在数学上等价,但浮点运算的舍入顺序不同,结果可能有细微差异。Mandelbrot 迭代会在 modZ < 4 时继续;如果坐标靠近边界,这种差异可能改变某个像素的迭代次数。

可以记录差异像素的 (j, i) 和迭代次数,再比较两种计算路径得到的该像素 x。如果像素靠近集合边界,且分块已覆盖所有列,舍入差异可以作为排查方向,但仅凭这一点还不能认定它就是原因。

建议的排查顺序

  1. 检查 xres % nstr,确认所有列都已覆盖。用小尺寸复现,并选择不能整除 xres 的 nstr。
  2. 确认每个 stream 的 offset、复制长度、kernel 参数和网格宽度都按该 stream 的实际列数计算。
  3. 对比有无 streams 两条路径的数组布局和差异像素索引。
  4. 检查 cudaStreamCreate、kernel launch 和 cudaStreamSynchronize 的错误返回值。当前代码片段只展示了部分 CUDA 错误检查,无法据此确认所有调用都成功。
  5. 分别比较同一像素的坐标和迭代次数,判断差异来自分块遗漏、索引问题还是浮点舍入。

主机数据通过 cudaMemcpyAsync 传输。若要依赖异步传输与 kernel 重叠执行,应确认主机缓冲区符合所用 CUDA 环境对异步传输的要求。传输语义或性能问题还需结合主机内存类型和实际执行情况核实。无论如何,异步传输本身不能替代对分块边界和覆盖范围的检查。

备注:内容仅供参考。