Day 69

GPU Kernels Learning Journey

Description

### Files: `k_means_leetgpu.cu` Today, I implemented a CUDA-accelerated K-Means Clustering for 2D data points as part of the newly added hard challenge on LeetGPU.

Code Files

k_means_leetgpu.cu

#include "solve.h"
#include <cuda_runtime.h>
#include <math.h>
#include <stdio.h>


__global__ void assign_and_accumulate(const float* data_x, const float* data_y,
                                      const float* centroid_x, const float* centroid_y,
                                      int* labels, float* sum_x, float* sum_y, int* count,
                                      int sample_size, int k) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= sample_size) return;
    
    float x = data_x[idx];
    float y = data_y[idx];
    float minDist = 1e20f;
    int best = 0;
 
    for (int i = 0; i < k; i++) {
        float dx = x - centroid_x[i];
        float dy = y - centroid_y[i];
        float dist = dx * dx + dy * dy;
        if (dist < minDist) {
            minDist = dist;
            best = i;
        }
    }
    labels[idx] = best;

    atomicAdd(&sum_x[best], x);
    atomicAdd(&sum_y[best], y);
    atomicAdd(&count[best], 1);
}


__global__ void update_centroids(float* centroid_x, float* centroid_y,
                                 const float* sum_x, const float* sum_y, const int* count,
                                 int k) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i >= k) return;
    
   
    if (count[i] > 0) {
        centroid_x[i] = sum_x[i] / count[i];
        centroid_y[i] = sum_y[i] / count[i];
    }
}

void solve(const float* data_x, const float* data_y, int* labels,
           float* initial_centroid_x, float* initial_centroid_y,
           float* final_centroid_x, float* final_centroid_y,
           int sample_size, int k, int max_iterations) {

    
    float *d_sum_x, *d_sum_y;
    int *d_count;
    cudaMalloc(&d_sum_x, k * sizeof(float));
    cudaMalloc(&d_sum_y, k * sizeof(float));
    cudaMalloc(&d_count, k * sizeof(int));

    
    float *d_old_centroid_x, *d_old_centroid_y;
    cudaMalloc(&d_old_centroid_x, k * sizeof(float));
    cudaMalloc(&d_old_centroid_y, k * sizeof(float));

    
    float host_old_x[128], host_old_y[128], host_new_x[128], host_new_y[128];

    const float threshold = 0.0001f;

    
    int threadsPerBlock = 256;
    int blocksPerGrid = (sample_size + threadsPerBlock - 1) / threadsPerBlock;

    
    int threadsPerBlockCent = 256;
    int blocksPerGridCent = (k + threadsPerBlockCent - 1) / threadsPerBlockCent;

    for (int iter = 0; iter < max_iterations; iter++) {
        
        cudaMemset(d_sum_x, 0, k * sizeof(float));
        cudaMemset(d_sum_y, 0, k * sizeof(float));
        cudaMemset(d_count, 0, k * sizeof(int));

       
        assign_and_accumulate<<<blocksPerGrid, threadsPerBlock>>>(data_x, data_y,
                                                                  initial_centroid_x, initial_centroid_y,
                                                                  labels, d_sum_x, d_sum_y, d_count,
                                                                  sample_size, k);
        cudaDeviceSynchronize();

      
        cudaMemcpy(d_old_centroid_x, initial_centroid_x, k * sizeof(float), cudaMemcpyDeviceToDevice);
        cudaMemcpy(d_old_centroid_y, initial_centroid_y, k * sizeof(float), cudaMemcpyDeviceToDevice);

        
        update_centroids<<<blocksPerGridCent, threadsPerBlockCent>>>(initial_centroid_x, initial_centroid_y,
                                                                      d_sum_x, d_sum_y, d_count, k);
        cudaDeviceSynchronize();

        
        cudaMemcpy(host_old_x, d_old_centroid_x, k * sizeof(float), cudaMemcpyDeviceToHost);
        cudaMemcpy(host_old_y, d_old_centroid_y, k * sizeof(float), cudaMemcpyDeviceToHost);
        cudaMemcpy(host_new_x, initial_centroid_x, k * sizeof(float), cudaMemcpyDeviceToHost);
        cudaMemcpy(host_new_y, initial_centroid_y, k * sizeof(float), cudaMemcpyDeviceToHost);

      
        bool converged = true;
        for (int i = 0; i < k; i++) {
            float dx = host_new_x[i] - host_old_x[i];
            float dy = host_new_y[i] - host_old_y[i];
            float distance = sqrtf(dx * dx + dy * dy);
            if (distance >= threshold) {
                converged = false;
                break;
            }
        }
        if (converged) {
            break;
        }
    }


    cudaMemcpy(final_centroid_x, initial_centroid_x, k * sizeof(float), cudaMemcpyDeviceToDevice);
    cudaMemcpy(final_centroid_y, initial_centroid_y, k * sizeof(float), cudaMemcpyDeviceToDevice);


    cudaFree(d_sum_x);
    cudaFree(d_sum_y);
    cudaFree(d_count);
    cudaFree(d_old_centroid_x);
    cudaFree(d_old_centroid_y);
}