Gradients differ across GPUs with DDP

username: xannex
cooked: <p>Hi,</p>
<p>What are possible reasons for gradients to differ across GPUs after a backward() call, when using DistributedDataParallel (DDP)? If I understood correctly wrapping the model with DDP in the main worker should take care of gradient averaging and their synchronization across GPUs?</p>
<p>Thank you</p>

username: wayi
cooked: <p>If you enable no_sync context manger, you will turn off the communication that averages gradients.</p>
<p>See: <a href="https://pytorch.org/docs/stable/generated/torch.nn.parallel.DistributedDataParallel.html#torch.nn.parallel.DistributedDataParallel.no_sync" class="inline-onebox" rel="noopener nofollow ugc">DistributedDataParallel — PyTorch 1.8.1 documentation</a></p>

username: xannex
cooked: <p>Sorry, maybe I was unclear. My problem is that the gradients are not averaging across GPUs and am searching for possible reasons as to why this is happening.</p>

username: wayi
cooked: <p>Are all the GPUs on the same machine, and they have the same type?</p>
<p>Do you mean the gradients are not sync even in the end of training?</p>
<p>Can you share some code to reproduce this?</p>

username: xannex
cooked: <p>Yes, the GPUs are on the same computing node, all GPUs are of the same type. What I mean by not synced is if I call tensor.data.grad after the backward call in the main worker they differ on each GPU. Which essentially means each GPUs are training separately but not really syncing the gradients at any point.</p>
<p>I’m afraid I cannot post the entire code and a minimal example might not capture the problem. That’s why I’m only asking for possible reasons in general.</p>

username: xannex
cooked: <p>My model does not contain a forward method, can this confuse the DDP?</p>

username: wayi
cooked: <blockquote>
<p>My model does not contain a forward method, can this confuse the DDP?</p>
</blockquote>
<p>I am confused. Do you mean that you have only used built-in layers such as:</p>
<pre><code class="lang-auto">           model = nn.Sequential(
                nn.Conv2d(i, j, k),
                nn.ReLU(),
                ....
            )
</code></pre>
<p>If so, you still called <code>forward</code> of each module implicitly, and the allreduce during backward pass should be triggered.</p>
<p>Without investigating the source code, I cannot find any other reason of unsynced gradients, if you didn’t use no_sync context manager. You should verify if allreduce is ever been invoked. A few ideas:</p>
<ol>
<li>You can try torch.profiler and check if there is any allreduce operator in your GPU traces.</li>
<li>Alternatively, you can try registering a <a href="https://pytorch.org/docs/master/ddp_comm_hooks.html#torch.distributed.algorithms.ddp_comm_hooks.powerSGD_hook.powerSGD_hook" rel="noopener nofollow ugc">PowerSGD DDP comm hook</a>, and check if there is any log about PowerSGD stats.</li>
<li>Not sure if you use the slower <code>DataParallel</code> instead of <code>DistributedDataParallel</code> can bypass this.</li>
</ol>

username: xannex
cooked: <p>I used a function outside of the model class to perform the forward pass. It seems DDP requires a forward method nested in the nn.Module, which is not stated in the documentation.</p>
<p>After changing my code to include the forward method the error is the following:</p>
<p><code>RuntimeError: Expected to mark a variable ready only once. This error is caused by one of the following reasons: 1) Use of a module parameter outside the 'forward' function. Please make sure model parameters are not shared across multiple concurrent forward-backward passes2) Reused parameters in multiple reentrant backward passes. For example, if you use multiple 'checkpoint' functions to wrap the same part of your model, it would result in the same set of parameters been used by different reentrant backward passes multiple times, and hence marking a variable ready multiple times. DDP does not support such use cases yet.</code></p>
<p>The code works fine on a single GPU. So I do not quite understand the error, since backpropagating twice through the same graph without <code>retain_graph=True</code> would not be possible anyway.</p>