一块RTX 3090加速训练YOLOv5s,时间减少11个小时,速度提升20%

news2024/11/9 5:15:06

dfd5de0fa186db2e3217987b03a67896.jpeg

作者|BBuf

很高兴为大家带来One-YOLOv5的最新进展,在《一个更快的YOLOv5问世,附送全面中文解析教程》发布后收到了很多算法工程师朋友的关注,十分感谢。

不过,可能你也在思考一个问题:虽然OneFlow的兼容性做得很好,可以很方便地移植YOLOv5并使用OneFlow后端来进行训练,但为什么要用OneFlow?能缩短模型开发周期吗?解决了任何痛点吗?本篇文章将尝试回答这几个问题。

我曾经也是一名算法工程师,开发机器也只有两张RTX 3090消费级显卡而已,但实际上大多数由我上线的检测产品也就是靠这1张或者2张RTX 3090完成的。

由于成本问题,很多中小公司没有组一个A100集群或者直接上数十张卡来训练检测模型的实力,所以这个时候在单卡或者2卡上将目标检测模型做快显得尤为重要。模型训练速度提升之后可以降本增效,提高模型生产率。

所以,近期我和实习生小伙伴一起凭借对YOLOv5的性能分析以及几个简单的优化,将单RTX 3090 FP32 YOLOv5s的训练速度提升了近20%对于需要迭代300个Epoch的COCO数据集来说,One-YOLOv5相比Ultralytics/YOLOv5缩短了11.35个小时的训练时间。

本文将分享我们的所有优化技术,如果你是一名PyTorch和OneFlow的使用者,尤其日常和检测模型打交道但资源相对受限,那么本文的优化方法将对你有所帮助。

One-YOLOv5链接:
https://github.com/Oneflow-Inc/one-yolov5

欢迎你给我们在GitHub上点个Star,我们会用更多高质量技术分享来回馈社区。对 One-YOLOv5 感兴趣的小伙伴可以添加bbuf23333进入One-YOLOv5微信交流群。

1

结果展示

我们展示一下分别使用One-YOLOv5以及Ultralytics/YOLOv5在RTX 3090单卡上使用YOLOv5s FP32模型训练COCO数据集的一个Epoch所需的耗时:

1d2f289b54c40a49eeaaac53cd1c9d5c.jpeg

可以看到,在单卡模式下,经过优化后的One-YOLOv5相比Ultralytics/YOLOv5的训练速度提升了20%左右。

然后我们再展示一下2卡DDP模式YOLOv5s FP32模型训练COCO数据集一个Epoch所需的耗时:

916d4570c83996ecbfdafca439470a08.jpeg

在DDP模式下,One-YOLOv5的性能依然领先,但还需要进一步,猜测可能是通信部分的开销比较大,后续我们会再研究一下。

2

 优化手段

我们深度分析了PyTorch的YOLOv5的执行序列,发现当前YOLOv5主要存在3个优化点。

第一,对于Upsample算子的改进,由于YOLOv5使用上采样是规整的最近邻2倍插值,所以我们可以实现一个特殊Kernel降低计算量并提升带宽。

第二,在YOLOv5中存在一个滑动更新模型参数的操作,这个操作启动了很多碎的CUDA Kernel,而每个CUDA Kernel的执行时间都非常短,所以启动开销不能忽略。我们使用水平并行CUDA Kernel的方式(MultiTensor)对其完成了优化,基于这个优化,One-YOLOv5获得了9%的加速。

第三,通过对YOLOv5nsys执行序列的观察发现,在ComputeLoss部分出现的bbox_iou是整个Loss计算部分的比较大的瓶颈,我们在bbox_iou函数部分完成了多个垂直的KernelFuse,使得它的开销从最初的3.xms降低到了几百个us。接下来将分别详细阐述这三种优化。

2.1 对UpsampleNearest2D的特化改进

这里直接展示我们对UpsampleNearest2D进行调优的技术总结,大家可以结合下面的PR链接来对应下面的知识点进行总结。我们在A100 40G上测试了UpsampleNearest2D算子的性能表现,这块卡的峰值带宽在1555Gb/s , 我们使用的CUDA版本为11.8。

进行 Profile 的程序如下:

import oneflow as flow


x = flow.randn(16, 32, 80, 80, device="cuda", dtype=flow.float32).requires_grad_()


