在上期文章中,我们对 HugeCTR Sparse Operation Kit (以下简称SOK) 的基本功能,性能,以及 API 用法做了初步的介绍,相信大家对如何使用 SOK 已经有了基本的了解。在这期文章中,我们将从在 TensorFlow 上使用 SOK 时常见的“数据并行-模型并行-数据并行”流程入手,带大家详细了解 SOK 的原理。
SOK 的 Embedding 计算主要分为三个阶段:input-dispatcher -> lookup -> output-dispatcher,接下来我们将以 all-to-all 稠密 Embedding 层为例,带大家梳理各个阶段的计算过程。
1. Input Dispatcher
Input Dispatcher 的职责是将数据以并行的形式的输入,分配到各个 GPU 上。总共分为以下几个步骤:
2. Lookup
Lookup 的功能比较简单,和单机的 lookup 的行为相同,就是用 input dispatcher 输出的 key,在本地的 embedding table 里查询出对应的 embedding vector,我们同样用一个简单的图来举例。注意下图中 Global Index 代表每个 embedding vector 在实际的 embedding table 中对应的 key,而 Index 则是当前 GPU 的“部分”embedding table 中的 key。
3. Output Dispatcher
和 input dispatcher 的功能对应,output dispatcher 是将 embedding vector 按照和 input dispatcher 相同的路径、相反的方向将 embedding vector 返回给各个 GPU,让模型并行的 lookup 结果重新变成数据并行。
可以看到, GPU 0 上输入的[0, 1, 3, 5],最终被转化为了[0.0, …], [0.1, …], [0.3, …], [0.5, …] 四个 embedding vector,虽然其中有 3 个 embedding vector 被存储在 GPU 1 上,但是以一种对用户透明的方式,在 GPU 0 上拿到了对应的 vector。在用户看来,就好像整个 embedding table 都存在 GPU 0 上一样。
4. Backward
在 backward 中,每个 GPU 会得到和 input 的 key 所对应的梯度,也就是数据并行的梯度。此时的梯度对应的 embedding vector 可能并不在当前 GPU 上,所以还需要做一步梯度的交换。这个步骤和 output dispatcher 的第三步骤中的工作流程的路径完全相同,只是方向相反。仍然以前面的例子举例,GPU 0 获取了 key [0, 1, 3, 5]的梯度,我们把它们分别叫做 grad0, grad1, grad3, grad5;由于 grad1,grad3,grad5 对应的 embedding vector 在 GPU 1 上,所以我们把它们和 GPU 1 上的 grad4, grad6 进行交换,最终在得到了 GPU 0 上的梯度为[grad0, grad4, grad6],GPU 1 上的梯度为[grad1, grad3, grad5, grad5, gard7]。
结语
以上就是 SOK 将数据并行转化为模型并行再转回数据并行的过程,这整个流程都被封装在了 SOK 的 Embedding Layer 中,用户可以直接调用相关的 Python API 即可轻松完成训练,更多信息,敬请参考 SOK 官方文档:https://nvidia-merlin.github.io/HugeCTR/sparse_operation_kit/master/index.html
以下是 HugeCTR 的 Github repo 以及其他发布的文章,欢迎感兴趣的朋友阅读和反馈。
Github: https://github.com/NVIDIA-Merlin/HugeCTR (更多文章详见 README)
| 留言与评论(共有 0 条评论) “” |