cuBLAS矩阵乘法性能分析(附代码示例)

2023-10-27

使用教程

矩阵乘法是神经网络中最基础、最重要的一个运算。在用CUDA实现矩阵乘法时,不需要我们手动写,cuBLAS库提供了现成的矩阵乘法算子,例如cublasGemmExcublasLtMatmul。其中后者是轻量级版本,API调用更灵活。例如对于整数乘法,cublasLtMatmul支持int8的输入输出,而cublasGemmEx只支持int8输入,int32输出。

今天我只给大家讲解cublasGemmEx,主要使用起来相对更简洁一点。

官方文档地址:

CUDA Toolkit Documentation

经过翻阅网上各种教程,我找到了一篇我认为写的最好的博客。例子举得非常好,写的很详细。地址如下:

有关CUBLAS中的矩阵乘法函数 - 爨爨爨好 - 博客园

具体的使用方法可以参见上面这篇博客,我这里就不再赘述了。

今天我主要给大家演示一下,不同数据类型的矩阵乘法,速度和结果上到底有多大的差异?

测试代码

我写了一个简单的测试代码:

#include <sys/time.h>
#include <cuda_profiler_api.h>
#include <cublas_v2.h>
#include <cuda.h>
#include <cuda_fp16.h>
#include <cuda_runtime.h>
#include <stdio.h>

int8_t float2int8(float f, float scale) {
    int8_t i = int8_t(f * scale);
    if (i < -127) i = -127;
    if (i > 127) i = 127;
    return i;
}

template <typename T, typename S>
void allocate_memory(int m, int n, int k, T **A, T **B, S **C) {
    cudaMallocManaged(A, m * k * sizeof(T));
    cudaMallocManaged(B, k * n * sizeof(T));
    cudaMallocManaged(C, m * n * sizeof(S));
}

template <typename T, typename S>
void free_memory(T *A, T *B, S *C) {
    cudaFree(A);
    cudaFree(B);
    cudaFree(C);
}

template <typename T, typename S>
int cublas_gemm_ex(cublasHandle_t handle, cublasOperation_t transA, cublasOperation_t transB,
                   int m, int n, int k, T *A, T *B, S *C, int lda, int ldb, int ldc,
                   S *alpha, S *beta, int algo) {
    cudaDataType_t AType, BType, CType, ComputeType;
    if (std::is_same<T, float>::value) {
        AType = BType = CType = ComputeType = CUDA_R_32F;
    } else if (std::is_same<T, __half>::value) {
        AType = BType = CType = ComputeType = CUDA_R_16F;
    } else if (std::is_same<T, int8_t>::value) {
        AType = BType = CUDA_R_8I;
        CType = ComputeType = CUDA_R_32I;
    } else {
        printf("Not supported data type.");
        return -1;
    }
    cublasStatus_t status;
    status = cublasGemmEx(handle,
                          transA,
                          transB,
                          m,
                          n,
                          k,
                          alpha,
                          A,
                          AType,
                          lda,
                          B,
                          BType,
                          ldb,
                          beta,
                          C,
                          CType,
                          ldc,
                          ComputeType,
                          static_cast<cublasGemmAlgo_t>(algo));
    
    if (status == CUBLAS_STATUS_SUCCESS)
        return 1;
    else
        return -1;
}

template <typename T, typename S>
void test_gemm(cublasHandle_t handle, int m, int n, int k, T *A, T *B, S *C,
               S *alpha, S *beta, int algo, int iteration) {
    float total_time = 0;
    for (int i = 0; i < iteration; ++i) {
        struct timeval start, end;
        cudaDeviceSynchronize();
        cudaProfilerStart();
        gettimeofday(&start, NULL);
        int success = cublas_gemm_ex(handle,
                                     CUBLAS_OP_N,
                                     CUBLAS_OP_N,
                                     n,
                                     m,
                                     k,
                                     B,
                                     A,
                                     C,
                                     n,
                                     k,
                                     n,
                                     alpha,
                                     beta,
                                     static_cast<cublasGemmAlgo_t>(algo));
        cudaDeviceSynchronize();
        gettimeofday(&end, NULL);
        cudaProfilerStop();
        if (success > 0 && i > 0)
            total_time += (end.tv_sec - start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) * 0.001;
    }
    if (total_time > 0)
        printf("algo %d: %.3f ms\n", algo, total_time / (iteration - 1));
}

