【CUDA编程】GPU上的topK程序

部署运行你感兴趣的模型镜像

CUDA编程的一些例子

最近在学习CUDA编程,写了一点简单的程序,刚开始用学习GPU编程,踩了很多坑,特此记录一下

基于堆排序的topK

#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>


#define K 10

__device__ void mySwap(int& num1, int& num2) {
	int tmp = num1;
	num1 = num2;
	num2 = tmp;
}

// 保持堆的特性
__device__ void heapify(int* data, int idx, int maxSize) {
	if (idx >= maxSize)
		return;
	int left = idx * 2 + 1, right = idx * 2 + 2;
	int maxIdx = idx;
	if (left < maxSize && data[left] < data[maxIdx])
		maxIdx = left;
	if (right < maxSize && data[right] < data[maxIdx])
		maxIdx = right;
	if (maxIdx != idx) {
		mySwap(data[idx], data[maxIdx]);
		heapify(data, maxIdx, maxSize);
	}
}


template<
int NUM_PER_BLOCK,
int NUM_PER_THREAD
>
__global__ void myTopK(int* input, int* output) {
	__shared__ int sdata[NUM_PER_BLOCK / NUM_PER_THREAD * K];
	int tid = threadIdx.x;
	int idx = blockDim.x * blockIdx.x + tid;

	int data[K];

	for (int i = K - 1; i >= 0; i--) {
		data[i] = input[i + NUM_PER_THREAD * idx];
		heapify(data, i, K);
	}
	for (int i = K; i < NUM_PER_THREAD; i++) {
		if (input[i + NUM_PER_THREAD * idx] > data[0]) {
			data[0] = input[i + NUM_PER_THREAD * idx];
			heapify(data, 0, K);
		}
	}
	for (int i = 0; i < K; i++) {
		sdata[idx * K + i] = data[i];
	}
	__syncthreads();
	int curNum = NUM_PER_BLOCK * K / NUM_PER_THREAD;

	while (true) {
		if (idx * NUM_PER_THREAD >= curNum)
			break;

		int sortNum = min((idx + 1) * NUM_PER_THREAD, curNum) - idx * NUM_PER_THREAD;
		if (sortNum <= K) {
			for (int i = 0; i < sortNum; i++) {
				data[i] = sdata[idx * NUM_PER_THREAD + i];
			}
		}
		else {
			for (int i = K - 1; i >= 0; i--) {
				data[i] = sdata[i + NUM_PER_THREAD * idx];
				heapify(data, i, K);
			}
			int end = min(NUM_PER_THREAD, sortNum); // 目前需要排序的数据量可能位于NUM_PER_THREAD和K之间,即NUM_PER_THREAD>sortNum>K
			for (int i = K; i < end; i++) {
				if (sdata[i + NUM_PER_THREAD * idx] > data[0]) {
					data[0] = sdata[i + NUM_PER_THREAD * idx];
					heapify(data, 0, K);
				}
			}
			sortNum = K;
		}
		__syncthreads();

		if (idx * NUM_PER_THREAD < curNum) {
			#pragma unroll
			for (int i = 0; i < sortNum; i++) {
				sdata[idx * K + i] = data[i];
			}
		}
		__syncthreads();

		curNum = curNum * K / NUM_PER_THREAD;
	}
	if (tid == 0) {
		for (int i = 0; i < K; i++) {
			output[i] = sdata[i];
		}
	}
}

int main() {
	const int BLOCK_SIZE = 256;
	const int N = 1024 * 1024;
	const int NUM_PER_BLOCK = N; // 1024
	const int NUM_PER_THREAD = NUM_PER_BLOCK / BLOCK_SIZE; // 4096

	assert(K < NUM_PER_THREAD);

	int* input = (int*)malloc(N * sizeof(int));
	int* output = (int*)malloc(K * sizeof(int));
	int* d_input;
	int* d_output;
	cudaError_t error = cudaMalloc(&d_input, N * sizeof(int));
	error = cudaMalloc(&d_output, K * sizeof(int));

	for (int i = 0; i < N; i++) {
		input[i] = rand() % 100;
	}

	for (int i = 0; i < K; i++) {
		int randIdx = rand() % N;
		input[randIdx] = i + 100;
		printf("input[%d] = %d\n", randIdx, input[randIdx]);
	}
	error = cudaMemcpy(d_input, input, N * sizeof(int), cudaMemcpyHostToDevice);

	dim3 BlockSize(BLOCK_SIZE, 1);
	myTopK <NUM_PER_BLOCK, NUM_PER_THREAD><< <1, BlockSize >> > (d_input, d_output);
	error = cudaMemcpy(output, d_output, K * sizeof(int), cudaMemcpyDeviceToHost);

	for (int i = 0; i < K; i++) {
		printf("output[%d] = %d.\n", i, output[i]);
	}
}

2路topK算法

#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>



__device__ void mySwap(int& num1, int& num2) {
	int tmp = num1;
	num1 = num2;
	num2 = tmp;
}

// 保持堆的特性
__device__ void heapify(int* data, int idx, int maxSize) {
	if (idx >= maxSize)
		return;
	int left = idx * 2 + 1, right = idx * 2 + 2;
	int maxIdx = idx;
	if (left < maxSize && data[left] < data[maxIdx])
		maxIdx = left;
	if (right < maxSize && data[right] < data[maxIdx])
		maxIdx = right;
	if (maxIdx != idx) {
		mySwap(data[idx], data[maxIdx]);
		heapify(data, maxIdx, maxSize);
	}
}


template<
	int NUM_PER_BLOCK,
	int NUM_PER_THREAD,
	int K
>
__global__ void myTopK(int* input, int* output) {
	__shared__ int sdata[NUM_PER_BLOCK / NUM_PER_THREAD * K];
	int tid = threadIdx.x;
	int idx = blockDim.x * blockIdx.x + tid;
	if (idx * NUM_PER_THREAD >= NUM_PER_BLOCK) // 数据量较少时,后面的线程直接退出
		return;

	int data[K];

	for (int i = K - 1; i >= 0; i--) {
		data[i] = input[i + NUM_PER_THREAD * idx];
		heapify(data, i, K);
	}

	for (int i = K; i < NUM_PER_THREAD; i++) {
		if (input[i + NUM_PER_THREAD * idx] > data[0]) {
			data[0] = input[i + NUM_PER_THREAD * idx];
			heapify(data, 0, K);
		}
	}

	for (int i = 0; i < K; i++) {
		sdata[tid * K + i] = data[i];
	}
	__syncthreads();
	int curNum = NUM_PER_BLOCK * K / NUM_PER_THREAD;

	while (true) {
		if (tid * NUM_PER_THREAD >= curNum)
			break;

		int sortNum = min((tid + 1) * NUM_PER_THREAD, curNum) - tid * NUM_PER_THREAD;
		if (sortNum <= K) {
			for (int i = 0; i < sortNum; i++) {
				data[i] = sdata[tid * NUM_PER_THREAD + i];
			}
		}
		else {
			for (int i = K - 1; i >= 0; i--) {
				data[i] = sdata[i + NUM_PER_THREAD * tid];
				heapify(data, i, K);
			}
			int end = min(NUM_PER_THREAD, sortNum); // 目前需要排序的数据量可能位于NUM_PER_THREAD和K之间,即NUM_PER_THREAD>sortNum>K

			for (int i = K; i < end; i++) {
				if (sdata[i + NUM_PER_THREAD * tid] > data[0]) {
					data[0] = sdata[i + NUM_PER_THREAD * tid];
					heapify(data, 0, K);
				}
			}
			sortNum = K;
		}
		__syncthreads();
		if (tid * NUM_PER_THREAD < curNum) {

			for (int i = 0; i < sortNum; i++) {
				sdata[tid * K + i] = data[i];
			}
		}
		__syncthreads();

		curNum = curNum * K / NUM_PER_THREAD;
	}
	if (tid == 0) {
		for (int i = 0; i < K; i++) {
			output[blockIdx.x * K + i] = sdata[i];
		}
	}
}

int main() {
	const int BLOCK_SIZE = 256;
	const int GRID_SIZE = 16;
	const int N = 1024 * 1024;

	const int GLOBAL_K = 16;
	const int BLOCK_K = 32;

	assert(BLOCK_K % GLOBAL_K == 0);

	int* input = (int*)malloc(N * sizeof(int));
	int* output = (int*)malloc(GLOBAL_K * sizeof(int));
	int* d_input;
	int* d_output;
	int* d_block_output;
	cudaError_t error = cudaMalloc(&d_input, N * sizeof(int));
	error = cudaMalloc(&d_output, GLOBAL_K * sizeof(int));
	error = cudaMalloc(&d_block_output, BLOCK_K * GRID_SIZE * sizeof(int));
	for (int i = 0; i < N; i++) {
		input[i] = rand() % 100;
	}

	for (int i = 0; i < GLOBAL_K; i++) {
		int randIdx = rand() % N;
		input[randIdx] = i + 100;
		printf("input[%d] = %d\n", randIdx, input[randIdx]);
	}
	error = cudaMemcpy(d_input, input, N * sizeof(int), cudaMemcpyHostToDevice);

	dim3 GridSize(GRID_SIZE, 1);
	dim3 BlockSize(BLOCK_SIZE, 1);
	myTopK <N / GRID_SIZE, N / (GRID_SIZE * BLOCK_SIZE), BLOCK_K> << <GridSize, BlockSize >> > (d_input, d_block_output);

	assert(BLOCK_K * GRID_SIZE / GLOBAL_K > GLOBAL_K); // 确保每个线程处理的数据量大于K
	myTopK <BLOCK_K * GRID_SIZE, BLOCK_K * GRID_SIZE / GLOBAL_K, GLOBAL_K> << <1, BlockSize >> > (d_block_output, d_output);
	error = cudaMemcpy(output, d_output, GLOBAL_K * sizeof(int), cudaMemcpyDeviceToHost);

	for (int i = 0; i < GLOBAL_K; i++) {
		printf("output[%d] = %d.\n", i, output[i]);
	}
}

您可能感兴趣的与本文相关的镜像

PyTorch 2.7

PyTorch 2.7

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

