【CUDA】 矩阵乘向量 matVecMul

Matrix - Vector Multiplication

矩阵-向量乘法是线性代数中的基本操作。它用于将一个矩阵与一个向量相乘。乘法的结果是与输入向量大小相同的向量。

矩阵和向量的乘法如图1所示。

图1

基础kernel与共享内存kernel

执行矩阵-向量乘法的基础kernel是使用单个线程执行输出向量的单个元素的乘法。这意味着所需的线程数等于输出向量中的元素数。线程以一维网格排列,每个线程被分配一个唯一的索引。线程的索引用于访问输入矩阵的对应行。对行和向量进行乘法操作,结果存储在输出向量的对应元素中。

在这种基础kernel中,每个线程将加载整个输入向量。这并不高效,因为输入向量被多次加载。为了避免这种情况,我们可以使用一个tile将输入向量存储在共享内存中。tile是一个大小等于块中线程数的一维数组。向量被加载到tile中,每个线程使用tile执行乘法。

Code

Host代码初始化输入矩阵和向量,随机赋值并调用kernel执行乘法。输入矩阵以线性化格式存储。

#include <iostream>
#include <cstdio>
#include <ctime>
#include <cmath>#include <cuda_runtime.h>
#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
#include "image2gray.cuh"
#include "helper_cuda.h"
#include "error.cuh"const int FORTIME = 50;int main(int argc, char* argv[])
{int rows, cols, t_x;rows = 4096;cols = 4096;t_x = 1024;thrust::host_vector<float> h_in_mat(rows * cols);thrust::host_vector<float> h_in_vec(cols);srand(time(NULL));for (int i = 0; i < rows * cols; i++) {h_in_mat[i] = rand() / (float)RAND_MAX;if (i < cols)h_in_vec[i] = rand() / (float)RAND_MAX;}thrust::host_vector<float> h_out(rows);for (int i = 0; i < rows; ++i)for (int j = 0; j < cols; ++j)h_out[i] += h_in_mat[i * cols + j] * h_in_vec[j];thrust::device_vector<float> d_in_mat = h_in_mat;thrust::device_vector<float> d_in_vec = h_in_vec;thrust::device_vector<float> d_out(rows);dim3 block(t_x);dim3 grid((rows + block.x - 1) / block.x);cudaEvent_t start, stop;checkCudaErrors(cudaEventCreate(&start));checkCudaErrors(cudaEventCreate(&stop));checkCudaErrors(cudaEventRecord(start));for (int i = 0; i < FORTIME; i++)mat_vec_mul << <grid, block >> > (thrust::raw_pointer_cast(d_out.data()),thrust::raw_pointer_cast(d_in_mat.data()),thrust::raw_pointer_cast(d_in_vec.data()),rows, cols);checkCudaErrors(cudaEventRecord(stop));checkCudaErrors(cudaEventSynchronize(stop));float elapsed_time;checkCudaErrors(cudaEventElapsedTime(&elapsed_time, start, stop));std::cout << "elapsed_time:" << elapsed_time / FORTIME << std::endl;bool success = true;for (int i = 0; i < rows; ++i)if (abs(h_out[i] - d_out[i]) >= 0.001) {std::cout << "Error at " << i << ": " << h_out[i] << " != " << d_out[i] << " (Mat Vec Mul)" << std::endl;success = false;break;}if (success)std::cout << "Success (Mat Vec Mul)" << std::endl;checkCudaErrors(cudaEventRecord(start));for (int i = 0; i < FORTIME; i++)mat_vec_mul_tiles << <grid, block, block.x * sizeof(float) >> > (thrust::raw_pointer_cast(d_out.data()),thrust::raw_pointer_cast(d_in_mat.data()),thrust::raw_pointer_cast(d_in_vec.data()),rows, cols);checkCudaErrors(cudaEventRecord(stop));checkCudaErrors(cudaEventSynchronize(stop));checkCudaErrors(cudaEventElapsedTime(&elapsed_time, start, stop));std::cout << "elapsed_time:" << elapsed_time / FORTIME << std::endl;success = true;for (int i = 0; i < rows; ++i)if (abs(h_out[i] - d_out[i]) >= 0.001) {std::cout << "Error at " << i << ": " << h_out[i] << " != " << d_out[i] << " (Mat Vec Mul Tiles)" << std::endl;success = false;break;}if (success)std::cout << "Success (Mat Vec Mul Tiles)" << std::endl;return 0;
}

Note:

helper_cuda.h 与error.cuh头文件为错误查验工具。后续会发布到Github。

去除checkCudaErrors等错误查验函数不影响程序运行。


下面显示了两个kernel。

template<typename T> __global__
void mat_vec_mul(T *out, T *in_mat, T *in_vec, int m, int n) {int i = blockIdx.x * blockDim.x + threadIdx.x;if(i >= m) return;T temp = 0;for (int k = 0; k < n; ++k)temp += in_mat[i * n + k] * in_vec[k];out[i] = temp;
}template<typename T> __global__
void mat_vec_mul_tiles(T *out, T *in_mat, T *in_vec, int m, int n) {extern __shared__ uint8_t tiles_uint8[];T *tiles = reinterpret_cast<T*>(tiles_uint8);int i = blockIdx.x * blockDim.x + threadIdx.x;T res = 0;int n_phases = (n + blockDim.x - 1) / blockDim.x;for (int phase = 0; phase < n_phases; ++phase){int elem_idx = phase * blockDim.x + threadIdx.x;tiles[threadIdx.x] = elem_idx < n ? in_vec[elem_idx] : 0;__syncthreads();if(i < m)for (int k = 0; k < blockDim.x && phase * blockDim.x + k < n; ++k)res += in_mat[i * n + phase * blockDim.x + k] * tiles[k];__syncthreads();}out[i] = res;
}

基础kernel

kernel首先计算线程的索引。如果索引在输出向量的范围内,则执行乘法。

int i = blockIdx.x * blockDim.x + threadIdx.x;
if(i >= m) return;

将矩阵的每一列与向量相应元素相乘,并将结果存储在输出向量的相应元素中。

T temp = 0;
for (int k = 0; k < n; ++k)temp += in_mat[i * n + k] * in_vec[k];
out[i] = temp;

共享内存kernel

kernel首先计算线程的索引。kernel不会检查边界条件,因为需要加载向量到tile中的超出输出向量范围的线程。

int i = blockIdx.x * blockDim.x + threadIdx.x;

现在,乘法被分成几个阶段。在每个阶段,线程将把向量的单个或零个元素(如果它们超出范围)加载到tile中。然后线程将执行加载到tile中的向量和输入矩阵行的相应元素的乘法。结果存储在输出向量的相应元素中。

在加载向量到tile中和执行乘法时,线程都会执行边界检查,以避免超出范围的访问。

T res = 0;
int n_phases = (n + blockDim.x - 1) / blockDim.x;
for (int phase = 0; phase < n_phases; ++phase){int elem_idx = phase * blockDim.x + threadIdx.x;tiles[threadIdx.x] = elem_idx < n ? in_vec[elem_idx] : 0;__syncthreads();if(i < m)for (int k = 0; k < blockDim.x && phase * blockDim.x + k < n; ++k)res += in_mat[i * n + phase * blockDim.x + k] * tiles[k];__syncthreads();
}
out[i] = res;

性能分析

运行时间:

矩阵维度:4096 × 4096

向量维度:4096

线程块维度:1024

由运行时间分析,引入共享内存效果比较明显,而且经测试输入向量维度越大,共享内存效果越明显。

Note:单次运行可能因为设备启动原因,各种算法运行时间差异较大,可采用循环20次以上取平均值。

笔者采用设备:RTX3060 6GB

PMPP项目提供的分析

kernel的性能是使用NvBench项目在多个gpu中测量的。研究的性能测量方法有:

内存带宽:每秒传输的数据量。

内存带宽利用率:占用内存带宽的百分比。

基础kernel

共享内存kernel

参考文献:

1、大规模并行处理器编程实战(第2版)

2、​​​PPMP

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

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

相关文章

【SOLID原则前端中的应用】开闭原则(Open/Closed Principle)- vue3示例

开闭原则&#xff08;Open/Closed Principle&#xff09;在Vue 3中的应用 开闭原则&#xff08;Open/Closed Principle&#xff0c;OCP&#xff09;规定&#xff0c;软件实体&#xff08;类、模块、函数等&#xff09;应该对扩展开放&#xff0c;对修改关闭。 也就是说&#xf…

大型网站软件系统架构演进过程

在我们的生活中,通常会使用大型网站系统,比如购物网站淘宝,京东,阿里1688;大型搜索引擎网站百度,社交类的如腾讯旗下的微信,QQ及新浪旗下的微博等,他们通常都有一下特点: 高并发、大流量&#xff1a;这些系统必须能够处理成千上万甚至数百万的并发用户请求&#xff0c;以及持续…

Dubbo内部通信流程

我当时在学习的过程中搭建过demo&#xff0c;具体流程就是&#xff0c;我先定义了一个api接口模块&#xff0c;还定义一个服务提供者模块&#xff0c;然后服务提供方实现该接口&#xff0c;定义该方法具体的实现impl类&#xff0c;服务提供方启动时&#xff0c;将要暴露的服务和…

在线快速制作二维码的技巧,支持生成多种内容二维码

现在用二维码来分享内容是很多场景下会使用的一种方式&#xff0c;常见的展示内容有图片、文件、文本、音频、视频等&#xff0c;都可以生成二维码之后。通过手机扫码来查看内容&#xff0c;有利于内容的快速传播&#xff0c;并且用户获取信息也更加的方便。 下面来教大家使用…

Web应用防火墙用在哪些场景?

WAF是Web Application Firewall的缩写&#xff0c;翻译为“Web应用防火墙”是一种网络安全设备或服务&#xff0c;用于保护Web应用程序免受各种网络攻击和漏洞的影响。 WAF特别设计用于识别和阻止特定于Web应用程序的攻击&#xff0c;例如SQL注入、跨站脚本(XSS)、跨站请求伪造…

力扣习题--哈沙德数

一、前言 本系列主要讲解和分析力扣习题&#xff0c;所以的习题均来自于力扣官网题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台 二、哈沙德数 1. 哈沙德数 如果一个整数能够被其各个数位上的数字之和整除&#xff0c;则称之为 哈沙德数&#xff08;Harshad number&…

CTF常用sql注入(二)报错注入(普通以及双查询)

0x05 报错注入 适用于页面无正常回显&#xff0c;但是有报错&#xff0c;那么就可以使用报错注入 基础函数 floor() 向下取整函数 返回小于或等于传入参数的最大整数。换句话说&#xff0c;它将数字向下取整到最接近的整数值。 示例&#xff1a; floor(3.7) 返回 3 floor(-2…

5.基于SpringBoot的SSMP整合案例-数据层开发

目录 1.新建项目 2.实体类开发&#xff1a; 2.1在pom.xml中增加Lombok坐标&#xff1a; 2.2添加Book实体类 3.数据层开发&#xff1a; 3.1 配置MyBatisPlus与Druid 3.2创建数据层接口 3.3写测试类 3.4点击运行&#xff1a; 4.数据层快速开发&#xff1a; 4.1配置MyB…

C++视觉开发 四.手势识别

本章记录传统手势识别&#xff0c;在表示0-5六个数值时的识别问题。例如识别剪刀石头布&#xff0c;手势&#xff0c;以及其表示的动作。在识别时将手势中的凹陷区域称为凸缺陷&#xff0c;其个数作为识别的重要依据。 需要注意&#xff0c;在凸缺陷个数为0时&#xff0c;无法…

S272钡铼技术4G无线RTU支持多路DIN输入和模拟量转换至4G网络

