如何将输入矩阵拆分为子矩阵以进行并行GPU处理?

时间:2018-05-17 13:46:11

标签: python cuda numba

我是stackoverflow的新手,我很高兴发布我的第一个问题。我实际上是编程世界的新手,但我还是有一些基本知识,我实际上是用Python来解决分类问题。我有一大组数据(在我的实际问题中n = 60326),每个向量大约有416维。我需要计算这些不同向量之间的曼哈顿距离,以便根据相似性对我的数据集进行分类(采用随机参考向量并将最接近的向量合并到0到1之间的距离范围)我已经熟悉了Kmeans和基本的ML聚类算法...我的实际问题是我需要使用GPU(CUDA)来加速时间,以便在开始分类之前初始计算距离矩阵,其大小为n²(60326 x 60326,我们可以将其减小到n²/ 2因为它是一个对称矩阵)所以我的实际问题是如何在这种情况下实现CUDA,我已经使用ANACONDA安装了CUDA包。

我从并行CPU的处理开始,它提供了内存错误

import pandas as pd
import time  
import time
import numpy as np
import random
from scipy.spatial import distance
from sklearn.metrics.pairwise import pairwise_distances
signatures=pd.read_csv("C:\\Users\\YMH1\\Documents\\Reduce Java code\\BOBST.txt", sep=' ',header=None,usecols=[*range(2,417)])
PartName = pd.read_csv('C:\\Users\\YMH1\\Documents\\Reduce Java code\\misumi_new.txt', sep=' ', header=None, usecols=[*range(0,1)])
signatures_vector=np.array(signatures)
PartName_vector=np.array(PartName)
D = pairwise_distances(X = signatures_vector, metric = 'manhattan', n_jobs = -1)
print(D)

现在,我正在尝试实施CUDA,因为它加快了时间,所以我编写了这个:

from __future__ import division
from numba import cuda, float32
import pandas as pd
import math
signatures=pd.read_csv("C:\\Users\\YMH1\\Documents\\Reduce Java code\\BOBST.txt", sep=';',header=None,usecols=[*range(2,418)])
signatures_vector=np.array(signatures)
@cuda.jit
def manhattan(an_array):
  x, y = cuda.grid(2)
  "Here we define the Manhattan distance"
  return an_array
data=signatures_vector
threadsperblock = (16, 16)
blockspergrid_x = math.ceil(data.shape[0] / threadsperblock[0])
blockspergrid_y = math.ceil(data.shape[1] / threadsperblock[1])
blockspergrid = (blockspergrid_x, blockspergrid_y)
manhattan[blockspergrid, threadsperblock](data)
print(data)

我的问题是如何在我们使用CUDA时定义曼哈顿标准,在曼哈顿城堡内进行哪些修改 非常感谢你

1 个答案:

答案 0 :(得分:0)

  

我的问题是如何在我们使用CUDA时定义曼哈顿标准,在曼哈顿城堡内进行哪些修改

根据documentationmanhattan距离度量是两个向量之间元素差异绝对值的总和。

您可能遇到的一个问题是内存空间问题。如果我们假设距离度量的输出(即矩阵元素)表示为普通的蟒蛇数量,则这可能占用8个字节的内存。对于规定的尺寸(60326),这意味着矩阵将占用60326 * 60326 * 8字节,几乎为30GB。即使我们假设您只存储了一半,即使我们假设32位的绝对差值总和,这仍然需要超过7GB的存储空间。

当我尝试使用sckit-learn方法运行这样的测试时,即使在具有128GB系统内存的计算机上也遇到了麻烦:

# cat t5.py
import numpy as np
import random
from scipy.spatial import distance
from sklearn.metrics.pairwise import pairwise_distances