m = flow.nn.Upsample(scale_factor=2.0, mode="nearest")


y = m(x)
print(y.device)
y.sum().backward()

https://github.com/Oneflow-Inc/oneflow/pull/9415 & https://github.com/Oneflow-Inc/oneflow/pull/9424 这两个 PR 分别针对 UpsampleNearest2D 这个算子(这个算子是 YOLO 系列算法大量使用的)的前后向进行了调优,下面展示了在 A100 上调优前后的带宽占用和计算时间比较:

826500331a91a8469e0dd42c63deea72.png

上述结果使用 /usr/local/cuda/bin/ncu -o torch_upsample /home/python3 debug.py 得到profile文件后使用Nsight Compute打开记录。

基于上述对 UpsampleNearest2D 的优化,OneFlow 在 FP32 和 FP16 情况下的性能和带宽都大幅超越之前未经优化的版本,并且相比于 PyTorch 也有较大幅度的领先。

本次优化涉及到的知识点总结如下(by OneFlow 柳俊丞):
 

  • 为常见的情况写特例,比如这里就是为采样倍数为2的Nearest插值写特例,避免使用NdIndexHelper带来的额外计算开销,不用追求再一个kernel实现中同时拥有通用型和高效性;
     

  • 整数除法开销大(但是编译器有的时候会优化掉一些除法),nchw中的nc不需要分开,合并在一起计算减少计算量;
     

  • int64_t除法的开销更大,用int32满足大部分需求,其实这里还有一个快速整数除法的问题;
     

  • 反向Kernel计算过程中循环dx相比循环dy ,实际上将坐标换算的开销减少到原来的1/4;
     

  • CUDA GMEM的开销的也比较大,虽然编译器有可能做优化,但是显式的使用局部变量更好;
     

  • 一次Memset的开销也很大,和写一次一样,所以反向Kernel中对dx使用Memset清零的时机需要注意;
     

  • atomicAdd开销很大,即使抛开为了实现原子性可能需要的锁总线等,atomicAdd需要把原来的值先读出来,再写回去;另外,half的atomicAdd 巨慢无比,慢到如果一个算法需要用到atomicAdd,那么相比于用half ,转成float ,再atomicAdd,再转回去还要慢很多;
     

  • 向量化访存。
     

对这个Kernel进行特化是优化的第一步,基于这个优化可以给YOLOv5的单卡 PipLine 带来1%的提升。

2.2 对bbox_iou函数进行优化 (垂直Fuse优化)

通过对nsys的分析,我们发现无论是One-YOLOv5还是Ultralytics/YOLOv5,在计算Loss的阶段都有一个耗时比较严重的bbox_iou函数,这里贴一下bbox_iou部分的代码:

def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
    # Returns Intersection over Union (IoU) of box1(1,4) to box2(n,4)


    # Get the coordinates of bounding boxes
    if xywh:  # transform from xywh to xyxy
        (x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
        w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
        b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
        b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
    else:  # x1, y1, x2, y2 = box1
        b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)
        b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)
        w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps)
        w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps)


    # Intersection area
    inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * \
            (b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)).clamp(0)


    # Union Area
    union = w1 * h1 + w2 * h2 - inter + eps


    # IoU
    iou = inter / union
    if CIoU or DIoU or GIoU:
        cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1)  # convex (smallest enclosing box) width
        ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1)  # convex height
        if CIoU or DIoU:  # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
            c2 = cw ** 2 + ch ** 2 + eps  # convex diagonal squared
            rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4  # center dist ** 2
            if CIoU:  # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
                v = (4 / math.pi ** 2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2)
                with torch.no_grad():
                    alpha = v / (v - iou + (1 + eps))
                return iou - (rho2 / c2 + v * alpha)  # CIoU
            return iou - rho2 / c2  # DIoU
        c_area = cw * ch + eps  # convex area
        return iou - (c_area - union) / c_area  # GIoU https://arxiv.org/pdf/1902.09630.pdf
    return iou  # IoU

以One-YOLOv5的原始执行序列图为例,我们发现bbox_iou函数这部分每一次运行都需要花2.6ms左右,并且可以看到这里有大量的小Kernel被调度,虽然每个小Kernel计算很快,但访问GlobalMemory以及多次KernelLaunch的开销也比较大,所以我们做了几个fuse来降低Kernel Launch的开销以及减少访问Global Memrory来提升带宽。

ea64b0f9a507de49e43b1e9cedc926c6.jpeg

经过我们的Kernel Fuse之后的耗时只需要600+us。

a5fc3221f6fe0aae82019685255d5c65.jpeg

具体来说我们这里做了如下的几个fuse:

  • fused_get_boundding_boxes_coord:https://github.com/Oneflow-Inc/oneflow/pull/9433

  • fused_get_intersection_area: https://github.com/Oneflow-Inc/oneflow/pull/9485

  • fused_get_iou: https://github.com/Oneflow-Inc/oneflow/pull/9475

  • fused_get_convex_diagonal_squared: https://github.com/Oneflow-Inc/oneflow/pull/9481

  • fused_get_center_dist: https://github.com/Oneflow-Inc/oneflow/pull/9446

  • fused_get_ciou_diagonal_angle: https://github.com/Oneflow-Inc/oneflow/pull/9465

  • fused_get_ciou_result: https://github.com/Oneflow-Inc/oneflow/pull/9462


然后我们在One-YOLOv5的train.py中扩展了一个 --bbox_iou_optim 选项,只要训练的时候带上这个选项就会自动调用上面的fuse kernel来对bbox_iou函数进行优化了,具体请看:https://github.com/Oneflow-Inc/one-yolov5/blob/main/utils/metrics.py#L224-L284 。对bbox_iou这个函数的一系列垂直Fuse优化使得YOLOv5整体的训练速度提升了8%左右,是一个十分有效的优化。

2.3 对模型滑动平均更新进行优化(水平Fuse优化)

在 YOLOv5 中会使用EMA(指数移动平均)对模型的参数做平均, 一种给予近期数据更高权重的平均方法, 以求提高测试指标并增加模型鲁棒。这里的核心操作如下代码所示:

def update(self, model):
        # Update EMA parameters
        self.updates += 1
        d = self.decay(self.updates)


        msd = de_parallel(model).state_dict()  # model state_dict
        for k, v in self.ema.state_dict().items():
            if v.dtype.is_floating_point:  # true for FP16 and FP32
                v *= d
                v += (1 - d) * msd[k].detach()
        # assert v.dtype == msd[k].dtype == flow.float32, f'{k}: EMA {v.dtype} and model {msd[k].dtype} must be FP32'

以下是未优化前的这个函数的时序图:

9b72b0ba5c56435c7e97c22bcea0df5a.jpeg

这部分的CUDAKernel的执行速度大概为7.4ms,而经过我们水平Fuse优化(即MultiTensor),这部分的耗时情况降低了127us。

f2646f3963c49dd85f0317f051bb32d7.jpeg

并且水平方向的Kernel Fuse也同样降低了Kernel Launch的开销,使得前后2个Iter的间隙也进一步缩短了。最终这个优化为YOLOv5的整体训练速度提升了10%左右。本优化实现的pr如下:https://github.com/Oneflow-Inc/oneflow/pull/9498

此外,对于Optimizer部分同样可以水平并行,所以我们在One-YOLOv5里设置了一个multi_tensor_optimizer标志,打开这个标志就可以让 optimizer 以及 EMA 的 update以水平并行的方式运行。

关于MultiTensor这个知识可以看 zzk 的这篇文章:https://zhuanlan.zhihu.com/p/566595789。zzk 在 OneFlow 中也实现了一套 MultiTensor 方案,上面的 PR 9498 也是基于这套 MultiTensor 方案实现的。介于篇幅原因我们就不展开MultiTensor的代码实现了,感兴趣朋友的可以留言后续单独讲解。

3

使用方法

上面已经提到所有的优化都集中于 bbox_iou_optim 和 multi_tensor_optimizer 这两个扩展的Flag,只要我们训练的时候打开这两个Flag就可以享受到上述优化了。其他的运行命令和One-YOLOv5没有变化,以One-YOLOv5在RTX 3090上训练YOLOv5为例,命令为:

python train.py 
--batch 16 
--cfg models/yolov5s.yaml 
--weights '' 
--data coco.yaml 
--img 640 
--device 0 
--epoch 1 
--bbox_iou_optim 
--multi_tensor_optimizer

4

总结

目前,YOLOv5s网络当以BatchSize=16的配置在GeForce RTX 3090上(这里指定BatchSize为16时)训练COCO数据集时,OneFlow相比PyTorch可以节省 11.35 个小时。希望这篇文章提到的优化技巧可以对更多的从事目标检测的工程师带来启发。

欢迎Star One-YOLOv5项目:
https://github.com/Oneflow-Inc/one-yolov5

One-YOLOv5的优化工作实际上不仅包含性能,我们目前也付出了很多心血在文档和源码解读上,后续会继续放出《YOLOv5全面解析教程》的其他文章,并将尽快发布新版本。

5

致谢

感谢同事柳俊丞在这次调优中提供的 idea 和技术支持,感谢胡伽魁同学实现的一些fuse kernel,感谢郑泽康和宋易承的MultiTensorUpdate实现,感谢冯文的精度验证工作以及文档支持,以及小糖对One-YOLOv5的推广,以及帮助本项目发展的工程师如赵露阳、梁德澎等等。本项目未来会继续发力做出更多的成果。
 

直播现在约!!!

12月8日(本周四)20:00,OneFlow框架开发工程师、ONNX核心成员分享ONNX最新特性和最佳实践,欢迎围观。

其他人都在看

  • OneFlow-ONNX v0.6.0正式发布

  • OneFlow源码解析:自动微分机制

  • 下载量突破10亿,MinIO的开源启示录

  • 更快的YOLOv5问世,附送全面中文解析教程

  • 李白:你的模型权重很不错,可惜被我没收了

  • 比快更快,开源Stable Diffusion刷新作图速度

  • OneEmbedding:单卡训练TB级推荐模型不是梦

欢迎Star、试用OneFlow最新版本:GitHub - Oneflow-Inc/oneflow: OneFlow is a deep learning framework designed to be user-friendly, scalable and efficient.OneFlow is a deep learning framework designed to be user-friendly, scalable and efficient. - GitHub - Oneflow-Inc/oneflow: OneFlow is a deep learning framework designed to be user-friendly, scalable and efficient.https://github.com/Oneflow-Inc/oneflow/

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

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

相关文章

SQL之substrate()函数用法

测试表字段查询如下: 测试在hive中截取前5位字符的第1种写法: 测试在hive中截取前5位字符的第2种写法: 测试在impala中截取前5位字符的第1种写法: 测试在impala中截取前5位字符的第2种写法: 结果: 1、在h…

带分数(蓝桥杯C/C++B组真题详解)

目录 题目 题目思路 题目代码 注解: 1.题目给定的判定条件为 2.关于next_permutation(start,end); 题目: 题目思路: 因为题目要求是满足在1到9中 不重复、不遗漏的所有满足条件的情况 所以我们可以通过全排列 把整数、分子、分母 …

B-树(B-Tree)与二叉搜索树(BST):讲讲数据库和文件系统背后的原理(读写比较大块数据的存储系统数据结构与算法原理)...

人类总喜欢发明创造一些新名词(比如说,简写/缩写/简称什么的),并通过这些名词把人群分成了三六九等。弄到最后,把自己都绕晕了。你看,首先就是,B树,不要与Binary tree或Btree混淆。B…

有奖征文 | 当我们谈操作系统时,我们在谈什么?

OS,Operating System,操作系统,计算机中最基本也是最重要的基础性系统软件。1991 年,大二学生 Linus Torvalds 写出 Linux0.01,经过几十年的发展,以 Linux 为代表的服务器操作系统,成长为一个既…

lambda之Stream流式编程

lambda之Stream流式编程 一、什么是 Stream Stream中文称为 “流”,通过将集合转换为这么一种叫做“流”的元素序列,通过声明性方式,能够对集合中的每个元素进行一系列并行或串行的流水线操作。换句话说,你只需要告诉流你的要求…

led护眼台灯对眼睛好?过来人说说led护眼灯是否真的能护眼

众所周知,现在绝大部分光源都是使用led发光,无论是室内照明灯、室外装饰灯、气氛调节灯、工作学习护眼台灯等等,都是使用led灯珠,那么也就有人会问了:led灯真的对眼睛好吗?Led护眼台灯真的能护眼吗&#xf…

TH7-搜附近

TH7-搜附近说明1、探花1.1、查询推荐列表dubbo服务1.1.1、实体对象1.1.2、定义接口1.1.3、编写实现1.1.4、单元测试1.2、查询推荐列表APP接口实现1.2.1、TanHuaController1.2.2、TanHuaService1.2.3、测试1.3、喜欢的dubbo服务1.3.1、定义接口1.3.2、编写实现1.4、左滑右滑1.4.…

