DataParallel memory consumption in PyTorch 0.4

rbeaudoin: <p>I believe I’m seeing a certain loss of functionality after upgrading from PyTorch 0.3.1 to 0.4.0.  Specifically I’m trying to use nn.DataParallel to train, on two GPU’s, a model with a parameter that takes up over half the memory of either GPU.  When the DataParallel library code attempts to replicate the model over both GPU’s it broadcasts the parameters to both, and runs out of GPU memory during the broadcast operation.  In 0.3.1 the broadcast operation was implemented in Python, and contained logic to avoid copy operations for which the source and target devices are the same; in 0.4 this operation has been moved into C code, and I’m guessing that this optimization was removed in the process.</p>
<p>If any of the PyTorch developers is reading this, can you verify that my guess is correct?  More important, can anyone suggest a work-around for this problem?  Am I missing some way to use DataParallel (either the module or the functional form) that will work to replicate a model on multiple GPU’s even when it has a parameter that takes up over half the memory of any single GPU?  (As I write this it occurs to me that someone may object that the gradients for such a model won’t fit in GPU memory anyhow, but that isn’t so, thanks to the wonderful feature of sparse tensor gradients.)</p>
<p>Thanks in advance for any light you can shed.</p>

SimonW: <p>Your are right in that there is a regression when we broadcast the tensors and I’m working on a fix. However, it should really only happen when you don’t have NCCL, and we ship with NCCL1 in binary… So I am a bit confused. Could you tell me how you installed PyTorch?</p>

inkplay: <p>Hi simonW I am not the original poster but I have downloaded windows 10 official build from the main site and would run into warnings on how my pytorch was not build from NCCL. I don’t have screen caps but I am positive I have ran into this problem. I haven’t touched pytorch in a while but I am fairly positive. Original version I had was from peterjc’s, once I saw official windows support I have since upgraded to 4.0.</p>

SimonW: <p>Thanks for the report. <a class="mention" href="/u/peterjc123">@peterjc123</a> Was our Windows binary built with NCCL1/2?</p>

rbeaudoin: <p>Thanks for such a quick response!  I used pip to install PyTorch into a python3 virtual environment (as I recall, nothing fancier than “pip install torch”).  If it matters, this is with Python 3.6.5 on Ubuntu 16.04; let me know if you need more details.  Should I be trying to install NCCL myself?</p>

SimonW: <p>It should be included. Could you try running</p>
<pre><code class="lang-auto">print(torch.cuda.nccl.is_available(torch.randn(1).cuda()))
print(torch.cuda.nccl.version())
</code></pre>
<p>and tell me the output?</p>
<p><a class="mention" href="/u/inkplay">@inkplay</a> it’d be great if you can run the above lines as well!</p>

rbeaudoin: <p>Here you go:</p>
<p>Python 3.6.5 (default, May 16 2018, 13:09:02)<br>
Type ‘copyright’, ‘credits’ or ‘license’ for more information<br>
IPython 6.4.0 – An enhanced Interactive Python. Type ‘?’ for help.</p>
<p>In [1]: import torch</p>
<p>In [2]: print(torch.cuda.nccl.is_available(torch.randn(1).cuda()))<br>
True</p>
<p>In [3]: print(torch.cuda.nccl.version())<br>
2115</p>

SimonW: <p>Can you also do this for me?</p>
<pre><code class="lang-auto">x = torch.randn(3).cuda()
ys = torch.cuda.comm.broadcast(x, [0, 1])  # assuming you have &gt;= 2 GPUs
print(x.storage().data_ptr())
print(ys[0].storage().data_ptr())
</code></pre>

rbeaudoin: <aside class="quote no-group" data-username="SimonW" data-post="8" data-topic="19302">
<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/simonw/48/1702_2.png" class="avatar"> SimonW:</div>
<blockquote>
<p>x = torch.randn(3).cuda() ys = torch.cuda.comm.broadcast(x, [0, 1]) # assuming you have &gt;= 2 GPUs print(x.storage().data_ptr()) print(ys[0].storage().data_ptr())</p>
</blockquote>
</aside>
<p>Python 3.6.5 (default, May 16 2018, 13:09:02)<br>
Type ‘copyright’, ‘credits’ or ‘license’ for more information<br>
IPython 6.4.0 – An enhanced Interactive Python. Type ‘?’ for help.</p>
<p>In [1]: import torch</p>
<p>In [2]: x = torch.randn(3).cuda()<br>
…: ys = torch.cuda.comm.broadcast(x, [0, 1])  # assuming you have &gt;= 2 GPUs<br>
…: print(x.storage().data_ptr())<br>
…: print(ys[0].storage().data_ptr())<br>
…:<br>
…:<br>
139669103522816<br>
139669103522816</p>
<p>In [3]:</p>

SimonW: <p>Hmm then it is really weird because <code>broadcast</code> on dense contiguous tensors don’t copy on the current device as expected. Could you double check that OOM happens when broadcasting the modules?, i.e. in <code>replicate(...) of replicate.py</code></p>

rbeaudoin: <p>I’m working on that now.  (It takes a bit to run my code; the model is big.)  I can tell you from memory that the offending call is to broadcast_coalesced, and that it happens inside Broadcast.forward, which is called (via Broadcast.apply) from replicate.  It might take me 'till tomorrow to get you a call stack.</p>

peterjc123: <p>Hi. The Windows version is built without NCCL. And neither is it ready for Windows.</p>

inkplay: <aside class="quote no-group" data-username="SimonW" data-post="6" data-topic="19302">
<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/simonw/48/1702_2.png" class="avatar"> SimonW:</div>
<blockquote>
<p>print(torch.cuda.nccl.is_available(torch.randn(1).cuda())) print(torch.cuda.nccl.version())</p>
</blockquote>
</aside>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discuss.pytorch.org/uploads/default/original/2X/a/a08c747dc0aa097467b07ae7f5907957a80f1893.png" data-download-href="https://discuss.pytorch.org/uploads/default/a08c747dc0aa097467b07ae7f5907957a80f1893" title="Screenshot_41"><img src="https://discuss.pytorch.org/uploads/default/optimized/2X/a/a08c747dc0aa097467b07ae7f5907957a80f1893_2_690x383.png" alt="Screenshot_41" data-base62-sha1="mUhjLwDGKU5mp1tMTsBETmTlvlV" width="690" height="383" srcset="https://discuss.pytorch.org/uploads/default/optimized/2X/a/a08c747dc0aa097467b07ae7f5907957a80f1893_2_690x383.png, https://discuss.pytorch.org/uploads/default/optimized/2X/a/a08c747dc0aa097467b07ae7f5907957a80f1893_2_1035x574.png 1.5x, https://discuss.pytorch.org/uploads/default/original/2X/a/a08c747dc0aa097467b07ae7f5907957a80f1893.png 2x" data-dominant-color="F6F4F3"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Screenshot_41</span><span class="informations">1288×715 39.1 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<pre data-code-wrap="False"><code class="lang-False">C:\Anaconda3\lib\site-packages\torch\cuda\nccl.py:24: UserWarning: PyTorch is not compiled with NCCL support
  warnings.warn('PyTorch is not compiled with NCCL support')
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
&lt;ipython-input-1-fdbbe4fefe36&gt; in &lt;module&gt;()
      1 import torch
      2 print(torch.cuda.nccl.is_available(torch.randn(1).cuda()))
----&gt; 3 print(torch.cuda.nccl.version())

C:\Anaconda3\lib\site-packages\torch\cuda\nccl.py in version()
     29 
     30 def version():
---&gt; 31     return torch._C._nccl_version()
     32 
     33 

