Swintransformer模型的优化

news2024/12/24 9:00:20

SwinTransformer模型优化

文章目录

  • SwinTransformer模型优化
    • 1.SwinTransformer概述
    • 2.性能瓶颈分析
    • 3.模型优化
      • 3.1.transpose消除
      • 3.2.更好的layergroup
        • 3.1.1.SliceOp
        • 3.1.2.SqueezeOp
        • 3.1.3.weight切分
    • 4.优化效果

1.SwinTransformer概述

自从Transformer在NLP任务上取得突破性的进展之后,业内一直尝试着把Transformer用于CV领域。之前的若干尝试都是将Transformer用在了图像分类领域,但这些方法都面临两个非常严峻的挑战,一是多尺度问题,二是计算复杂度的问题。

基于这两个挑战,swint的作者提出了一种层级式提取的Transformer,并通过移动窗口的方式来学习特征。在窗口内计算自注意力可以带来更高的效率;同时通过移动的操作,让相邻的窗口之间有了交互,变相达到了一种全局建模的能力,进而解决了上面两个问题。
在这里插入图片描述

Swin Transformer将transformer结构与cnn的思想相结合,提出了一个可以广泛应用到各个计算机视觉领域的backbone,在检测、分类和分割等任务的数据集上都呈现出很好的效果,可以应用于很多对精度有较高要求的场景。Swin Transformer之所以能有这么大的影响力主要是因为在 ViT 之后,它通过在一系列视觉任务上的强大表现 ,进一步证明了Transformer是可以在视觉领域取得广泛应用的。

下表中展示了目前swin-t模型在1684X上的性能情况,本文主要针对FP16和INT8模型进行优化部署。

prectime(ms)
FP3241.890
FP167.411
INT85.505

2.性能瓶颈分析

在这里插入图片描述

通过bmprofile工具可视化FP16模型在1684X上的运行状态,这里截取了模型中的一个block。从图中可以看出大量的permute(transpose)层穿插其中,一方面带来较大的数据搬运开销,另一方面使得网络无法layergroup,并行效果较差。

3.模型优化

3.1.transpose消除

在这里插入图片描述

观察图中的attention结构,共有3个transpose层。其中第一个transpose层可以拆解为2个transpose,一是把QKV所在的维度(3)移到了最前面,二是将head所在维度(3)与patch所在维度(49)交换了顺序。由于后面紧跟着split操作是为了将QKV拆分成三个分支,那么此处完全可以不做第一个transpose,而让其直接在原维度上进行split。这样再把第二个transpose的执行改变顺序,让他分别向下移动到三个分支上。这样处理的原因是:在tpu-mlir中是支持transpose与相邻的matmul算子融合的,因此当transpose下移到matmul算子上一层就可以与matmul融合。

细心的读者可能会发现一个问题,QK相乘的matmul其右输入已经有一个transpose了,再叠加另一个transpose在一起还能融合吗?又与哪个transpose融合呢?为了解释这个问题,我们可以从下面这张图来分析。
在这里插入图片描述

这是我们预期图优化后达到的效果,可以看到这里matmul是将49x32和32x49这两个矩阵做乘法,64和3可以看作batch。刚好我们的tpu-mlir中是支持hdim_is_batch这种优化的。因此对于这种情况,优化后左右两个transpose都被消除掉,在matmul的输出位置会再新增一个transpose。之后这个matmul再与右面剩下的transpose进行Rtrans融合就可以了。效果如下图所示:
在这里插入图片描述

这个输出多出来的transpose可以继续被向下移动至下一个matmul之前,此时网络结构如图:

在这里插入图片描述

对于第二个matmul,再次应用hdim_is_batch的优化,消除左右输入的transpose层,之后在输出额外加入的transpose就可以刚好和网络最后的transpose层抵消,至此,所有的transpose都被消除了。

在这里插入图片描述

相关代码:tpu-mlir/lib/Dialect/Top/Canonicalize/MatMul.cpp

MatmulWithPermuteAndSplit这个pattern就是用于识别swint中的attention结构,并将transpose+split+squeeze的结构进行调整,其目的就是为了让整块结构可以成功的利用我们编译器已有的一系列针对transpose+matmul这个组合的优化。

3.2.更好的layergroup

TPU 分为 Local Memory 和 Global Memory,一个独立的计算指令的执行会经过 gdma(global → local),bdc,gdma(local→ global)的过程,在模型的执行过程中,我们希望gdma搬运类的操作越少越好,这样可以更大程度地利用我们的TPU算力。基于这个想法,在tpu-mlir中设计了LayerGroup的功能,LayerGroup经过计算,可以将多个计算指令划分到一个 Group ,在一个 Group 内,每个 Op 直接使用上一个 Op 计算后存放在 Local Memory的数据 ,可以减少每两个Op数据衔接之间的搬出与搬入,从而减少了 io 的时间。因此,layergroup的效果往往也是我们优化一个模型要考虑的因素。

在完成了3.1中的优化工作后,按照运算逻辑,attention应该可以Group到一起,但实际情况并不如此,如图所示,这里截取了final.mlir的一部分attention结构,这里的各个op都是global layer,说明其中仍存在优化点。
在这里插入图片描述

经过在tpu-mlir中的debug,分析其原因有两点,一是SliceOp的local layer不支持5维的情况,二是SqueezeOp没有支持localgen。下面针对这两点进行优化。

3.1.1.SliceOp

代码:tpu-mlir/lib/Dialect/Tpu/Interfaces/Common/Slice.cpp

其中 LogicalResult tpu::SliceOp::LocalGenSupport()用于判断该Op能否支持locallayer,其中

else if (module::isBM1684XFamily()) {
    if((int)getRunMode(getOperation()) == 1) {
      return failure();
    }
    const auto offset = module::getI64Array(getOffset());
    const auto steps = module::getI64Array(getSteps());
    if (num_dims > 2) {
      if (steps->at(1) != 1)
        return failure();
    }
    if (num_dims > 4) {
      return failure();
    }
}

在这段代码中观察到,对于1684X芯片,在num_dims>4时直接认为不支持local layer。这里我们对逻辑做进一步完善,在group3d的情况下,5维的shape会按照[n,c,d,h*w]来处理,所以此时如果仅做slice_d,是不会导致数据有跨npu整理的行为的,那么此时他也是允许local layer的。

if(num_dims == 5){
    int64_t in_shape[5];
    int64_t out_shape[5];
    tpu_mlir::group_type_t group_type = GROUP_3D;
    module::getNCDHW(getInput(), in_shape[0],in_shape[1],in_shape[2],in_shape[3], in_shape[4],group_type);
    module::getNCDHW(getOutput(), out_shape[0],out_shape[1],out_shape[2],out_shape[3], out_shape[4], group_type);
    for(int i=0; i<5; ++i){
      if(in_shape[i]!=out_shape[i] && (i!=2)){
        return failure();
      }
    }
    return success();
}

3.1.2.SqueezeOp

SqueezeOp还没有支持local layer的codegen,但是1684X的后端中reshape算子是有local实现的,SqueezeOp刚好可以使用。

首先在TpuOps.td文件中给Tpu_SqueezeOp添加localgen的通用接口定义:DeclareOpInterfaceMethods<LocalGenInterface, [“LocalGenSupport”]>

在这个接口定义的基础上我们需要实现两部分,调用后端算子的接口和判断是否支持local的逻辑。

代码:tpu-mlir/lib/Dialect/Tpu/Interfaces/BM1684X/Squeeze.cpp

这个文件中实现了SqueezeOp调用芯片后端算子的接口,我们为其新增codegen_local_bm1684x的接口。

void tpu::SqueezeOp::codegen_local_bm1684x(int64_t n_step, int64_t c_step,int64_t h_step, int64_t d_step,int64_t w_step,group_type_t group_type,local_sec_info_t &sec_info) {
  auto op = getOperation();
  auto input_spec = BM168x::get_input_spec(op, group_type);
  auto output_spec = BM168x::get_output_spec(op,group_type);
  if (input_spec->at(0).addr == output_spec->at(0).addr) {
    return;
  }
  auto shape = module::getShape(getOutput());
  reshape_spec_t spec = {0};
  spec.dims = shape.size();
  for (size_t i = 0; i < shape.size(); ++i) {
    spec.shape[i] = shape[i];
  }

  BM168x::call_local_func("backend_api_reshape_local", &spec, sizeof(spec),&sec_info, input_spec->data(), output_spec->data());
}

代码:lib/Dialect/Tpu/Interfaces/Common/Squeeze.cpp

这个文件中实现了SqueezeOp支持localgen的判断逻辑。

LogicalResult tpu::SqueezeOp::LocalGenSupport() {
  if (module::isCV18xx() || module::isBM1684Family()) {
    return failure();
  }
  auto ishape = module::getShape(getInput());
  auto oshape = module::getShape(getOutput());
  if (ishape.size() < 2 || oshape.size() < 2 || ishape[0] != oshape[0] || ishape[1] != oshape[1]) {
    return failure();
  }
  return success();
}

