When should I call `dist.destory_process_group()`?

Fei_Liu: <p>Or should I never call it if I’m not re-init another process group?</p>
<p>Context: I found some peculiar bug when doing multi-node GPU training: “early exits of remote peer”. It can be reproduced with the following code (at least with my distributed GPU cluster):</p>
<pre><code class="lang-auto">dist.init_process_group('nccl', rank=rank, world_size=world_size)
# do the work
# dadadadadadada
# done

if rank == 0:
    time.sleep(30)  # the key trick: let rank 0 lag behind so it exits later than the others

dist.destory_process_group()
</code></pre>
<p>With this, I noticed that, sometimes (non deterministic, but fairly high probability, ~3 out of 10), rank 0 will complain “early exits of remote peers” when the program finishes. And this happens only if I run on two or more machines (nodes). If it’s single-node-multi-process then it’ll be fine (100% fine, never ran into issues). It seems to me that the <code>dist.destory_process_group()</code> doesn’t do any sync across nodes and just blatantly exit the process (that’s how I “feel” based on my observation of the crushes and error messages, not necessarily how it actually is), or at least the sync is not persistent so the issue sometimes occurs. Now if I do a sync before the destory:</p>
<pre><code class="lang-auto">dist.barrier()
dist.destroy_process_group()
</code></pre>
<p>The issue will be alleviated, but still happens (say 1 out of 50).</p>
<p>What’s more interesting: if I just remove the <code>dist.destory_process_group()</code>, then everything seems fine (or at least a lot better). Now all the nodes will be waiting on the sleep from rank 0 and exit together. But even in this case, the issue still happens occasionally. My current solution is to just do a <code>dist.barrier()</code> at the end of the program, which looks nasty, let alone that I’m 0% sure whether that’s the right thing to do.</p>
<p>Questions:</p>
<ol>
<li>Does the above indicate that I have some misunderstanding of the dist init/destroy api? Or something is off with my distributed cluster environment (e.g. torch is not properly installed? some network issue causing the “early exit of remote peers” ?)</li>
<li>Does <code>dist.destory_process_group()</code> do any sync? I suppose it should, ideally? (So any barrier() call is redundant?)</li>
<li>What’s the “right” way to init/destroy the process group properly, especially in a multi-node training environment?</li>
</ol>

zuliani99: <p>Do you have completely solved this issue? I’m asking this since I’m also encountering this behaviour that you describe.</p>

H-Huang: <p>I’m not sure where the “early exits of remote peers” message is coming from so a stack trace would be helpful, but here are the question answers:</p>
<ol>
<li>The understanding is in general correct. <code>init_process_group</code> is a synchronization point for the program. In general, destroy does not actually need to be called if the program is going to shutdown anyways.</li>
<li>It does not do a sync. destroy_process_group() will clear the state of the process group for that particular process. The distributed package holds state and information about that process group. If the program is exitting anyways, then the garbage collector does that for you</li>
<li>You really only need <code>init_process_group</code>, <code>barrier()</code> at the end will be helpful for debug if there is some desync issue, but if the program is implemented correctly (all collectives have their other corresponding collectives across other ranks) then there shouldn’t be a problem</li>
</ol>

Fei_Liu: <p>I just no longer use <code>destroy_process_group</code> anymore. This worked well in my use case.</p>

zuliani99: <aside class="quote no-group" data-username="Fei_Liu" data-post="1" data-topic="174299">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/fei_liu/48/36219_2.png" class="avatar"> Fei_Liu:</div>
<blockquote>
<p>My current solution is to just do a <code>dist.barrier()</code> at the end of the program, which looks nasty, let alone that I’m 0% sure whether that’s the right thing to do.</p>
</blockquote>
</aside>
<p><a class="mention" href="/u/fei_liu">@Fei_Liu</a> So you removed the <code>dist.destory_process_group()</code> at the end of the function and instead of it you placed a <code>dist.barrier()</code>, correct?</p>

Fei_Liu: <p>I didn’t even bother using <code>dist.barrier()</code> in this case. For my case the <code>distroy_process_group</code> (if any) runs at the end of the program. So in theory it’s not needed as <a class="mention" href="/u/h-huang">@H-Huang</a> mentioned: the GC will take care of everything when the process exits. It’s more of a neat reason to use <code>distroy_process_group</code>. Now I just let go and stop being too neat essentially.</p>

zuliani99: <p>Ok but <code>destroy_process_group</code> has to be called each time the computation of each process ends or only one time on the main function before actually terminating the entire application?</p>