Skip to content
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

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

Open
continue-coding opened this issue Apr 24, 2024 · 24 comments
Labels

Comments

@continue-coding
Copy link

continue-coding commented Apr 24, 2024

请提出你的问题 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->raw_stream();
CheckInitialized();
impl_->SynchronizeStream(dev_id_, stream);
}

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

@continue-coding
Copy link
Author

@ronny1996 辛苦大佬帮忙看一下

@continue-coding
Copy link
Author

continue-coding commented Apr 24, 2024

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

44a47e672196fffb6a43c4142eda074f

@ronny1996
Copy link
Contributor

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

@continue-coding
Copy link
Author

你好,出现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
Copy link
Author

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

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

@continue-coding
Copy link
Author

continue-coding commented Apr 25, 2024

你好,出现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
Copy link
Contributor

ronny1996 commented Apr 25, 2024

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

image

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

@continue-coding
Copy link
Author

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

image

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

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

@ronny1996
Copy link
Contributor

ronny1996 commented Apr 25, 2024

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

image

@continue-coding
Copy link
Author

continue-coding commented Apr 25, 2024

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

image

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

@continue-coding
Copy link
Author

continue-coding commented Apr 25, 2024

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

image

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

@ronny1996
Copy link
Contributor

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

@continue-coding
Copy link
Author

continue-coding commented Apr 25, 2024

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

set_stream这里吗?已经加过了
image
另外,我已经注掉了destroy stream的操作,应该是不会有stream释放的
image
哦,这里还有stream_置空操作,我把destroy都注掉看看

stream_ = nullptr;

@continue-coding
Copy link
Author

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

raw_stream被置空的原因找到了,虽然我注释掉了destroy stream的操作,但是phi::stream::Stream::Destroy()还有将stream_置为空指针的操作。

stream_ = nullptr;

image
那么问题就剩下为什么会去同步一个已被销毁的stream了?不知道把这里的锁提到Destroy前面能不能解决这个问题
std::unique_lock lock(g_streams_mutex);

@ronny1996
Copy link
Contributor

ronny1996 commented Apr 25, 2024

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

能在加条日志看下吗

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

image

@continue-coding
Copy link
Author

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

能在加条日志看下吗

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

image

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

@continue-coding
Copy link
Author

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

@ronny1996
Copy link
Contributor

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

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

@paddle-bot paddle-bot bot added status/following-up 跟进中 and removed status/new-issue 新建 labels Apr 26, 2024
@continue-coding
Copy link
Author

continue-coding commented Apr 26, 2024

@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
Copy link
Author

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

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

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

@ronny1996
Copy link
Contributor

ronny1996 commented Apr 26, 2024

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

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

@continue-coding
Copy link
Author

continue-coding commented Apr 26, 2024

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

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

您说的finalize是这里吗?

void Finalize() override {

目前打印的日志看到finalize在memcpyh2d之后,所以不太能确定设置标志位是否可以拦截到。不过我打印finalize的位置是在调用插件的finalize api返回之后,我试下在finalize一开始就设置标志位看看是否可行吧。

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

@ronny1996
Copy link
Contributor

ronny1996 commented Apr 28, 2024

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

您说的finalize是这里吗?

void Finalize() override {

目前打印的日志看到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
Copy link
Author

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

您说的finalize是这里吗?

void Finalize() override {

目前打印的日志看到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的情况,暂时先这么处理吧。辛苦大佬后面帮助修复下,谢谢!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants