【CUDA】 矩阵乘向量 matVecMul

news2024/11/25 12:30:18

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.coloradmin.cn/o/1893021.html

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

相关文章

教育行业的网络安全:保护学生数据与防范网络欺凌

在数字化的春风中&#xff0c;教育行业迎来了知识的繁花似锦&#xff0c;然而&#xff0c;随之而来的网络安全风暴也悄然逼近。学生数据的脆弱性与网络欺凌的阴影交织成一幅复杂的画卷&#xff0c;呼唤着教育工作者与技术专家共同编织一张密不透风的网络安全之网。本文深入探讨…

深度之眼(二十九)——神经网络基础知识(四)-循环神经网络

文章目录 一、 学习目标二、序列数据三、语言模型四、循环神经网络4.1 RNN的反向传播 五、门控循环单元-GNU5.1 候选隐藏状态 六、长短期记忆网络-LSTM七、回顾 一、 学习目标 二、序列数据 序列数据是常见的数据类型&#xff0c;前后数据通常具有关联性 三、语言模型 综合…

前后端分离:四种开发模式与实践指南

前后端分离&#xff1a;四种开发模式与实践指南 什么是前后端分离 当业务变得越来越复杂或产品线越来越多时&#xff0c;原有的开发模式就无法满足业务需求了。 产品越来越多&#xff0c;展现层的变化越来越快、越来越多&#xff0c;此时应该进行前后端分离的分层抽象&#…

【C语言】break 关键字

当在C语言中使用break关键字时&#xff0c;它通常用于两种主要情况&#xff1a;在循环中和在switch语句中。让我们详细看看每种情况下的用法和作用。 在循环中的使用&#xff1a; 在循环中&#xff0c;break语句的作用是立即终止当前所在的循环&#xff0c;然后跳出循环体执行…

Qt 使用 QZipReader 解压文件

Qt 使用 QZipReader 解压文件 文章目录 Qt 使用 QZipReader 解压文件摘要关于 QZipReader使用 QZipReader代码解释&#xff1a; 快速解 extractAll 关键字&#xff1a; Qt、 QZipReader、 extractAll、 Zip、 解压缩 摘要 每日一坑&#xff0c;坑坑难过&#xff0c;今日在…

深入解析.[datastore@cyberfear.com].mkp勒索病毒:威胁与防范

引言 在数字化时代&#xff0c;网络安全问题日益严峻&#xff0c;其中勒索病毒&#xff08;Ransomware&#xff09;作为一种极具破坏性的恶意软件&#xff0c;严重威胁着个人用户和企业机构的数据安全。.[ datastorecyberfear.com].mkp勒索病毒便是这一领域中的一颗“毒瘤”&am…

广东第二师范学院携手泰迪智能科技助力学子实习实践发展

为进一步推动和深化产教融合、校企合作&#xff0c;充分发挥企业在技术技能人才培养的重要作业。7月2日&#xff0c;广东第二师范学院统计学专业与广东泰迪智能科技股份有限公司联合开展学生专业见习活动。广东第二师范学院统计学专业专业教师曹俊飞、郑铮、泰迪智能科技高校事…

Python学生信息管理系统(完整代码)

引言&#xff1a;&#xff08;假装不是一个大学生课设&#xff09;在现代教育管理中&#xff0c;学生管理系统显得尤为重要。这种系统能够帮助教育机构有效地管理学生资料、成绩、出勤以及其他教育相关活动&#xff0c;从而提高管理效率并减少人为错误。通过使用Python&#xf…

ESP32S SENSOR与VDET引脚 无法输出问题 注意PWM输出的任意引脚并不包括所有引脚

问题记录&#xff1a; 注意PWM输出的任意引脚并不包括所有引脚&#xff0c;需要排除无法作为输出的引脚。数据手册中并没有在管脚表格中标明&#xff0c;如下表&#xff1a; 我在做esp32智能手环的时候&#xff0c;将GPIO39引脚&#xff08;SENSOR_VN&#xff09;作为蜂鸣器的P…

h5 video 播放视频

纯属娱乐&#xff0c;非技术之谈 https://andi.cn/page/621497.html

latex 报错解决①aligned ②begin document

1. 是aligned&#xff0c;不是align&#xff01;&#xff01; 网上写的公式大多是这样的 \begin{equation}\label{eq:2} \begin{align} Q\left( {s,t} \right) a{s^2} 2bst c{t^2} 2ds 2et f \end{align} \end{equation}但是报错&#xff1a; ! Package amsmath Erro…

顶顶通呼叫中心中间件(mod_cti基于FreeSWITCH)-http话术接口测试流程

文章目录 前言联系我们部署http话术PHP例子Java例子 登录ccadmin-web配置拨号方案创建与注册分机创建分机注册分机 测试 前言 用户一直想体验机器人话术的效果&#xff0c;但却找不到门路。本文提供了配置机器人话术接口的配置流程&#xff0c;供用户体验。用户可以根据本文的…

深度学习简介-AI(三)

深度学习简介 深度学习简介深度学习例子深度学习训练优化1.随机初始化2.优化损失函数3.优化器选择4.选择/调整模型结构 深度学习常见概念隐含层/中间层随机初始化损失函数导数与梯度优化器Mini Batch/epoch 深度学习训练逻辑图 深度学习简介 深度学习例子 猜数字 A: 我现在心…

Python特征工程 — 1.3 对数与指数变换

目录 1 对数变换 1.1 对数变换的概念 1.2 对数变换实战 2 指数变换 2.1 指数变换的概念 2.2 指数变换实战 3 Box-Cox变换 3.1 Box-Cox变换概念 3.2 Box-Cox变换实战 1 对数变换 1.1 对数变换的概念 特征对数变换和指数变换是数据预处理中的两种常用技术&#xff0c;…

基于Hadoop平台的电信客服数据的处理与分析④项目实现:任务15:数据生产

任务描述 电信数据生产是一个完整且严密的体系&#xff0c;这样可以保证数据的鲁棒性。在本项目的数据生产模块中&#xff0c;我们来模拟生产一些电信数据。同时&#xff0c;我们必须清楚电信数据的格式和数据结构&#xff0c;这样才能在后续的数据产生、存储、分析和展示环节…

前端基础:CSS(篇一)

目录 css概述 CSS与HTML的关系 基本语法 行内样式表 代码 运行 内嵌样式表 代码 运行 外部样式表 代码 运行 选择器 标签选择器 代码 运行 id选择器 代码 运行 类选择器 代码 运行 选择器优先问题 通配选择器 选中所有的标签 代码 运行 选择器组…

网安小贴士(6)TCP/IP分层

一、前言 1983年&#xff0c;美国国防部决定将TCP/IP作为所有计算机网络的标准协议&#xff0c;这标志着TCP/IP正式成为互联网的基础协议。随着个人计算机的普及和网络技术的发展&#xff0c;TCP/IP模型被广泛应用于各种网络环境中&#xff0c;包括局域网&#xff08;LAN&#…

MySQL单表千万级数据查询优化大家怎么说(评论有亮点)

题图来自APOD 上次写了一篇MySQL优化实战的文章“MySQL千万级数据从190秒优化到1秒全过程”。 这篇文章主要还是在实战MySQL优化&#xff0c;所以从造数据到查询SQL优化SQL都没有业务或者其它依赖&#xff0c;优化的技巧也不涉及软件架构就是纯SQL优化。 由于笔者经验有限和…

AGI 之 【Hugging Face】 的【Transformer】的 [ Transformer 架构 ] / [ 编码器 ]的简单整理

AGI 之 【Hugging Face】 的【Transformer】的 [ Transformer 架构 ] / [ 编码器 ]的简单整理 目录 AGI 之 【Hugging Face】 的【Transformer】的 [ Transformer 架构 ] / [ 编码器 ]的简单整理 一、简单介绍 二、Transformer 三、Transformer架构 四、编码器 1、自注意…