[8] CUDA之向量点乘和矩阵乘法

news2024/9/20 8:13:59

CUDA之向量点乘和矩阵乘法

  • 计算类似矩阵乘法的数学运算

1. 向量点乘

  • 两个向量点乘运算定义如下:
#真正的向量可能很长,两个向量里边可能有多个元素
(X1,Y1,Z1) * (Y1,Y2,Y3) = X1Y1 + X2Y2 + X3Y3
  • 这种原始输入是两个数组而输出却缩减为一个(单一值)的运算,在CUDA里边叫规约运算
  • 该运算对应的内核函数如下:
#include "stdio.h"
#include<iostream>
#include <cuda.h>
#include <cuda_runtime.h>
#define N 1024
#define threadsPerBlock 512


__global__ void gpu_dot(float* d_a, float* d_b, float* d_c) {
	//Declare shared memory
	__shared__ float partial_sum[threadsPerBlock];
	int tid = threadIdx.x + blockIdx.x * blockDim.x;
	
	//Calculate index for shared memory 
	int index = threadIdx.x;
	//Calculate Partial Sum
	float sum = 0;
	while (tid < N)
	{
		sum += d_a[tid] * d_b[tid];
		tid += blockDim.x * gridDim.x;
	}

	// Store partial sum in shared memory
	partial_sum[index] = sum;

	// synchronize threads 
	__syncthreads();

	// Calculating partial sum for whole block in reduce operation
	int i = blockDim.x / 2;
	while (i != 0) {
		if (index < i)
			partial_sum[index] += partial_sum[index + i];
		__syncthreads();
		i /= 2;
	}
	//Store block partial sum in global memory
	if (index == 0)
		d_c[blockIdx.x] = partial_sum[0];
}

  • 每个块都有单独的一份共享内存副本,所以每个线程ID索引到的共享内存只能是当前块自己的那个副本

  • 当线线程总数小于元素数量的时候,它也会循环将tid 索引累加偏移到当前线程总数,继续索引下一对元素,并进行计算。每个线程得到的部分和结果被写入到共享内存。我们将继续使用共享内存上的这些线程的部分和计算出当前块的总体部分和

  • 在对共享内存中的数据读取之前,必须确保每个线程都已经完成了对共享内存的写入,可以通过 __syncthreads() 同步函数做到这一点

  • 计算当前块部分和的方法:

    • 1.让一个线程串行循环将这些所有的线程的部分和进行累加
    • 2.并行化:每个线程累加2个数的操作,并将每个线程的得到的1个结果覆盖写入这两个数中第一个数的位置,因为每个线程都累加了2个数,因此可以在第一个数中完成操作(此时第一个数就是两个数的和),后边对剩余的部分重复这个过程,类似将所有数对半分组相加吧,一组两个数,加完算出的新的结果作为新的被加数
    • 上述并行化的方法是通过条件为(i!=0)while循环进行的,后边的计算类似二分法,重复计算中间值与下一值的和,知道总数为0
  • main函数如下:

int main(void) 
{
	//Declare Host Array
	float *h_a, *h_b, h_c, *partial_sum;
	//Declare device Array
	float *d_a, *d_b, *d_partial_sum;
	
	//Calculate total number of blocks per grid
	int block_calc = (N + threadsPerBlock - 1) / threadsPerBlock;
	int blocksPerGrid = (32 < block_calc ? 32 : block_calc);
	
	// allocate memory on the host side
	h_a = (float*)malloc(N * sizeof(float));
	h_b = (float*)malloc(N * sizeof(float));
	partial_sum = (float*)malloc(blocksPerGrid * sizeof(float));

	// allocate the memory on the device
	cudaMalloc((void**)&d_a, N * sizeof(float));
	cudaMalloc((void**)&d_b, N * sizeof(float));
	cudaMalloc((void**)&d_partial_sum, blocksPerGrid * sizeof(float));

	// fill the host array with data
	for (int i = 0; i<N; i++) {
		h_a[i] = i;
		h_b[i] = 2;
	}

	cudaMemcpy(d_a, h_a, N * sizeof(float), cudaMemcpyHostToDevice);
	cudaMemcpy(d_b, h_b, N * sizeof(float), cudaMemcpyHostToDevice);
	//Call kernel 
	gpu_dot << <blocksPerGrid, threadsPerBlock >> >(d_a, d_b, d_partial_sum);

	// copy the array back to host memory
	cudaMemcpy(partial_sum, d_partial_sum, blocksPerGrid * sizeof(float), cudaMemcpyDeviceToHost);

	// Calculate final dot product on host
	h_c = 0;
	for (int i = 0; i<blocksPerGrid; i++) {
		h_c += partial_sum[i];
	}
	printf("The computed dot product is: %f\n", h_c);
}
  • 在main函数中添加如下代码,检查该点乘结果是否正确:
#define cpu_sum(x) (x*(x+1))
	if (h_c == cpu_sum((float)(N - 1)))
	{
		printf("The dot product computed by GPU is correct\n");
	}
	else
	{
		printf("Error in dot product computation");
	}
	
	// free memory on host and device
	cudaFree(d_a);
	cudaFree(d_b);
	cudaFree(d_partial_sum);
	free(h_a);
	free(h_b);
	free(partial_sum);

在这里插入图片描述

矩阵乘法

  • 矩阵乘法A*B,A的行数需等于B的列数,将A的某行与B的所有的列进行点乘,然后对A的每一行以此类推
  • 下面将给出不使用共享内存和使用共享内存的内核函数来计算矩阵乘法
  • 先给出不使用共享内存的内核:
//Matrix multiplication using shared and non shared kernal
#include "stdio.h"
#include<iostream>
#include <cuda.h>
#include <cuda_runtime.h>
#include <math.h>
#define TILE_SIZE 2


//Matrix multiplication using non shared kernel
__global__ void gpu_Matrix_Mul_nonshared(float* d_a, float* d_b, float* d_c, const int size)
{
	int row, col;
	col = TILE_SIZE * blockIdx.x + threadIdx.x;
	row = TILE_SIZE * blockIdx.y + threadIdx.y;

	for (int k = 0; k < size; k++)
	{
		d_c[row * size + col] += d_a[row * size + k] * d_b[k * size + col];
	}
}
  • 每个元素的线性索引可以这样计算:用它的行号乘以矩阵的宽度,再加上它的列号即可
  • 使用共享内存的内核函数如下:
// Matrix multiplication using shared kernel
__global__ void gpu_Matrix_Mul_shared(float *d_a, float *d_b, float *d_c, const int size)
{
	int row, col;
	//Defining Shared Memory,共享内存数量=块数
	__shared__ float shared_a[TILE_SIZE][TILE_SIZE];
	__shared__ float shared_b[TILE_SIZE][TILE_SIZE];
	
	col = TILE_SIZE * blockIdx.x + threadIdx.x;
	row = TILE_SIZE * blockIdx.y + threadIdx.y;
	for (int i = 0; i< size / TILE_SIZE; i++) 
	{
		shared_a[threadIdx.y][threadIdx.x] = d_a[row* size + (i*TILE_SIZE + threadIdx.x)];
		shared_b[threadIdx.y][threadIdx.x] = d_b[(i*TILE_SIZE + threadIdx.y) * size + col];
		__syncthreads(); 
		for (int j = 0; j<TILE_SIZE; j++)
			d_c[row*size + col] += shared_a[threadIdx.y][j] * shared_b[j][threadIdx.x];
		__syncthreads(); 

	}
}
  • 主函数代码如下:
int main()
{
	const int size = 4;
	//Define Host Array
	float h_a[size][size], h_b[size][size],h_result[size][size];
	//Defining device Array
	float *d_a, *d_b, *d_result; 
	//Initialize host Array
	for (int i = 0; i<size; i++)
	{
		for (int j = 0; j<size; j++)
		{
			h_a[i][j] = i;
			h_b[i][j] = j;
		}
	}

	cudaMalloc((void **)&d_a, size*size*sizeof(int));
	cudaMalloc((void **)&d_b, size*size * sizeof(int));
	cudaMalloc((void **)&d_result, size*size* sizeof(int));


	//copy host array to device array

	cudaMemcpy(d_a, h_a, size*size* sizeof(int), cudaMemcpyHostToDevice);
	cudaMemcpy(d_b, h_b, size*size* sizeof(int), cudaMemcpyHostToDevice);
	
	//Define grid and block dimensions
	dim3 dimGrid(size / TILE_SIZE, size / TILE_SIZE, 1);
	dim3 dimBlock(TILE_SIZE, TILE_SIZE, 1);
	//gpu_Matrix_Mul_nonshared << <dimGrid, dimBlock >> > (d_a, d_b, d_result, size);

	gpu_Matrix_Mul_shared << <dimGrid, dimBlock >> > (d_a, d_b, d_result, size);

	cudaMemcpy(h_result, d_result, size*size * sizeof(int),	cudaMemcpyDeviceToHost);
	printf("The result of Matrix multiplication is: \n");
	
	for (int i = 0; i< size; i++)
	{
		for (int j = 0; j < size; j++)
		{
			printf("%f   ", h_result[i][j]);
		}
		printf("\n");
	}
	cudaFree(d_a);
	cudaFree(d_b);
	cudaFree(d_result);
	return 0;
}

在这里插入图片描述

  • ——————END——————

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1703362.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql高级篇学习(数据表的设计方法,索引优化)

使用docker 安装 mysql 安装 docker # yum 包更新到最新 yum update# 卸载旧的 docker yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine # 安装 gcc 环境 yum -y install…

【程序员如何送外卖】

嘿&#xff0c;咱程序员要在美团送外卖&#xff0c;那还真有一番说道呢。 先说说优势哈&#xff0c;咱程序员那逻辑思维可不是盖的&#xff0c;规划送餐路线什么的&#xff0c;简直小菜一碟。就像敲代码找最优解一样&#xff0c;能迅速算出怎么送最省时间最有效率。而且咱平时…

基于 Wireshark 分析 UDP 协议

一、UDP 协议 UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是一种无连接的传输层协议&#xff0c;常用于传输即时数据&#xff0c;如音频、视频和实时游戏数据等。 UDP 的特点如下&#xff1a; 1. 无连接性&#xff1a;UDP 不需要在发送数…

如何在Namecheap上购买域名

文章目录 如何在Namecheap上购买国外域名&#xff0c;话不多说直接上步骤↓1&#xff1a;注册Namecheap账号2&#xff1a;选购域名3&#xff1a;如何付款4&#xff1a;付款购买域名5&#xff1a;总结 如何在Namecheap上购买国外域名&#xff0c;话不多说直接上步骤↓ 原文链接…

通过键值对访问字典

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在Python中&#xff0c;如果想将字典的内容输出也比较简单&#xff0c;可以直接使用print()函数。例如&#xff0c;要想打印dictionary字典&#xff…

10.SpringBoot 统一处理功能

文章目录 1.拦截器1.1在代码中的应用1.1.1定义拦截器1.1.2注册配置拦截器 1.2拦截器的作用1.3拦截器的实现 2.统一数据返回格式2.1 为什么需要统⼀数据返回格式&#xff1f;2.2 统⼀数据返回格式的实现 3.统一异常处理4.SpringBoot专业版创建项目无Java8版本怎么办&#xff1f;…

Java后端面经

1.可重复读&#xff0c;已提交读&#xff0c;这两个隔离级别表现的现象是什么&#xff0c;区别是什么样的&#xff1f; 可重复读&#xff1a;表示整个事务看到的事务和开启后的事务能看到的数据是一致的&#xff0c;既然数据是一致的&#xff0c;所以不存在不可重复读。而且不…

Word整理论文参考文献

1.安装Zotero软件 2.安装Zotero的Chrome网站插件&#xff0c;并将插件固定到浏览器 3.安装Word的Zotero插件 4.在DBLP网站https://dblp.org/search 搜索需要添加的参考文献->点击BibTex->点击网页右上角的Zotero符号&#xff08;即第二步所指的符号&#xff09;->至…

雷军-2022.8小米创业思考-9-爆品模式:产品力超群,具有一流口碑,最终实现海量长销的产品。人人都向往;做减法;重组创新;小白模式

第九章 爆品模式 小米方法论的第三个关键词&#xff0c;就是一切以产品为出发点&#xff0c;打造爆品模式。 大多数人对“爆品”的着眼点仅在于“爆”&#xff0c;也就是产品卖得好。希望产品大卖这没有错&#xff0c;但是“爆”是“品”的结果&#xff0c;爆品是打造出来的&…

Revit的特性 - 族类型和族实例、联动更新

Revit 模型的表示方式 Revit 是 Autodesk 推出的一款建筑建模软件&#xff0c;主要应用于建筑信息模型&#xff08;Building Information Modeling&#xff0c;简称BIM&#xff09;领域。Revit发布至今已经超过20年&#xff0c;他的核心理念是以族的概念来表达建筑模型。 在Re…

图生文模型llava

llava-llama-3-8b-v1_1 是一个 LLaVA 模型&#xff0c;由 XTuner 使用 ShareGPT4V-PT 和 InternVL-SFT 从 meta-llama/Meta-Llama-3-8B-Instruct 和 CLIP-ViT-Large-patch14-336 进行微调。 https://huggingface.co/xtuner/llava-llama-3-8b-v1_1-gguf

Pencils Protocol与Trust 钱包联合活动,参与瓜分超$200K的奖励

实现全新品牌升级的 Pencils Protocol 目前已经结束了 Season 2&#xff0c;并进入到了 Season 3 阶段&#xff0c;目前用户可以通过 Pencils Protocol 的 Staking 池来获得超过 APR 收益的同时&#xff0c;还能获得多重积分奖励。 而在 Season 3 阶段&#xff0c;为了进一步促…

使用 Django ORM 进行数据库操作

文章目录 创建Django项目和应用定义模型查询数据更新和删除数据总结与进阶聚合和注解跨模型查询原始SQL查询 Django是一个流行的Web应用程序框架&#xff0c;它提供了一个强大且易于使用的对象关系映射&#xff08;ORM&#xff09;工具&#xff0c;用于与数据库进行交互。在本文…

微信小程序图片懒加载如何实现?

微信小程序开发时&#xff0c;对于有图片的列表在加载时&#xff0c;为了用户体验更好&#xff0c;必需要对图片做懒加载。 如下图所示&#xff0c;页面在打开时&#xff0c;图片会按需加载&#xff0c;这样用户体验没有那么生硬。 以下将介绍图片懒加载的步骤&#xff1a; 1.…

ssh远程连接的相关配置

连接同一个局域网下&#xff1a; 正好这里来理解一下计算机网络配置中的ip地址配置细节&#xff0c; inet 172.20.10.13: 这是主机的IP地址&#xff0c;用于在网络中唯一标识一台设备。在这个例子中&#xff0c;IP地址是172.20.10.13。 netmask 255.255.255.240: 这是子网掩码…

【软件设计师】——7.软件工程基础

目录 7.1 软件工程概述 7.2 需求分析 7.3 软件设计 7.4 软件开发方法及模型 7.4.1 软件开发方法 7.4.2 软件开发模型 7.5 软件测试 7.6 软件维护 7.7 软件质量保证 7.7.1 软件质量特性 7.7.2 程序质量评审 7.7.3 设计质量评审 7.8 软件过程改进 7.9 项目管理 7.1 …

文献分享《Microbiome and cancer》

人类微生物群构成了一个复杂的多王国群落&#xff0c;与宿主在多个身体部位共生相互作用。宿主-微生物群的相互作用影响多 种生理过程和各种多因素的疾病条件。在过去的十年中&#xff0c;微生物群落被认为会影响多种癌症类型的发展、进展、转移 形成和治疗反应。虽然微生物对癌…

PyQt5-新手避坑指南(持续更新)

文章目录 一&#xff0e;前言二&#xff0e;开发环境三&#xff0e;坑1.程序没有详细报错就退出了2.qrc资源文件的使用3.QLabel文字自动换行4.图片自适应大小5.checkbox自定义样式后✓不见了6.多线程 四&#xff0e;记录 一&#xff0e;前言 本篇博客整理了一些初学者容易犯的…

BFS解决最短路问题(详解)

目录 BFS简介 && 框架&#xff1a; 一.二叉树的最小深度 二&#xff1a;迷宫中里入口最近的出口&#xff1a; 三.最小基因变化: 四&#xff1a;单词接龙&#xff1a; ​五&#xff1a;为高尔夫比赛砍树&#xff1a; BFS简介 && 框架&#xff1a; 说到BFS…

常见API(JDK7时间、JDK8时间、包装类、综合练习)

一、JDK7时间——Date 1、事件相关知识点 2、Date时间类 Data类是一个JDK写好的Javabean类&#xff0c;用来描述时间&#xff0c;精确到毫秒。 利用空参构造创建的对象&#xff0c;默认表示系统当前时间。 利用有参构造创建的对象&#xff0c;表示指定的时间。 练习——时间计…