从 Dataset 代码改动所反思的…

今天上午两个学生找我反馈,说自己写的一份代码昨天在 MegStudio 上还可以跑通,现在怎么跑不通了?报错如下图所示:

我一看,原来是 dataloader 在对传入 dataset 的类型做了检查,发现不对劲,然后就挂了。同样的一份代码,怎么之前能跑通,今天就突然跑不通了呢?费劲交流了一下才发现,在未经过人为调整的情况下,MegStudio 项目所用的 MegEngine 版本发生了变化,从 v1.0.0 变成了 v1.1.0 版本。

MegStudio 改动了什么

MegStudio 后台今日做了一个奇怪的改动,将所有环境为 MegEngine v1.0.0 版本的项目自动替换为 v1.1.0 版本(如下图),这个行为是非预期的。我们希望 MegStudio 上能够添加对 MegEngine v1.1.0 版本的支持,指的是在创建新项目以及编辑项目环境的时候 MegEngine v1.1.0 版本能作为可选项,而不是将其它已经写好的 v1.0.0 版本的项目全部自动地换成 v1.1.0 版本。

CEEFA290-4564-4E99-8E0F-044C961456DC

  • MegStudio 研发的疑惑是,既然能兼容**(然而目前确实会有兼容性破坏)**,为什么要提供旧版本;
  • MegEngine 发版后如何让 MegStudio 进行支持,目前没有清晰具体的流程。

MegEngine 改动了什么

即使 MegStudio 正确地增加了 v1.1.0 版本选项(而不是更新替换 v1.0.0), 一份常见的 v1.0.0 版本的 MegEngine 代码也理应能够在 v1.1.0 版本跑通 —— 这说明 v1.1.0 发生了 Breaking Change.

在远古 MGE-630 需求中提出:要让 dataloader 支持 StreamDataset, 需要对 dataloader 和 dataset 做一定程度的重构。在重构之前,天元的官网教程(以及对应的 MegStudio 官方提供的入门项目)都是教用户直接继承 Dataset 类:

在 v1.1.0 版本中 Dataset 和 Dataloader 都进行了重构,导致用户封装数据集时必须继承自 MapDataset 或 StreamDataset :

得知这一改动后 Tutorials 中也进行了修改:

在 v1.2.0 版本中,MapDataset 被重命名为 Dataset. 教程又可以改回原样了,似乎岁月静好。但是这就导致在 v1.0.0 和 v1.2.0 的 dataloader 代码和 v1.1.0 之间互不兼容, v1.1.0 用户神奇地获得了一个叫做 MapDataset 的概念,然而除了 v1.1.0 版本以外不会再用到。 :sweat:

所以当时在新增对 StreamDataset 支持的时候,为什么没有考虑到原本就有的继承 Dataset 的写法呢?

@xxr 由于对 Commits 的整理(让一切看起来正常),这种奇怪的变动似乎要无迹可寻了。

1赞

这个 API 的变动就是考虑的不够周全,具体的细节可能需要开发这部分的同学回答一下这个变化的思维过程了。

不过这带来了几个疑问

  • 这个 API 改动为什么没有知会足够多的人?或者知会后为什么没人指出问题
  • 我们应该是有个 ResNet50 训练测试做为集成测试,为什么这个测试中没能反映出这一点?

没懂这里的意思,合入前 rebase commit 导致了什么问题么?

例如尝试在 GitHub 查看 meta_dataset.py 的历史时,只能直观地看到我们最后将 MapDataset 重构回 Dataset 的这一改动,已经找不到中间的细节。或者说我们为了让最终结果看上去正确进行了 Rebase Squash,但目前来看这个 commits 不能体现出一开始就设计了 Dataset 和 StreamDataset.

这个API的改动的确是考虑不够周全,没想到要兼容以前的代码
最初的想法比较直接,MapDataset和StreamDataset本身就是平行的关系,所以在写代码的时候自然就分成了两类,也就是v1.1的样子。后来收到Chai的反馈,需要保持和PyTorch一致的接口,因此把MapDataset改名为Dataset并作为所有数据集的基类,也就是v1.2的样子。

这个API的改动并没有刻意地支会其他人,但是作为 mr 的内容是所有人都可见的,只不过没人注意到这点。

或许文档列出的使用方法也需要加入到CI中去,防止后续可能出现的非预期的API的改动。

1赞

文档跑 CI 是可行的,正在测试如何让文档和 MegEngine 一样可以支持多版本… :eyes:

1赞

做为一个基础组件,最重要的是从自身就要意识到 break API 是一项多么重大的影响,从而在导致 API 变化之前,从最开始设计 API 就充分的考虑。

靠 CI 来围追堵截,避免出错是一部分;但总有一些 API change 是无法直接测试测出来的(比如你限定 API2 在搭配 API1 的时候就会行为发生奇妙的变化),需要大家写代码的时候就意识到这一点。