作者:王枫 | 旷视算法研讨员

收到 MegEngine 团队的邀请来写这篇稿子,本意是想让我介绍一下BaseDet(一个依据MegEngine写成的方针检测库房,相似 detectron2 之于 pytorch)。由于大部分介绍结构的稿件总是在抓着一些代码中的 feature 张狂介绍,而我本人并不是很喜爱这种风格(由于这些内容很像是把文档翻译成了文章),所以本文在介绍 BaseDet 之外,共享在完结 BaseDet 进程中面对的问题和考虑。这些内容涉及的范围比较广,有关于深度学习结构、软件工程和开源项目等诸多内容;而这些问题和考虑当然也不仅仅来源于 BaseDet,一起也包含 MegEngine 团队在不断完善各种功用时分的踩坑与反思。

本文不会介绍详细的检测模型是怎么样的,也不会介绍完成时分的运用的提点 trick 或许详细的细节,假如你对细节感兴趣,能够参阅一个我之前写的炼丹细节blog。可是假如你关怀“现在的各类练习结构是怎样规划的”,“为什么会有这种规划”之类的问题,本文或许能够帮你了解一些内在的原因。

mmdet 与 detectron2

提到检测结构,简直任何一个做过检测相关研讨的的人都用过 mmdet 或许 detectron2 其间的一个,而做检测运用相关的人则十分倾向于运用 YOLO 系列的各个结构。在文章后面咱们会聊,研讨和运用选用不同的方法的现象是存在一些比较深入的原因的。

在那之前,咱们仍是聊回 BaseDet 和 mmdet/detectron2 这两大结构的一些联系。BaseDet 其实学习了一些 mmdet 和 detectron2 中精华的规划和理念:Trainer 和 hook。

Trainer 定义了练习逻辑的最中心组件:模型、优化器和 dataloader,简直大部分的练习场景都能够用着三个组件完结,也便是下面的逻辑:

data = next(dataloader)
loss = model(data)
loss.backward()
solver.step()

在 mmdet/detectron2/BaseDet 里边,所有的练习中心流程都是上面这个十分简略的函数,而至于 dataloader,model 和 solver 这三个经常发生变化的目标,一般是借助工厂模式的 build 方法发生的,要改哪个部分,用户只需求自己 build 就行了。

Hook 则是练习逻辑的外延,由于在练习进程中常常会插入一些特定的需求,比方练习的一些数据 log 进 tensorboard/wandb、每练习完几个 epoch 就对模型进行一下测验、保存练习的断点等一些功用,这些功用以及对应的延伸功用都依赖于 hook 的引进。

了解了 Trainer 和 Hook 的概念之后,用户其实就能够很简略对自己的需求做扩大,而比方 dataloader、model、solver 都是能够自己 build 出来的,为了用户能够把 mmdet/detectron2 当作一个库房运用,这两个结构都供给了注册机制(registry)。

需求留意的是,hook 和 registry 的引进都是依据这样的 trade-off:牺牲掉一部分用户的运用门槛,交换结构的灵活性的提升,把一部分关于保护人员的困难转移给了一部分用户。关于 YOLO 系列的结构(比方 YOLOv5/YOLOX 等)就不会存在这样的 trade-off:一方面模型很少,另一方面便是大部分用户仍是倾向于 clone 下来自己魔改 code,关于这样的用户集体来说,知道在哪里修正就一定能发生作用是最重要的,此刻 KISS 准则( Keep It Simple and Stupid )就显得分外重要。

MegEngine 和 DTR

BaseDet是依据MegEngine的一个检测结构,假如要聊 feature,本质上也是聊 MegEngine 的 feature,究竟 BaseDet 仅仅帮助用户完结一些根本的练习任务,有趣的 feature 仍是由底层结构支撑的,所以这个部分咱们来聊一聊 MegEngine。

为了用户的迁移性,MegEngine 在一些 API 上和 numpy 做了对齐,这点上和 google 的 jax 是比较相似的,优点是由于 numpy 的 api 比较稳定且 well-known;而 MegEngine 在 module 的上的规划比较接近 torch,由于用户关于 torch 的 module 的用法是相对熟悉的。关于大部分 torch 用户,要转 MegEngine 仍是相对比较丝滑的,最需求留意的点便是:在 MegEngine 里边,autograd 是由一个叫做 GradManager 的 class 操控的,有点相似 tensorflow 的 GradientTape,这样做的优点在于便利操控资源的管理,不简略像 torch 相同呈现奇怪的内存走漏现象(关于这个现象感兴趣的同学,能够参阅之前我写的另一个blog)。

我个人最喜爱的 MegEngine 的 feature 是由@圆角骑士魔理沙提出来的 DTR(Dynamic Tensor Rematerialization,引荐去看原文),以 FCOS 的 baseline 为例,在 2080Ti 上单卡练习,不开 DTR batchsize 只能开到 8,翻开 DTR 的状况下,batchsize 能翻一倍开到 16(当然练习速度也会变慢)。

