发表博客之:cutlass demo讲解,在 sm75 机器上用 cuda core计算 fp32 矩阵乘法!对cutlass 感兴趣的看客别走开!!

文章目录

  • [发表博客之:cutlass demo讲解,在 sm75 机器上用 cuda core计算 fp32 矩阵乘法!对cutlass 感兴趣的看客别走开!!](https://cyj666.blog.csdn.net/article/details/138469553)
  • 深入理解 cutlass 在 sm75 cuda core 下的 fp32计算逻辑
    • `cutlass::gemm::device::Gemm`理解!

发表博客之:cutlass demo讲解,在 sm75 机器上用 cuda core计算 fp32 矩阵乘法!对cutlass 感兴趣的看客别走开!!

  • 各位老板好,我今儿要给各位演示一下,在 sm75 机器上用 cuda core计算 fp32 矩阵乘法!
    • 同时和cublas比较性能呢!
  • 由于sm75机器上没有tf32 的tensor core,因此在sm75 如T4机器上计算sgemm,只能用cuda core计算!
  • 下面我将逐步解释代码,要让小白都能听得懂!

  • 估计很多用户想立刻把我写的这个demo先跑起来看看是啥效果,那就请你把我下面的代码放到文件里面
  • 文件起个名字叫A.cu
  • 然后用 编译命令

nvcc A.cu -o a.out -arch sm_75 -lcublas -I /root/cutlass/include/ -std=c++17
/root/cutlass是cutlass仓库的路径,如果cutlass仓库在别的目录下,请小可爱你自己修改!

代码里面比较了cublascutlass的性能,请各位看官自己测试下!我测试的性能是下面这样的,你也快来测试下吧!

T4,fp32,性能

MNKcublascutlass
512 * 512 * 5121.316768 ms1.597312 ms
128 * 4096 * 409615.638016 ms20.250624 ms

  • 下面开始看我写的代代码吧!请用户先看 void init函数以及它之前的部分。
  • 首先映入眼帘的是一些头文件和宏定义,其中宏定义WARMUP和REPEATE是用来测试性能的!
  • init函数用来初始化矩阵,这里用随机数初始化!

  • 紧接着是下面的函数,CutlassSgemmNN,这个函数就是将cutlass提供的device级别的Gemm类以及与其相关的一些函数封装了一下。
  • 这个函数假设A,B,C都是column major的哦!请你一定要注意哦!
    • 他的参数有M,N,K alpha,beta,A,B,C,都是好理解的参数
    • lda表示啥呢?
      • (1)如果A是row major,他表示的是A矩阵的第[0][0][1][0]之间的物理距离,也就是两行之间同一个元素间的距离!
      • (2)如果A是col major,他表示的是A矩阵的第[0][0][0][1]之间的物理距离,也就是两列之间同一个元素间的距离!
        • 有人很好奇,卧槽,这个lda不就应该是M吗?其实不一定哦,你想一下如果A矩阵是某个更大矩阵的子矩阵呢!是不是就不能说他是M啦!
      • 这里由于我们假设A是col major,lda的含义就是(2)了!并且由于我这个例子里面 A A A是一个完整的矩阵,因此lda其实就是M啦!

  • 下面就是main函数啦!
  • 我们构建了一个a和一个b和一个c,我们在cpu上计算 c = a ∗ b c=a*b c=ab(也就是代码里面baseline的计算哦!)时候是将abc都看成row major的矩阵的!
  • 可以我们将要调用的CutlassSgemmNN默认输入输出都是col major的呢,这个咋办呀?
  • 这里面涉及到一个技巧
    • 那就是row majorA矩阵shape为[M,K]
    • A.T的shape显然是[K,M]
    • 如果A.T也是row major,那么A.TA的在内存中的线性数据肯定不同的!
    • 如果A.Tcol major,那么A.TA在内存中的线性数据就相同了!
      • 上面这两个数据在内存中线性排布是一摸一样的!!!仔细想想啊!
  • 关于矩阵运算在数学上有,如果c=a*b,那么c.T=b.T*a.T
    • 也就是说我们为了求得到row majorc矩阵,可以通过获得col majorc.T来求得!因为他俩在内存中线性排布是一摸一样的!
    • 也就是用col majorb.Tcol majora.T来求得即可!
    • col majorb.T 的地址,显然就是b地址啊!
    • col majora.T 的地址,显然就是a地址啊!
  • 其他的一些注释我都放到代码里啦!请各位看官看下哦!

#include <stdio.h>
#include <chrono>
#include <ctime>
#include <iostream>
#include <ratio>
#include "cublas_v2.h"
#include "cutlass/gemm/device/gemm.h"#define WARMUP 10
#define REPEATE 10using DATATYPE = float;
using ACCU_DATATYPE = float;void init(DATATYPE *a, int size) {for (int i = 0; i < size; i++) {a[i] = (rand() % 9999) / 10000.0;}
}cudaError_t CutlassSgemmNN(int M, int N, int K, float alpha, float const *A, int lda, float const *B, int ldb, float beta,float *C, int ldc) {// 这个 ColumnMajor 这句话太好理解了,就是col major的意思!using ColumnMajor = cutlass::layout::ColumnMajor;// 下面实例化了一个模版类`class LinearCombination`,这个类的一些参数请看这个文件// https://github.com/NVIDIA/cutlass/blob/main/include/cutlass/epilogue/thread/linear_combination.h吧!using EpilogueOutputOp = cutlass::epilogue::thread::LinearCombination<float, 1, float, float>;// 下面这个牛逼了!实例化了一个模版类`class Gemm`,这个模版类的参数很多,请你们看这个文件吧。// https://github.com/NVIDIA/cutlass/blob/main/include/cutlass/gemm/device/gemm.husing CutlassGemm = cutlass::gemm::device::Gemm<float, ColumnMajor,float, ColumnMajor,float, ColumnMajor,float,cutlass::arch::OpClassSimt,cutlass::arch::Sm75,cutlass::gemm::GemmShape<128,128, 8>,cutlass::gemm::GemmShape<32,64, 8>,cutlass::gemm::GemmShape<1,1, 1>, EpilogueOutputOp,cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<>,2,1,1,true >;// 上面只是实例化了模版类,下面这行是真的用模版类实例化了对象!CutlassGemm gemm_operator;// 下面这些是参数哦!// 计算的是D = alpha * A * B + beta * C// 这里面D也就是C哦,又因为beta我们在demo里面给了他是0,alpha给了是1,因此就相当于计算C=A*B啦!CutlassGemm::Arguments args({M, N, K},  // Gemm Problem dimensions{A, lda},   // Tensor-ref for source matrix A{B, ldb},   // Tensor-ref for source matrix B{C, ldc},   // Tensor-ref for source matrix C{C, ldc},   // Tensor-ref for destination matrix D{alpha, beta}, // Scalars used in the Epilogue2); // 这个2表示用split-k算法,且k=2哦!size_t bytes = CutlassGemm::get_workspace_size(args);void * workspace;cudaMalloc( (void**)&workspace, bytes);cutlass::Status status = gemm_operator(args, workspace);if (status != cutlass::Status::kSuccess) {return cudaErrorUnknown;}return cudaSuccess;
}int main(void) {int m = 512;int n = 512;int k = 512;DATATYPE *a, *b;a = (DATATYPE *)malloc(sizeof(DATATYPE) * m * k);b = (DATATYPE *)malloc(sizeof(DATATYPE) * k * n);init(a, m * k);init(b, k * n);ACCU_DATATYPE *c;c = (ACCU_DATATYPE *)malloc(sizeof(ACCU_DATATYPE) * m * n);memset(c, 0, sizeof(ACCU_DATATYPE) * m * n);float *c_cpu_fp32 = (float *)malloc(sizeof(float) * m * n);memset(c_cpu_fp32, 0, sizeof(float) * m * n);DATATYPE *dev_a, *dev_b;ACCU_DATATYPE *dev_c;cublasHandle_t handle;cublasCreate(&handle);// allocate the memory on the GPU cudaMalloc((void **)&dev_a, m * k * sizeof(DATATYPE));cudaMalloc((void **)&dev_b, k * n * sizeof(DATATYPE));cudaMalloc((void **)&dev_c, m * n * sizeof(ACCU_DATATYPE));cudaMemcpy(dev_a, a, m * k * sizeof(DATATYPE), cudaMemcpyHostToDevice);cudaMemcpy(dev_b, b, k * n * sizeof(DATATYPE), cudaMemcpyHostToDevice);cudaEvent_t beg, end;for (int i = 0; i < WARMUP + REPEATE; i++) {if (i == WARMUP) {cudaEventCreate(&beg);cudaEventCreate(&end);cudaEventRecord(beg);}const float alpha = 1.0f;const float beta = 0.0f;// 这里为了求得`row major`的`c`矩阵,选择计算`col major`的`c.T`,因为这俩个地址是一模一样的!// `c.T=b.T*a.T`我们为了获得col major的c.T,// `col major`的`b.T` 的shape是[n,k],地址就是dev_b// `col major`的`a.T` 的shape是[k,m],地址就是dev_a// 因此下面的参数是下面填写的那样哦!cublasSgemm(handle,CUBLAS_OP_N,CUBLAS_OP_N,n,m,k,&alpha,dev_b,n,dev_a,k,&beta,dev_c,n);// 这个是cutlass的gemm!// CutlassSgemmNN(n, m, k, alpha, dev_b, n, dev_a, k, beta, dev_c, n);}cudaEventRecord(end);cudaEventSynchronize(end);float elapsed_time;cudaEventElapsedTime(&elapsed_time, beg, end);printf("gpu gemm compute time: %f ms\n", elapsed_time);// 把gpu运算的结果拷贝到host端c上!cudaMemcpy(c, dev_c, m * n * sizeof(ACCU_DATATYPE), cudaMemcpyDeviceToHost);// 在cpu上计算结果,这个结果作为baseline,用来确保cutlass没有算错哦!for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {double sum = 0.f;for (int ii = 0; ii < k; ii++) {sum += a[i * k + ii] * b[ii * n + j];}c_cpu_fp32[i * n + j] = sum;}}// 看看baseline和gpu上的diff,然后输出!double max_diff = -1.;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {double c_gpu_fp32 = c[i * n + j];if (std::abs(c_cpu_fp32[i * n + j] - c_gpu_fp32) > max_diff) {max_diff = std::abs(c_cpu_fp32[i * n + j] - c_gpu_fp32);}}}printf("max_diff: %f\n", max_diff);cudaDeviceReset();free(a);free(b);free(c);free(c_cpu_fp32);return 0;
}

深入理解 cutlass 在 sm75 cuda core 下的 fp32计算逻辑

  • 我们绝对不能满足于只是调用接口,我们要深入理解cutlass在sm75 fp32 cuda core上的计算逻辑,深入理解cutlass代码!
  • 那我们先需要看一下这个类了, cutlass::gemm::device::Gemm

cutlass::gemm::device::Gemm理解!

  • 首先这个显然是一个device级别的接口,也就是面向小白用户的接口。但是小白想要使用他的话,因为他是个模版类,所以必须要先实例化他的!
    • 因而必须得了解并掌握每个模版参数的含义!
  • 直接读代码是比较繁琐的,但是又是必不可少的过程的!因此我们直接读代码把!
  • 这个类的代码请点击这里
  • 先看前9个参数,这些都是简单的哦。
template </// Element type for A matrix operandtypename ElementA_,/// Layout type for A matrix operandtypename LayoutA_,/// Element type for B matrix operandtypename ElementB_,/// Layout type for B matrix operandtypename LayoutB_,/// Element type for C and D matrix operandstypename ElementC_,/// Layout type for C and D matrix operandstypename LayoutC_,/// Element type for internal accumulationtypename ElementAccumulator_ = ElementC_,/// Operator class tagtypename OperatorClass_ = arch::OpClassSimt,/// Tag indicating architecture to tune fortypename ArchTag_ = arch::Sm70,
  • 参数OperatorClass_表示是由cuda core呢还是tensor core计算呢?默认是cuda core,也就是arch::OpClassSimt
    • 如果是让tensor core计算,那么就要用arch::OpClassTensorOp
  • ArchTag_就表示计算能力啦,默认是arch::Sm70
  • 上面几个参数平平无奇!下面看下面几个参数吧。

  • 再接着往下面是三个参数,这些参数是有默认值的。具体需要看DefaultGemmConfiguration这个类了。
  • 但是他们三个的含义是清晰的。
   /// Threadblock-level tile size (concept: GemmShape)typename ThreadblockShape_ = typename DefaultGemmConfiguration<OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,ElementAccumulator_>::ThreadblockShape,/// Warp-level tile size (concept: GemmShape)typename WarpShape_ = typename DefaultGemmConfiguration<OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,ElementAccumulator_>::WarpShape,/// Instruction-level tile size (concept: GemmShape)typename InstructionShape_ = typename DefaultGemmConfiguration<OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,ElementAccumulator_>::InstructionShape,

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

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

相关文章

基于Spring Boot的线上交流互动系统设计与实现

基于Spring Boot的线上交流互动系统设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 系统功能界面图&#xff0c;在系统首页可以查看首页…

「 网络安全常用术语解读 」通用安全通告框架CSAF详解

1. 简介 通用安全通告框架&#xff08;Common Security Advisory Framework&#xff0c;CSAF&#xff09;通过标准化结构化机器可读安全咨询的创建和分发&#xff0c;支持漏洞管理的自动化。CSAF是OASIS公开的官方标准。开发CSAF的技术委员会包括许多公共和私营部门的技术领导…

如何使用预训练的通用音频表示进行心脏杂音检测

心脏杂音检测是心血管疾病诊断中的一个重要方面&#xff0c;通过听诊器进行检查是常见方法&#xff0c;但对临床医生的经验依赖很大。为了减少心脏声音解释中对熟练临床医生的需求&#xff0c;探索自动化心脏听诊的深度学习方法很有必要。然而&#xff0c;尽管深度学习模型通常…

Redis Cluster集群方案什么情况下会导致整个集群不可用?

Redis 没有使用哈希一致性算法&#xff0c;而是使用哈希槽。 Redis 中的哈希槽一共有16384个&#xff0c;计算给定 密钥的哈希槽&#xff0c;我们只需要对密钥的 CRC16 去取 16384。假设集群中有A、B、C三个集群节点&#xff0c; 不存在复制模式下&#xff0c;每个集群的节点包…

、、、、、

、、 、 transient 关键字总结 1&#xff09;transient修饰的变量不能被序列化&#xff1b;2&#xff09;transient只作用于实现 Serializable 接口&#xff1b;3&#xff09;transient只能用来修饰普通成员变量字段&#xff1b;4&#xff09;不管有没有 transient 修饰&…

网络工程师必学知识:SSH登录抓包分析报文交互过程

网络工程师必学知识:SSH登录抓包分析报文交互过程 1.概述:2.SSH传输层协议:3.SSH用户认证协议:4.SSH连接协议:5.抓包看看:6.总结:1.概述: SSH(Secure Shell ,安全外壳协议),就是在不安全的协议外层再加一层安全外壳。比如说telnet+SSH=stelnet。 SSH由三个组件构成:…

ASP.NET网上书店

摘要 本设计尝试用ASP.NET在网络上架构一个电子书城&#xff0c;以使每一位顾客不用出门在家里就能够通过上网来轻松购书。本文从理论和实践两个角度出发&#xff0c;对一个具有数据挖掘功能电子书城进行设计与实现分析。论文首先较为详尽地介绍了面向对象分析与设计的有关概念…

C++实验五 : 类的继承 -----CUST

【题目】 1.定义person类&#xff0c;包括数据私有成员&#xff1a;姓名&#xff0c;性别&#xff1b;共用成员函数&#xff1a;带参数构造函数&#xff0c;display函数输出本类对象的所有数据成员值。 2.定义student类&#xff0c;保护继承person类&#xff1b;增加保护数据成…

docker desktop实战部署oracle篇

1、前言 oracle数据库官方已提供现成的镜像&#xff0c;可以直接拿来部署了。 由于项目中需要使用oracle数据库的分表功能&#xff0c;之前安装的是standard版本&#xff0c;无奈只能重新安装。网上查了一番&#xff0c;使用的方法都比较传统老旧&#xff1a;下载安装包手动安…

golang获取变量动态类型

类型断言&#xff1a;data.(Type) 类型断言是最常用的获取变量动态类型的方法之一。允许在运行时将接口值转换为其具体类型。 data 是一个接口类型的变量。 Type 是一个具体的类型。 这个表达式的含义是&#xff0c;如果 data 的底层值是 Type 类型&#xff0c;那么 value 将接…

深度学习之GAN网络

目录 关于GAN网络 关于生成模型和判别模型 GAN网路的特性和搭建步骤&#xff08;以手写字体识别数据集为例&#xff09; 搭建步骤 特性 GAN的目标函数&#xff08;损失函数&#xff09; 目标函数原理 torch.nn.BCELoss&#xff08;实际应用的损失函数&#xff09; 代码…

百度下拉框负面信息如何删除?

百度头条360等搜索引擎&#xff0c;作为人们获取信息的主要途径之一。然而&#xff0c;一些知名的企业或个人可能会面临在搜索的下拉框中出现负面信息的问题&#xff0c;这可能对其声誉和形象造成不良影响。小马识途营销顾问根据自身从业经验&#xff0c;针对这类情况提出以下建…

轻盈高效开源的WEB在线客服平台:Go-Fly

Go-Fly&#xff1a;即刻沟通&#xff0c;非凡服务&#xff0c;轻松连接每一个对话&#xff0c;让客服日常更简单高效&#xff01;- 精选真开源&#xff0c;释放新价值。 概览 Go-Fly 是一款基于 Go 语言 构建的开源即时通讯与客服管理系统&#xff0c;专为寻求高效、可定制在线…

网安学习笔记day-15,交换机工作原理

交换机工作原理 交换机是二层设备&#xff0c;基于MAC表工作。 MAC地址是有48位二进制组成&#xff0c;也就是6字节&#xff0c;通常分为6段&#xff0c;用十六进制表示。 交换机通信方式&#xff1a; 单播&#xff1a;点对点发送数据 广播&#xff1a;向所有设备发送数据…

【c++算法篇】双指针(上)

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;算法笔记仓 朋友们大家好啊&#xff0c;本篇文章我们来到算法的双指针部分 目录 1.移动零2.复写零3.快乐数4.盛水最多的容器 1.移动零 题目链接&#xff1a;283.移动零 题目描述&#xff1a; 算法…

【Linux】进程控制 之 进程创建 进程终止 进程等待 进程替换

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

A股上市公司财务松弛数据集(2000-2022年)

01、数据介绍 财务松弛是指企业在运营过程中&#xff0c;由于各种原因导致其财务状况出现一定程度的松弛或宽裕状态。这种状态通常表现为企业持有较多的现金和流动性资产&#xff0c;同时负债相对较少&#xff0c;或者企业有较多的未使用授信额度等。 本数据包括&#xff1a;…

【LeetCode】链表oj专题

前言 经过前面的学习&#xff0c;咋们已经学完了链表相关知识&#xff0c;这时候不妨来几道链表算法题来巩固一下吧&#xff01; 如果有不懂的可翻阅之前文章哦&#xff01; 个人主页&#xff1a;小八哥向前冲~-CSDN博客 数据结构专栏&#xff1a;数据结构【c语言版】_小八哥…

SQL注入基础-5

一、Access注入 1、asp网站常用数据库&#xff1a;access&#xff0c;mssql 2、access数据库 (1)没有库&#xff0c;没有端口 (2)结构&#xff1a;表--》字段--》数据 3、注入流程&#xff1a; 判断类型判断表名&#xff1a;遍历、爆破判断列名判断列名下的数据长度查出数…

【管理篇】如何处理团队里的老资格员工和高能力员工?

目录标题 两类员工对比&#x1f93a;老资格员工高能力员工 作为领导你应该怎么做&#xff1f; 在管理团队时&#xff0c;处理老资格员工和高能力员工是一项至关重要的任务。这两类员工在团队中扮演着不同的角色和有着不同的需求&#xff0c;因此需要针对性的管理和激励。下面将…