FP16 (AMP) training slow down with PyTorch 1.6.0

Liyang_Lu: <p>Hi,</p>
<p>I’m experiencing strange slow training speed with PyTorch 1.6.0+AMP.</p>
<p>I built 2 docker images, and the only difference between them is one have torch 1.5.0+cu101 and the other have torch 1.6.0+cu101. On these two docker images, I ran same code (Huggingface xlmr-base model for token classification) on same hardware (P40 GPU), with no distributed data parallel or gradient accumulation. The table below summarizes the training speed I got:</p>
<div class="md-table">
<table>
<thead>
<tr>
<th>samples/s</th>
<th>PyTorch 1.5.0</th>
<th>PyTorch 1.6.0</th>
<th>diff</th>
</tr>
</thead>
<tbody>
<tr>
<td>FP32</td>
<td>51.97</td>
<td>51.57</td>
<td>-0.4</td>
</tr>
<tr>
<td>FP16 with apex.amp O1</td>
<td>51.25</td>
<td>47.43</td>
<td>-3.82</td>
</tr>
<tr>
<td>FP16 with apex.amp O2</td>
<td>56.68</td>
<td>49.09</td>
<td>-7.59</td>
</tr>
<tr>
<td>FP16 with torch.cuda.amp</td>
<td>N/A</td>
<td>47.17</td>
<td>N/A</td>
</tr>
</tbody>
</table>
</div><p>Comparing between 1.5.0 and 1.6.0, the FP32 speeds are close, but the speeds with APEX AMP O1 and O2 are both significantly slower with 1.6.0. PyTorch 1.6.0 native AMP is also much slower compared to 1.5.0+apex.amp. All 3 FP16 AMP configurations with 1.6.0 are slower than FP32.</p>
<p>Again, only difference is the PyTorch version in the docker images. Other things common in the images are: cuda 10.1, cudnn 7.6.5.32-1+cuda10.1, python 3.6.8,</p>
<p>Do you have any suggestions on this problem?</p>
<p>Edit:</p>
<p>Upon further investigation, I tried to build a docker imaged by extending <code>pytorch/pytorch:1.6.0-cuda10.1-cudnn7-devel</code>, and do same experiments on it. Now I get comparable results on 1.6.0 in it:</p>
<div class="md-table">
<table>
<thead>
<tr>
<th>samples/s</th>
<th style="text-align:center">docker image A</th>
<th style="text-align:center"></th>
<th style="text-align:center">docker image B</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td style="text-align:center">PyTorch 1.5.0</td>
<td style="text-align:center">PyTorch 1.6.0</td>
<td style="text-align:center">PyTorch 1.6.0</td>
</tr>
<tr>
<td>FP32</td>
<td style="text-align:center">51.97</td>
<td style="text-align:center">51.57</td>
<td style="text-align:center">51.44</td>
</tr>
<tr>
<td>FP16 with apex.amp O1</td>
<td style="text-align:center">51.25</td>
<td style="text-align:center">47.43</td>
<td style="text-align:center">50.57</td>
</tr>
<tr>
<td>FP16 with apex.amp O2</td>
<td style="text-align:center">56.68</td>
<td style="text-align:center">49.09</td>
<td style="text-align:center">55.98</td>
</tr>
<tr>
<td>FP16 with torch.cuda.amp</td>
<td style="text-align:center">N/A</td>
<td style="text-align:center">47.17</td>
<td style="text-align:center">48.26</td>
</tr>
</tbody>
</table>
</div><p>In docker image A, I have:</p>
<pre><code class="lang-auto">FROM nvidia/cuda:10.1-devel-ubuntu18.04

ENV CUDNN_VERSION=7.6.5.32-1+cuda10.1
ENV PYTORCH_VERSION=1.6.0+cu101 # or 1.5.0+cu101
ENV TORCHVISION_VERSION=0.7.0+cu101 # or 0.6.0+cu101
...
...
RUN apt-get update &amp;&amp; \
    apt-get install -y --allow-change-held-packages --allow-downgrades --no-install-recommends \
    libcudnn7=${CUDNN_VERSION} \
    libcudnn7-dev=${CUDNN_VERSION} \

RUN pip install torch==${PYTORCH_VERSION} torchvision==${TORCHVISION_VERSION} -f https://download.pytorch.org/whl/torch_stable.html
...
...
</code></pre>
<p>In docker image B, I have:</p>
<pre><code class="lang-auto">FROM pytorch/pytorch:1.6.0-cuda10.1-cudnn7-devel
...
...
</code></pre>
<p>So my new question is what could be the issue with docker image A that causes FP16 speed regression from 1.5.0 to 1.6.0? Am I installing pytorch correctly in it?</p>
<p>Thank you</p>

BramVanroy: <p>Just to be sure that you did a fair bemchmark: did you set all seeds, set cudnn deterministic mode and disabled its benchmark mode? Different seeds can mean differently shuffled batches, meaning which might be more efficient in some cases vs. others (depending on how the padding is done). Something like this:</p>
<pre><code class="lang-python">def set_seed(seed: int):
    """Set all seeds to make results reproducible (deterministic mode).
       When seed is None, disables deterministic mode.
    :param seed: an integer to your choosing
    """
    if seed is not None:
        torch.manual_seed(seed)
        torch.cuda.manual_seed_all(seed)
        torch.backends.cudnn.deterministic = True
        torch.backends.cudnn.benchmark = False
        np.random.seed(seed)
        random.seed(seed)
        os.environ['PYTHONHASHSEED'] = str(seed)
</code></pre>

Liyang_Lu: <p>Thank you for the quick reply. Yes the seeds are all set. The loss curves are overlapping so the batches are deterministic.<br>
I also repeated experiments multiple times with different model sizes and data, all have similar phenomenon. The gap is just too big to be the result of randomness.</p>

Liyang_Lu: <p>New updates added to the original post.</p>

ptrblck: <p>Did you synchronize the code properly before starting and stopping the timer?<br>
If not, this would yield invalid profiling results, since CUDA operations are asynchronously executed and your Python script might “run ahead”.<br>
Also note that you shouldn’t expect speedups on the P40, as no TensorCores are available on this device.</p>

Liyang_Lu: <p>Yes, I understand. The throughput calculation is over many updates, so the slow down is real.<br>
And for the comparisons, I keep the hardware all same. Even though P40 doesn’t have TensorCores, I did see speedups with APEX O2 in two of the three configurations above. And the one in which I installed Pytorch with <code>pip install torch==1.6.0+cu101</code>, the speed up is gone.</p>

ptrblck: <p>Multiple iterations are a proper way to stabilize the results, however synchronizations are still needed.</p>