vector_length = 416
num_signatures = 60000
sig_pattern = np.array([0,1,2,3], dtype=np.float32).reshape(4,1)
signatures = np.tile(sig_pattern,(num_signatures, vector_length//sig_pattern.shape[1]))
E = pairwise_distances(signatures, metric = 'manhattan', n_jobs = -1)
print(E[:8,:8])
# time python t5.py

Traceback (most recent call last):
  File "t5.py", line 17, in <module>
    E = pairwise_distances(signatures, metric = 'manhattan', n_jobs = -1)
  File "/root/anaconda2/lib/python2.7/site-packages/sklearn/metrics/pairwise.py", line 1247, in pairwise_distances
    return _parallel_pairwise(X, Y, func, n_jobs, **kwds)
  File "/root/anaconda2/lib/python2.7/site-packages/sklearn/metrics/pairwise.py", line 1096, in _parallel_pairwise
    for s in gen_even_slices(Y.shape[0], n_jobs))
  File "/root/anaconda2/lib/python2.7/site-packages/sklearn/externals/joblib/parallel.py", line 789, in __call__
    self.retrieve()
  File "/root/anaconda2/lib/python2.7/site-packages/sklearn/externals/joblib/parallel.py", line 699, in retrieve
    self._output.extend(job.get(timeout=self.timeout))
  File "/root/anaconda2/lib/python2.7/multiprocessing/pool.py", line 572, in get
    raise self._value
multiprocessing.pool.MaybeEncodingError: Error sending result:
'[array([[ 0.,  416.,  832., ...,  416.,  832., 1248.],
       [ 416.,    0.,  416., ...,    0.,  416.,  832.],
       [ 832.,  416.,    0., ...,  416.,    0.,  416.],
       ...,
       [ 416.,    0.,  416., ...,    0.,  416.,  832.],
       [ 832.,  416.,    0., ...,  416.,    0.,  416.],
       [1248.,  832.,  416., ...,  832.,  416.,    0.]])]'. Reason: 'OverflowError('cannot serialize a string larger than 2 GiB',)'

real    31m47.361s
user    405m28.155s
sys     8m19.851s

在那种情况下,在我的机器上计算大约需要30分钟。输出测试矩阵看起来大致正确,但python引发了错误,因为某些中间表示大于2GB。

内存大小问题也是在numba / cuda实现中需要考虑的重要问题之一。

然而,要执行的操作相对简单。根据我的测试,它可以比numpy / scikit-learn方法快得多。

这是一个有效的例子:

# cat t4.py
import numpy as np
import numba as nb
from numba import cuda,float32

vector_length = 416
num_vecs_per_block = 8
sm_size = vector_length*num_vecs_per_block
#num_sig must be divisible by num_vecs_per_block
@cuda.jit('void(float32[:,:], float32[:,:], int32, int32)')
def manhattan(signatures, distances, num_sig, vec_len):
    sm = cuda.shared.array(sm_size, float32)
    temp = cuda.local.array(num_vecs_per_block, dtype = float32)
    bid = cuda.blockIdx.x
    tid = cuda.threadIdx.x
    bdim = cuda.blockDim.x
# load shared memory with vectors for comparison
    if tid < num_vecs_per_block:
        for i in range(vec_len):
            sm[i*num_vecs_per_block+tid] = signatures[i, bid*num_vecs_per_block+tid];
    cuda.syncthreads()
#block-stride loop through the vector array
# the addition below to tid results in only elements above the diagonal being computed
# since the output matrix is symmetric
    svec = tid +(bid*num_vecs_per_block)
    while svec < num_sig:
        for i in range(num_vecs_per_block):
            temp[i] = 0
        for i in range(vec_len):
            src = signatures[i,svec]
            for j in range(num_vecs_per_block):
                #elementwise difference
                sdist = src - sm[i*num_vecs_per_block + j]
                #absolute value
                if sdist < 0:
                    sdist *= -1
                #sum
                temp[j] += sdist
        for i in range(num_vecs_per_block):
            distances[bid*num_vecs_per_block+i, svec] = temp[i]
        svec += bdim

