Loss explodes in validation. Takes a few training steps to recover. Only when using DistributedDataParallel

Milas: <p>When I train my network on multiple machines (using DistributedDataParallel) I observe my loss exploding when I switch my network to evaluation using <code>model.eval()</code> and <code>torch.no_grad()</code>.</p>
<p>When outside the <code>torch.no_grad()</code> context, I switch to <code>model.train()</code> and observe a loss that is way worse than what I was observing at the end of the epoch.</p>
<p>This only happens when using DistributedDataParallel.</p>
<p><strong>Training</strong><br>
<img src="https://discuss.pytorch.org/uploads/default/original/2X/8/8d504778f024d56dde5ad32ee8c3b9cd80929a54.png" alt="train" data-base62-sha1="ka7jSMrJ2lvQ9QiKYWbgoDdjWp6" width="640" height="480"></p>
<p>the spikes appear at the beginning of the epoch, just after the validation step. The loss at that moment is close to what I observe in validation. Has anyone an idea about what could be causing that ?</p>
<p>Thanks</p>

pietern: <p>Hi Milas! This looks very odd. Is it possible that you’re seeing some data contamination between your training and validation datasets? The fact that you run with <code>no_grad</code> during evaluation mode, as well as setting <code>model.eval()</code> all sounds perfectly normal.</p>
<p>You say that if you run without <code>DistributedDataParallel</code> you don’t observe this issue? Does this happen for any number of processes &gt; 1?</p>

Milas: <p>Hi Pieter.</p>
<p>Thanks for your answer. This looks odd indeed. There is no contamination between the sets. Everything is in separated folders and the dataset class only gets the right folder.</p>
<p>I did try many things.<br>
Even when I set to train, never switch to eval and only do training steps without ever going to the validation dataset this still occurs at the beginning of the epochs. I tried with either <code>torch.utils.data.distributed.DistributedSampler</code> or simply with random sampling without splitting the dataset between machines.</p>
<ul>
<li>I am using filesystem synchronisation.</li>
<li>My code is fairly based on the imagenet example.</li>
<li>I am using batchnorm and have seen that it does not get properly synchronized but it should not be a problem I guess since the distribution of my samples should stay the same.</li>
<li>I do have some custom initialisations in the __init__ of my modules.</li>
</ul>
<p>If I find something I will keep this thread updated.</p>

pietern: <p>I was looking at <code>def train</code> in <code>DistributedDataParallel</code>, more specifically <a href="https://github.com/pytorch/pytorch/blob/bb15580e88cc963df22eab291fbe182f42426ad5/torch/nn/parallel/distributed.py#L370-L374" rel="nofollow noopener">these lines</a> and am worried the slicing of replicas may be causing this. Looking at <code>def train</code> in <code>nn.Module</code> I don’t see how the train mode would be set on <code>self.module</code>. Can you try removing the slicing to ensure the train mode is set on every module replica (even if there aren’t any) and see what happens? Since the train mode controls whether or not you’re accumulating running stats in batch norm layers, this could explain both the regression <strong>and</strong> the recovery after a couple of iterations.</p>

pietern: <p>Please disregard my previous suggestion – this is working as intended.</p>

Milas: <p>Yes that was also my thought at first but the <code>super(DistributedDataParallel, self).train(mode)</code> should take care of <code>model_copies[0]</code></p>

pietern: <p><a class="mention" href="/u/milas">@Milas</a> There was a bug in DDP that didn’t take into account evaluation mode, but this was only introduced in <a href="https://github.com/pytorch/pytorch/pull/18953" rel="nofollow noopener">https://github.com/pytorch/pytorch/pull/18953</a> which was merged 2 weeks ago. Not 25 days ago when you first started noticing this issue. This issue was fixed last night with <a href="https://github.com/pytorch/pytorch/pull/19897" rel="nofollow noopener">https://github.com/pytorch/pytorch/pull/19897</a> and <a href="https://github.com/pytorch/pytorch/pull/19901" rel="nofollow noopener">https://github.com/pytorch/pytorch/pull/19901</a>.</p>
<p>Can you confirm this this still an issue with the current nightly build?</p>

Milas: <p><a class="mention" href="/u/pietern">@pietern</a> unfortunately it still occurs with the nightly. Tried to remove as many custom things as I could but it still happens. Perhaps having non homogeneous  gpus is the issue ? Since I don’t control the machines the job is spawned on I usually end up on a mix of kepler and pascal architectures.</p>
<p>The train / eval modes probably have nothing to do with this since I tried switching to train at the very beginning and never switching again and it still occurs. Currently I am not using DDP but if I find where it goes wrong I will definitely update.</p>

pietern: <p><a class="mention" href="/u/milas">@Milas</a> Thank you for the update. I doubt that heterogeneous GPUs is the issue… I would expect the numerical behavior to be largely identical across architectures. If you also mix library versions (e.g. cuDNN) this is more likely though.</p>
<p>Any more information sure is appreciated!</p>

Milas: <p>The bug was on my side. The order of the data loaded was not deterministic. The problem disappeared once I fixed that.</p>
<p>Thanks for your time and for the help.</p>

laurentdillard: <p>Hello <a class="mention" href="/u/milas">@Milas</a>, I’m running into the same kind of issues. Could you elaborate as to why the order of the data loaded has to be deterministic ?</p>

Milas: <p><code>torch.utils.data.distributed.DistributedSampler</code> uses the epoch number as a seed to deterministically randomize the data to create coherent bigger batches (i.e. a different subsample of the dataset is seen each time through training and the samples do not overlap between the different training instances.)</p>
<p>Also do you scale the learning rate according to the number of instances you spawn ?</p>

laurentdillard: <p>Thank you very much for your help. Turns out for me the problem was in my custom loss function.</p>