给定上述模型定义,ElasticDL 的 master 进程按照 asynchronous 或者 synchronous SGD 方法,协调 workers 来做训练。当使用 asynchronous SGD 方法时,master 会启动一个高性能的 parameter server,供各个 workers 使用。当使用 synchronous SGD 时,ElasticDL 使用和才云科技合作研发的一个 Kubernetes-native 的 fault-tolerable AllReduce 实现 FTlib。
Master 负责动态数据划分
弹性训练过程的一个容易被忽略的前提是动态数据划分 (Dynamic data partitioning)。在用 MPI 写分布式程序的时候,因为作业中进程数量是恒定的,所以经常采用静态数据 划分的做法 —— 在训练之前把训练数据预先分成 N 个文件,对应作业中的 N 个 worker 进程。这个做法在弹性调度的时候就失效了 —— 因为弹性调度时,作业中的进程数量是可变的。为此,需要实现动态数据划分。
ElasticDL 的动态数据划分是基于索引的。ElasticDL 要求训练数据是一个或者多个 RecordIO 格式的文件,或者是 MaxCompute 数据库系统中的表 (table)。这两种数据源都允许 master 进程在开始训练之前,在基本存储单元 (block) 间快速跳跃着扫描数据,把数据分成小段,称之为任务 (task)。每个 task 包括的内容如下:- 第一条记录相对于文件(或者表)开始处的偏移 (offset),
扫描结果是很多 tasks,master 把这些 tasks 放进一个 TODO 队列里。这个队列不一定需要是 master 进程里的数据结构,可以是放在 etcd 里的 —— 因为 etcd 是不死的,所以 master 即使被高优先级作业抢占了,这个信息也不会丢失;可以通过在资源富余时重启 master 进程来恢复作业状态。
扫描和划分数据的同时,master 开始请 Kubernetes 启动 workers,总数不超过用户指定的数量 N(最大并发度)。每当一个 worker 启动起来了,master 会收到 Kubernetes 发来的通知;master 在一个 etcd 数据结构里记录“活着”的 workers。
扫描和划分数据结束之后,master 就依次从 TODO 队列里取出 task,通过 gRPC 发给某一个活着的 worker,同时 master 把这个 task 挪进 DOING 队列里。接收到 task 的 worker 负责打开文件(或者表),并且从指定的 offset 开始依次读取记录,并且更新本 地模型。根据用户选择的 asynchronous 或者 synchronous 算法,workers 会通过调用 parameter server 或者 AllReduce 来协调更新全局模型。
当一个 worker 处理完了接收到的 task,它通过 gRPC 返回一个表示成功的标记;master 就把这个 task 从 DOING 队列挪到 DONE 队列了。当所有 task 都从 TODO 挪进了 DONE, 则说明一个 epoch 完成了。
如果一个 worker 失败了(比如被更高优先级作业抢占了),则 master 的 gRPC call 会 timeout;此时,master 把对应的 task 从 DOING 队列挪回 TODO 队列了。下一次有 worker 完成 task 时,master 会把这个 task 再发出去。这里有一个细节:有的 task 可 能被某个 worker 使用了一部分,也因此影响到了模型更新;此时 worker 被抢占,那么这 部分已经被处理的数据会因为 task 的下一次分发,被重复使用。不过这个并不影响机器学 习训练要求数据统计一致性的假设。而且其他动态数据划分方法造成的数据复用情况可能更 严重。
Woker 调用 TensorFlow Eager Execution
ElasticDL worker 接收到的一个 task 通常包括多个 minibatches。对于每个 task, worker 打开对应的文件或者表,随后做如下操作:- 用本地模型 (local model) 作为参数调用用户定义的 forward 函数以计算 cost。如果 模型很大,则部分参数可能来自于 parameter server。
- 给定 cost,worker 利用 TensorFlow eager execution 的 GradientTape 机制,进行 backward 计算,得到梯度 (gradient)。
如果是 synchronous SGD,此时 worker 调用 AllReduce 实现 FTlib 来同步 gradients 并且更新模型。如果是 asynchronous SGD,worker 不定时的向 parameter server 上传 gradients,也不定时地从 parameter server 获取全局模型参数。
高效训练的优化
相对于 2019 年秋季 ElasticDL 在 Google Developer Day 上亮相时的状态,最近几个月 ElasticDL 项目针对性能优化做了很多工作。当时 ElasticDL 使用 Redis 作为 parameter server。现在有了自己的用 Go 语言写的 parameter server。相对于 Redis, ElasticDL parameter server 可以做一些深度学习计算,从而减少 worker 和 parameter server 之 间通信的次数。
这个变化和其他优化工作一起让同样的训练作业,总体训练时间下降了约 13 倍。最近一个基于 DeepFM 模型的试验展示,用两个 parameter server 进程和四个 workers 进程来训练,10 个 epochs 的总体时间从 1350 秒(ElasticDL 的 2019年9月版本)下降到 106 秒(2020年2月版本)。这些优化策略包括: