-
Notifications
You must be signed in to change notification settings - Fork 55
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
Swin graph 3d 并行,打开 acc grad 报错 #232
Comments
这个 Check 是我加的,去年年初重构移除 Logical Graph 的时候搞的。理论上不会出现。我看看代码回想一下逻辑 😂 |
德澎你这个测试,是在哪台机器上做的? @Ldpe2G 金山还是类脑 |
之前是在金山上做的 |
验证这个分支: 是否解决上述问题。 这个 BUG 的原因是: swin Variable Op 后面有一个 B2P 的 boxing,该 boxing 会插入 zero boxing task node。但是在 : |
不过我有一个问题,B2P 是一个不太常见的 boxing, swin 开 3-D 并行,Variable 后面跟一个 B2P 的消费,是否符合预期? @Ldpe2G @strint @leaves-zwx @L1aoXingyu |
我理解目前应该不存在 B2P 的实际应用场景 |
不符合预期,需要看看是怎么来的~ |
得看下是哪个模块导致这个 |
这组配置可以跑起来train.train_micro_batch_size = 8
train.num_accumulation_steps = 2
train.test_micro_batch_size = 16 这组配置虽然不会报错,但是会卡住,有几张卡利用率 100%,其他为0%train.train_micro_batch_size = 32
train.num_accumulation_steps = 4
train.test_micro_batch_size = 128
|
不是,相反,grad acc 的次数,应该是至少 stage 数量的 2 倍。 |
那可能是网络结构上有问题。 @leaves-zwx 文骁辅助德澎 debug 一下吧。 这个跟中兴那边的 swin-T 有交集。 |
This comment was marked as duplicate.
This comment was marked as duplicate.
是的,我看下 |
这个在 libai 里是默认开启的。 @L1aoXingyu |
我这边试验了一下,关掉能跑起来 |
第一步,先确认各 rank 运行卡在什么位置(也就是什么 op 上),基本上有2种方法:
简单的说就是先确认各 rank 各 stream 卡在何处。 |
我怎么记得这个 BUG 之前出现过。。。 上次是触发 1024 的上限,导致异步变同步死锁的 @leaves-zwx 😂 |
如果减少 Transformer layer 的层数会跑起来吗 |
偏序执行不一定唯一是 nccl 调用顺序不同的原因 |
void Kernel::Launch(KernelContext* ctx) const {
LOG(INFO) << "before: " << this->kernel_conf_.op_attribute().op_conf().name();
ctx->WillForward(ctx, this);
Forward(ctx);
ctx->DidForward(ctx, this);
LOG(INFO) << "after: " << this->kernel_conf_.op_attribute().op_conf().name();
} 所有 Log 文件: https://oneflow-static.oss-cn-beijing.aliyuncs.com/log.tar.gz |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
I20220419 11:41:22.487932 2724642 eager_boxing_logger.cpp:42] Boxing route: symmetric-acyclic-nd-sbp-to-nd-sbp -> naive-b-to-1 -> naive-1-to-1 -> naive-1-to-p -> symmetric-acyclic-nd-sbp-to-nd-sbp
I20220419 11:41:22.488044 2724642 eager_boxing_logger.cpp:43] Logical shape: (256,100)
I20220419 11:41:22.488057 2724642 eager_boxing_logger.cpp:44] Altered state of sbp: (S(0), B) -> (B, B) -> (B, B) -> (B, B) -> (P, P) -> (S(0), B)
I20220419 11:41:22.488121 2724642 eager_boxing_logger.cpp:45] Altered state of placement: oneflow.placement(type="cuda", ranks=[[0, 1], [2, 3]]) -> oneflow.placement(type="cuda", ranks=[[0, 1], [2, 3]]) -> oneflow.placement(type="cuda", ranks=[[0]]) -> oneflow.placement(type="cuda", ranks=[[4]]) -> oneflow.placement(type="cuda", ranks=[[4, 5], [6, 7]]) -> oneflow.placement(type="cuda", ranks=[[4, 5], [6, 7]]) 看 rank 0 LOG 的时候发现这一段,包含了 B -> P 的 boxing |
看起来是的
|
System-GradientAccumulation-VariableRepeat-model.layers.1.blocks.0.attn.relative_position_bias_table-366,model.layers.1.blocks.0.attn-gather_nd-162_grad,{0:cuda:0-0 1:cuda:1-1 2:cuda:2-2 3:cuda:3-3 (2 2)},{0:cuda:0-0 1:cuda:1-1 2:cuda:2-2 3:cuda:3-3 (2 2)},(B, B),(P, P),System-GradientAccumulation-VariableRepeat-model.layers.1.blocks.0.attn.relative_position_bias_table-366/out_0,kFloat,(169 6),NaiveB2PSubTskGphBuilder,- 看 boxing cvs 里面是有 B->P 的
|
这个是正常现象,因为整个集群都还在第1个 micro-batch 内,没运行到第2个 micro-batch,所以第2个 System-Src-WaitAndSendIds_1157 在等待用户触发。 |
日志上传一下, @leaves-zwx 文骁再分析一下死锁原因 |
最新卡住的原因如上图,一句话概括,nccl logical op 的延迟创建 communicator 机制与 forward/backward 的不确定性执行顺序相互作用发生了死锁。
比较奇怪的是修改了 nccl 的代码后把 ncclCommInitRank 中的 ncclCudaMemcpy 改成了非同步式(copy 发生在单独的 stream 上,并且阻塞至 copy 结束才继续往下执行)后,程序可以越过 nccl 1211 继续往下执行一段,但最终仍然还是死锁在某处,看起来像是 nccl 调用还是有某种死锁冲突,需要进一步的 debug 确认死锁原因。 |
|
目前初步 debug 出的原因是反向子图的 System-Boxing-BoxingCopy-1899 被放置在 forward stream 上,因为前向已被阻塞,所以 System-Boxing-BoxingCopy-1899 也无法执行。 |
补充一下 slice boxing copy 卡住的图例
死锁的原理与前面 backward buffer op 被放到 forward stream 造成死锁的原因高度相似。#232 (comment) |
https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/core/job_rewriter/insert_nccl_logical_op_pass.cpp#L749 是不是因为 nccl logical 不应该复用compute stream,这个判断去掉试试 |
可以,nccl logical 独立 compute 0 创建一个新的 stream,是可行的。 |
这是个重要的发现,能找到源代码里是什么地方处理的吗? |
把 nccl logical 的前后向都独立出来,分别放到 nccl_compute_0 和 nccl_compute_1 stream 中去,原来的 default compute stream 中只剩下 tick, repeat, unpack, boxing copy/zeros/identity 等 op。 仍然死锁
gather_nd-33_grad 为什么阻塞 gather_nd-33 的执行2th backward 的 gather_nd-33_grad 阻塞 4th forward 的 gather_nd-33 的执行,主要是因为流水下的 regst 依赖关系,如下图所示: 从 repeat 有一条控制边连向 boxing zeros 1546,gather_nd-33_grad 消费 boxing zeros 1546,gather_nd-33_grad 如果不能执行,则 boxing zeros 1546 消费的 ctrl regst 不能还给 repeat,repeat 的 out regst 是有上限的,所以 gather_nd-33 可以提前执行但不能无限提前执行。 注意: repeat 到 boxing zeros 1546 的控制边在 rank0 上是不存在的,所以的可以看到 rank0 forward thread 中的 gather_nd-33 并没有被 rank0 backward thread 中的 gather_nd-33_grad 阻塞。 cudnn conv2d filter grad 为什么会阻塞?其实被阻塞的 cuda 调用不止 cudnn conv2d filter grad,还有1个 copy H2D,还有 cudaStreamSynchronize (或 cudaEventSynchronize)。看起来像是 device sync 阻塞,但是翻遍所有的 thread stack,没有发现外部的 device sync 调用,只能猜测是内部某种同步机制被触发,特别是我们还修改了 nccl 某处逻辑。目前 libcuda 缺乏调试手段。 cudnn conv2d filter grad 栈
CopyHD 栈
cudaStreamSynchronize 栈
如果不开启 kernel sync,cudaEventSynchronize 栈
TODO目前 libcuda 没有进一步调试手段,无法继续查证为什么 cudnn conv2d filter grad,copy hd 等卡住。有可能是我们修改了 nccl 源码的原因,目前先把 nccl 还原,然后修改 nccl commnunicator init 的逻辑解决 commnunicator init 的死锁,然后再测试看会不会死锁。 或者先把 cudnn conv2d filter grad 注释掉,使 conv2d filter grad kernel 空跑,看看还会不会出现该问题。 LOGoneflow.INFO |
不同的线程上 op 的顺序不同? 其实这个就是pathways 为什么说gang scheduling的原因 |
不同进程上的 op 顺序不同是存在的,有时也确实造成死锁,但上面这个死锁不是这个原因 forward thread: gather_nd-33 -> nccl_1199 这几个 op 的序在 rank0 和 rank1 上是一致的。 但 rank1 上存在 1 条 repeat --> boxing_zeros -> gather_nd-33_grad 的控制边,实际上形成了 gather_nd-33_grad ---> gather_nd-33 这样的间接依赖关系(不在同一个 iter 内),但 rank0 上没有这样的依赖。
gang scheduling 能使很多问题变简单,当初实现 pipeline parallel 时也考虑过 gang scheduling 的方案,但当时选择了修改最小时间最短的方案。这里有最近一次的讨论:https://github.com/Oneflow-Inc/OneTeam/issues/1238#issuecomment-1095016837 |
为什么有的rank上有控制边,有的没有,不同rank上的子图不一样 |
B -> P 这样的 boxing,在本 rank 上有1个 boxing identity 的操作,其他 rank 上都是 boxing zeros 的操作,到 boxing identity 的边是 data regst ,到 boxing zeros 的边是 ctrl regst。 |
如果要实现 forward/backward 共用 stream,应该是需要实现 gang scheduling |
修改了 repeat 的 ctrl out regst 的 num 后,NCCL_LAUNCH_MODE=PARALLEL 下可以运行通过,但在 NCCL_LAUNCH_MODE=GROUP 下还是会死锁。需要:
|
Swin-T 3D 并行(2x2x2)死锁问题当前阶段总结debug 过程中发现以下问题:
在 Oneflow-Inc/oneflow#8226 目前做了2处修改,然后测试不再死锁。 其中第1点修改(nccl_compute_stream 独立)可以解决上面的问题1和3。在这2个问题中,某些原本属于 backward 的 op 被错误的放置在 forward stream 中,这是因为在 nccl logical ops pass 中,stage0 (在这个case中,rank0和rank1都属于stage0) 被分为2个 nccl_compute_stream,基本认为可以对应着 forward stream 和 backward stream。但当时实现时选择让 nccl_compute_stream_0 (也即 forward stream) 复用 default_compute_stream (不开启 nccl_use_compute_stream 时或无 pipeline parallel 时一般只有这个 default_compute_stream)。所以一般认为有2个主要 compute stream 在工作(忽略 copy stream 和 independent stream 等)。但由于某些原因,有些 op 在被添加时,它被加入到 default_compute_stream 中,但它的前后依赖 op 都在 backward stream 上,这就会导致 default_compute_stream (即 forward stream) 上插入一些与其他 op 没有依赖关系的 op,既然没有依赖关系,那插入的位置就只看物理时间点,所以有时在不同rank上插入位置会有很大的差异。这个修改就是让 nccl_compute_stream 不再复用 default_compute_stream,那么就变成3个主要的 compute stream 在工作,分别是 default_compute_stream, nccl_compute_stream_0, nccl_compute_stream_1,其中后2者充当着 forward stream 和 backward stream 的角色,而 default_compute_stream 中只留下了 tick, repeat, unpack, buffer, boxing copy/zeros/identity 等 op。这样修改后,可以避免1个与其他stream上op有依赖关系op插入到与其没有任何关联的stream上。 第2点修改(repeat ctrl out regst num 修复)可以解决上面的第4点问题,这个修改可以使后向不再阻塞前向(会导致不同 rank 的执行节奏差的比较远)。 为什么只改一部分问题就不死锁了?死锁的本质原因是什么?我们从一个简单的示意图看起: 图中的 D 如果起到屏障作用(比如有同步之类的操作)那么它在 rank0 和 rank1 上不同的插入的位置就会引起死锁问题(A, B, C 都是需要同步的操作,例如 nccl)。 上面的问题1、2、3点实质上都是起到了这个屏障作用。比如 nccl communicator init 带来的 device sync,放置错误的 backward ops 会使依赖其的 backward nccl op 带来屏障。屏障的特点是其与其他在同一个 thread 中执行的算子没有依赖关系。 而问题4则是屏障在rank0插入在 B 之后,rank1插入在B之前的原因。gather_nd-33_grad 消费的 variable,在 acc > 1 时消费的实际是 repeat;如果此时 grad 与 variable 的 sbp 不一致,且是 B -> P 这种情况,那么还需要插入 slice_boxing;这个 slice_boxing 会带来不平衡的子图,其在 rank0 上插入的是 boxing identity,有数据边直接连向 repeat,而在其他 rank 上插入的是 boxing zeros,只有控制边连向 repeat,而控制边的 regst num 是 1,数据边的 regst num 是 4(acc_steps == 4),那么我们就可以想见 rank0 上的某轮前向的某些 op (比如 B) 可以比其他 rank 上的同一轮的前向的同样 op 更早执行,因为其他 rank 上的 B 还要等后向归还那个 regst_num 只有1的控制边。所以这就造成了上面这种示意图的情况,rank0 上屏障在 B 之后,rank1 上屏障在 B 之前。 为什么问题2不改就可以解除死锁? 实际上经过测试只改掉问题4 (repeat ctrl out regst num) 测试就不再发生死锁了,因为改掉 4 后,屏障分别插在 B 之前和 B 之后的情形就消失了,即使有产生屏障的问题(比如 2)也不会带来死锁。 是 swin-T 的特殊网络结构和问题4共同作用带来的死锁。这也是为什么之前测试的 bert gpt 没有死锁,而到 swin-T 这里频繁发生的原因。(其实还跟目前 swin-T 错误的 sbp 配置有关系,这样才会出现奇怪的 slice boxing) swin-T 与 gpt 结构上的1个重大差别是:在每一层 transformer layer 的 activation 与 variable 计算前有1个 gather 操作,例如 variable -> gather -> matmul。注意这个 gather 与 source op (variable) 非常接近,所以哪怕是非常靠后的 transformer layer 的 gather 也有可能早于非常靠前的比如 embeding 中的 conv2d 操作。这比较违反直觉,但这是 oneflow 执行的一种特点,即执行时完全不考虑算子的 python 序。 swin-T 在执行时,前向刚开始没多久就需要执行大量的 gather 操作先,且这些 gather 每次执行是顺序不确定的,同时由于 repeat ctrl out regst num 带来的其他 rank 上(除 rank0)的 gather 反向会阻塞 gather 前向的问题,使 rank0 和 rank1 上的某轮前向��行物理时间点上差别比较大,才导致屏障们有可乘之机插入到不同位置。问题4的修复使 rank0 和 rank1 上的执行步调变得基本一致,屏障们很难再插入到不同的关键点位置。 但很难不代表不可能,如果有新的不常见的模型结构,或者只是不同 rank 的执行波动导致了执行的步调不一致,屏障们仍然有可能插在不同关键点位置导致死锁。所以消除屏障仍然是必须的。修复 repeat ctrl out regst num 问题只是降低了死锁发生的概率。如果有 gang scheduling 可能能完全避免死锁。 所以问题2仍然需要解决。 对于问题5的猜测:NCCL_LAUNCH_MODE=GROUP 在 single-client 下有用,其保证不同 device 上的 nccl 执行顺序保持一致。但在 multi-client 下可能是负作用,因为1个rank只有1个device,GROUP 只带来了更加严格的执行顺序要求,使原本在 PARALLEL 模式下稍微宽松一点的可以执行下去的偏序在 GROUP 下执行不了。 但如果没有特别紧急的其他事情,还是有必要调试一下看 GROUP 下到底是什么执行序导致的死锁。一是增加对 nccl 执行方式的更深的理解;二是排除可能存在的潜在 bug。 @chengtbf 的修改 Oneflow-Inc/oneflow#8087 可以不是必要的?其想把 backward buffer op 放置到单独的 stream 里面去,但是否只要不跟 forward op 在1个 stream 就行了?nccl compute stream 独立那个改法也能达到同样目的。 |
这段话不是那么清晰,可以refine下 |
将 forward nccl_compute_stream 从 default_compute_stream 中分离出来。这样在 last stage 或者不开启 pipeline parallel 情况下(即 forward 和 backward 是同1个 nccl_compute_stream),对内存共享算法有冲击吗?毕竟之前是所有op都在1个stream 内,现在分在nccl_compute_stream和default_compute_stream 2个stream内,会导致chain断裂,内存复用结果不佳吗? |
不会。因为 nccl compute stream 里的 subgraph 就是一个 chain。合并不到一个 subgraph(同一个 nccl stream) 里的,本就不在一个 chain 里。 |
如果main在调用nccl初始化代码之前做vm sync呢? |
由于 graph 统一初始化 nccl comm 不是指令,就算在做这个之前调用 vm sync 也不能完全规避上面提的情况吧,只能说降低概率。除非说我 vm sync 后有1种办法把 vm 锁住,然后等待我 graph 的 nccl comm 统一初始化完成后,再解锁 vm 才能完全保证安全吧? |
vm sync做完之后,vm就没有任务了呀。 |
明白了,现在 main 线程正在准备做 graph 的 nccl comm 统一初始化这个时间点上,也不可能再插入其他指令了。 |
是 |
实验分支:#215
文件 swin_cifar100.py 关键配置
报错信息
The text was updated successfully, but these errors were encountered: