5 votes

Le GPU est plus lent que le CPU pour Pytorch sur Google Colaboratory.

Le GPU entraîne ce réseau en 16 secondes environ. Le CPU en 13 secondes environ. (Je décommente/commente les lignes appropriées pour faire le test). Quelqu'un peut-il voir ce qui ne va pas dans mon code ou dans l'installation de pytorch ? (J'ai déjà vérifié que le GPU est disponible, et qu'il y a suffisamment de mémoire disponible sur le GPU.

from os import path
from wheel.pep425tags import get_abbr_impl, get_impl_ver, get_abi_tag
platform = '{}{}-{}'.format(get_abbr_impl(), get_impl_ver(), get_abi_tag())

accelerator = 'cu80' if path.exists('/opt/bin/nvidia-smi') else 'cpu'
print(accelerator)
!pip install -q http://download.pytorch.org/whl/{accelerator}/torch-0.4.0-{platform}-linux_x86_64.whl torchvision
print("done")

#########################

import torch
from datetime import datetime

startTime = datetime.now()

dtype = torch.float
device = torch.device("cpu") # Comment this to run on GPU
# device = torch.device("cuda:0") # Uncomment this to run on GPU

# N is batch size; D_in is input dimension;
# H is hidden dimension; D_out is output dimension.
N, D_in, H, D_out = 64, 1024, 128, 8

# Create random Tensors to hold input and outputs.
x = torch.randn(N, D_in, device=device, dtype=dtype)
t = torch.randn(N, D_out, device=device, dtype=dtype)

# Create random Tensors for weights.
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
w3 = torch.randn(D_out, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-9
for i in range(10000):
    y_pred = x.mm(w1).clamp(min=0).mm(w2).clamp(min=0).mm(w3)

    loss = (y_pred - t).pow(2).sum()

    if i % 1000 == 0:
        print(i, loss.item())

    loss.backward()

    # Manually update weights using gradient descent
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # Manually zero the gradients after updating weights
        w1.grad.zero_()
        w2.grad.zero_()

print(datetime.now() - startTime)

12voto

iacolippo Points 1514

Je vois que vous chronométrez des choses que vous ne devriez pas chronométrer (définition de dtype, device, ...). Ce qui est intéressant à chronométrer ici est la création des tenseurs d'entrée, de sortie et de poids.

startTime = datetime.now()
# Create random Tensors to hold input and outputs.
x = torch.randn(N, D_in, device=device, dtype=dtype)
t = torch.randn(N, D_out, device=device, dtype=dtype)
torch.cuda.synchronize()
print(datetime.now()-startTime)

# Create random Tensors for weights.
startTime = datetime.now()
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
w3 = torch.randn(D_out, D_out, device=device, dtype=dtype, requires_grad=True)
torch.cuda.synchronize()
print(datetime.now()-startTime)

et la boucle d'entraînement

startTime = datetime.now()

for i in range(10000):
    y_pred = x.mm(w1).clamp(min=0).mm(w2).clamp(min=0).mm(w3)

    loss = (y_pred - t).pow(2).sum()

    if i % 1000 == 0:
        print(i, loss.item())

    loss.backward()

    # Manually update weights using gradient descent
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # Manually zero the gradients after updating weights
        w1.grad.zero_()
        w2.grad.zero_()
torch.cuda.synchronize()
print(datetime.now() - startTime)

Pourquoi le GPU est plus lent

Je l'exécute sur ma machine avec une GTX1080 et un très bon CPU, donc le timing absolu est plus bas, mais l'explication devrait rester valable. Si vous ouvrez un Jupyter notebook et l'exécutez sur le CPU :

0:00:00.001786 time to create input/output tensors
0:00:00.003359 time to create weight tensors
0:00:04.030797 time to run training loop

Maintenant, vous réglez le dispositif sur cuda et nous appelons cela un "démarrage à froid" (rien n'a été exécuté auparavant sur le GPU dans cet ordinateur portable).

0:00:03.180510 time to create input/output tensors
0:00:00.000642 time to create weight tensors
0:00:03.534751 time to run training loop

Vous constatez que le temps d'exécution de la boucle d'apprentissage est légèrement réduit, mais qu'il y a une surcharge de 3 secondes car vous devez déplacer les tenseurs du CPU vers la mémoire vive du GPU.

Si vous l'exécutez à nouveau sans fermer le carnet Jupyter :

0:00:00.000421 time to create input/output tensors
0:00:00.000733 time to create weight tensors
0:00:03.501581 time to run training loop

L'overhead disparaît, car Pytorch utilise un fichier de type allocateur de mémoire cache pour accélérer les choses.

Vous pouvez remarquer que l'accélération obtenue sur la boucle d'entraînement est très faible, car les opérations que vous effectuez sont sur des tenseurs de taille assez petite. Lorsque je travaille avec des architectures et des données de petite taille, je fais toujours un test rapide pour voir si je gagne quelque chose en l'exécutant sur GPU. Par exemple, si je règle N, D_in, H, D_out = 64, 5000, 5000, 8 La boucle d'apprentissage s'exécute en 3,5 secondes sur la GTX1080 et en 85 secondes sur le CPU.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X