完成上述优化后让我们再来编译模型看一下效果:

在这里插入图片描述

可以看到刚刚几个global layer已经整理到一个group中了。

3.1.3.weight切分

从上面group的效果来看,还存在着一个比较特殊的情况,这里AddOp并没有和其他层group到一起。这里的原因是,add的一个输入为权重,但是tpu-mlir目前对权重的处理是不进行切分,所以在切分遇到weight时,就认为其不支持group。但是像add这种点对点运算的Op,如果输入为权重,理论上也是可以进行切分的。

为了支持这个功能,涉及修改的地方较多,感兴趣的读者可以先了解一下tpu-mlir中layer group的过程实现,相关的讲解视频在开源社区:layer group精讲

此处概括一下支持weight切分的方式:

1.给top层WeightOp增加 allow_split的参数

2.LocalGenSupport支持add sub mul div max min这类点对点操作

3.做layer group之前给符合要求的op配置allow_split

4.完善layer group切分时涉及到输入为weight的分支逻辑

完成上述优化后让我们再来编译模型看一下效果:
在这里插入图片描述

AddOp也成功的合入了group。

4.优化效果

使用bmprofile工具再次观察模型的运行情况,与优化前相比,节省了大量GDMA搬运的时间,BDC计算与GDMA搬运数据的并行效果更好了。

在这里插入图片描述

模型的性能变化情况:

FP16INT8
优化前7.411ms5.505ms
优化后3.522ms2.228ms
优化效果性能提升110%性能提升145%

FP16模型和INT8模型在1684X上的运行速度都得到了大幅度提升。至此,这一阶段的swint优化工作完成。

| 7.411ms | 5.505ms |
| 优化后 | 3.522ms | 2.228ms |
| 优化效果 | 性能提升110% | 性能提升145% |

FP16模型和INT8模型在1684X上的运行速度都得到了大幅度提升。至此,这一阶段的swint优化工作完成。

希望这篇记录文档能为其他类似模型的优化工作提供帮助。

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

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

相关文章

HikariDataSource类的作用和使用

HikariDataSource是一个Java数据库连接池的实现&#xff0c;它属于HikariCP连接池库。连接池是一个用于管理数据库连接的工具&#xff0c;它可以帮助优化数据库连接的创建和销毁过程&#xff0c;提高数据库操作的性能和效率。 HikariDataSource类的作用是创建和管理数据库连接…

为什么要选择文件传输软件?有哪些最佳高速文件传输软件?

是否经历过这样的场景&#xff0c;正在努力地完成工作任务&#xff0c;但是由于制作的数据无法及时传送给合作伙伴&#xff0c;工作流程被打断了&#xff1f;这听起来很令人沮丧&#xff0c;对吧&#xff1f;可是&#xff0c;这种情况在现实中并不罕见。 因此&#xff0c;需要…

微服务与Nacos概述

微服务概述 软件架构的演变&#xff1a;单体架构、垂直应用架构、流式计算架构 SOA、微服务架构和服务网格。 微服务是一种软件开发架构&#xff0c;它将一个大型应用程序拆分为一系列小型、独立的服务。每个服务都可以独立开发、部署和扩展&#xff0c;并通过轻量级的通信机…

C++ 派生类成员的标识与访问——虚基类及其派生类构造函数

虚基类的使用非常方便&#xff0c;简单&#xff0c;这是由于程序中所有类使用的都是自动生成的默认构造函数。如果虚基类声明有非默认的&#xff08;即带参的&#xff09;构造函数&#xff0c;并且没有声明默认形式的构造函数。这时&#xff0c;在整个继承关系中&#xff0c;直…

1 swagger简单案例

1.1 加入依赖 <!--swagger图形化接口--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version> </dependency><dependency><groupId>io.spri…

通过Idea部署Tomcat服务器(详细图文教学)

1.在idea中创建项目 有maven构建工具就创建maven&#xff0c;没有就正常创建一个普通的java程序 创建普通java项目 2.添加框架 3.配置 Tomcat 注意&#xff1a;创建web项目后我们需要配置tomcat才能运行&#xff0c;下面我们来进行配置。 4.添加部署 回到服务器 5.完善配置 6…

·[K8S:使用calico网络插件]:解决集群节点NotReady问题

文章目录 一&#xff1a;安装calico&#xff1a;1.1&#xff1a;weget安装Colico网络通信插件&#xff1a;1.2&#xff1a;修改calico.yaml网卡相关配置&#xff1a;1.2.1&#xff1a;查看本机ip 网卡相关信息&#xff1a;1.2.2&#xff1a;修改calico.yaml网卡interface相关信…

TEC半导体热电冷却技术在高速电主轴热变形补偿中的应用

摘要&#xff1a;电主轴Z向热变形是影响高速数控机床加工精度的主要因素&#xff0c;目前常用的补偿技术是流体介质形式的液冷和风冷&#xff0c;也出现了基于帕尔贴原理的TEC半导体冷却技术。目前TEC冷却技术在电主轴热变形补偿中存在的主要问题是无法对主轴热变形量进行直接调…

LeetCode 热题 100 JavaScript--142. 环形链表 II

给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数…

【Redis】项目使用redis做缓存除了击穿穿透雪崩我们还要考虑哪些?

文章目录 前言高并发写高并发读总结 前言 相信大家在项目中都是用过redis&#xff0c;比如用来做一个分布式缓存来提高程序的性能。 当使用到了redis来做缓存&#xff0c;那么我们就必须要考虑几个问题&#xff0c;除了缓存击穿&#xff0c;缓存穿透&#xff0c;缓存雪崩&…

【Linux命令详解 | cat命令】用于显示或连接文件

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. 显示文件内容2. 创建文件3. 连接文件4. 显示行号5. 压缩空行6. 显示特殊字符7. 显示行号和特殊字符8. 从标准输入读取9. 显示文件开头或结尾10. 备份文件11. 显示文件内容至多屏幕大小12. 转义正则表达式13. 显示…

集成学习:机器学习模型如何“博采众长”

前置概念 偏差 指模型的预测值与真实值之间的差异&#xff0c;它反映了模型的拟合能力。 方差 指模型在不同的训练集上产生的预测结果的差异&#xff0c;它反映了模型的稳定性。 方差和偏差对预测结果所造成的影响 在机器学习中&#xff0c;我们通常希望模型的偏差和方差都…

ffmpeg源码编译成功,但是引用生成的静态库(.a)报错,报错位置在xxx_list.c,报错信息为某变量未定义

背景&#xff1a;本文是对上一个文章的补充&#xff0c;在源码编译之前&#xff0c;项目是有完整的ffmpeg编译脚本的&#xff0c;只不过新增了断点调试ffmpeg&#xff0c;所以产生的上面的文章&#xff0c;也就是说&#xff0c;我在用make编译成功后&#xff0c;再去做的源码编…

快速上手React:从概述到组件与事件处理

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

3.4 网络安全管理设备

数据参考&#xff1a;CISP官方 目录 IDS (入侵检测系统)网络安全审计漏洞扫描系统VPN&#xff08;虚拟专网&#xff09;堡垒主机安全管理平台 一、IDS (入侵检测系统) 入侵检测系统&#xff08;IDS&#xff09;是一种网络安全设备&#xff0c;用于监测和检测网络中的入侵行…

OLTP和OLAP的区别以及使用场景

1、什么是OLTP&#xff1f; 全称OnLine Transaction Processing&#xff0c;联机事务处理系统&#xff0c;就是对数据的增删改查等操作 存储的是业务数据&#xff0c;来记录某类业务事件的发生&#xff0c;比如下单、支付、注册等 典型代表有Mysql、Oracle等数据库&#xff…

微服务 云原生:基于 Gogs + Drone 进行项目 CI/CD

传统构建部署 以一个简单的前后端项目来说&#xff0c;分别编写前后端的 Dockerfile 文件并构建镜像&#xff0c;然后编写 docker-compose.yml 构建部署&#xff0c;启动运行。 一个简单的例子&#xff1a; 前端&#xff1a; 项目名&#xff1a;kubemanagement-web技术栈&am…

【雕爷学编程】Arduino动手做(193)---移远 BC20 NB+GNSS模块10

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

每天一道leetcode:剑指 Offer 53 - I. 在排序数组中查找数字 I(适合初学者二分查找)

今日份题目&#xff1a; 统计一个数字在排序数组中出现的次数。 示例1 输入: nums [5,7,7,8,8,10], target 8 输出: 2 示例2 输入: nums [5,7,7,8,8,10], target 6 输出: 0 提示 0 < nums.length < 10^5 -10^9 < nums[i] < 10^9 nums 是一个非递减数组…