PaddlePaddle / Paddle

PArallel Distributed Deep LEarning: Machine Learning Framework from Industrial Practice (『飞桨』核心框架,深度学习&机器学习高性能单机、分布式训练和跨平台部署)
http://www.paddlepaddle.org/
Apache License 2.0
22.25k stars 5.59k forks source link

phi::Device::SynchronizeStream传入的stream的raw_stream成员为空指针是否是正常的? #63804

Open continue-coding opened 6 months ago

continue-coding commented 6 months ago

请提出你的问题 Please ask your question

我在使用custom device训练ppocr-det模型时遇到了训练结束时偶尔会发生coredump的情况,报错调用栈如下: image 因为是在SynchronizeStream处挂掉的,我打印了传入SynchronizeStream的stream,发现发生coredump时传入SynchronizeStream的raw_stream都是空指针。 image 之后我在SynchronizeStream的开始阶段加入判断逻辑:如果传入的raw_stream是空指针就返回,不做流同步。

void Device::SynchronizeStream(const stream::Stream* stream) { if(!stream->raw_stream()) { LOG(INFO) << "[SynchronizeStream]raw stream is null, skip synchronize!"; return; } LOG(INFO) << "[SynchronizeStream]stream: " << stream << " raw stream: " << stream->rawstream(); CheckInitialized(); impl->SynchronizeStream(devid, stream); }

这样修改后不会再发生coredump,可见coredump是因为传入SynchronizeStream的raw_stream为空指针导致的。 image 所以我比较好奇传给SynchronizeStream的raw_stream为空是正常的行为吗?如果是异常行为,那我在SynchronizeStream中添加的遇空返回的处理方式是否是可行的?会不会造成别的影响? 烦请飞桨的大佬帮助解答我的疑惑,谢谢!

continue-coding commented 6 months ago

@ronny1996 辛苦大佬帮忙看一下

continue-coding commented 6 months ago

分析下图发现stream 0x1108cfc0的raw_stream 0x1128d320在最后调用流同步前已经被销毁了,可能这是导致raw_stream为空指针的原因。但是为什么在训练快结束时,raw_stream被销毁后,还会调用memcpyh2d,进而调用SynchronizeStream呢? image 并且我尝试过将所有的destroy stream操作都去掉,还是会出现raw_stream变成空指针,进而导致coredump,感觉很离奇。 37584d2941038b88b4647b57328bbf6a

44a47e672196fffb6a43c4142eda074f

ronny1996 commented 6 months ago

你好,出现raw_stream=0并且传到了插件runtime中是不正常的,能否打开 GLOG_v=10 上传下第一次出现raw_stream=0并传到memcpy中的日志

continue-coding commented 6 months ago

你好,出现raw_stream=0并且传到了插件runtime中是不正常的,能否打开 GLOG_v=10 上传下第一次出现raw_stream=0并传到memcpy中的日志

尝试过开启GLOG_v=10,但是可能是因为这个问题是偶现的,或者是别的原因,开启GLOG时没有遇到coreddump的情况。目前尝试了下注释掉phi::CustomDevice::Finalize和phi::CustomDevice::DeInitDevice,跑了几次没遇到coredump,怀疑可能是设备已经去初始化了导致stream被置空?我再打印下Finalize和DeInitDevice的信息,有进展再同步给您吧

continue-coding commented 6 months ago

你好,出现raw_stream=0并且传到了插件runtime中是不正常的,能否打开 GLOG_v=10 上传下第一次出现raw_stream=0并传到memcpy中的日志

另外补充一下,应该是raw_stream还没传入插件侧就已经挂了,因为raw stream为空的话应当是同步null 流,同步null流应该不会导致coredump。并且报错调用栈信息也是没有进到插件侧的流同步api的

continue-coding commented 6 months ago

你好,出现raw_stream=0并且传到了插件runtime中是不正常的,能否打开 GLOG_v=10 上传下第一次出现raw_stream=0并传到memcpy中的日志

尝试过开启GLOG_v=10,但是可能是因为这个问题是偶现的,或者是别的原因,开启GLOG时没有遇到coreddump的情况。目前尝试了下注释掉phi::CustomDevice::Finalize和phi::CustomDevice::DeInitDevice,跑了几次没遇到coredump,怀疑可能是设备已经去初始化了导致stream被置空?我再打印下Finalize和DeInitDevice的信息,有进展再同步给您吧

很遗憾,phi::CustomDevice::Finalize和phi::CustomDevice::DeInitDevice发生在raw_stream变为空指针之后,可能也不是这个原因导致的。那为什么raw_stream会被置空就百思不得其解了 image

ronny1996 commented 6 months ago

@continue-coding 你好,请问下,raw_stream为空只有训练完成才出现,还是过程中也会出现?

image

这里好像是过程中也会出现?

continue-coding commented 6 months ago

