Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >如何判断算法是否有可优化空间?

如何判断算法是否有可优化空间?

作者头像
BBuf
发布于 2020-10-30 03:18:37
发布于 2020-10-30 03:18:37
1.3K00
代码可运行
举报
文章被收录于专栏:GiantPandaCVGiantPandaCV
运行总次数:0
代码可运行

❝【GiantPandaCV导语】计算Armv7a架构理论gflops以及自己写的某个算法的gflops的方法,另外提供了一个脚本可以显示native版矩阵乘法各个尺寸对应的gflops。 ❞

1. 前言

之前一直在写一些算法怎么优化,包括算法逻辑甚至是更加底层一些的文章,但是测试工作都做得比较随意,也就是粗略的比较时间。最近准备学习一下矩阵乘法的优化,觉得这种比较方式实际上是看不出太多信息的,比如不知道当前版本的算法在某块指定硬件上是否还存在优化空间。因此,这篇文章尝试向大家介绍另外一个算法加速的评判标准,即算法的浮点峰值(gflops)。

❝gflops代表计算量除以耗时获得的值。 ❞

之前高叔叔发了一篇文章教会我们如何计算硬件的浮点峰值(https://zhuanlan.zhihu.com/p/28226956),高叔叔的开源代码是针对x86架构的。然后,我针对移动端(ArmV7-a架构)模仿了一下,在测出硬件的浮点峰值之后,手写了一个Native版的矩阵乘法并计算这个算法的gflops,以判断当前版本的算法离达到硬件浮点峰值还有多少优化空间。

2. Cortex-A17 硬件浮点峰值计算

详细原理请查看:浮点峰值那些事 。这里再截取一下计算浮点峰值的操作方法部分:

来自https://zhuanlan.zhihu.com/p/28226956

所以参考这一方法,即可在移动端测出浮点峰值,首先写出测试的核心代码实现,注意gflops的计算方法就是用计算量除以程序耗时:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <time.h>
#include <stdio.h>

#define LOOP (1e9)
#define OP_FLOATS (80)

void TEST(int);

static double get_time(struct timespec *start,
                       struct timespec *end) {
    return end->tv_sec - start->tv_sec + (end->tv_nsec - start->tv_nsec) * 1e-9;
}



int main() {
    struct timespec start, end;
    double time_used = 0.0;

    clock_gettime(CLOCK_MONOTONIC_RAW, &start);

    TEST(LOOP);

    clock_gettime(CLOCK_MONOTONIC_RAW, &end);

    time_used = get_time(&start, &end);
    printf("perf: %.6lf \r\n", LOOP * OP_FLOATS * 1.0 * 1e-9 / time_used);
}

注意这里的TEST是使用了纯汇编实现,即test.S文件,代码如下,为什么一次循环要发射10条vmla.f32指令,上面截取的计算方法部分讲的很清楚,这个地方也可以自己多试几组值获得更加精细的硬件FLOPs:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.text
.align 5

.global TEST

TEST:

.loop2:
    vmla.f32 q0, q0, q0
    vmla.f32 q1, q1, q1
    vmla.f32 q2, q2, q2
    vmla.f32 q3, q3, q3
    vmla.f32 q4, q4, q4
    vmla.f32 q5, q5, q5
    vmla.f32 q6, q6, q6
    vmla.f32 q7, q7, q7
    vmla.f32 q8, q8, q8
    vmla.f32 q9, q9, q9

    subs r0,r0,    #1
    bne .loop2

我在Cortex-A17上测试了单核的浮点峰值,结果如下:

测试结果

然后大概知道了硬件的浮点峰值,我们在优化自己的算法时就至少心中有数了。

3. 实现Native矩阵乘法,记录浮点峰值

接着,我们参考https://github.com/flame/how-to-optimize-gemm来实现一个Native版的矩阵乘法,即A矩阵的一行乘以B矩阵的一列获得C矩阵的一个元素(计算量为2 * M * N * K),并统计它的运算时间以计算gflops,另外为了发现矩阵乘法的gflops和矩阵尺寸的关系,我们将各个尺寸的矩阵乘法的gflops写到一个txt文件里面,后面我们使用Python的matplotlib库把这些数据画到一张图上显示出来。首先实现不同尺寸的矩阵乘法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define A( i, j ) a[ (i)*lda + (j) ]
#define B( i, j ) b[ (i)*ldb + (j) ]
#define C( i, j ) c[ (i)*ldb + (j) ]
// gemm C = A * B + C
void MatrixMultiply(int m, int n, int k, float *a, int lda, float *b, int ldb, float *c, int ldc)
{
    for(int i = 0; i < m; i++){
        for (int j=0; j<n; j++ ){    
            for (int p=0; p<k; p++ ){      
                C(i, j) = C(i, j) + A(i, p) * B(p, j);
            }
        }
    }
}

测试和统计FLOPs部分的代码比较长,就贴一点核心部分吧,完整部分可以到github获取(https://github.com/BBuf/ArmNeonOptimization/tree/master/optimize_gemm):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// i代表当前矩阵的长宽,长宽都等于i
for(int i = 40; i <= 800; i += 40){
       m = i;
       n = i;
       k = i;
       gflops = 2.0 * m * n * k * 1.0e-09;
       lda = m;
       ldb = n;
       ldc = k;
       a = (float *)malloc(lda * k * sizeof(float));
       b = (float *)malloc(ldb * n * sizeof(float));
       c = (float *)malloc(ldc * n * sizeof(float));
       prec = (float *)malloc(ldc * n * sizeof(float));
       nowc = (float *)malloc(ldc * n * sizeof(float));
       // 随机填充矩阵
       random_matrix(m, k, a, lda);
       random_matrix(k, n, b, ldb);
       random_matrix(m, n, prec, ldc);

       memset(prec, 0, ldc * n * sizeof(float));

       copy_matrix(m, n, prec, ldc, nowc, ldc);

       // 以nowc为基准,判断矩阵运行算结果是否正确
       MatrixMultiply(m, n, k, a, lda, b, ldb, nowc, ldc);

       // 循环20次,以最快的运行时间为结果
       for(int j=0; j < 20; j++){
           
           copy_matrix(m, n, prec, ldc, c, ldc);

           clock_gettime(CLOCK_MONOTONIC_RAW, &start);

           MatrixMultiply(m, n, k, a, lda, b, ldb, c, ldc);

           clock_gettime(CLOCK_MONOTONIC_RAW, &end);

           time_tmp = get_time(&start, &end);
           
           if(j == 0)
               time_best = time_tmp;
           else
               time_best = min(time_best, time_tmp);
       }

       diff = compare_matrices(m, n, c, ldc, nowc, ldc);

       if(diff > 0.5f || diff < -0.5f){
           exit(0);
       }

       printf("%d %le %le \n", i, gflops / time_best, diff);
       fflush(stdout);

       free(a);
       free(b);
       free(c);
       free(prec);
       free(nowc);
}
printf("\n");
fflush(stdout);

「在编译之后运行时只需要新增一个重定向命令,即可获得记录了矩阵大小和GFlops的txt文件,例:./unit_test >> now.txt, 注意now.txt需要自己先创建,并保证它有可写的权限。」

接下来我们使用下面的脚本将now.txt用图片的方式显示出来,并将图片保存到本地:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import matplotlib.pyplot as plt
import numpy as np

def solve(filename):
    f = open(filename)
    sizes = [40]
    times = [0.0]
    title = 'origin'
    while True:
        line = f.readline()
        if line:
            slices = line.split(" ")
            if len(slices) <= 2:
                break;
            size = int(slices[0])
            time = float(slices[1])
            sizes.append(size)
            times.append(time)
    return title, sizes, times

if __name__ == '__main__':
    plt.xlabel('size')
    plt.ylabel('gflops')
    t, x, y = solve('now.txt')
    plt.plot(x, y, label=t)
    plt.legend()
    plt.savefig('origin.png')
    plt.show()

我们来看一下结果:

Native版矩阵乘法的gflops

从这张图可以看到,在矩阵长宽取100的时候可以达到最高的gflops大概是0.25gflops,相对硬件的理论浮点峰值只有2-3%,所以此算法的优化空间还是非常巨大的,接下来我们就可以使用如减少乘法次数,内存对齐,分块等策略去改进这个算法获得更好的gflops。这样,我们在算法优化的过程中就可以更加直观的看到算法的性能。

4. 小结

这篇文章只是矩阵优化部分的开篇,主要是受到高叔叔的文章启发给对移动端或者PC端感兴趣的同学提供一个gflops的计算实例,并提供一个将gflops更加直观显示的脚本工具,希望对大家有用。

5. 参考

  • https://zhuanlan.zhihu.com/p/65436463
  • https://zhuanlan.zhihu.com/p/28226956
  • https://github.com/flame/how-to-optimize-gemm
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GiantPandaCV 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
基于how-to-optimize-gemm初探矩阵乘法优化
这次,我们来聊一个轻松一点的话题,那就是给你一个矩阵A和一个矩阵B,使用矩阵乘法获得目标矩阵C,相信大家都不难写出下面的代码:
BBuf
2020/11/09
1.4K0
基于how-to-optimize-gemm初探矩阵乘法优化
道阻且长_再探矩阵乘法优化
【GiantPandaCV导语】本文记录了笔者最近的一些优化gemm的思路和实现,这些思路大多是公开的方案,例如来自how-to-optimize-gemm工程的一些优化手段,来自ncnn的一些优化手段等。最终,笔者目前实现的版本在armv7a上可以达到50%左右的硬件利用率(这个利用率的确还不高,笔者也是一步步学习和尝试,大佬轻喷),本文记录了这些思路以及核心实现方法。改好的行主序代码(x86+armv7a版本)可以直接访问https://github.com/BBuf/how-to-optimize-gemm获取。
BBuf
2020/12/08
6570
道阻且长_再探矩阵乘法优化
cuBLAS矩阵乘法性能分析(附代码示例)
矩阵乘法是神经网络中最基础、最重要的一个运算。在用CUDA实现矩阵乘法时,不需要我们手动写,cuBLAS库提供了现成的矩阵乘法算子,例如cublasGemmEx和cublasLtMatmul。其中后者是轻量级版本,API调用更灵活。例如对于整数乘法,cublasLtMatmul支持int8的输入输出,而cublasGemmEx只支持int8输入,int32输出。
godweiyang
2021/09/08
2.6K0
OpenBLAS 中矩阵运算函数学习
OpenBLAS 库实现成熟优化的矩阵与矩阵乘法的函数 cblas_sgemm 和矩阵与向量乘法函数 cblas_sgemv,二者使用方法基本相同,参数较多,所以对参数的使用做个记录。
泽霖
2023/11/29
7370
【BBuf的cuda学习笔记十】Megatron-LM的gradient_accumulation_fusion优化
这篇文章来解析一下Megaton-LM涉及到的一个优化gradient_accumulation_fusion。这里fusion的意思是在gemm接口中会将当前的结果累加到先前计算的梯度上,所有这些都在一个操作中完成,可以避免多次访问global memory提升算子的带宽。下面解析一下这个优化的调度逻辑和cuda实现。
BBuf
2023/08/25
1.9K0
【BBuf的cuda学习笔记十】Megatron-LM的gradient_accumulation_fusion优化
【TVM 三代优化巡礼】在X86上将普通的矩阵乘法算子提速90倍
本文主要梳理一下在21年接触到优化gemm的知识,做一个学习总结。行文的顺序大概为:
BBuf
2022/05/27
1.2K0
【TVM 三代优化巡礼】在X86上将普通的矩阵乘法算子提速90倍
可以让深度学习编译器来指导算子优化吗
之前在阅读Ansor论文的时候(https://zhuanlan.zhihu.com/p/390783734)我就在想这样一个问题,既然Ansor是在人为指定的推导规则下启发式的生成高性能的Scheduler模板。那么这个算子生成的Scheduler模板是否可以反过来指导我们写程序呢?嗯,然后我就开启了这个实验,但最近因为工作的事情delay得厉害,终于在这个周末抽出时间来更新这个实验结果并且记录了这篇文章。由于笔者只对GEMM的优化熟悉,这里就以优化X86的GEMM为例子来探索。希望这篇文章能为你带来启发,文章所有的实验代码都放到了https://github.com/BBuf/tvm_learn ,感兴趣的可以点个star一起学习(学习TVM的4个月里,这个工程已经收到了快100star了,我很感激)。
BBuf
2021/09/14
9180
可以让深度学习编译器来指导算子优化吗
cuDNN 5对RNN模型的性能优化
原文:Optimizing Recurrent Neural Networks in cuDNN 5 作者:Jeremy Appleyard 翻译:赵屹华 审校:刘翔宇 责编:周建丁(zhoujd@csdn.net) 在GTC2016大会上,NVIDIA发布了最新版本的深度学习开发包,其中包括了cuDNN 5。第五代cuDNN引入了新的特性,提升了性能,并且支持最新一代的NVIDIA Tesla P100 GPU。cuDNN的新特性包括: 使用Winograd卷积算法,计算前向、后向卷积速度更快; 支
用户1737318
2018/06/06
2.3K0
解析卷积高速计算中的细节,有代码有真相
卷积是深度学习中的基础运算,那么卷积运算是如何加速到这么快的呢,掰开揉碎了给你看。
小白学视觉
2019/09/10
1.3K0
解析卷积高速计算中的细节,有代码有真相
【社区投稿】给 NdArray 装上 CUDA 的轮子
Ndarry是Rust编程语言中的一个高性能多维、多类型数组库。它提供了类似 numpy 的多种多维数组的算子。与 Python 相比 Rust 生态缺乏类似 CuPy, Jax 这样利用CUDA 进行加速的开源项目。虽然 Hugging Face 开源的 candle 可以使用 CUDA backend 但是 candle 项瞄准的是大模型的相关应用。本着自己造轮子是最好的学习方法,加上受到 Karpathy llm.c 项目的感召(这个项目是学习如何编写 CUDA kernel 的最好参考之一),我搞了一个 rlib 库给 NdArray 加上一个跑在 CUDA 上的矩阵乘法。ndarray-linalg 库提供的点乘其中一个实现(features)是依赖 openblas 的,对于低维的矩阵性能可以满足需求,但是机器学习,深度学习这些领域遇到的矩阵动辄上千维,openblas 里古老的优化到极致的 Fortran 代码还是敌不过通过并行性开挂的CUDA。
MikeLoveRust
2024/05/29
1430
【社区投稿】给 NdArray 装上 CUDA 的轮子
【AlexeyAB DarkNet框架解析】五,卷积层的前向传播解析
今天来介绍一下DarkNet中卷积层的前向传播和反向传播的实现,卷积层是卷积神经网络中的核心组件,了解它的底层代码实现对我们理解卷积神经网络以及优化卷积神经网络都有一些帮助。
BBuf
2020/02/21
1.2K0
【AlexeyAB DarkNet框架解析】五,卷积层的前向传播解析
矩阵相乘在GPU上的终极优化:深度解析Maxas汇编器工作原理
在从事深度学习框架的实现工作时,了解到 Nervana 有一个称为 Maxas 的汇编代码生成器项目,可以生成性能超过 nVidia 官方版本的矩阵相乘的 GPU 机器码,由此对其工作原理产生兴趣。
机器之心
2020/05/19
9230
矩阵相乘在GPU上的终极优化:深度解析Maxas汇编器工作原理
Im2Col+GEMM的改进方法MEC,一种更加高效的卷积计算策略
前面介绍了Im2Col+GEMM来实现卷积以在某些条件下获得更好的访存和计算效率,详见:详解Im2Col+Pack+Sgemm策略更好的优化卷积运算 。然后,最近偶然发现了Im2Col+GEMM的一个改进版本即MEC: Memory-efficient Convolution for Deep Neural Network ,这是发表在ICML 2017的文章,它主要优化了Im2Col+GEMM计算策略中的内存消耗,并且也能提升一点速度,是一个不错的卷积加速算法。所以我在这里结合论文然后复现了一下代码实现来为分享一下。
BBuf
2020/10/30
2.4K0
Im2Col+GEMM的改进方法MEC,一种更加高效的卷积计算策略
【从零开始学深度学习编译器】二,TVM中的scheduler
在【从零开始学深度学习编译器】一,深度学习编译器及TVM 介绍我们已经知道TVM可以将各种深度学习训练框架的模型(计算图)转化为内部的Graph IR(Relay),然后通过TVM提供的指令生成模块将Graph IR翻译成特定硬件可执行的指令或者代码。总的来说的TVM的思想可以总结为表示和调度分离,所谓表示就是IR,调度就是scheduler。同时,在高性能计算方面TVM提供了多种调度源语(scheduler),包含了大多数常见的优化手段如算子融合,读写缓存,分块计算,并行计算等等,这些计算方法都可以通过scheduler进行实现。所以这一节,我们就一起来探索一下TVM中的scheduler。
BBuf
2021/04/16
2K0
【AI系统】QNNPack 算法
QNNPACK(Quantized Neural Networks PACKage 是 Marat Dukhan (Meta) 开发的专门用于量化神经网络计算的加速库,其卓越的性能表现一经开源就击败了几乎全部已公开的加速算法。到目前为止,QNNPACK 仍然是已公开的,用于移动端(手机)的,性能最优的量化神经网络加速库。本文将会深入介绍 QNNPACK 算法的实现过程。
用户11307734
2024/12/06
730
性能比拼!超详细的Tengine GEMM矩阵乘法汇编教程
Tengine 是OPEN AI LAB 针对前端智能设备开发的软件开发包,核心部分是一个轻量级,模块化,高性能的AI 推断引擎,并支持用DLA、GPU、xPU作为硬件加速计算资源异构加速。
CV君
2019/12/27
2.2K0
AI芯片:高性能卷积计算中的数据复用
深度学习的发展过程中,较高的计算量是制约其应用的因素之一。卷积神经网络中,主要计算为三维的卷积计算(后简称为卷积),现有的主流处理器难以高性能,高效能的完成卷积计算。相比一般的通用计算,卷积计算中存在的大量数据复用以及计算的规则性,在硬件的微架构(后简称为架构)设计和计算优化上有很大的优化空间,由此诞生了众多针对深度学习加速的AI芯片。卷积计算过程可以表示如下
sea-wind
2019/09/11
2.4K0
AI芯片:高性能卷积计算中的数据复用
详解Im2Col+Pack+Sgemm策略更好的优化卷积运算
❝[GiantPandaCV导语] 「这篇文章是基于NCNN的Sgemm卷积为大家介绍Im2Col+Pack+Sgemm的原理以及算法实现,希望对算法优化感兴趣或者做深度学习模型部署的读者带来帮助」。 ❞
BBuf
2020/09/21
3.1K0
详解Im2Col+Pack+Sgemm策略更好的优化卷积运算
AI部署篇 | CUDA学习笔记2:矩阵乘法与GPU优化(附CUDA代码)
获得 C 矩阵的计算方法都是相同的,只不过使用的是矩阵 A、B 不同的元素来进行计算,即不同数据的大量相同计算操作,这种计算是特别适合使用GPU来计算,因为GPU拥有大量简单重复的计算单元,通过并行就能极大的提高计算效率。
集智书童公众号
2022/02/10
5.9K0
AI部署篇 | CUDA学习笔记2:矩阵乘法与GPU优化(附CUDA代码)
DeepSeek开源周 Day03:从DeepGEMM看大模型算力提速的矩阵乘法
今天是DeepSeek开源周的第三天,继FlashMLA和DeepEP之后,DeepSeek开源了DeepGEMM库。作为一个专注于FP8精度通用矩阵乘法的高性能库,DeepGEMM在提供极致性能的同时保持了令人惊讶的代码简洁性。
致Great
2025/02/27
1860
DeepSeek开源周 Day03:从DeepGEMM看大模型算力提速的矩阵乘法
推荐阅读
相关推荐
基于how-to-optimize-gemm初探矩阵乘法优化
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档