J'utilise pyTorch pour exécuter des calculs sur mon GPU (RTX 3000, CUDA 11.1). Une étape consiste à calculer la distance entre un point et un tableau de points. Pour le coup, j'ai testé 2 fonctions pour déterminer laquelle est la plus rapide comme suit :
import datetime as dt
import functools
import timeit
import torch
import numpy as np
device = torch.device("cuda:0")
# define functions for calculating distance
def dist_geom(a, b):
dist = (a - b)**2
dist = dist.sum(axis=1)**0.5
return dist
def dist_linalg(a, b):
dist = torch.linalg.norm(a - b, axis=1)
return dist
# create dummy data
a = np.random.randint(0, 100000, (100000, 10, 10)).astype(np.float64)
b = np.random.randint(0, 100000, (1, 10)).astype(np.float64)
# send data to GPU
a = torch.from_numpy(a).to(device)
b = torch.from_numpy(b).to(device)
# test runtime of each
iterations = 1000
t = timeit.Timer(functools.partial(dist_linalg, a, b))
linalg_delta = t.timeit(number=iterations) / iterations
print("Linear algebra time: ", linalg_delta, " seconds per iteration")
t = timeit.Timer(functools.partial(dist_geom, a, b))
geom_delta = t.timeit(number=iterations) / iterations
print("Geometry time: ", geom_delta, " seconds per iteration")
print("linear algebra:geometry ratio: ", linalg_delta / geom_delta)
Cela donne le résultat suivant :
Linear algebra time: 0.000743145 seconds per iteration
Geometry time: 0.001446731 seconds per iteration
linear algebra:geometry ratio: 0.5136718574496572
Donc la fonction d'algèbre linéaire est ~2x plus rapide. Mais si j'appelle la fonction de géométrie en premier :
t = timeit.Timer(functools.partial(dist_geom, a, b))
geom_delta = t.timeit(number=iterations) / iterations
print("Geometry time: ", geom_delta, " seconds per iteration")
t = timeit.Timer(functools.partial(dist_linalg, a, b))
linalg_delta = t.timeit(number=iterations) / iterations
print("Linear algebra time: ", linalg_delta, " seconds per iteration")
print("linear algebra:geometry ratio: ", linalg_delta / geom_delta)
J'obtiens ce résultat :
Geometry time: 0.001213497 seconds per iteration
Linear algebra time: 0.001136769 seconds per iteration
linear algebra:geometry ratio: 0.9367711663069623
Le temps de dist_geom est presque identique à celui de l'exécution initiale, mais le temps de dist_linalg est maintenant 1,46x plus long !
J'ai testé cela de plusieurs façons et le résultat est toujours le même : l'ordre d'appel semble avoir de l'importance... beaucoup. Je pense qu'il me manque un point fondamental ici, donc toute aide pour comprendre ce qui se passe sera appréciée (et je soupçonne que ce sera si simple que je me sentirai stupide).
J'ai créé deux ensembles de tenseurs. Ce qui suit donne le même temps d'exécution quel que soit l'ordre.
# create 2 tensors for geometry test
a1 = np.random.randint(0, 100000, (100000, 10, 10)).astype(np.float64)
b1 = np.random.randint(0, 100000, (1, 10)).astype(np.float64)
a1 = torch.from_numpy(a1).to(device)
b1 = torch.from_numpy(b1).to(device)
t = timeit.Timer(functools.partial(dist_geom, a, b))
geom_delta = t.timeit(number=iterations) / iterations
print("Geometry time: ", geom_delta, " seconds per iteration")
# create 2 different tensors for the linalg function
a2 = np.random.randint(0, 100000, (100000, 10, 10)).astype(np.float64)
b2 = np.random.randint(0, 100000, (1, 10)).astype(np.float64)
a2 = torch.from_numpy(a2).to(device)
b2 = torch.from_numpy(b2).to(device)
t = timeit.Timer(functools.partial(dist_linalg, a, b))
linalg_delta = t.timeit(number=iterations) / iterations
print("Linear algebra time: ", linalg_delta, " seconds per iteration")
print("linear algebra:geometry ratio: ", linalg_delta / geom_delta)
Geometry time: 0.0012010019999999998 seconds per iteration
Linear algebra time: 0.0007349769999999999 seconds per iteration
linear algebra:geometry ratio: 0.6119698385181707
Cela dit, si je définis à la fois a1/b1 et a2/b2 avant les appels de fonction, je vois à nouveau la différence de temps. Au départ, je pensais que cela était dû aux temps de chargement de la mémoire, mais cela ne correspond pas vraiment, n'est-ce pas ?