@continue-coding 你好,请问下,raw_stream为空只有训练完成才出现,还是过程中也会出现?

image

这里好像是过程中也会出现?

目前是只有训练完成时会出现,因为都是以训练100步的场景来触发的,所以这张图里其实是已经在训练快结束的时候了。

ronny1996 commented 6 months ago

你好,推测是这里的同步导致的,通常这里的 pool.Get(place)->Wait(); 中的 stream 不应该为空,你可以尝试下在这里打印出place吗?

image

continue-coding commented 6 months ago

你好,推测是这里的同步导致的,通常这里的 pool.Get(place)->Wait(); 中的 stream 不应该为空,你可以尝试下在这里打印出place吗?

image

确实是挂在了这里,因为是这里的wait调用了流同步。这里的流同步使用的是customcontext里的流,按理来说是不会为空的。我打印下这里的place信息,再同步给您吧

continue-coding commented 6 months ago

你好,推测是这里的同步导致的,通常这里的 pool.Get(place)->Wait(); 中的 stream 不应该为空,你可以尝试下在这里打印出place吗?

image

这是coredump时的place和ctx信息,好像没啥异常的。 image 打印的代码是这样的: image

ronny1996 commented 6 months ago

能在 Paddle/paddle/phi/backends/stream.cc 这个文件里会导致修改 stream_ 值的函数里都打下日志吗?怀疑可能哪个地方修改了

continue-coding commented 6 months ago

能在 Paddle/paddle/phi/backends/stream.cc 这个文件里会导致修改 stream_ 值的函数里都打下日志吗?怀疑可能哪个地方修改了

set_stream这里吗?已经加过了 image 另外,我已经注掉了destroy stream的操作,应该是不会有stream释放的 image 哦,这里还有stream_置空操作,我把destroy都注掉看看 https://github.com/PaddlePaddle/Paddle/blob/13caba9766eb4b07d87273bfc1378908644b39fe/paddle/phi/backends/stream.cc#L111

continue-coding commented 6 months ago

能在 Paddle/paddle/phi/backends/stream.cc 这个文件里会导致修改 stream_ 值的函数里都打下日志吗?怀疑可能哪个地方修改了

raw_stream被置空的原因找到了,虽然我注释掉了destroy stream的操作,但是phi::stream::Stream::Destroy()还有将stream_置为空指针的操作。 https://github.com/PaddlePaddle/Paddle/blob/13caba9766eb4b07d87273bfc1378908644b39fe/paddle/phi/backends/stream.cc#L111 image 那么问题就剩下为什么会去同步一个已被销毁的stream了?不知道把这里的锁提到Destroy前面能不能解决这个问题 https://github.com/PaddlePaddle/Paddle/blob/13caba9766eb4b07d87273bfc1378908644b39fe/paddle/phi/backends/stream.cc#L39

ronny1996 commented 6 months ago

1415574 貌似是主线程,推测是主线程退出了,1415699 上好像还有操作没做完,主线程退出时会调用 Stream::ReleaseAll() 清理所有 stream,怀疑是dataloader

能在加条日志看下吗

Paddle/paddle/fluid/operators/reader/buffered_reader.cc

image

continue-coding commented 6 months ago

1415574 貌似是主线程,推测是主线程退出了,1415699 上好像还有操作没做完,主线程退出时会调用 Stream::ReleaseAll() 清理所有 stream,怀疑是dataloader

能在加条日志看下吗

Paddle/paddle/fluid/operators/reader/buffered_reader.cc

image

确实像您推测的那样,发生coredump的线程还在读数据,而主线程此时已经清理了stream image

continue-coding commented 6 months ago

@ronny1996 大佬,这个问题有什么好的解决方法吗?我之前在SynchronizeStream中增加遇到raw_stream为空即返回的方式只是个权宜之计,有什么从根本上解决问题的方法吗?

ronny1996 commented 6 months ago

@continue-coding 能把Paddle/PaddleCustomDevice的commit以及复现的方法发下吗?我们查一下

你们可以临时解决下,可以在python里手动把dataloader清理掉

continue-coding commented 6 months ago

@continue-coding 能把Paddle/PaddleCustomDevice的commit以及复现的方法发下吗?我们查一下

你们可以临时解决下,可以在python里手动把dataloader清理掉

1.我们是内部基于Paddle自定义硬件接入功能实现的插件,代码还没开源,所以无法提供给您。主框架我们是基于paddle的release/2.6分支做的。 2.复现的方法就是训练ppocr-det 100步会偶现。 https://github.com/PaddlePaddle/PaddleOCR/blob/main/configs/det/ch_ppocr_v2.0/ch_det_res18_db_v2.0.yml 3.手动在python里清理dataloader应该怎么做呢?

continue-coding commented 6 months ago

@continue-coding 能把Paddle/PaddleCustomDevice的commit以及复现的方法发下吗?我们查一下