当然,有许多完成细节是原文没有考虑的,依据 engine 团队的收拾,也在这儿共享一些坑点(建议看完论文再来看这儿的坑点,了解更深入一些):

  1. 多卡支撑。原始论文没考虑这个问题,其实说起来解决方法很简略,便是无脑把需求做 send/recv 通讯的 tensor 当成 immutable 的,不要 drop 就好了。
  2. 显存碎片经常会导致算法不实用,实际上估值函数需求与内存分配器联动。这儿咱们为了便利了解举个比方。假设显存的状况是有 200Mb 能够自由运用,其排布方法是[A(90M) B(10M) C(90M) D(10M)],其间 A、B、C、D 都是 tensor,括号里边是 tensor 需求的显存大小。假设有新的 tensor E 需求 15M 的空间,假设 DTR 默许算出来是 drop 掉 B 和 D,可是由于显存不连续,此刻还需求 drop 掉 A 或许 C,那么一开始 drop 掉 B 和 D的行为就很不划算,不如一开始就 drop 掉 A 或许 C,所以说估值函数实际上是需求和内存分配器做联动。在 pytorch 里边很难获取到现在各个 blob 的请求状况,而 mge 里的显存分配器规划的比较洁净,请求释放也都有统一的当地,所以 DTR 这个机制完成的也相对洁净一些。
  3. 涉及跨 iter 操作的时分会有一些麻烦,比方 ema 中需求进行特殊处理,能够参阅 BaseDet 里边的示例code。呈现问题的原因在于:比方 ema 这样的操作,一般会使得 tensor 的核算前史成为一个无限长(和练习长度相同)的东西,而 DTR 就会把前史上用到的 tensor 都记下来(重算进程需求运用),这就会导致呈现走漏现象。
  4. 原始论文里收到 pytorch 限需求手动填阈值,大部分用户并不是很喜爱这种调用方法,最终在 MegEngine 里边运用的是一个自适应的阈值。关于用户来说,只需求在 code 里边加上mge.dtr()就能简略敞开功用了。

不同用户的不同需求

在旷视内部有一个很棒的帖子,讲的是用户一般只会用到软件中 15% 的功用,而不同类型的用户运用的往往是同一个软件中那不同的 15% 部分。在完结 BaseDet 的进程中,我接触到了不同的用户人群,了解到这些人群关于结构的不同需求。举个比方:

  • 研讨人员:灵活,但一起有需求的功用的时分能够简略翻开(比方 ema)。喜爱 pytorch-lightning/timm 这种 lite 的东西,关怀练习/评测逻辑,练习出来的模型点数越高越好。
  • 产品研发:关怀的重要的参数能够简略配置,便利交付。喜爱 onnx/torchscript 这种中间产物,不关怀练习评测模型的逻辑,像保姆相同帮他们搞个 demo 走通流程最好。
  • 深度学习结构研发:需求简略就能跑起来的库房,便利追溯问题。上层爱咋写咋写,爱咋封装咋封装,喜爱练习结构供给比方保存 crashing context、profiler、benchmark 等功用。

所以比方 mmdet/detectron2 这类结构都是支撑简略的 yaml config 和 lazy eval 的功用的,看起来或许有些矛盾,可是这种做法能够满意不同集体的需求。

前面提到过,做检测运用相关的人则十分倾向于运用 YOLO 系列的各个结构,一部分原因便是大部分用户是直接 clone 下来库房直接改 code 的,所以在这些结构中,很少供给比方 registry 和 hook 这类概念,由于这些概念自身并没有供给灵活性,反而引进了剩余的概念。

由于BaseDet自身是为了辅佐产品而存在的,所以是依据 product first 的准则而规划开发的,也就不可避免地在运用体会上存在一些 bias,开源出来的意图其实便是为了纠正这种 bias,还能给 MegEngine 的用户供给一种code 参阅,期望社区能够给予一些适当的反应,这些反应也是 codebase 前进的方向。

BaseDet 运用示例:studio.brainpp.com/project/288…

后记

留下来一段话,送给这世界上愿意花费时间精力去 maintain 项意图开发人员,也是我这一段时间来的深入感悟:任何一段 code 都值得不断花费时间去打磨,可是打磨之后的 code 并不是真实的产出,关键在于进程中的考虑和学习。不应该和自己保护的的库房过度绑定,总有一些更重要的事情在等着你。


更多 MegEngine 信息获取,您能够:查看文档、和GitHub 项目,或加入 MegEngine 用户交流 QQ 群:1029741705。欢迎参加 MegEngine 社区奉献,成为Awesome MegEngineer,荣誉证书、定制礼品享不断。