<think>好的,用户想要用模板参数k来编写CUDATopK算法代码。首先,我需要回顾一下CUDA编程的基本知识,特别是模板在CUDA中的应用。模板参数在CUDA中允许我们在编译时确定某些值,这对于优化性能很有帮助,比如展开循环或者调整内存分配。 接下来,TopK算法的常见实现方法有哪些呢?通常,可以使用快速选择算法或者堆排序。但在GPU上,考虑到并行性,可能需要分块处理数据。例如,将数据分成多个块,每个块内部找出TopK元素,然后合并这些块的结果,再从中选出最终的TopK。这样可以利用GPU的并行计。 然后,用户提到使用模板参数k,这意味着k在编译时是已知的。这可以带来一些优化,比如预先分配共享内存的大小,避免动态分配带来的开销。例如,如果k是模板参数,可以在内核函数中声明共享内存数组的大小为k,这样每个线程块在处理时就能高效地利用共享内存。 另外,参考用户提供的引用[1]中提到,他们使用了torch::topk()函数,并且在结果返回后进行了排序,因为结果不保证顺序。这可能意味着在CUDA内核中实现的TopK算法本身可能不排序,需要后续处理。所以在设计代码时,是否需要在内核中进行排序,或者只是选出TopK元素,后续在主机端进行排序? 还有一个问题是,如何高效地在GPU上合并各个块的TopK结果。可能需要多次调用内核函数,或者在单个内核中进行多阶段处理。例如,首先每个线程块处理一部分数据,生成局部TopK,然后将这些局部结果传递给下一个内核,最终得到全局TopK。 考虑到模板参数k,代码结构可能包括一个模板化的内核函数,比如template <int K> __global__ void topk_kernel(...)。这样,在编译时会为不同的K生成特定的内核实例,优化性能。 另外,内存分配方面,每个线程块可能需要共享内存来存储中间结果。使用模板参数k可以确定共享内存的大小,比如extern __shared__ int shared_mem[];,然后每个块使用K * sizeof(int)的空间。或者,如果K较小,可以直接在共享内存中静态分配。 还需要考虑数据的分块处理。假设输入数组大小为N,每个块处理一个块的数据,比如每个块处理1024个元素,那么块的数量就是N/1024。每个块内部找到自己的TopK,然后将这些结果写入全局内存。接着再启动另一个内核来处理这些中间结果,最终得到全局的TopK。 在实现过程中,可能需要注意线程同步,特别是在块内进行归约或排序时。使用共享内存时,需要__syncthreads()来确保所有线程完成数据写入。 最后,代码的结构可能包括主机端的调用,根据不同的K值实例化不同的内核。例如,使用switch语句根据K的值来调用对应的模板实例,或者使用CUDA的启动宏来动态处理。 总结一下,步骤大致如下: 1. 定义模板化的CUDA内核函数,参数为K。 2. 每个线程块处理数据的一部分,使用共享内存存储局部TopK。 3. 在块内进行局部TopK的筛选,可能使用快速选择或堆排序。 4. 合并所有块的局部TopK,再次调用内核进行全局TopK筛选。 5. 主机端代码处理内存分配、内核启动和结果处理。 需要注意的细节包括共享内存的大小、线程块和网格的配置、以及多次内核调用的协调。此外,如果K值较大,可能需要调整算法以避免共享内存不足的问题。</think>以下是使用模板参数k实现的CUDA TopK算法的代码实现和解析: ### CUDA TopK算法实现(模板参数k) ```cpp template <int K> __global__ void topk_kernel(const float* input, float* output_values, int* output_indices, int n) { extern __shared__ char shared_mem[]; float* s_values = reinterpret_cast<float*>(shared_mem); int* s_indices = reinterpret_cast<int*>(&s_values[K * blockDim.y]); const int tid = threadIdx.x; const int bid = blockIdx.x; // 每个线程块处理n/blockDim.y个元素 for (int i = 0; i < K; ++i) { s_values[threadIdx.y * K + i] = -FLT_MAX; s_indices[threadIdx.y * K + i] = -1; } // 加载数据到共享内存 for (int idx = bid * blockDim.y + threadIdx.y; idx < n; idx += gridDim.x * blockDim.y) { float val = input[idx]; // 插入排序逻辑 for (int i = 0; i < K; ++i) { if (val > s_values[threadIdx.y * K + i]) { for (int j = K - 1; j > i; --j) { s_values[threadIdx.y * K + j] = s_values[threadIdx.y * K + j - 1]; s_indices[threadIdx.y * K + j] = s_indices[threadIdx.y * K + j - 1]; } s_values[threadIdx.y * K + i] = val; s_indices[threadIdx.y * K + i] = idx; break; } } } __syncthreads(); // 合并各个线程的TopK结果 // 此处可添加归并排序逻辑... } ``` ### 实现解析 1. **模板参数应用** 通过`template <int K>`声明模板参数,使得编译器能为不同的K值生成特化代码,从而优化内存访问和循环展开[^1]。 2. **共享内存分配** 使用`extern __shared__`动态分配共享内存,将前K*blockDim.y个元素用于存储值,后K*blockDim.y个元素存储索引。 3. **数据加载策略** 每个线程块处理`n/(gridDim.x * blockDim.y)`个元素,采用跨步访问模式提高内存合并访问效率。 4. **局部TopK筛选** 使用插入排序算法维护局部TopK列表,时间复杂度为$O(nK)$,当K较小时效率较高。 ### 主机端调用示例 ```cpp template <int K> void launch_topk(const float* d_input, float* d_output_values, int* d_output_indices, int n) { const int threads_per_block = 256; const int blocks = (n + threads_per_block - 1) / threads_per_block; topk_kernel<K><<<blocks, threads_per_block, 2*K*sizeof(float)*threads_per_block>>>( d_input, d_output_values, d_output_indices, n ); } ``` ### 性能优化建议 1. **使用寄存器优化** 当K较小时(如K<=32),可将TopK列表存储在寄存器中而非共享内存,减少内存访问延迟。 2. **多阶段归并** 在大规模数据场景下采用两阶段处理: ```mermaid graph TD 原始数据 --> 块内TopK筛选 --> 全局归并 --> 最终结果 ``` 3. **原子操作优化** 在全局归并阶段使用`atomicCAS`实现无锁竞争更新,可参考PyTorch的`fast_topk.cuh`实现策略[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值