你们可以临时解决下,可以在python里手动把dataloader清理掉

@ronny1996 能麻烦您评估下我们目前的处理方式,即在SynchronizeStream中增加遇到raw_stream为空即返回的逻辑,可以临时解决这个问题吗?这个方法有没有什么风险?

ronny1996 commented 6 months ago

这样也可以,但是后续的操作可能还会抛错,这和硬件的runtime有关。还有一种方式是finalize被调用时设置一个标志位,插件runtime里接入的api都去判断这个标志位,true就直接返回,不去调用硬件runtime

手动调用直接del dataloader对象,应该能保证python退出时,dataloader线程都结束了

continue-coding commented 6 months ago

这样也可以,但是后续的操作可能还会抛错,这和硬件的runtime有关。还有一种方式是finalize被调用时设置一个标志位,插件runtime里接入的api都去判断这个标志位,true就直接返回,不去调用硬件runtime

手动调用直接del dataloader对象,应该能保证python退出时,dataloader线程都结束了

您说的finalize是这里吗? https://github.com/PaddlePaddle/Paddle/blob/b10062a9d9a6ec7744c42e6eaeabe667fdb6bd44/paddle/phi/backends/custom/custom_device.cc#L96 目前打印的日志看到finalize在memcpyh2d之后,所以不太能确定设置标志位是否可以拦截到。不过我打印finalize的位置是在调用插件的finalize api返回之后,我试下在finalize一开始就设置标志位看看是否可行吧。

手动del dataloader对象,您是指比如在下面这个位置,train返回时手动销毁dataloader吗?如果是的话可能需要修改的模型套件会比较多,可能不是通用的解决方法? https://github.com/PaddlePaddle/PaddleOCR/blob/b5eedf727e96a6efec493ab0574e372c8cc21bf6/tools/program.py#L555

ronny1996 commented 6 months ago

这样也可以,但是后续的操作可能还会抛错,这和硬件的runtime有关。还有一种方式是finalize被调用时设置一个标志位,插件runtime里接入的api都去判断这个标志位,true就直接返回,不去调用硬件runtime 手动调用直接del dataloader对象,应该能保证python退出时,dataloader线程都结束了

您说的finalize是这里吗?

https://github.com/PaddlePaddle/Paddle/blob/b10062a9d9a6ec7744c42e6eaeabe667fdb6bd44/paddle/phi/backends/custom/custom_device.cc#L96

目前打印的日志看到finalize在memcpyh2d之后,所以不太能确定设置标志位是否可以拦截到。不过我打印finalize的位置是在调用插件的finalize api返回之后,我试下在finalize一开始就设置标志位看看是否可行吧。 手动del dataloader对象,您是指比如在下面这个位置,train返回时手动销毁dataloader吗?如果是的话可能需要修改的模型套件会比较多,可能不是通用的解决方法? https://github.com/PaddlePaddle/PaddleOCR/blob/b5eedf727e96a6efec493ab0574e372c8cc21bf6/tools/program.py#L555

你好,finalize确实可能在memcpyh2d之后,可以先按照你们的方法,在sync_stream里判断stream是否为空,如果有其他api报错,应该是相似问题,可能也得修改下。这个问题我们后面会修复。

continue-coding commented 6 months ago

这样也可以,但是后续的操作可能还会抛错,这和硬件的runtime有关。还有一种方式是finalize被调用时设置一个标志位,插件runtime里接入的api都去判断这个标志位,true就直接返回,不去调用硬件runtime 手动调用直接del dataloader对象,应该能保证python退出时,dataloader线程都结束了

您说的finalize是这里吗? https://github.com/PaddlePaddle/Paddle/blob/b10062a9d9a6ec7744c42e6eaeabe667fdb6bd44/paddle/phi/backends/custom/custom_device.cc#L96

目前打印的日志看到finalize在memcpyh2d之后,所以不太能确定设置标志位是否可以拦截到。不过我打印finalize的位置是在调用插件的finalize api返回之后,我试下在finalize一开始就设置标志位看看是否可行吧。 手动del dataloader对象,您是指比如在下面这个位置,train返回时手动销毁dataloader吗?如果是的话可能需要修改的模型套件会比较多,可能不是通用的解决方法? https://github.com/PaddlePaddle/PaddleOCR/blob/b5eedf727e96a6efec493ab0574e372c8cc21bf6/tools/program.py#L555

你好,finalize确实可能在memcpyh2d之后,可以先按照你们的方法,在sync_stream里判断stream是否为空,如果有其他api报错,应该是相似问题,可能也得修改下。这个问题我们后面会修复。

大佬好,我试过在Finalize和DeInitDevice中设置标志位,但是没有奏效。 image image image 目前看dataloader的异步读stream相关的api只调到了流同步,我们也暂时没有遇到其他api上发生coredump的情况,暂时先这么处理吧。辛苦大佬后面帮助修复下,谢谢!