作者:王枫 | 旷视算法研究员
收到 MegEngine 团队的邀请来写这篇稿子,本意是想让我介绍一下 BaseDet(一个基于 MegEngine 写成的目标检测仓库,类似 detectron2 之于 pytorch )。因为大部分介绍框架的稿件总是在抓着一些代码中的 feature 疯狂介绍,而我本人并不是很喜欢这种风格(因为这些内容很像是把文档翻译成了文章),所以本文在介绍 BaseDet 之外,分享在完成 BaseDet 过程中面临的问题和思考。这些内容涉及的范围比较广,有关于深度学习框架、软件工程和开源项目等诸多内容;而这些问题和思考当然也不仅仅来源于 BaseDet ,同时也包含 MegEngine 团队在不断完善各种功能时候的踩坑与反思。
本文不会介绍具体的检测模型是怎么样的,也不会介绍实现时候的使用的提点 trick 或者具体的细节,如果你对细节感兴趣,可以参考一个我之前写的炼丹细节 blog。但是如果你关心“现在的各类训练框架是怎样设计的”,“为什么会有这种设计”之类的问题,本文或许可以帮你理解一些内在的原因。
提到检测框架,几乎任何一个做过检测相关研究的的人都用过 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 )就显得格外重要。
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 团队的整理,也在这里分享一些坑点(建议看完论文再来看这里的坑点,理解更深刻一些):
mge.dtr()
就能简单开启功能了。在旷视内部有一个很棒的帖子,讲的是用户通常只会用到软件中 15% 的功能,而不同类型的用户使用的往往是同一个软件中那不同的 15% 部分。在完成 BaseDet 的过程中,我接触到了不同的用户人群,了解到这些人群对于框架的不同需求。举个例子:
所以诸如 mmdet/detectron2 这类框架都是支持简单的 yaml config 和 lazy eval 的功能的,看起来可能有些矛盾,但是这种做法能够满足不同群体的需求。
前面提到过,做检测应用相关的人则非常倾向于使用 YOLO 系列的各个框架,一部分原因就是大部分用户是直接 clone 下来仓库直接改 code 的,所以在这些框架中,很少提供诸如 registry 和 hook 这类概念,因为这些概念本身并没有提供灵活性,反而引入了多余的概念。
因为 BaseDet 本身是为了辅助产品而存在的,所以是基于 product first 的原则而设计开发的,也就不可避免地在使用体验上存在一些 bias ,开源出来的目的其实就是为了纠正这种 bias,还能给 MegEngine 的用户提供一种 code 参考,希望社区能够给予一些适当的反馈,这些反馈也是 codebase 前进的方向。
BaseDet 使用示例:https://studio.brainpp.com/project/28826?name=BaseDet%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B
留下来一段话,送给这世界上愿意花费时间精力去 maintain 项目的开发人员,也是我这一段时间来的深刻感悟:任何一段 code 都值得不断花费时间去打磨,但是打磨之后的 code 并不是真正的产出,关键在于过程中的思考和学习。不应该和自己维护的的仓库过度绑定,总有一些更重要的事情在等着你。
更多 MegEngine 信息获取,您可以:查看文档、和 GitHub 项目,或加入 MegEngine 用户交流 QQ 群:1029741705 。欢迎参与 MegEngine 社区贡献,成为 Awesome MegEngineer,荣誉证书、定制礼品享不停。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.