钡铼第四代RTU S272是一款先进的工业级4G远程遥测终端&#xff0c;为各种远程工业数据采集和控制系统提供了高效解决方案。结合了现代通信技术和多功能的输入输出接口&#xff0c;S272不仅支持多路数字量和模拟量输入&#xff0c;还具备灵活的扩展性和强大的控制功能&#xff0…

WEB攻防-XSS跨站反射型存储型DOM型标签闭合输入输出JS代码解析

文章目录 XSS跨站-输入输出-原理&分类&闭合XSS跨站-分类测试-反射&存储&DOM反射型XSS存储型XSSDOM-base型XSS XSS跨站-输入输出-原理&分类&闭合 漏洞原理&#xff1a;接受输入数据&#xff0c;输出显示数据后解析执行 基础类型&#xff1a;反射(非持续…

典型案例 | 基于全数字实时仿真的嵌入式DevOps解决方案

为丰富浙江省信息技术应用创新&#xff08;以下简称“信创”&#xff09;产业生态&#xff0c;在全社会各领域形成示范效应&#xff0c;浙江省经信厅联合省密码管理局开展2023年浙江省深化信创典型案例评选工作。 经过征集申报、专家评选、名单公示等程序&#xff0c;确定36个…

实现前端项目自动构建和部署(Gitee Go)

前言 相信所有的前端开发者都希望将自己的代码部署在服务器上让所有人都能访问到&#xff0c;但是却不知道如何进行部署。其实要是实现代码上线非常简单&#xff0c;我们只需要将build之后的代码上传到服务器&#xff0c;然后通过Nginx起一个服务指向我们build后的代码就可以了…

Cocos 7.2~7.4

这几天没更新CSDN&#xff0c;跑去玩Cocos了。自从知道我的粉丝百分之十之八九都是假人&#xff0c;更新确实没什么动力了。主要还是把这边当成一个日记本吧。 选择cocos的原因也很简单。会点js&#xff0c;技术栈比较接近&#xff0c;上手估计也快。简单记录下这几天的内容 主…

@amap/amap-jsapi-loader 实现高德地图中添加多边围栏,并可编辑,编辑后获得围栏各个点的经纬度

先上一张效果图 看看是不是大家想要的效果&#xff5e; ❤️ 希望其中的小点能帮助大家&#xff0c;主要看怎么绘制在地图上的代码即可 1.第一步要加入项目package.json中或者直接yarn install它都可以 想必大家应该都会 "amap/amap-jsapi-loader": "0.0.7&qu…

C语言作业笔记

1. 要找俩个数使其相加等于一个数&#xff0c;那么俩个数从头尾出发&#xff0c;先动一边&#xff0c;假设是尾先动&#xff0c;一开始俩个数相加大于sum&#xff08;小于的话就动头&#xff09;&#xff0c;那么总有一时刻俩数相加小于sum&#xff0c;则就在那一刻停下来&…

关于5G和卫星

手机&#xff0c;已经串联起了我们生活中的一切环节。我们随时随地拿出手机&#xff0c;都能畅快地上网。 这一切是如此地理所当然&#xff0c;以至于我们甚至想不到这样不可思议的问题&#xff1a; 移动通信网络真的无处不在吗&#xff1f; 我们都知道&#xff0c;地球虽叫…

毕业论文初稿写作方法与过程

毕业论文初稿写作方法与过程 毕业论文是大学生在学业结束前必须完成的一项重要任务&#xff0c;它不仅是对学生所学知识的综合运用&#xff0c;也是对学生研究能力和写作能力的检验。写好毕业论文初稿是完成高质量毕业论文的关键一步。下面将具体阐述毕业论文初稿的写作方法和过…

Redis 7.x 系列【18】事务

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2. 命令2.1 MULTI2.2 EXEC2.3 DISCARD2.4 WATCH2.5 UNWATCH 3. 事务中的错误4.…

无法识别为 cmdlet、函数、脚本文件或可运行程序的名称

一、遇到问题 PS D:\software\nacos\nacos-server-2.3.1\bin> startup.cmd -m standalone startup.cmd : 无法将“startup.cmd”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c; 请确保路径正确&#xff0c;然后…