fofa搜索漏洞技巧

fofa搜索漏洞技巧整理,主要有以下十个方面:搜索HTTP响应头中含有"thinkphp"关键词的网站和IP;加上标题带有后台的;加上时间,现在新网站有thinkphp日志泄露的有很多; 搜索html正文中含有"管理后台"关键词的网站和IP body="管理后台"等。 …

Linux内核缓存

【推荐阅读】 轻松学会linux下查看内存频率,内核函数,cpu频率 纯干货,linux内存管理——内存管理架构(建议收藏) 一篇长文叙述Linux内核虚拟地址空间的基本概括 页缓存和块缓存 内核为块设备提供了两种通用的缓存方案: 页缓存&a…

光华股份深交所上市:市值51亿 应收账款余额超5亿

雷递网 雷建平 12月8日浙江光华科技股份有限公司(简称:“光华股份”,证券代码:001333)今日在深交所主板上市。光华股份本次发行3200万股,发行价为27.76元,募资8.88亿元。光华股份开盘价为33.31元…

开源,是不道德的!

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,非公众号转载保留此声明。请删掉你的github开源代码,让CV工程师成为真正的工程师。不要做真正的代码分享,因为除了满足一下你的虚荣…

vue-cli中学习vue

vue部分知识 大部分学习内容及代码在gitee仓库 生命周期 基本介绍 生命周期描述beforeCreate组件实例被创建之初created组件实例已经完全创建beforeMount组件挂载之前mounted组件挂载到实例上去之后beforeUpdate组件数据发生变化,更新之前updated组件数据更新之后…

springboot知识点

基本介绍 微服务最早由Martin Fowler与James Lewis于2014年共同提出,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,这些服务基于业…

TH8-小视频方案

TH8-小视频方案说明1、我的访客1.1、dubbo服务1.1.1、实体对象1.1.2、定义接口1.1.3、编写实现1.2、记录访客数据1.3、首页谁看过我1.3.1、VO对象1.3.2、MovementController1.3.3、MovementService2、小视频功能说明3、FastDFS2.1、FastDFS是什么?2.2、工作原理2.1.…

会员消费占比高达96%,孩子王究竟是怎么做到的?

👆点击关注公众号👆1.孩子王:依靠会员“稳江山”2021年上半年,增长黑盒独家发布了一篇关于孩子王的研究文章《万字拆解孩子王:充满矛盾的母婴零售之王》,彼时,孩子王尚在二度上市的前夕等待敲钟…

JAVA SCRIPT设计模式--行为型--设计模式之Template Method模板方法(22)

JAVA SCRIPT设计模式是本人根据GOF的设计模式写的博客记录。使用JAVA SCRIPT语言来实现主体功能,所以不可能像C,JAVA等面向对象语言一样严谨,大部分程序都附上了JAVA SCRIPT代码,代码只是实现了设计模式的主体功能,不代…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java课程教学过程f6oz5

对于即将毕业或者即将做课设的同学而言,由于经验的欠缺,面临的第一个难题就是选题,确定好题目之后便是开题报告,如果选题首先看自己学习那些技术,不同技术适合做不同的产品,比如自己会些简单的Java语言&…

BSV 上的 Graftroot

我们已经演示了如何使用无合约的合约在 BSV 上实现 Taproot。我们将展示了其后续提案 Graftroot 可以以类似的方式实施。 BTC 中的 Grabroot 与 Taproot 类似,有两种方式可以使用锁定在由多方创建的聚合公钥 P 中的资金: 合作案例:又名默认…

kubernetes 安装 Harbor 仓库

文章目录kubernetes 安装 Harbor 仓库1. 下载 Harbor2. 安装 docker3. 优化 docker 配置4. 下载 docker-compose5. 安装 Harbor:one: 上传 harbor 文件包:two: 解压:three: 修改配置文件:four: 执行安装脚本安装:five: 配置开机自启6. 登陆测试:one: 浏览器登陆:two: 命令行登陆…

为什么需要对相机标定?

以下内容来自系统教程如何搞定单目/鱼眼/双目/阵列 相机标定? 点击领取相机标定资料和代码 为什么需要对相机标定? 我们所处的世界是三维的,而相机拍摄的照片却是二维的,丢失了其中距离/深度的信息。从数学上可以简单理解为&…