int main() {
    int m = 4096, n = 8192, k = 1024;
    printf("shape: (%d, %d) x (%d, %d)\n", m, k, k, n);
    int start_algo = CUBLAS_GEMM_DEFAULT;
    int end_algo = CUBLAS_GEMM_ALGO23;
    int start_algo_t_op = CUBLAS_GEMM_DEFAULT_TENSOR_OP;
    int end_algo_t_op = CUBLAS_GEMM_ALGO15_TENSOR_OP;
    int iteration = 10;

    float *fA, *fB, *fC;
    __half *hA, *hB, *hC;
    int8_t *iA, *iB; int32_t *iC;
    float f_alpha = 1, f_beta = 0;
    __half h_alpha = __float2half_rn(1.0), h_beta = __float2half_rn(0.0);
    int32_t i_alpha = 1, i_beta = 0;
    allocate_memory(m, n, k, &fA, &fB, &fC);
    allocate_memory(m, n, k, &hA, &hB, &hC);
    allocate_memory(m, n, k, &iA, &iB, &iC);
    for (int i = 0; i < m * k; ++i) {
        fA[i] = float(i % 255 - 127) / 127;
        hA[i] = __float2half_rn(fA[i]);
        iA[i] = float2int8(fA[i], 127);
    } 
    for (int i = 0; i < k * n; ++i) {
        fB[i] = float(i % 255 - 127) / 127;
        hB[i] = __float2half_rn(fB[i]);
        iB[i] = float2int8(fB[i], 127);
    }
    cublasHandle_t handle;
    cublasCreate(&handle);
    
    printf(">>>>>>>>>>>>>>>>> test fp32 >>>>>>>>>>>>>>>>>\n");
    for (int algo = start_algo; algo <= end_algo; ++algo)
        test_gemm(handle, m, n, k, fA, fB, fC, &f_alpha, &f_beta, algo, iteration);
    for (int algo = start_algo_t_op; algo <= end_algo_t_op; ++algo)
        test_gemm(handle, m, n, k, fA, fB, fC, &f_alpha, &f_beta, algo, iteration);
    

    printf(">>>>>>>>>>>>>>>>> test fp16 >>>>>>>>>>>>>>>>>\n");
    for (int algo = start_algo; algo <= end_algo; ++algo)
        test_gemm(handle, m, n, k, hA, hB, hC, &h_alpha, &h_beta, algo, iteration);
    for (int algo = start_algo_t_op; algo <= end_algo_t_op; ++algo)
        test_gemm(handle, m, n, k, hA, hB, hC, &h_alpha, &h_beta, algo, iteration);

    printf(">>>>>>>>>>>>>>>>> test int8 >>>>>>>>>>>>>>>>>\n");
    for (int algo = start_algo; algo <= end_algo; ++algo)
        test_gemm(handle, m, n, k, iA, iB, iC, &i_alpha, &i_beta, algo, iteration);
    for (int algo = start_algo_t_op; algo <= end_algo_t_op; ++algo)
        test_gemm(handle, m, n, k, iA, iB, iC, &i_alpha, &i_beta, algo, iteration);
    
    printf(">>>>>>>>>>>>>>>>> compare result >>>>>>>>>>>>>>>>>\n");
    printf("fp32: ");
    for (int i = 0; i < 10; ++i)
        printf("%.5f%c", fC[i], " \n"[i==9]);
    printf("fp16: ");
    for (int i = 0; i < 10; ++i)
        printf("%.5f%c", float(hC[i]), " \n"[i==9]);
    printf("int8: ");
    for (int i = 0; i < 10; ++i)
        printf("%.5f%c", float(iC[i])/127/127, " \n"[i==9]);

    free_memory(iA, iB, iC);
    free_memory(fA, fB, fC);
    free_memory(hA, hB, hC);
    return 0;
}

代码保存为test_gemm.cpp,然后执行下面命令进行编译:

nvcc test_gemm.cpp -o test_gemm -L/usr/local/cuda/lib64 -lcudart -lcuda -lcublas

最后执行./test_gemm运行就行了。

这里计算的是

C = A \cdot B,其中
A的维度是
(m, k)
B的维度是
(k, n)
C的维度是
(m, n)。由于在C++和Python中新建的数组默认都是行优先存储,而cuBLAS计算矩阵乘法是默认是列优先存储。所以你新建的矩阵送到cuBLAS矩阵乘法算子后,它默认识别成了列优先存储。因此需要调整一下运算顺序,或者对矩阵进行转置。

你需要记住一点,「行优先存储的矩阵送到cuBLAS后,相当于做了一次转置,同样计算得到的矩阵

也是列优先存储的,你需要转置后再用行优先存储来正常读取」。而根据矩阵的运算法则,我们有:

所以三个转置后的矩阵就不需要经过任何处理了,直接送到cuBLAS里计算就行了。

运行结果

我对比了三种数据类型:fp32fp16int8,测试环境是V100显卡、CUDA 10.1。由于V100显卡没有int8的tensor core,所以速度并不能达到最快。要想全速进行int8的矩阵乘法,推荐使用sm75及以上的显卡,例如T4、A100等等。此外我还对比了不同的GEMM算法的效果。

执行上面的运行命令后,会输出如下的结果:

shape: (4096, 1024) x (1024, 8192)
>>>>>>>>>>>>>>>>> test fp32 >>>>>>>>>>>>>>>>>
algo -1: 4.831 ms
algo 2: 5.293 ms
algo 3: 5.406 ms
algo 4: 5.297 ms
algo 5: 5.098 ms
algo 6: 4.874 ms
algo 11: 4.870 ms
algo 18: 7.219 ms
algo 19: 6.061 ms
algo 20: 5.631 ms
algo 99: 1.110 ms
algo 100: 1.159 ms
algo 101: 1.688 ms
algo 102: 4.944 ms
algo 103: 4.744 ms
algo 104: 4.700 ms
algo 105: 4.679 ms
algo 106: 4.679 ms
algo 107: 4.675 ms
algo 108: 4.676 ms
algo 109: 4.677 ms
algo 110: 4.676 ms
algo 111: 4.676 ms
algo 112: 4.678 ms
algo 113: 4.675 ms
algo 114: 4.676 ms
algo 115: 4.689 ms
>>>>>>>>>>>>>>>>> test fp16 >>>>>>>>>>>>>>>>>
algo -1: 2.423 ms
algo 1: 2.460 ms
algo 2: 2.565 ms
algo 3: 2.518 ms
algo 5: 2.398 ms
algo 6: 2.416 ms
algo 99: 0.737 ms
algo 100: 1.581 ms
algo 101: 1.032 ms
algo 102: 0.978 ms
algo 103: 0.767 ms
algo 104: 0.790 ms
algo 105: 0.803 ms
algo 106: 0.774 ms
algo 107: 2.656 ms
algo 108: 2.577 ms
algo 109: 2.518 ms
algo 110: 0.925 ms
algo 111: 0.951 ms
algo 112: 0.935 ms
algo 113: 0.909 ms
algo 114: 2.549 ms
algo 115: 2.532 ms
>>>>>>>>>>>>>>>>> test int8 >>>>>>>>>>>>>>>>>
algo -1: 1.232 ms
algo 0: 7.544 ms
algo 1: 1.217 ms
algo 2: 1.294 ms
algo 3: 2.362 ms
algo 99: 1.243 ms
algo 100: 1.244 ms
algo 101: 1.237 ms
algo 102: 1.232 ms
algo 103: 1.230 ms
algo 104: 1.224 ms
algo 105: 1.222 ms
algo 106: 1.224 ms
algo 107: 1.225 ms
algo 108: 1.224 ms
algo 109: 1.218 ms
algo 110: 1.217 ms
algo 111: 1.217 ms
algo 112: 1.218 ms
algo 113: 1.218 ms
algo 114: 1.216 ms
algo 115: 1.217 ms
>>>>>>>>>>>>>>>>> compare result >>>>>>>>>>>>>>>>>
fp32: 52.38629 44.76633 37.65229 31.04420 24.94203 19.34578 14.25543 9.67102 5.59253 2.01996
fp16: 52.46875 44.84375 37.40625 31.21875 24.95312 19.39062 14.28125 9.69531 5.61328 2.05078
int8: 52.38626 44.76632 37.65230 31.04421 24.94203 19.34577 14.25544 9.67103 5.59254 2.01996

这里简单解释一下,algo -1到23表示不使用tensor core算法的结果,algo 99到115表示使用tensor core算法的结果。

可以看到图中缺失了一部分算法的结果,因为那些算法可能不适用于当前的矩阵乘法,因此报错了。

汇总一下各自最快的结果(不使用vs使用tensor core):

  • fp32: 4.83 1.11
  • fp16: 2.41 0.73
  • int8: 1.21 1.21

由于V100显卡没有int8的tensor core,所以int8的两个结果是相同的。结果也符合我们的预期,速度上fp32慢于fp16慢于int8。所以在实际的深度学习应用中,流行使用混合精度,也就是用fp16来进行训练和推理。

而int8是速度最快的,所以如果训练和推理也都能使用int8的话,速度上将会迈上一个新的台阶。

那么一个浮点数的矩阵乘法怎么转变为整数的矩阵乘法呢?这里我不会详细讲,后续会出一个详细的量化教程。

简单来说,对于一个浮点数

f,假设范围在
[-1, 1]之间,那我们可以将它表示成一个
[-127, 127]之间的8位整数
i,转换关系为:

那么浮点数矩阵乘法

f_3 = f_1 \cdot f_2就可以表示为:

所以只需要计算int8矩阵乘法

i_1 \cdot i_2,然后得到int32类型的输出结果之后,除以
127^2就可以得到原始的浮点数结果了。

那么由于这里有个类型转换的操作,所以会产生误差。但是在我们的样例中,int8的误差竟然比fp16还要小很多,结果和fp32几乎一模一样。这主要由于是我构造的矩阵数据分布非常均匀有规律,因此计算误差会很小,实际深度网络中int8的误差会较大。

结语

int8甚至更低比特的量化的实际收益非常大,提速可以达到将近2倍。虽然现在有很多现成的自动量化工具,但是效果上或多或少都有一定的损失,速度上也没有达到极致。因此今后量化是一个不错的方向,值得一试。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

cuBLAS矩阵乘法性能分析(附代码示例) 的相关文章

  • 不同枚举类型的范围和可转换性

    在什么条件下可以从一种枚举类型转换为另一种枚举类型 让我们考虑以下代码 include
  • C# 中通过 Process.Kill() 终止的进程的退出代码

    如果在我的 C 应用程序中 我正在创建一个可以正常终止或开始行为异常的子进程 在这种情况下 我通过调用 Process Kill 来终止它 但是 我想知道该进程是否已退出通常情况下 我知道我可以获得终止进程的错误代码 但是正常的退出代码是什
  • 无法在 Python 3 中导入 cProfile

    我试图将 cProfile 模块导入 Python 3 3 0 但出现以下错误 Traceback most recent call last File
  • Pandas:merge_asof() 对多行求和/不重复

    我正在处理两个数据集 每个数据集具有不同的关联日期 我想合并它们 但因为日期不完全匹配 我相信merge asof 是最好的方法 然而 有两件事发生merge asof 不理想的 数字重复 数字丢失 以下代码是一个示例 df a pd Da
  • Fabric env.roledefs 未按预期运行

    On the 面料网站 http docs fabfile org en 1 10 usage execution html 给出这个例子 from fabric api import env env roledefs web hosts
  • C++ OpenSSL 导出私钥

    到目前为止 我成功地使用了 SSL 但遇到了令人困惑的障碍 我生成了 RSA 密钥对 之前使用 PEM write bio RSAPrivateKey 来导出它们 然而 手册页声称该格式已经过时 实际上它看起来与通常的 PEM 格式不同 相
  • 将多个表映射到实体框架中的单个实体类

    我正在开发一个旧数据库 该数据库有 2 个具有 1 1 关系的表 目前 我为每个定义的表定义了一种类型 1Test 1Result 我想将这些特定的表合并到一个类中 当前的类型如下所示 public class Result public
  • 使用 Bearer Token 访问 IdentityServer4 上受保护的 API

    我试图寻找此问题的解决方案 但尚未找到正确的搜索文本 我的问题是 如何配置我的 IdentityServer 以便它也可以接受 授权带有 BearerTokens 的 Api 请求 我已经配置并运行了 IdentityServer4 我还在
  • 控件的命名约定[重复]

    这个问题在这里已经有答案了 Microsoft 在其网站上提供了命名指南 here http msdn microsoft com en us library xzf533w0 VS 71 aspx 我还有 框架设计指南 一书 我找不到有关
  • 对年龄列进行分组/分类

    我有一个数据框说df有一个柱子 Ages gt gt gt df Age 0 22 1 38 2 26 3 35 4 35 5 1 6 54 我想对这个年龄段进行分组并创建一个像这样的新专栏 If age gt 0 age lt 2 the
  • 如何在 Python 中追加到 JSON 文件?

    我有一个 JSON 文件 其中包含 67790 1 kwh 319 4 现在我创建一个字典a dict我需要将其附加到 JSON 文件中 我尝试了这段代码 with open DATA FILENAME a as f json obj js
  • 解释 Python 中的数字范围

    在 Pylons Web 应用程序中 我需要获取一个字符串 例如 关于如何做到这一点有什么建议吗 我是 Python 新手 我还没有找到任何可以帮助解决此类问题的东西 该列表将是 1 2 3 45 46 48 49 50 51 77 使用
  • 垃圾收集器是否在单独的进程中运行?

    垃圾收集器是否在单独的进程中启动 例如 如果我们尝试测量某段代码所花费的进程时间 并且在此期间垃圾收集器开始收集 它会在新进程上启动还是在同一进程中启动 它的工作原理如下吗 Code Process 1 gt Garbage Collect
  • Conda SafetyError:文件大小不正确

    使用创建 Conda 环境时conda create n env name python 3 6 我收到以下警告 Preparing transaction done Verifying transaction SafetyError Th
  • 什么时候虚拟继承是一个好的设计? [复制]

    这个问题在这里已经有答案了 EDIT3 请务必在回答之前清楚地了解我要问的内容 有 EDIT2 和很多评论 有 或曾经 有很多答案清楚地表明了对问题的误解 我知道这也是我的错 对此感到抱歉 嗨 我查看了有关虚拟继承的问题 class B p
  • 覆盖子类中的字段或属性

    我有一个抽象基类 我想声明一个字段或属性 该字段或属性在从该父类继承的每个类中具有不同的值 我想在基类中定义它 以便我可以在基类方法中引用它 例如覆盖 ToString 来表示 此对象的类型为 property field 我有三种方法可以
  • Python:如何将列表列表的元素转换为无向图?

    我有一个程序 可以检索 PubMed 出版物列表 并希望构建一个共同作者图 这意味着对于每篇文章 我想将每个作者 如果尚未存在 添加为顶点 并添加无向边 或增加每个合著者之间的权重 我设法编写了第一个程序 该程序检索每个出版物的作者列表 并
  • 将控制台重定向到 .NET 程序中的字符串

    如何重定向写入控制台的任何内容以写入字符串 对于您自己的流程 Console SetOut http msdn microsoft com en us library system console setout aspx并将其重定向到构建在
  • Python Selenium:如何在文本文件中打印网站上的值?

    我正在尝试编写一个脚本 该脚本将从 tulsaspca org 网站获取以下 6 个值并将其打印在 txt 文件中 最终输出应该是 905 4896 7105 23194 1004 42000 放置的动物 的 HTML span class
  • 如何防止用户控件表单在 C# 中处理键盘输入(箭头键)

    我的用户控件包含其他可以选择的控件 我想实现使用箭头键导航子控件的方法 问题是家长控制拦截箭头键并使用它来滚动其视图什么是我想避免的事情 我想自己解决控制内容的导航问题 我如何控制由箭头键引起的标准行为 提前致谢 MTH 这通常是通过重写

随机推荐

  • ReactNative ListView + 上拉加载更多 + 下拉刷新

    ListView 上拉加载更多 下拉刷新 一 内容简介 ListView列表在添加了上拉加载更多功能之后再添加下拉刷新 二 代码实现 1 引入原生组件 RefreshControl import ListView View Text Act
  • mysql表的约束

    目录 一 表的约束分类 1 not null 非空 输入的数据内容不能为空 2 unique key 唯一键 输入的数据可以为null或者跳过赋予他的值 但是如果输入数据不能相同 3 primary key 主键 每个表中必须有唯一的主键
  • matlab编写dbscan聚类

    在Matlab中编写DBSCAN聚类的方法有很多种 一种常用的方法是手动编写代码 下面是一个简单的DBSCAN示例 function labels nClusters dbscan data eps MinPts data 数据点 eps
  • Qt 使用QInputDialog弹出输入框获取用户输入数据

    简要说明 在开发Qt程序的过程中 我们可能会需要在程序中弹出输入框 并且获取用户输入的数据 一种比较麻烦的做法就是新建一个对话框类 然后在主界面中调用对话框类 获取返回值 使用QInputDialog对话框类可以通过访问不同的接口函数 弹出
  • php导出数据xlsx

    lists 二维数组 public function xlsx lists 生成文件名 date date Y m d H i s time fileName XXXX date xlsx 头部标题 xlsx header array 序号
  • Java Springboot--swagger配置

    文章转载自 第一步 配置pom xml文件
  • 视线估计(Gaze Estimation)简介概述

    PaperWeekly 原创 作者 俞雨 单位 瑞士洛桑联邦理工学院博士 研究方向 视线估计 头部姿态估计 本文七个篇章总计涵盖 29 篇论文 总结了自深度学习以来 视线估计领域近五年的发展 概述 1 1 问题定义 广义的 Gaze Est
  • [Unity]Lua本地时间、倒计时和正计时。

    惯例 直接上代码 正计时开始时的时间戳 self begin time os time 倒计时时长 01 30 00 self countdown time 5400 是否开始计时 self is update local time tru
  • 一文搞定在Ubuntu安装tldr

    目录 第一步 执行安装命令 第二步 更新tldr数据库 第三步 测试tldr功能 补充 未成功返回的错误类型 在安装之前你得先在Ubuntu上登入你自己的账户 当然你肯定在刚安装好Ubuntu的时候就注册自己的账户并且登录了 第一步 执行安
  • 大页内存(HugePages)在通用程序优化中的应用

    今天给大家介绍一种比较新奇的程序性能优化方法 大页内存 HugePages 简单来说就是通过增大操作系统页的大小来减小页表 从而避免快表缺失 这方面的资料比较贫乏 而且网上绝大多数资料都是介绍它在Oracle数据库中的应用 这会让人产生一种
  • 2021-01-17

    静态路由实验 实验目的 1 全网所有网段全部基于192 168 1 0 24划分所得 2 R1 R4每台设备均有两个环回 3 全网可达 4 尽量减少路由条目 且防止环路 5 R5的环回5 5 5 5 24不能出现在其他的设备路由表中 6 按
  • 2021-04-12

    NLP 自然语言处理 和CV相比 nlp最大的特点是特征是离散的 不像cv中是一幅图 nlp是一个个的句子 简单说几点nlp的难点 1 相同意思的句子表达有多种 我爱踢足球 足球是我的爱好 我的爱好之一是足球 2 相同词在不同语境中意思不同
  • vue项目 v-for无法渲染问题

    使用map 函数 可能是解决了对象指向问题 目前还不知道原因 postlist fav2是在data 中定义的数组 在created 里对postlist fav2进行了数组对象的初始化操作 然后就无法渲染 使用map方法才能渲染到页面上
  • 写入单元格_Excel VBA单元格的基本操作(一)

    在Excel VBA中 对单元格的操作可以有多种形式来定义表示 1 打开Visual Basic 添加模块和过程 称之为 单元格操作 Sub 单元格操作 End Sub 2 单元格第一种表达方式 直接定位到某个单元格 B3 Sub 单元格操
  • ES6的Class的prototype、__proto__

    ES6继承与ES5的区别 ES6通过class实现继承 class的继承通过关键字extends实现 class Parent constructor name this name name getName console log this
  • Linux下基于Zynq用EthLite+GmiitoRgmii实现100M网络通信

    目录 前言 一 IP核配置 1 ETHLITE配置 2 GMIITORGMII配置 二 IP 连接关系 三 设备树描述 前言 本文将介绍如何在Linux下使用EthLite加GmiitoRgmii实现百兆网络通信 此方法只需要一个中断 若工
  • 百度AIStudio平台 持久化安装包

    目录 查看环境 创建目录 安装在该目录下 重启后仍可用 查看环境 平台使用的是conda创建的虚拟环境进行安装的包 不过我们可以使用pip安装工具快速安装 而且使用conda默认安装的包将在下次启动服务时还原 注 该教程不适用于tensor
  • Create a PCL visualizer in Qt with QtDesigner

    这是PCL文档中的例程实现 原文地址 http pointclouds org documentation tutorials qt visualizer php more on qt and pcl 介绍一下环境 Ubuntu16 04
  • std::numeric_limits 出错

    not enough actual parameters for macro max for std numeric limits
  • cuBLAS矩阵乘法性能分析(附代码示例)

    使用教程 矩阵乘法是神经网络中最基础 最重要的一个运算 在用CUDA实现矩阵乘法时 不需要我们手动写 cuBLAS库提供了现成的矩阵乘法算子 例如cublasGemmEx和cublasLtMatmul 其中后者是轻量级版本 API调用更灵活