cuda学习2

第2.4课 官方库的使用

 主要的官方库如上,调用时候可能出现类似“无法解析的外部符号cublasShutdown,该符号在函数main中被引用”这样的错误,需要在vs属性->链接器->输入,里面加入对应得lib库,即cublas.lib,cufft,lib,curand.lib等。

使用库时可查看官方说明https://docs.nvidia.com/cuda/,但是有人说官方的代码有一些错误跑不通,自行留意,满是英文我才看不懂,都是CSDN搜的 :D

着重介绍curand为矩阵赋初值。有两种方式,

// 需要头文件curand_kernel.h, 利用__device__的随机函数
__global__ void Matrix_init_gpu(float* a, int N) {
	int x = threadIdx.x + blockDim.x * blockIdx.x;
	curandState state;
	long seed = N;
	curand_init(seed, x, 0, &state);
	if (x < N)	a[x] = curand_uniform(&state);
}

//需要头文件curand.h, 利用curand对gpu上的数组进行初始化
void Matrix_init(float* a, int N) {
	curandGenerator_t gen;
	curandCreateGenerator(&gen, CURAND_RNG_PSEUDO_MRG32K3A);
	curandSetPseudoRandomGeneratorSeed(gen, 11ULL);
	curandGenerateUniform(gen, a, N);	// 指定算法生成,生成0-1的随机数
}

常用的是下面的方面,用CPU直接调用即可,也就是main函数直接用就行。前三行照着写,不用了解太多,最后一行指定算法生成:

cudaGenerate(curandGenerator_t g,unsigned int * p,size_t N);生成伪随机序列,返回的随机数是32bit的无符号整型。

cudaGenerateLongLong(curandGenerator_t g,unsigned long long * p,size_t N);生成伪随机序列,返回的随机数是64bit的无符号整型。

cudaGenerateUniform(curandGenerator_t g,float * p,size_t N);生成伪随机序列,返回的随机数是0-1的单精度浮点型。

cudaGenerateNormal(curandGenerator_t g,float * p,size_t N,float mean,float stddev);按照指定的均值和方差生成生成伪随机序列。

下面两种方法都用一下,给出代码实例:


#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "curand.h"
#include "curand_kernel.h"
#include <stdio.h>
#include <iostream>
#include<iomanip>
using namespace std;


// 需要头文件curand_kernel.h, 利用__device__的随机函数
__global__ void Matrix_init_gpu(float* a, int N) {
	int x = threadIdx.x + blockDim.x * blockIdx.x;
	curandState state;
	long seed = N;
	curand_init(seed, x, 0, &state);
	if (x < N)	a[x] = curand_uniform(&state);
}

//需要头文件curand.h, 利用curand对gpu上的数组进行初始化
void Matrix_init(float* a, int N) {
	curandGenerator_t gen;
	curandCreateGenerator(&gen, CURAND_RNG_PSEUDO_MRG32K3A);
	curandSetPseudoRandomGeneratorSeed(gen, 11ULL);
	curandGenerateUniform(gen, a, N);	// 指定算法生成,生成0-1的随机数
}


int main()
{
	int m = 16;
	int N = m * m;
	
	float* p_da, * p_db, * p_h;

	p_h = (float*)malloc(N * sizeof(float)); //在主机侧声明内存空间

	cudaMalloc((void**)&p_da, N * sizeof(float));
	cudaMalloc((void**)&p_db, N * sizeof(float));

	Matrix_init_gpu << <4, 64 >> > (p_da, N);
	Matrix_init(p_db, N);

	cudaMemcpy(p_h, p_da, N * sizeof(float), cudaMemcpyDeviceToHost); //将随机数传送到主机
	printf("\np_da: \n");
	for (int i = 0; i < N; i++) { //输出结果
		if (i % m == 0)printf("\n");
		cout << setw(10) << p_h[i] << " ";
	}

	cudaMemcpy(p_h, p_db, N * sizeof(float), cudaMemcpyDeviceToHost); //将随机数传送到主机
	printf("\np_db: \n");
	for (int i = 0; i < N; i++) { //输出结果
		if (i % m == 0) printf("\n");
		cout << setw(10) << p_h[i] << " ";
	}

	cudaFree(p_da);
	cudaFree(p_db);
	free(p_h); //释放主机侧内存空间
}

再说一下,共享内存的事情,上节课说了,共享内存比global memory快几十倍,怎么用共享内存呢?只需要在声明变量的时候前面加上__shared__前缀,当然需要在GPU端函数用。那么用共享内存来计算矩阵乘法的代码就变成了:

// 通过共享内存,进行矩阵的乘法运算
__global__ void Matrix_multi(float* a, float* b, float* c, int m) {
	int x = threadIdx.x;
	int y = threadIdx.y;
	__shared__ float a_shared[256];
	__shared__ float b_shared[256];
	if (x < m && y < m) {
		a_shared[y * m + x] = a[y * m + x];
		b_shared[y * m + x] = b[y * m + x];
	}
	__syncthreads();
	float output = 0;
	if (x < m && y < m) {
		for (int i = 0; i < m; i++) {
			output += a_shared[y * m + i] * b_shared[i * m + x];
		}
		c[y * m + x] = output;
	}

}

这样的话运行得更快,在数据量大的时候提速很明显。要注意线程的同步,_syncthreads()的运用。

第3课 常见模型

这些模型用cuda解决比较方便,提速明显

1.映射(一对一)

 实例:

__global__ void square(float* a, int N) {
	int id = threadIdx.x + blockDim.x * blockIdx.x;

	if (id < N) {
		float s = a[id];//减少读次数
		a[id] = s * s;
	}
}

2.聚合(多对一)

典型:卷积计算。

为了避免一个block的边界点无法计算卷积的值,有以下两种方法:

1、设置n*n大小的block,把对应坐标的矩阵数据读取到共享内存里,然后其中的(n-2)*(n-2)个线程进行卷积运算

2、设置n*n大小的block,分两次读取矩阵,把(n+2)*(n+2)的矩阵数据读取到内存里,然后进行卷积计算

大家能想明白是什么意思吗?我想明白但是代码不会写,下节课给代码。哎!!!!

 3.分散(一对多)

 4.转置(一对一)

 5.排序

例子:奇偶排序。通过比较数组中相邻的(奇偶)位置数字对,若其排序顺序错误则交换。下一步对所有偶奇位置数字对进行检查并交替。直到结束。

用cuda可以将排序的时间复杂度降至O(N)。

 代码:这个函数需要狠狠地理解一下。待排序数组是一个有256个float数的数组。对数组进行排序的时候需要注意线程的同步,接下来开始介绍排序具体操作。

共有256个线程对整个256长度的数组分别进行256次排序,

0号线程调用:进入for循环,第一次j=0,idx=0,对0、1位置上的数字俩排了序,等其他线程排好序。第二次,i=1,j=1,idx=1,对1、2位置上的数字俩排了序,等其他线程排好序。第三次:i=2,j=,idx=0,对0、1位置排了序,等其他线程排好序....如此循环256次。

那么可想而知,1号线程应该是循环对2、3/3、4位置块排序。

...

就这样循环256次,把这个数组排序好了。

这里还用到了动态规划共享内存,前缀extern,后面不用指定数组长度,共享内存比较小,所以最好使用动态规划。extern __shared__ float s_a[];


#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "curand.h"
#include "curand_kernel.h"
#include <stdio.h>
#include <iostream>

using namespace std;


// 需要头文件curand_kernel.h, 利用__device__的随机函数
__global__ void Matrix_init_gpu(float* a, int N) {
	int x = threadIdx.x + blockDim.x * blockIdx.x;
	curandState state;
	long seed = N;
	curand_init(seed, x, 0, &state);
	if (x < N)	a[x] = curand_uniform(&state);
}

//需要头文件curand.h, 利用curand对gpu上的数组进行初始化
void Matrix_init(float* a, int N) {
	curandGenerator_t gen;
	curandCreateGenerator(&gen, CURAND_RNG_PSEUDO_MRG32K3A);
	curandSetPseudoRandomGeneratorSeed(gen, 11ULL);
	curandGenerateUniform(gen, a, N);	// 指定算法生成,生成0-1的随机数
}

// 通过共享内存,进行排序
__global__ void sort(float* a, int N) {
	int x = threadIdx.x;
	extern __shared__ float s_a[];//动态规划
	s_a[x] = a[x];
	__syncthreads();

	for (int i = 0; i < N; i++)
	{
		int j = i % 2;
		int idx = 2 * x + j;
		if (idx + 1 < N && s_a[idx] < s_a[idx + 1]) {
			float tmp = s_a[idx];
			s_a[idx] = s_a[idx + 1];
			s_a[idx + 1] = tmp;
		}
		__syncthreads();	//不要放if里,否则会失效
	}

	a[x] = s_a[x];
	__syncthreads();
}

int main()
{
	int m = 10; //输出时每行元素个数
	int N = 256;
	//curandGenerator_t gen; //生成随机数变量
	float* p_d, * p_h, * p_hs;

	p_h = (float*)malloc(N * sizeof(float)); //在主机侧声明内存空间
	p_hs = (float*)malloc(N * sizeof(float));

	cudaMalloc((void**)&p_d, N * sizeof(float)); //GPU侧声明随机数存储缓冲器的内存空间

	Matrix_init(p_d, N); //随机数对数组初始化


	cudaMemcpy(p_h, p_d, N * sizeof(float), cudaMemcpyDeviceToHost); //将随机数传送到主机
	printf("\np_da: \n");
	for (int i = 0; i < N; i++) { //输出结果
		if (i % m == 0)printf("\n");
		cout << " " << p_h[i] << " ";
	}

	// 排序算法
	sort << <1, N, N * sizeof(float) >> > (p_d, N);
//这里的第三个参数指的是共享内存的大小

	cudaMemcpy(p_h, p_d, N * sizeof(float), cudaMemcpyDeviceToHost); //将随机数传送到主机
	printf("\np_d: \n");
	for (int i = 0; i < N; i++) { //输出结果
		if (i % m == 0) printf("\n");
		cout << " " << p_h[i] << " ";
	}

	cudaFree(p_d); //释放GPU侧内存空间
	free(p_h); //释放主机侧内存空间
	free(p_hs);
}

接下来再做一个训练,做累计求和。

假设这时候有一组数组,把他内部所有值加起来的累加值是多少进行一个计算,那么这个时候可以用0号线程对1、2块累加,1号对3、4,2号对5、6...加完了之后,0号线程再对加完后的1、2块累加........这次累加需要用的线程块是上次的一半,另一半线程不用工作了。

综上,用cuda可以对累加求和实现提速。

代码中,由于共享内存较小,所以运用了两次核函数,先算出一个中间函数,再对这个由中间值组成的中间函数进行累加。


#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "curand.h"
#include "curand_kernel.h"
#include <stdio.h>
#include <iostream>
#include <time.h>

using namespace std;


// 需要头文件curand_kernel.h, 利用__device__的随机函数
__global__ void Matrix_init_gpu(float *a, int N) {
	int x = threadIdx.x + blockDim.x * blockIdx.x;
	curandState state;
	long seed = N; 
	curand_init(seed, x, 0, &state);
	if (x < N)	a[x] = curand_uniform(&state);
}

//需要头文件curand.h, 利用curand对gpu上的数组进行初始化
void Matrix_init(float *a, int N) {
	curandGenerator_t gen; 
	curandCreateGenerator(&gen, CURAND_RNG_PSEUDO_MRG32K3A); 
	curandSetPseudoRandomGeneratorSeed(gen, 11ULL); 
	curandGenerateUniform(gen, a, N);	// 指定算法生成,生成0-1的随机数
}

// 通过共享内存,数组进行求和
__global__ void reduce_kernel(float *d_in, float *d_out) {
	int myid = threadIdx.x + blockDim.x * blockIdx.x;
	int tid = threadIdx.x;
	int blockid = blockIdx.x;

	extern __shared__ float sdata[];

	sdata[tid] = d_in[myid];		// ** 把d_in[myid]赋值到sdata[tid]
	__syncthreads();

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

	if (tid == 0) {
		d_out[blockid] = sdata[0];
	}
}

void reduce(float *d_in, float *d_mid, float *d_out, int array_size) {
	int ThreadsPerBlock = 1024;
	int threadnum = ThreadsPerBlock;
	int blocks = array_size / ThreadsPerBlock;

	reduce_kernel << < blocks, threadnum, threadnum * sizeof(float) >> > (d_in, d_mid);
	reduce_kernel << <1, threadnum, threadnum * sizeof(float) >> > (d_mid, d_out);
}

int main()
{
	int array_size = 1 << 20;
	int array_bytes = array_size * sizeof(float);
	//curandGenerator_t gen; //生成随机数变量
	float *d_in, *d_mid, *d_out, *h;
	float sum = 0;

	h = (float *)malloc(array_bytes); //在主机侧声明内存空间

	cudaMalloc((void **)&d_in, array_bytes); //GPU侧声明随机数存储缓冲器的内存空间
	cudaMalloc((void **)&d_mid, 1024);
	cudaMalloc((void **)&d_out, 1);

	Matrix_init(d_in, array_size);
	cudaMemcpy(h, d_in, array_bytes, cudaMemcpyDeviceToHost); //将随机数传送到主机
	clock_t t_start_cpu = clock();
	for (int i = 0; i < array_size; i++)
	{
		sum += h[i];
	}
	cout << "sum_cpu:" << sum << endl;
	cout << "CPU耗时:" << (double)(clock() - t_start_cpu) / CLOCKS_PER_SEC << "毫秒" << endl;

	clock_t t_start_gpu = clock();
	reduce(d_in, d_mid, d_out, array_size);
	cudaDeviceSynchronize();

	clock_t cpy_time = clock();
	cudaMemcpy(h, d_out, array_bytes, cudaMemcpyDeviceToHost); //将随机数传送到主机
	cout << "sum_gpu:" << h[0] << endl;
	cout << "GPU耗时:" << (double)(clock() - t_start_gpu) / CLOCKS_PER_SEC << "毫秒" << endl;
	cout << "数组传输的耗时" << (double)(clock() - cpy_time) / CLOCKS_PER_SEC << "毫秒" << endl;

	free(h);
	cudaFree(d_in);
	cudaFree(d_mid);
	cudaFree(d_out);

	return 0;
}

最后CPU和GPU计算的结果会不一样,不要问我为啥,我不知道...有人说是小数位保留的问题,可我算出来差的也太大了,我也不知道为啥。真的不懂。

 未完待续

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值