GPU memory consumption control during inference process

OronG13: <p>Hello,</p>
<p>I have a laptop with:<br>
OS Name - Microsoft Windows 10 Enterprise 2016 LTSB<br>
Processor - Intel® Core™ i7-6700HQ CPU @ 2.60GHz, 2592 Mhz, 4 Core(s), 8 Logical Processor(s)<br>
|Installed Physical Memory (RAM)|16.0 GB|<br>
|Total Physical Memory|15.8 GB|<br>
|Available Physical Memory|8.92 GB|<br>
|Total Virtual Memory|18.3 GB|<br>
|Available Virtual Memory|8.75 GB|<br>
|Page File Space|2.53 GB|</p>
<p>Adapter Description	Intel® HD Graphics 530<br>
Adapter Description	NVIDIA Quadro M2000M</p>
<p>The NVIDIA GPU has only 4GB RAM</p>
<p>I have a model which successfully inferenced by the PyTorch C++ interface using the torch::jit::script::Module.</p>
<p>When the application  runs as a stand alone process in the system everything is working fine but when I add an additional CUDA based applications which consume also parts of the GPU memory, the following operation fails:</p>
<pre><code class="lang-auto">std::shared_ptr&lt;torch::jit::script::Module&gt; module;
output = module-&gt;forward(inputs).toTensor();
CUDA(cudaDeviceSynchronize());
</code></pre>
<p>I assume that the failure root cause is unavailable GPU memory.</p>
<p>Questions:</p>
<ul>
<li>
<p>Is there any way to control this dynamic failure?</p>
</li>
<li>
<p>Is there a way to statically know how much GPU memory will be required for inference operation based on the model?</p>
</li>
<li>
<p>Is there a way to tell the PyTorch to use the CPU memory in case no more available GPU memory exist?<br>
As the Tensorflow C++ interface provide:<br>
For example:</p>
</li>
</ul>
<pre><code class="lang-auto">tensorflow::SessionOptions sessionOptions;
m_sessionOptions.config.mutable_gpu_options()-&gt;set_allow_growth(true);
</code></pre>
<p>Or:</p>
<pre><code class="lang-auto">sessionOptions.config.mutable_gpu_options()-&gt;set_per_process_gpu_memory_fraction(0.8);
</code></pre>
<p>Thanks,</p>

ptrblck: <p>You could wrap your code in a <code>try..except</code> block and push all operations to the CPU in case an OOM error is raised.<br>
Besides that I’m not aware of any API calls to limit the GPU memory.</p>

OronG13: <p>Thank you.<br>
I have two questions:</p>
<ol>
<li>
<p>Is there any query provided by the PyTorch C++ APIs interface to check ahead how much device memory will be consumed by the model nodes and their weights?<br>
By this way I will be able to decide ahead on which device I will inference the model.</p>
</li>
<li>
<p>Is there a PyTorch C++ APIs which enable to split the model nodes between multiple devices? for example split the model between the CPU &amp; GPU…<br>
I readed here:<br>
<a href="https://pytorch.org/cppdocs/" rel="nofollow noopener">PyTorch C++ Opcodes</a><br>
that this interface provide the ability to use multiple GPUs.<br>
So I thought that maybe it can be processed also by the CPU while part of it processed by the GPU.</p>
</li>
</ol>

ptrblck: <ol>
<li>
<p>No, since memory fragmentation and in particular different backends might not yield deterministic results and might also differ based on the device, versions, as well as available memory.<br>
E.g. if you are using <code>torch.backends.cudnn.benchmark = True</code>, cudnn will profile different kernels for your current workload and will select the fastest one, which would fit in the available workspace. This would also mean that different algorithms (and thus memory footprints) might be used, if your device is almost full. The best way would be to use the deterministic mode and run a single training iteration in order to estimate the memory usage.</p>
</li>
<li>
<p>Since the <code>to()</code> operation is differentiable you can easily apply model sharding via:</p>
</li>
</ol>
<pre><code class="lang-python">class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.module0 = nn.Linear(1, 1).to('cuda:0')
        self.module1 = nn.Linear(1, 1).to('cuda:1')
        self.module2 = nn.Linear(1, 1).cu('cpu') # to('cpu') not necessary here
        
    def forward(self, x):
        x = x.to('cuda:0')
        x = self.module0(x)
        x = x.to('cuda:1')
        x = self.module1(x)
        x = x.to('cpu')
        x = self.module2(x)
        return x
</code></pre>