-
Notifications
You must be signed in to change notification settings - Fork 5.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Operator & Variable Place Design #11771
Comments
Considering the scale of changes, the content after 3.5 maybe not in schedule, or be scheduled later |
Thanks for writing this doc. I think adding attrs, such as PlaceGroup, is necessary during "some stage" of tranformation. It's necessary to guide the eventual placement and execution. I have some comments: PlaceGroup probably shouldn't be added directly to Program. Perhaps we should first transform program to graph, then analyze based on the graph. Program probably won't be visible to executor directly. Instead, executor should see a intermediate representation that is more suitable for guiding execution, (with dependency, placement and even operation overhead, memory reuse related information) Executor might not be the right place to decide whether to execute a part of the model. Instead, the partition can be done before execution. Correctness Verification should be considered as the first-class citizen of the design. But it's not mentioned here. Maybe serv_op should not be an op. But I'm not quite sure yet. |
Maybe we can take ProgramDesc as the IR? I'm not sure.. maybe the
Thanks for kind advice, this is very important, so that we can guarantee that the transpiler has done everything right, I will add the description doc of correctness later. |
I think use
|
您好,此issue在近一个月内暂无更新,我们将于今天内关闭。若在关闭后您仍需跟进提问,可重新开启此问题,我们将在24小时内回复您。因关闭带来的不便我们深表歉意,请您谅解~感谢您对PaddlePaddle的支持! |
Operator & Variable Placment Design
1. 背景
在开始介绍我们的 Variable Place Design 之前, 我们先看一下, Paddle之前都是如何构造一个分布式的NN训练网络的
1.1 构造 Forward Pass
参考 uci_housing 的例子, 我们构造了一个非常简单的 Forward Pass:
通过上述代码, 我们得到了如下的 ProgramDesc:
1.2 构造 Backward Pass
在实践中, 我们把复杂的通过 Forward Pass 构造 Backward Pass 的过程用 append_backward 封装起来:
通过构造 Backward Pass, 我们可以得到如下的 ProgramDesc:
其实就是往后添加了一些新的Operators
1.3 添加 Optimizer, 完成整个训练程序
我们添加了一个 Optimizer, 将 Forward Pass 和 Backward Pass 串起来, 得到一个完整的训练程序, 这段代码也被封装到了
append_opt_op
方法里现在, 我们得到了如下的 ProgramDesc:
还是简单的添加一个opt_op即可;
2. 痛点
到目前为止, 一切都还是自然而简单的, 并不复杂, 但是, 当我们引入 Distribute Transpiler 时, 突然发现我们面临了一个大问题, 非常非常多的, 繁琐而没有头绪的步骤开始接踵而至, 我们要做的包括:
很明显, 这不是一个我们想接受的好方案, 任意的牵扯到transpiler的改动都会为分布式执行带来不稳定的因素;
思考一下深层次的原因, Dist-Transpiler 其实做了三件事:
下面我们来讨论后两件:
2.1 Program 拆分
对于拆分这件事来说, 其实 Distribute Transpiler 没有说清楚, 他到底是在做
Placement
(把 op 或 variable 放到某一个具体的运行实例上), 还是在做Replication
(把 op 或 variable 复制多份, 放到多个运行实例上), 目前的现状是, 他两个事情都做了:Placement
, 将 opt_op 放到了 pserver program 中;Replication
, 每一个 trainer program 都有这些 op;Placement
, 又有Replication
;问题在于, 他没告诉用户, 他在什么时候, 会对什么部分, 做
Replication
还是Placement
, 抑或是, 两者都有;2.2 Program 修改
对于修改来说, 现在 Distribute Transpiler 做的事情, 主要是插入新的 op, 和新的 block, 同样的, 插入新的 op 或 block 也没有具体的规则;
他没告诉用户, 他在什么时候, 会对什么部分, 执行这些插入动作, 插入的具体是什么;
3. 解决方案
为了简化上面的步骤, 我们引入一个概念:
3.1 PlaceGroup 详细设计
3.1.1 和 place.h 兼容
place.h 中的 Place 设计可以总结如下:
PlaceGroup 可以作为 OpAttr 或 VarAttr 被序列化到 ProgramDesc 中, 在兼容 Place的设计的同时, PlaceGroup 的详细设计如下:
一个代表
pserver/127.0.0.1:7164/gpu:[1,2]
这个 PlaceGroup 的实际例子如下:加入这个概念后, Distribute Transpiler做的上述步骤以被简化为一件事: 为 Program 内部的 Op 和 Var 填充他们的 PlaceGroup 信息;
3.2 对 Op 分配 PlaceGroup
针对上面的 uci_housing 的例子, Distribute Transpiler 需要为如下 ProgramDesc 中的 Op 分配 PlaceGroup:
现在假设我们有2个 trainer (192.168.10.1, 192.168.10.2), 1个pserver (127.0.0.1:7164); 现在要获取 192.168.10.1 这个 trainer 被 transpile 后的 program_desc;
我们依旧可以按照之前的 Distribute Transpiler 的方法来分配, opt_op 和相关的 Vars 被分配到某个具体的 pserver, 其他的 op 都留在 trainer, 于是我们得到如下的结果:
可以看到, 只需要一个 Program, 每个 Executor 就知道自己该执行这个 Program 中的哪个部分, 现在我们来看下 Executor 的具体执行方法;
3.3 Executor 执行方法
3.3.1 Program 解析
Executor 拿到 Program 后, 会先判断, 这个 Program 中有哪些 Op 属于自己, 哪些 Op 不属于自己, Executor 只会执行属于自己的部分;
[//]: # (注意到, 大部分 PlaceGroup 中, 我们没有指明具体的 node (即 trainer ip 和 port), 只是指明了role (即 trainer 这个角色), 这个设计会让 2 个 trainer 都认为自己可以执行这些 op, 相当于把这些 op 复制了多份放到多个 trainer 上;)
假设当前的 Executor 是 pserver, 那么我们会执行的 ProgramDesc 是:
pserver仅会执行 opt_op, 剩下的 op 都是属于别人的, 他不会执行, ParallelExecutor的优化过程也不会包括那些属于别人的 op.
到目前为止, Dist-Transpiler已经有了明确的约束, 即, 他要通过 PlaceGroup 来说清楚, 哪个 Executor 执行哪些 Op, 从而解决多机的 Op 分配问题;
但是我们还是需要插入 send_op 和 recv_op 来同步我们的 Var, 但这个步骤其实可以通过给 Var 加入 PlaceGroup 信息来省去;
3.4 为 Var 分配 PlaceGroup
和 Op 一样, 针对上面的 Program Desc, Distribute Transpiler 需要为如下 ProgramDesc 中的 Var 分配 PlaceGroup:
3.4.1 分配样例
分配后的结果为:
即:
3.4.2 根据 PlaceGroup 获取 Var
这时我们会发现, 像 w 这样的 var, 我们放置在了 Pserver, 像 w@GRAD 这样的 var, 我们放置在了 Trainer, 于是对于 mul 这个 Op 来说, Input Var 的位置发生了变化:
所以 mul 在执行之前, 需要根据 transpile 后 w 的 PlaceGroup 信息, 即:
这一段, 了解到 w 是放置在 pserver 的, 所以在执行 mul 之前, 需要在 OperatorBase 的 Run 方法中等待 w 变量, 直到 w 变量可用, 再执行 mul 的 RunImpl 方法;
3.5 去掉 serv_op
于是, 我们不再需要 listen_and_serv_op 去监听变量了, 因为其实 Program 并没有发生变化, 只是各部分的执行位置发生了改变, 所以我们不需要一个实现了 Server 功能的 serv_op 去执行指定的 block, 我们只需要按照 Program 指定好的顺序, 执行整个 Program 即可;
根据这个思想, 用户的使用方式也可以发生简化, 以 fit_a_line 为例:
3.6 Distribute Transpiler 整体流程叙述:
引入上述 PlaceGroup 概念之后, transpile 整体流程简化如下:
(特定优化部分)
(通用部分)
The text was updated successfully, but these errors were encountered: