我怎么知道我的 Embarrassingly Parallel 任务是否适合 GPU?
How do I know if my Embarassingly Parallel Task is Suitable for GPU?
我们是说需要对大量行进行每行相当轻的计算的任务根本不适合 GPU 吗?
我需要在行独立的 table 上进行一些数据处理。所以它是令人尴尬的平行。我有一个 GPU,所以......天作之合?它与此示例非常相似,它计算每行每个条目的移动平均值(行是独立的。)
import numpy as np
from numba import guvectorize
@guvectorize(['void(float64[:], intp[:], float64[:])'], '(n),()->(n)')
def move_mean(a, window_arr, out):
window_width = window_arr[0]
asum = 0.0
count = 0
for i in range(window_width):
asum += a[i]
count += 1
out[i] = asum / count
for i in range(window_width, len(a)):
asum += a[i] - a[i - window_width]
out[i] = asum / count
arr = np.arange(2000000, dtype=np.float64).reshape(200000, 10)
print(arr)
print(move_mean(arr, 3))
像这个例子一样,我对每一行的处理不是很数学。相反,它在行中循环并执行一些求和、赋值和其他零散的操作,并加入一些条件逻辑。
我尝试在 Numba 库中使用 guVectorize 将其分配给 Nvidia GPU。它工作正常,但我没有得到加速。
这种任务原则上适合GPU吗?也就是说,如果我更深入地研究 Numba 并开始调整线程、块和内存管理或算法实现,理论上我应该得到加速。或者,这种问题根本上只是不适合架构。
下面的答案似乎表明它不适合,但我还不太相信。
和
您的任务显然受内存限制,但这并不意味着您不能从 GPU 中获利,但它可能不如 CPU 受限任务直接。
让我们看看常见配置并做一些数学运算:
- CPU-大约 RAM 内存带宽。 24GB/秒
- CPU-大约 GPU 传输带宽。 8GB/秒
- 大约 GPU-RAM 内存带宽。 180GB/秒
假设我们需要传输 24 GB 的数据来完成任务,那么我们将有以下最佳时间(是否以及如何达到这些时间是另一个问题!):
- 场景:仅CPU时间= 24GB/24GB/s = 1秒。
- 场景:数据必须从 CPU 传输到 GPU(24GB/8GB/s = 3 秒)并在那里处理(24GB/180GB/s = 0.13 秒)导致 3.1 秒。
- 场景:数据已经在设备上,所以只需要24GB/180GB/s = 0.13秒。
如您所见,存在加速的潜力,但仅限于第 3 种情况 - 当您的数据已经在 GPU 设备上时。
然而,实现最大带宽是一项颇具挑战性的事业。
例如,在 CPU 上按行处理矩阵时,您希望数据处于行优先顺序(C 顺序)以便充分利用L1 缓存:在读取 double 时,您实际上将 8 个 double 加载到缓存中,并且您不希望在处理剩余的 7 个之前将它们从缓存中逐出。
另一方面,在 GPU 上,您希望内存访问为 coalesced,例如线程 0
应该访问地址 0
,线程 1
- 地址 1
等等。为此,数据必须按列优先顺序(Fortran 顺序)。
还有一件事需要考虑:测试性能的方式。您的测试阵列只有大约 2MB 大,因此对于 L3 缓存来说足够小了。 L3 缓存的带宽取决于用于计算的内核数量,但至少会在 100GB/s 左右——不会比 GPU 慢很多,并且在 CPU.
上并行化时可能会快得多
您需要更大的数据集才能不被缓存行为所愚弄。
有点跑题的说法:从数值的角度来看,你的算法不是很稳健。
如果 window 宽度为 3,如您的示例所示,但一行中大约有 10**4
个元素。因此对于最后一个元素,该值是大约 10**4
加法和减法的结果,每个加法和减法都会为该值添加一个舍入误差 - 与仅完成三个加法 "naively" 相比,这是很大的不同.
当然,它可能并不重要(如您的示例中连续 10 个元素),但也可能有一天会咬到您...
我们是说需要对大量行进行每行相当轻的计算的任务根本不适合 GPU 吗?
我需要在行独立的 table 上进行一些数据处理。所以它是令人尴尬的平行。我有一个 GPU,所以......天作之合?它与此示例非常相似,它计算每行每个条目的移动平均值(行是独立的。)
import numpy as np
from numba import guvectorize
@guvectorize(['void(float64[:], intp[:], float64[:])'], '(n),()->(n)')
def move_mean(a, window_arr, out):
window_width = window_arr[0]
asum = 0.0
count = 0
for i in range(window_width):
asum += a[i]
count += 1
out[i] = asum / count
for i in range(window_width, len(a)):
asum += a[i] - a[i - window_width]
out[i] = asum / count
arr = np.arange(2000000, dtype=np.float64).reshape(200000, 10)
print(arr)
print(move_mean(arr, 3))
像这个例子一样,我对每一行的处理不是很数学。相反,它在行中循环并执行一些求和、赋值和其他零散的操作,并加入一些条件逻辑。
我尝试在 Numba 库中使用 guVectorize 将其分配给 Nvidia GPU。它工作正常,但我没有得到加速。
这种任务原则上适合GPU吗?也就是说,如果我更深入地研究 Numba 并开始调整线程、块和内存管理或算法实现,理论上我应该得到加速。或者,这种问题根本上只是不适合架构。
下面的答案似乎表明它不适合,但我还不太相信。
和
您的任务显然受内存限制,但这并不意味着您不能从 GPU 中获利,但它可能不如 CPU 受限任务直接。
让我们看看常见配置并做一些数学运算:
- CPU-大约 RAM 内存带宽。 24GB/秒
- CPU-大约 GPU 传输带宽。 8GB/秒
- 大约 GPU-RAM 内存带宽。 180GB/秒
假设我们需要传输 24 GB 的数据来完成任务,那么我们将有以下最佳时间(是否以及如何达到这些时间是另一个问题!):
- 场景:仅CPU时间= 24GB/24GB/s = 1秒。
- 场景:数据必须从 CPU 传输到 GPU(24GB/8GB/s = 3 秒)并在那里处理(24GB/180GB/s = 0.13 秒)导致 3.1 秒。
- 场景:数据已经在设备上,所以只需要24GB/180GB/s = 0.13秒。
如您所见,存在加速的潜力,但仅限于第 3 种情况 - 当您的数据已经在 GPU 设备上时。
然而,实现最大带宽是一项颇具挑战性的事业。
例如,在 CPU 上按行处理矩阵时,您希望数据处于行优先顺序(C 顺序)以便充分利用L1 缓存:在读取 double 时,您实际上将 8 个 double 加载到缓存中,并且您不希望在处理剩余的 7 个之前将它们从缓存中逐出。
另一方面,在 GPU 上,您希望内存访问为 coalesced,例如线程 0
应该访问地址 0
,线程 1
- 地址 1
等等。为此,数据必须按列优先顺序(Fortran 顺序)。
还有一件事需要考虑:测试性能的方式。您的测试阵列只有大约 2MB 大,因此对于 L3 缓存来说足够小了。 L3 缓存的带宽取决于用于计算的内核数量,但至少会在 100GB/s 左右——不会比 GPU 慢很多,并且在 CPU.
上并行化时可能会快得多您需要更大的数据集才能不被缓存行为所愚弄。
有点跑题的说法:从数值的角度来看,你的算法不是很稳健。
如果 window 宽度为 3,如您的示例所示,但一行中大约有 10**4
个元素。因此对于最后一个元素,该值是大约 10**4
加法和减法的结果,每个加法和减法都会为该值添加一个舍入误差 - 与仅完成三个加法 "naively" 相比,这是很大的不同.
当然,它可能并不重要(如您的示例中连续 10 个元素),但也可能有一天会咬到您...