【问题标题】:Parallelize loops using OpenCL in Python在 Python 中使用 OpenCL 并行化循环
【发布时间】:2019-01-12 08:24:30
【问题描述】:

我在矩阵 y 中有一个给定的数据集,我想用它训练不同的 SOM。 SOM 是一维的(一条线),其神经元数量各不相同。我首先训练了一个大小为 N=2 的 SOM,最后训练了 N=NMax,总共得到了 NMax-2+1 SOM。对于每个 SOM,我想在训练结束后存储权重,然后再转到下一个 SOM。

在这里使用 PyOpenCL 的全部意义在于每个外部循环都独立于其他循环。也就是说,对于N 的每个值,脚本并不关心当N 取其他值时会发生什么。运行脚本NMax-2+1 手动更改N 的值可能会得到相同的结果。

考虑到这一点,我希望能够使用 GPU 同时执行这些独立迭代中的每一个,从而显着减少所花费的时间。不过速度的提升会小于1/(NMax-2+1),因为每次迭代都比之前的更昂贵,至于N 的值越大,计算就越多。

有没有办法“翻译”这段代码以在 GPU 上运行?我以前从未使用过 OpenCL,因此请告诉我这是否过于宽泛或愚蠢,以便我可以提出更具体的问题。该代码是自包含的,因此请随意尝试。开头声明的四个常量可以更改为您喜欢的任何值(假设NMax > 1 和所有其他都是严格正数)。

import numpy as np
import time

m = 3 # Dimension of datapoints
num_points = 2000 # Number of datapoints
iterMax = 150 # Maximum number of iterations
NMax = 3 # Maximum number of neurons
#%%
np.random.seed(0)
y = np.random.rand(num_points,m) # Generate always the same dataset
sigma_0 = 5 # Initial value of width of the neighborhood function
eta_0 = 1 # Initial value of learning rate
w = list(range(NMax - 1))
wClusters = np.zeros((np.size(y,axis = 0),NMax - 1)) # Clusters for each N

t_begin = time.clock() # Start time
for N in range(NMax-1): # Number of neurons for this iteration
    w[N] = np.random.uniform(0,1,(N+2,np.size(y,axis=1))) - 0.5 # Initialize weights
    iterCount = 1
    while iterCount < iterMax:
        # Mix up the input patterns
        mixInputs = y[np.random.permutation(np.size(y,axis = 0)),:]
        # Sigma reduction
        sigma = sigma_0 - (sigma_0/(iterMax + 1)) * iterCount
        s2 = 2*sigma**2
        # Learning rate reduction
        eta = eta_0 - (eta_0/(iterMax + 1)) * iterCount
        for selectedInput in mixInputs: # Pick up one pattern
            # Search winning neuron
            aux = np.sum((selectedInput - w[N])**2, axis = -1)
            ii = np.argmin(aux) # Neuron 'ii' is the winner
            jjs = abs(ii - list(range(N+2)))
            dists = np.min(np.vstack([jjs , abs(jjs-(N+2))]), axis = 0)
            # Update weights
            w[N] = w[N] + eta * np.exp((-dists**2)/s2).T[:,np.newaxis] * (selectedInput - w[N])
        print(N+2,iterCount)
        iterCount += 1    
    # Assign each datapoint to its nearest neuron
    for kk in range(np.size(y,axis = 0)):
        aux = np.sum((y[kk,] - w[N])**2,axis=-1)
        ii = np.argmin(aux) # Neuron 'ii' is the winner
        wClusters[kk,N] = ii + 1
t_end = time.clock() # End time
#%%
print(t_end - t_begin)

【问题讨论】:

    标签: python parallel-processing opencl gpu pyopencl


    【解决方案1】:

    我试图给出一个比较完整的答案。

    首先:

    此代码是否可以使用 (py)OpenCL 在 GPU 上运行?

    很可能是的。

    这可以自动完成吗?

    不(afaik)。

    我收到的关于 OpenCL 的大部分问题都类似于:“是否值得将这段代码移植到 OpenCL 以获得加速收益?”您说的是,您的外循环独立于其他运行的结果,这使得代码基本上可并行化。在一个简单的实现中,每个 OpenCL 工作元素将执行相同的代码,但输入参数略有不同。不考虑主机和设备之间数据传输的开销,这种方法的运行时间将等于最慢迭代的运行时间。根据外部循环中的迭代,这可能是一个巨大的速度增益。只要数字保持相对较小,您就可以尝试使用 python 中的 multiprocessing 模块在 CPU 而不是 GPU 上并行化这些迭代。

    移植到 GPU 通常只有在要并行运行大量进程(大约 1000 个或更多)时才有意义。所以在你的情况下,如果你真的想要一个巨大的速度提升,看看你是否可以并行化所有计算inside循环。例如,您有 150 次迭代和 2000 个数据点。如果您能以某种方式并行化这 2000 个数据点,则可以提供更大的速度增益,这可以证明将整个代码移植到 OpenCL 的工作是合理的。

    TL;DR: 首先尝试在 CPU 上并行化。如果您发现需要同时运行数百个以上的进程,请迁移到 GPU。

    更新:使用多处理(无回调)在 CPU 上进行并行化的简单代码

    import numpy as np
    import time
    import multiprocessing as mp
    
    m = 3 # Dimension of datapoints
    num_points = 2000 # Number of datapoints
    iterMax = 150 # Maximum number of iterations
    NMax = 10 # Maximum number of neurons
    #%%
    np.random.seed(0)
    y = np.random.rand(num_points,m) # Generate always the same dataset
    sigma_0 = 5 # Initial value of width of the neighborhood function
    eta_0 = 1 # Initial value of learning rate
    w = list(range(NMax - 1))
    wClusters = np.zeros((np.size(y,axis = 0),NMax - 1)) # Clusters for each N
    
    def neuron_run(N):
        w[N] = np.random.uniform(0,1,(N+2,np.size(y,axis=1))) - 0.5 # Initialize weights
        iterCount = 1
        while iterCount < iterMax:
            # Mix up the input patterns
            mixInputs = y[np.random.permutation(np.size(y,axis = 0)),:]
            # Sigma reduction
            sigma = sigma_0 - (sigma_0/(iterMax + 1)) * iterCount
            s2 = 2*sigma**2
            # Learning rate reduction
            eta = eta_0 - (eta_0/(iterMax + 1)) * iterCount
            for selectedInput in mixInputs: # Pick up one pattern
                # Search winning neuron
                aux = np.sum((selectedInput - w[N])**2, axis = -1)
                ii = np.argmin(aux) # Neuron 'ii' is the winner
                jjs = abs(ii - list(range(N+2)))
                dists = np.min(np.vstack([jjs , abs(jjs-(N+2))]), axis = 0)
                # Update weights
                w[N] = w[N] + eta * np.exp((-dists**2)/s2).T[:,np.newaxis] * (selectedInput - w[N])
            print(N+2,iterCount)
            iterCount += 1    
        # Assign each datapoint to its nearest neuron
        for kk in range(np.size(y,axis = 0)):
            aux = np.sum((y[kk,] - w[N])**2,axis=-1)
            ii = np.argmin(aux) # Neuron 'ii' is the winner
            wClusters[kk,N] = ii + 1
    
    t_begin = time.clock() # Start time   
    #%%
    
    def apply_async():
        pool = mp.Pool(processes=NMax)
        for N in range(NMax-1):
            pool.apply_async(neuron_run, args = (N,))
        pool.close()
        pool.join()
        print "Multiprocessing done!"
    
    if __name__ == '__main__':
        apply_async()
    
    t_end = time.clock() # End time 
    print(t_end - t_begin)
    

    【讨论】:

    • 使用 multiprocessing 在 CPU 上并行化甚至比非并行化还要慢。这有意义吗?
    • 原则上不应该这样。在某些情况下,调度和开销(启动线程/任务/进程)以及内存管理是瓶颈,并行程序速度较慢。您能否确认(在您的任务管理器中)并行程序正在所有内核上运行?如果只使用一个核心,那可能就是问题所在。如果您想了解更多信息,也可以给我发邮件解决您的具体问题,我觉得在这个问题的范围内回答起来很复杂。
    • 更新:您的代码在我的系统上运行 4 个神经元约 38 秒,并行化约 13 秒(在我的答案中编辑了并行代码)
    • 在 16 个内核上,发布的代码在大约 30 秒内运行 32 个神经元。
    • 再提一句:代码中的大部分时间都花在了搜索获胜神经元的块中。 (对于 mixInputs 的循环)。你应该开始加速这个块(可能是并行化)。
    猜你喜欢
    • 1970-01-01
    • 2017-03-17
    • 2020-09-14
    • 1970-01-01
    • 2016-09-29
    • 1970-01-01
    相关资源
    最近更新 更多