num_signatures = 60000
sig_pattern = np.array([0,1,2,3], dtype=np.float32)
signatures = np.tile(sig_pattern,(num_signatures//sig_pattern.shape[0], vector_length))
distances  = np.zeros((num_signatures, num_signatures), dtype=np.float32)
threadsperblock = 1024
blockspergrid   = num_signatures//num_vecs_per_block
d_signatures = cuda.to_device(signatures)
d_distances = cuda.device_array_like(distances)
manhattan[blockspergrid, threadsperblock](d_signatures, d_distances, num_signatures, vector_length)
d_distances.copy_to_host(distances)
print(distances[:16,:16])
# time python t4.py
[[   0.  416.  832. 1248.    0.  416.  832. 1248.    0.  416.  832. 1248.    0.  416.  832. 1248.]
 [ 416.    0.  416.  832.  416.    0.  416.  832.  416.    0.  416.  832.  416.    0.  416.  832.]
 [ 832.  416.    0.  416.  832.  416.    0.  416.  832.  416.    0.  416.  832.  416.    0.  416.]
 [1248.  832.  416.    0. 1248.  832.  416.    0. 1248.  832.  416.    0. 1248.  832.  416.    0.]
 [   0.  416.  832. 1248.    0.  416.  832. 1248.    0.  416.  832. 1248.    0.  416.  832. 1248.]
 [ 416.    0.  416.  832.  416.    0.  416.  832.  416.    0.  416.  832.  416.    0.  416.  832.]
 [ 832.  416.    0.  416.  832.  416.    0.  416.  832.  416.    0.  416.  832.  416.    0.  416.]
 [1248.  832.  416.    0. 1248.  832.  416.    0. 1248.  832.  416.    0. 1248.  832.  416.    0.]
 [   0.    0.    0.    0.    0.    0.    0.    0.    0.  416.  832. 1248.    0.  416.  832. 1248.]
 [   0.    0.    0.    0.    0.    0.    0.    0.  416.    0.  416.  832.  416.    0.  416.  832.]
 [   0.    0.    0.    0.    0.    0.    0.    0.  832.  416.    0.  416.  832.  416.    0.  416.]
 [   0.    0.    0.    0.    0.    0.    0.    0. 1248.  832.  416.    0. 1248.  832.  416.    0.]
 [   0.    0.    0.    0.    0.    0.    0.    0.    0.  416.  832. 1248.    0.  416.  832. 1248.]
 [   0.    0.    0.    0.    0.    0.    0.    0.  416.    0.  416.  832.  416.    0.  416.  832.]
 [   0.    0.    0.    0.    0.    0.    0.    0.  832.  416.    0.  416.  832.  416.    0.  416.]
 [   0.    0.    0.    0.    0.    0.    0.    0. 1248.  832.  416.    0. 1248.  832.  416.    0.]]

real    0m11.580s
user    0m5.579s
sys     0m5.922s
#

在这种情况下,我将signatures向量限制为60000,而不是60320,因为较大的数字导致输出矩阵太大而无法放入我的16GB Tesla P100 GPU的可用内存中。 如果您的GPU内存少于16GB,则此代码无法按原样运行。您需要将问题缩小到较少数量的签名向量。将矢量分成两组,并在两组之间进行距离计算,填写整个矩阵,应该相对简单。

然而,我的测试机器上的numba代码在大约11秒内运行,并且似乎为我打印的非常小的16x16输出矩阵产生了相同的结果。

如果我对这段代码进行了剖析,我实际上发现GPU内核在大约3秒内运行,从GPU到CPU的巨大输出矩阵的数据传输时间大约需要6秒,剩下的大约需要2秒钟。 python开销。

实际的GPU算法是面向块的。每个块负责将8个向量与整个向量数组进行比较。每个块首先将8个向量加载到共享内存中,然后遍历整个向量数组,计算与这8个向量中的每一个相对应的曼哈顿距离。该块使用块跨步循环来遍历整个阵列。在每次循环迭代结束时,对应于当前比较的8曼哈顿距离被写入输出数组。

此外,代码有一个细微的变化,因此只计算对角线以上的矩阵输出值。由于计算是按块完成的,因此不计算在对角线上方没有元素的块。这会将处理时间缩短一半左右,但完整输出矩阵仍在内存中。出于这个原因,我上面的16x16输出的左下象限都是零,因为8x8象限完全是&#34;低于&#34;由于问题中已经指出的相似性情况,因此跳过了对角线的处理。