详解矩阵乘法中的Strassen算法

2024-06-02 16:38

本文主要是介绍详解矩阵乘法中的Strassen算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

机器学习中需要训练大量数据,涉及大量复杂运算,例如卷积、矩阵等。这些复杂运算不仅多,而且每次计算的数据量很大,如果能针对这些运算进行优化,可以大幅提高性能。

一、矩阵乘法

如下图所示:

Figure 1 Matrix Multiplication

二、Strassen算法

Figure 2 x^3 vs. x^2.807

三、Strassen原理详解

Strassen算法正是从这个角度出发,实现了降低算法复杂度!

实现步骤可以分为以下4步:

3.1 Strassen实现步骤

 

四、Strassen算法的代码实现

我们以MNN中关于Strassen算法源码实现来学习:https://github.com/alibaba/MNN/blob/master/source/backend/cpu/compute/StrassenMatmulComputor.cpp。

类StrassenMatrixComputor提供了3个API供调用:

_generateTrivalMatMul(const Tensor* AT, const Tensor* BT, const Tensor* CT);

普通矩阵乘法计算

_generateMatMul(const Tensor* AT, const Tensor* BT, const Tensor* CT, int currentDepth);

Strassen算法的矩阵乘法

_generateMatMulConstB(const Tensor* AT, const Tensor* BT, const Tensor* CT, int currentDepth);

Strassen算法的矩阵乘法(和MatMul的区别在于内存Buffer是否允许复用)

我们以_generateMatMul为例来学习下Strassen算法如何实现,可以分成如下几步:

第一步:使用Strassen算法收益判断

在矩阵操作中,因为需要对矩阵的维数进行扩展,涉及大量读写操作,这些读写操作都需要大量循环,如果读写次数超出使用Strassen乘法的收益的话,就得不偿失了,那么就使用普通的矩阵乘法

    /*Compute the memory read / write cost for expandMatrix Mul need eSub*lSub*hSub*(1+1.0/CONVOLUTION_TILED_NUMBWR), Matrix Add/Sub need x*y*UNIT*3 (2 read 1 write)*/float saveCost =(eSub * lSub * hSub) * (1.0f + 1.0f / CONVOLUTION_TILED_NUMBWR) - 4 * (eSub * lSub) * 3 - 7 * (eSub * hSub * 3);if (currentDepth >= mMaxDepth || e <= CONVOLUTION_TILED_NUMBWR || l % 2 != 0 || h % 2 != 0 || saveCost < 0.0f) {return _generateTrivialMatMul(AT, BT, CT);}

第二步:分块

    auto aStride = AT->stride(0);auto a11     = AT->host<float>() + 0 * aUnit * eSub + 0 * aStride * lSub;auto a12     = AT->host<float>() + 0 * aUnit * eSub + 1 * aStride * lSub;auto a21     = AT->host<float>() + 1 * aUnit * eSub + 0 * aStride * lSub;auto a22     = AT->host<float>() + 1 * aUnit * eSub + 1 * aStride * lSub;auto bStride = BT->stride(0);auto b11     = BT->host<float>() + 0 * bUnit * lSub + 0 * bStride * hSub;auto b12     = BT->host<float>() + 0 * bUnit * lSub + 1 * bStride * hSub;auto b21     = BT->host<float>() + 1 * bUnit * lSub + 0 * bStride * hSub;auto b22     = BT->host<float>() + 1 * bUnit * lSub + 1 * bStride * hSub;auto cStride = CT->stride(0);auto c11     = CT->host<float>() + 0 * aUnit * eSub + 0 * cStride * hSub;auto c12     = CT->host<float>() + 0 * aUnit * eSub + 1 * cStride * hSub;auto c21     = CT->host<float>() + 1 * aUnit * eSub + 0 * cStride * hSub;auto c22     = CT->host<float>() + 1 * aUnit * eSub + 1 * cStride * hSub;

第三步:分治和递归

Strassen算法核心就是分治思想。这一步可以写成下列所示伪代码:

1. If n = 1 Output A × B
2. Else
3. Compute A11,B11, . . . ,A22,B22 % by computing m = n/2
4. P1   Strassen(A11,B12 − B22)
5. P2   Strassen(A11 + A12,B22)
6. P3   Strassen(A21 + A22,B11)
7. P4   Strassen(A22,B21 − B11)
8. P5   Strassen(A11 + A22,B11 + B22)
9. P6   Strassen(A12 − A22,B21 + B22)
10. P7   Strassen(A11 − A21,B11 + B12)
11. C11   P5 + P4 − P2 + P6
12. C12   P1 + P2
13. C21   P3 + P4
14. C22   P1 + P5 − P3 − P7
15. Output C
16. End If

例如其中的一步代码如下所示:

   {// S1=A21+A22, T1=B12-B11, P5=S1T1auto f = [a22, a21, b11, b12, xAddr, yAddr, eSub, lSub, hSub, aStride, bStride]() {MNNMatrixAdd(xAddr, a21, a22, eSub * aUnit / 4, eSub * aUnit, aStride, aStride, lSub);MNNMatrixSub(yAddr, b12, b11, lSub * bUnit / 4, lSub * bUnit, bStride, bStride, hSub);};mFunctions.emplace_back(f);auto code = _generateMatMul(X.get(), Y.get(), C22.get(), currentDepth);if (code != NO_ERROR) {return code;}}

递归执行,得到最终结果!

这篇关于详解矩阵乘法中的Strassen算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Spring 缓存在项目中的使用详解

《Spring缓存在项目中的使用详解》Spring缓存机制,Cache接口为缓存的组件规范定义,包扩缓存的各种操作(添加缓存、删除缓存、修改缓存等),本文给大家介绍Spring缓存在项目中的使用... 目录1.Spring 缓存机制介绍2.Spring 缓存用到的概念Ⅰ.两个接口Ⅱ.三个注解(方法层次)Ⅲ.

Spring Boot 整合 Redis 实现数据缓存案例详解

《SpringBoot整合Redis实现数据缓存案例详解》Springboot缓存,默认使用的是ConcurrentMap的方式来实现的,然而我们在项目中并不会这么使用,本文介绍SpringB... 目录1.添加 Maven 依赖2.配置Redis属性3.创建 redisCacheManager4.使用Sp

Spring Cache注解@Cacheable的九个属性详解

《SpringCache注解@Cacheable的九个属性详解》在@Cacheable注解的使用中,共有9个属性供我们来使用,这9个属性分别是:value、cacheNames、key、key... 目录1.value/cacheNames 属性2.key属性3.keyGeneratjavascriptor

PyTorch中cdist和sum函数使用示例详解

《PyTorch中cdist和sum函数使用示例详解》torch.cdist是PyTorch中用于计算**两个张量之间的成对距离(pairwisedistance)**的函数,常用于点云处理、图神经网... 目录基本语法输出示例1. 简单的 2D 欧几里得距离2. 批量形式(3D Tensor)3. 使用不

Python模拟串口通信的示例详解

《Python模拟串口通信的示例详解》pySerial是Python中用于操作串口的第三方模块,它支持Windows、Linux、OSX、BSD等多个平台,下面我们就来看看Python如何使用pySe... 目录1.win 下载虚www.chinasem.cn拟串口2、确定串口号3、配置串口4、串口通信示例5

Nginx 413修改上传文件大小限制的方法详解

《Nginx413修改上传文件大小限制的方法详解》在使用Nginx作为Web服务器时,有时会遇到客户端尝试上传大文件时返回​​413RequestEntityTooLarge​​... 目录1. 理解 ​​413 Request Entity Too Large​​ 错误2. 修改 Nginx 配置2.1

springboot项目redis缓存异常实战案例详解(提供解决方案)

《springboot项目redis缓存异常实战案例详解(提供解决方案)》redis基本上是高并发场景上会用到的一个高性能的key-value数据库,属于nosql类型,一般用作于缓存,一般是结合数据... 目录缓存异常实践案例缓存穿透问题缓存击穿问题(其中也解决了穿透问题)完整代码缓存异常实践案例Red

Java 的 Condition 接口与等待通知机制详解

《Java的Condition接口与等待通知机制详解》在Java并发编程里,实现线程间的协作与同步是极为关键的任务,本文将深入探究Condition接口及其背后的等待通知机制,感兴趣的朋友一起看... 目录一、引言二、Condition 接口概述2.1 基本概念2.2 与 Object 类等待通知方法的区别

Java List排序实例代码详解

《JavaList排序实例代码详解》:本文主要介绍JavaList排序的相关资料,Java排序方法包括自然排序、自定义排序、Lambda简化及多条件排序,实现灵活且代码简洁,文中通过代码介绍的... 目录一、自然排序二、自定义排序规则三、使用 Lambda 表达式简化 Comparator四、多条件排序五、

Java实例化对象的​7种方式详解

《Java实例化对象的​7种方式详解》在Java中,实例化对象的方式有多种,具体取决于场景需求和设计模式,本文整理了7种常用的方法,文中的示例代码讲解详细,有需要的可以了解下... 目录1. ​new 关键字(直接构造)​2. ​反射(Reflection)​​3. ​克隆(Clone)​​4. ​反序列化