AttributeError: module 'torch._C' has no attribute '_nccl_version'```

Installed pytorch using conda method,  from the main site Windows -&gt; conda -&gt; 3.6 -&gt; cuda 8.0

Edited: Just read the replies from peterjc and OP, it makes sense now.</code></pre>

rbeaudoin: <p>OK, sorry about the delay, but here is a traceback showing the memory failure:</p>
<p>THCudaCheck FAIL file=/pytorch/aten/src/THC/generic/THCStorage.cu line=58 error=2 : out of memory<br>
Traceback (most recent call last):<br>
File “semantic_matching.py”, line 849, in <br>
train_model(model, dataset)<br>
File “semantic_matching.py”, line 766, in train_model<br>
pos_energy = parallel_model(*pos_batch)<br>
File “/iscsi/rdata/rbeaudoin/projects/sme-memory/lib/python3.6/site-packages/torch/nn/modules/module.py”, line 491, in <strong>call</strong><br>
result = self.forward(*input, **kwargs)<br>
File “/iscsi/rdata/rbeaudoin/projects/sme-memory/lib/python3.6/site-packages/torch/nn/parallel/data_parallel.py”, line 113, in forward<br>
replicas = self.replicate(self.module, self.device_ids[:len(inputs)])<br>
File “/iscsi/rdata/rbeaudoin/projects/sme-memory/lib/python3.6/site-packages/torch/nn/parallel/data_parallel.py”, line 118, in replicate<br>
return replicate(module, device_ids)<br>
File “/iscsi/rdata/rbeaudoin/projects/sme-memory/lib/python3.6/site-packages/torch/nn/parallel/replicate.py”, line 12, in replicate<br>
param_copies = Broadcast.apply(devices, *params)<br>
File “/iscsi/rdata/rbeaudoin/projects/sme-memory/lib/python3.6/site-packages/torch/nn/parallel/_functions.py”, line 17, in forward<br>
outputs = comm.broadcast_coalesced(inputs, ctx.target_gpus)<br>
File “/iscsi/rdata/rbeaudoin/projects/sme-memory/lib/python3.6/site-packages/torch/cuda/comm.py”, line 40, in broadcast_coalesced<br>
return torch._C._broadcast_coalesced(tensors, devices, buffer_size)<br>
RuntimeError: cuda runtime error (2) : out of memory at /pytorch/aten/src/THC/generic/THCStorage.cu:58</p>
<p>I hope this helps.</p>

rbeaudoin: <p>Additional information (if it helps):  I tried repeating the experiment SimonW suggested, but with a larger tensor to broadcast.  Up to a tensor of size roughly 10GB the broadcast succeeds, but at 11GB there is an out of memory error (and I’m working with two GPUs each having just over 12GB of memory):</p>
<p>In [1]: import torch</p>
<p>In [2]: x = torch.randn(int(2.75e9)).cuda()</p>
<h2>In [3]: ys = torch.cuda.comm.broadcast(x, [0, 1])<br>
THCudaCheck FAIL file=/pytorch/aten/src/THC/generic/THCStorage.cu line=58 error=2 : out of memory</h2>
<p>RuntimeError                              Traceback (most recent call last)<br>
 in ()<br>
----&gt; 1 ys = torch.cuda.comm.broadcast(x, [0, 1])</p>
<p>/iscsi/rdata/rbeaudoin/projects/sme-memory/lib/python3.6/site-packages/torch/cuda/comm.py in broadcast(tensor, devices)<br>
19         corresponding to indices from <code>devices</code>.<br>
20     “”"<br>
—&gt; 21     return torch._C._broadcast(tensor, devices)<br>
22<br>
23</p>
<p>RuntimeError: cuda runtime error (2) : out of memory at /pytorch/aten/src/THC/generic/THCStorage.cu:58</p>
<p>So, is there just an overhead of about 1GB of temporary storage that gets allocated on the source GPU of the broadcast?  If so, is there any way to reduce it?</p>
<p>OTOH, it occurs to me to add that the total size of all the parameters of the actual model I’m using is only about 7GB, so evidently there is additional overhead when replicating that model as opposed to just broadcasting a single tensor as in this experiment.  I wish I knew what to make of that.</p>
<p>OK, a little more data:  Consider the difference between</p>
<p>In [1]: import torch</p>
<p>In [2]: inputs = [torch.nn.Parameter(torch.zeros(1).cuda()), torch.nn.Parameter(torch.zeros(int(2e9)).cuda())]</p>
<h2>In [3]: outputs = torch.cuda.comm.broadcast_coalesced(inputs, [0, 1])<br>
THCudaCheck FAIL file=/pytorch/aten/src/THC/generic/THCStorage.cu line=58 error=2 : out of memory</h2>
<p>RuntimeError                              Traceback (most recent call last)<br>
 in ()<br>
----&gt; 1 outputs = torch.cuda.comm.broadcast_coalesced(inputs, [0, 1])</p>
<p>/iscsi/rdata/rbeaudoin/projects/sme-memory/lib/python3.6/site-packages/torch/cuda/comm.py in broadcast_coalesced(tensors, dev<br>
ices, buffer_size)<br>
38         corresponding to indices from <code>devices</code>.<br>
39     “”"<br>
—&gt; 40     return torch._C._broadcast_coalesced(tensors, devices, buffer_size)<br>
41<br>
42</p>
<p>RuntimeError: cuda runtime error (2) : out of memory at /pytorch/aten/src/THC/generic/THCStorage.cu:58</p>
<p>and</p>
<p>In [1]: import torch</p>
<p>In [2]: inputs = [torch.nn.Parameter(torch.zeros(int(2e9)).cuda())]</p>
<p>In [3]: outputs = torch.cuda.comm.broadcast_coalesced(inputs, [0, 1])</p>
<p>In [4]:</p>
<p>(No error in the second case.)  I also tried two bare tensors rather than Parameters, and in that case there is no out of memory error either.  Note that the total size of a vector of float32’s of length 2e9 is about 8GB.  So it looks like something causes quite a bit of memory overhead when broadcast_coalesced is fed a list of more than one Parameter, for whatever that is worth.</p>

mrdrozdov: <p>Any update here? I’m seeing a similar problem on Linux with pytorch installed through conda and CUDA 9.</p>

mrdrozdov: <p><a class="mention" href="/u/simonw">@SimonW</a> I ran these lines as you suggested:</p>
<pre><code class="lang-auto">x = torch.randn(3).cuda()
ys = torch.cuda.comm.broadcast(x, [0, 1])  # assuming you have &gt;= 2 GPUs
print(x.storage().data_ptr())
print(ys[0].storage().data_ptr())
</code></pre>
<p>And had similar output, indicating that a copy isn’t happening during the broadcast. Is this a problem?</p>

SimonW: <p>Hi,<br>
I tried to reproduce according to your instruction, but couldn’t get OOM with the following code</p>
<pre><code class="lang-auto">MB = 1024*1024
N = int(10.5 * MB * 1024 / 4)

x = torch.zeros(N, device='cuda')
x1 = torch.zeros(1, device='cuda')
x = nn.Parameter(x)
x1 = nn.Parameter(x1)
ts = torch.cuda.comm.broadcast_coalesced([x, x1], [0, 1])
</code></pre>
<p>This is on 2 GPUs each with 12G memory. OOM triggers if I broadcast a 11GB tensor, but not when I broadcast a 10.5GB tensor and a 4 byte tensor.</p>
<p>There are two possibilities: 1. the bug was somehow fixed because I am using a master build. 2. the bug only happens with NCCL 1 because I’m using NCCL 2.</p>
<p>I’ll find a NCCL 1 build on Monday and see if I can reproduce.</p>

SimonW: <p>Not copying is the expected behavior. What’s your pytorch version and NCCL version (obtained via <code>torch.cuda.nccl.version()</code>)?</p>

SimonW: <p>I found NCCL1 has a lower tensor size limit, and prepared this patch <a href="https://github.com/pytorch/pytorch/pull/11466">https://github.com/pytorch/pytorch/pull/11466</a> . However, this can really only happen if you have <strong>a</strong> huge tensor (not some tensors that adds up to be huge). Is this the case with your code?</p>