【问题标题】:Kmeans clustering acceleration in GPU(CUDA)GPU(CUDA)中的Kmeans聚类加速
【发布时间】:2015-05-25 02:22:31
【问题描述】:

我是一个相当新的 cuda 用户。我正在练习我的第一个 cuda 应用程序,我尝试使用 GPU(GTX 670)加速 kmeans 算法。

简而言之,每个线程都在一个点上工作,该点与所有集群中心进行比较,并将一个点分配给具有最小距离的中心(内核代码可以在下面使用 cmets 看到)。

根据 Nsight Visual Studio,我的占用率为 99.61%(1024 个块,每个块 1024 个线程),99.34% 的流式多处理器活动,79.98% 的扭曲问题效率,没有共享内存库冲突,18.4GFLOPs Single MUL 和 55.2 GFLOPs Single ADD(使用给定参数完成 kmeans 内核大约需要 14.5 毫秒)。

根据维基百科,GTX670 的峰值性能为 2460 GFLOPs。我离它还很近。除此之外,一些论文声称它们可以达到峰值性能的一半以上。我看不出我可以进一步优化这个内核代码。有什么可以应用于内核的优化吗?任何建议或帮助表示赞赏,我可以根据需要提供任何其他信息。

Complete Code

提前致谢。

#define SIZE 1024*1024 //number of points
#define CENTERS 32     //number of cluster centroids
#define DIM 8          //dimension of each point and center
#define cudaTHREADSIZE 1024 //threads per block
#define cudaBLOCKSIZE SIZE/cudaTHREADSIZE //number of blocks for kernel

__global__ void kMeans(float *dp, float *dc,int *tag, int *membershipChangedPerBlock)
{
    //TOTAL NUMBER OF THREADS SHOULD BE EQUAL TO THE NUMBER OF POINTS, BECAUSE EACH THREAD WORKS ON A SINGLE POINT
    __shared__ unsigned char  membershipChanged[cudaTHREADSIZE];
    __shared__ float dc_shared[CENTERS*DIM];

    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    int threadID = threadIdx.x;

    membershipChanged[threadIdx.x] = 0;
    //move centers to shared memory, because each and every thread will call it(roughly + %10 performance here)
    while(threadID < CENTERS*DIM){
        dc_shared[threadID] = dc[threadID];

        threadID += blockDim.x;
    }
    __syncthreads();

    while(tid < SIZE){
        int   index,prevIndex;
        float dist, min_dist;

        index = 0;//all initial point indices(centroid number) are assigned to 0.
        prevIndex = 0;
        dist = 0;
        min_dist = 0;

        //euclid distance for center 0
        for(int dimIdx = 0; dimIdx < DIM; dimIdx++){
            min_dist += (dp[tid + dimIdx*SIZE] - dc_shared[dimIdx*CENTERS])*(dp[tid + dimIdx*SIZE] - dc_shared[dimIdx*CENTERS]);
        }

        //euclid distance for other centers with distance comparison
        for(int centerIdx = 1; centerIdx < CENTERS; centerIdx++){
            dist = 0;
            for(int dimIdx = 0; dimIdx < DIM; dimIdx++){
                dist += (dp[tid + dimIdx*SIZE] - dc_shared[centerIdx + dimIdx*CENTERS])*(dp[tid + dimIdx*SIZE] - dc_shared[centerIdx + dimIdx*CENTERS]);    
            }   
            //compare distances, if found a shorter one, change index to that centroid number
            if(dist < min_dist){
                min_dist = dist;
                index = centerIdx;
            }
        }

        if (tag[tid] != index) {//if a point's cluster membership changes, flag it as changed in order to compute total membership changes later on
            membershipChanged[threadIdx.x] = 1;
        }
        tag[tid] = index;

        __syncthreads();//sync before applying sum reduction to membership changes


        //sum reduction
        for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
            if (threadIdx.x < s) {
                membershipChanged[threadIdx.x] +=
                    membershipChanged[threadIdx.x + s];
            }
            __syncthreads();
        }

        if (threadIdx.x == 0) {
            membershipChangedPerBlock[blockIdx.x] = membershipChanged[0];
        }
        tid += blockDim.x * gridDim.x;
    }
}

【问题讨论】:

  • 我建议提供一个其他人可以编译和运行的完整代码。
  • 您在问题的任何地方都没有提到内存带宽。我假设您发布的代码是全局内存带宽有限的。
  • 我用 nsight 查找了内存统计报告,这是imgur.com/fa45zfi 的结果。 GTX670 拥有 192.256 GB/s 的全局内存带宽。根据这些结果,L2 缓存使用 137.63 GB/s 带宽。这是否意味着我实际上使用了 0.7 的峰值带宽?
  • @talonmies 在查看 Nsight nvreport 的 Experiment Results 的 Issue Efficiency 选项卡后,我发现 84.55% 的问题停滞原因是执行依赖。每个线程都在一个点上工作,因此一个线程无法计算下一个维度距离并将其与总距离相加而不计算前一个维度距离。我的目标是通过使用线程寄存器来增加并行维度距离计算的数量,最后我将对这些半总距离求和。希望这将通过使用更多的带宽和更少的执行依赖停顿来增加我的 FLOP。

标签: cuda parallel-processing gpgpu k-means nsight


【解决方案1】:

我的建议是将您的工作与更有经验的 GPU 开发人员的工作进行比较。看了video之后,我发现 Kmeans 算法是 Byran Catanzaro 写的。你可以找到源代码:

https://github.com/bryancatanzaro/kmeans

我也是初学者,但恕我直言,最好使用“Trust”之类的库。 GPU 编程是一个非常复杂的问题,很难达到最大性能“信任”将帮助您。

【讨论】:

  • 我的目标是学习优化我自己的代码以用于学习目的,它很有效。我一直致力于自己编写现有的库并比较它们的性能。每当我能够击败图书馆时,它都会激励我,即使它需要一些特定的输入:) Teşekkürler。
【解决方案2】:

查看复制 scikit api 的 rapids.aicuml

来自文档的示例:

from cuml import KMeans
from cuml.cluster import KMeans

import cudf
import numpy as np
import pandas as pd

def np2cudf(df):
    # convert numpy array to cuDF dataframe
    df = pd.DataFrame({'fea%d'%i:df[:,i] for i in range(df.shape[1])})
    pdf = cudf.DataFrame()
    for c,column in enumerate(df):
        pdf[str(c)] = df[column]
    return pdf

a = np.asarray([[1.0, 1.0], [1.0, 2.0], [3.0, 2.0], [4.0, 3.0]],
               dtype=np.float32)
b = np2cudf(a)
print("input:")
print(b)

print("Calling fit")
kmeans_float = KMeans(n_clusters=2)
kmeans_float.fit(b)

print("labels:")
print(kmeans_float.labels_)
print("cluster_centers:")
print(kmeans_float.cluster_centers_)

【讨论】:

    猜你喜欢
    • 2018-05-15
    • 2016-02-02
    • 2013-09-02
    • 2013-11-18
    • 1970-01-01
    • 1970-01-01
    • 2015-11-20
    • 2015-02-20
    • 2017-06-08
    相关资源
    最近更新 更多