如何在CUDA中定义曼哈顿范数以实现大矩阵GPU并行距离计算?
如何用Numba CUDA实现曼哈顿距离矩阵计算?
我是StackOverflow的新用户,很高兴发布我的第一个问题。我刚接触编程领域,但具备一些基础知识,目前正在用Python解决一个分类问题。我拥有一个大型数据集(实际场景中n=60326),每个向量约有416个维度。我需要计算这些向量之间的曼哈顿距离,以便基于相似度对数据集进行分类(选取随机参考向量,合并距离在0到1之间的最近向量)。我已了解Kmeans及基础机器学习聚类算法……
当前的问题是,我需要使用GPU(CUDA)来加速分类前的距离矩阵计算,该矩阵大小为n²(60326×60326,因是对称矩阵可简化为n²/2)。我已通过Anaconda安装了CUDA包。
我最初尝试CPU并行处理,但出现了内存错误:
import pandas as pd import time import numpy as np import random from scipy.spatial import distance from sklearn.metrics.pairwise import pairwise_distances signatures = pd.read_csv(r"C:\Users\YMH1\Documents\Reduce Java code\BOBST.txt", sep=' ', header=None, usecols=[*range(2,417)]) PartName = pd.read_csv(r'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(r"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时如何定义曼哈顿范数,需要对manhattan函数内部做哪些修改?非常感谢。
你的CUDA代码问题分析与修正方案
首先,你的现有CUDA代码有几个关键问题:
- 核函数没有接收输出矩阵的参数,也没有实际计算曼哈顿距离,只是返回了输入数组
- 网格和线程块的维度设置错误:你需要计算的是n×n的距离矩阵,所以应该基于输入向量的数量(
data.shape[0])来设置,而不是向量的维度(data.shape[1]) - 没有处理GPU内存的传输,直接用CPU数组传入核函数会导致效率低下甚至错误
下面是修正后的完整实现,我会一步步解释:
1. 完整的Numba CUDA曼哈顿距离计算代码
from __future__ import division from numba import cuda, float32 import pandas as pd import numpy as np import math # 读取数据(注意用原始字符串避免转义问题) signatures = pd.read_csv(r"C:\Users\YMH1\Documents\Reduce Java code\BOBST.txt", sep=';', header=None, usecols=[*range(2,418)]) signatures_vector = signatures.values.astype(np.float32) # 转为float32节省GPU内存 n_samples = signatures_vector.shape[0] n_features = signatures_vector.shape[1] # 定义CUDA核函数:计算所有向量对的曼哈顿距离 @cuda.jit def manhattan_distance_kernel(input_matrix, output_matrix): # 获取当前线程的全局索引:i对应第i个向量,j对应第j个向量 i, j = cuda.grid(2) # 检查索引是否在有效范围内(避免越界访问) if i < input_matrix.shape[0] and j < input_matrix.shape[0]: distance = 0.0 # 遍历所有维度,计算曼哈顿距离:sum(|x_i - x_j|) for k in range(input_matrix.shape[1]): distance += abs(input_matrix[i, k] - input_matrix[j, k]) # 将结果写入输出矩阵 output_matrix[i, j] = distance # 设置线程块和网格大小(通常选择32x32或16x16的线程块,根据GPU性能调整) threads_per_block = (16, 16) blocks_per_grid_x = math.ceil(n_samples / threads_per_block[0]) blocks_per_grid_y = math.ceil(n_samples / threads_per_block[1]) blocks_per_grid = (blocks_per_grid_x, blocks_per_grid_y) # 分配GPU内存:输入数据传到GPU,输出矩阵在GPU上初始化 d_input = cuda.to_device(signatures_vector) d_output = cuda.device_array((n_samples, n_samples), dtype=np.float32) # 启动核函数 manhattan_distance_kernel[blocks_per_grid, threads_per_block](d_input, d_output) # 将结果从GPU拷贝回CPU(注意:60326x60326的矩阵非常大,约14GB,可能需要考虑分批处理) output_matrix = d_output.copy_to_host() # 如果你只需要对称矩阵的上三角/下三角,可以在核函数里只计算i >= j的部分,节省一半计算量
2. 关键修改点说明
- 核函数参数:新增了
output_matrix参数,用于存储计算出的距离矩阵 - 线程索引:每个线程负责计算一对向量
(i,j)的距离,索引对应输出矩阵的位置 - 曼哈顿距离计算:通过循环遍历所有维度,累加每个维度差值的绝对值
- 内存管理:使用
cuda.to_device将CPU数据传到GPU,cuda.device_array在GPU上创建输出数组,最后用copy_to_host取回结果 - 越界检查:必须确保线程索引
i和j不超过样本数量,避免数组越界错误
3. 针对大型数据集的优化建议
你的数据集有60326个样本,生成的距离矩阵非常大(约14GB的float32数据),可能超出GPU的显存容量,这里有几个优化方向:
- 利用对称矩阵特性:只计算
i >= j(或i <= j)的部分,减少一半的计算量和内存占用,核函数里可以修改为if i <= j,然后拷贝时填充对称部分 - 分块处理:将数据集分成多个小批次,每次计算一个批次内的距离矩阵,或者计算批次与批次之间的距离,避免一次性占用过多显存
- 共享内存加速:对于每个线程块,把需要用到的向量片段加载到共享内存中,减少全局内存的访问次数,进一步提升计算速度(适合有CUDA优化经验后尝试)
内容的提问来源于stack exchange,提问作者Mrabah Yassine
相关产品推荐
相关产品推荐

