tienen la suerte de tener acceso a un sistema con una unidad de procesamiento gráfico NVIDIA (GPU). ¿Sabía que hay un método absurdamente fácil para usar las capacidades de su GPU utilizando una biblioteca de Python destinada y utilizada predominantemente para aplicaciones de aprendizaje automático (ML)?
No se preocupe si no está al día en los entresijos de ML, ya que no lo usaremos en este artículo. En su lugar, le mostraré cómo usar la biblioteca Pytorch para acceder y usar las capacidades de su GPU. Compararemos los tiempos de ejecución de Pitón programas que utilizan la popular biblioteca numérica Numpy, Ejecutando en la CPU, con código equivalente que usa Pytorch en la GPU.
Antes de continuar, recapitulemos rápidamente lo que una GPU y Pytorch son.
¿Qué es una GPU?
Una GPU es un chip electrónico especializado diseñado inicialmente para manipular y alterar rápidamente la memoria para acelerar la creación de imágenes en un búfer de cuadro destinado a la salida a un dispositivo de visualización. Su utilidad como dispositivo de manipulación de imagen rápida se basó en su capacidad para realizar muchos cálculos simultáneamente, y todavía se usa para ese propósito.
Sin embargo, las GPU se han vuelto invaluables en el aprendizaje automático, la capacitación y el desarrollo de modelos de idiomas grandes. Su capacidad inherente para realizar cálculos altamente paralelizables los convierte en caballos de batalla ideales en estos campos, ya que emplean modelos y simulaciones matemáticas complejas.
¿Qué es Pytorch?
Pytorch es una biblioteca de aprendizaje automático de código abierto desarrollada por el Laboratorio de Investigación de IA de Facebook (Feria). Se utiliza ampliamente para el procesamiento del lenguaje natural y las aplicaciones de visión por computadora. Dos de las principales razones por las que Pytorch puede usarse para las operaciones de GPU son,
- Una de las estructuras de datos principales de Pytorch es el tensor. Los tensores son similares a las matrices y matrices en otros lenguajes de programación, pero están optimizados para ejecutarse en una GPU.
- Pytorch tiene soporte CUDA. Pytorch se integra perfectamente con CUDA, una plataforma de computación paralela y un modelo de programación desarrollado por NVIDIA para la computación general en sus GPU. Esto permite que Pytorch acceda directamente al hardware GPU, acelerando los cálculos numéricos. CUDA permitirá a los desarrolladores usar Pytorch para escribir software que utilice completamente la aceleración de GPU.
En resumen, el soporte de Pytorch para las operaciones de GPU a través de CUDA y sus eficientes capacidades de manipulación de tensores lo convierten en una excelente herramienta para desarrollar funciones de Python aceleradas con GPU con altas demandas computacionales.
Como mostraremos más adelante, no tener para usar Pytorch para desarrollar modelos de aprendizaje automático o entrenar modelos de idiomas grandes.
En el resto de este artículo, configuraremos nuestro entorno de desarrollo, instalaremos Pytorch y ejecutaremos algunos ejemplos en los que compararemos algunas implementaciones de Pytorch computacionalmente pesadas con la implementación equivalente de Numpy y veremos qué diferencias de rendimiento, si alguna, encontramos.
Requisitos previos
Necesita una GPU NVIDIA en su sistema. Para verificar su GPU, emita el siguiente comando en el mensaje de su sistema. Estoy usando el subsistema de Windows para Linux (WSL).
$ nvidia-smi
>>
(base) PS C:\Users\thoma> nvidia-smi
Fri Mar 22 11:41:34 2024
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 551.61 Driver Version: 551.61 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name TCC/WDDM | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GeForce RTX 4070 Ti WDDM | 00000000:01:00.0 On | N/A |
| 32% 24C P8 9W / 285W | 843MiB / 12282MiB | 1% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| 0 N/A N/A 1268 C+G ...tility\HPSystemEventUtilityHost.exe N/A |
| 0 N/A N/A 2204 C+G ...ekyb3d8bbwe\PhoneExperienceHost.exe N/A |
| 0 N/A N/A 3904 C+G ...cal\Microsoft\OneDrive\OneDrive.exe N/A |
| 0 N/A N/A 7068 C+G ...CBS_cw5n
etc ..
Si ese comando no es reconocido y está seguro de que tiene una GPU, probablemente significa que le falta un controlador NVIDIA. Simplemente siga el resto de las instrucciones en este artículo, y debe instalarse como parte de ese proceso.
Si bien los paquetes de instalación de Pytorch pueden incluir bibliotecas CUDA, su sistema aún debe instalar los controladores de GPU NVIDIA apropiados. Estos controladores son necesarios para que su sistema operativo se comunique con el hardware de la Unidad de Procesamiento de Gráficos (GPU). El kit de herramientas CUDA incluye controladores, pero si está utilizando el CUDA agrupado de Pytorch, solo necesita asegurarse de que sus controladores de GPU estén actuales.
Hacer clic este enlace Para ir al sitio web de NVIDIA e instalar los últimos controladores compatibles con su sistema y especificaciones de GPU.
Configuración de nuestro entorno de desarrollo
Como una mejor práctica, debemos establecer un entorno de desarrollo separado para cada proyecto. Uso conda, pero uso cualquier método que le convenga.
Si desea ir por la ruta de condena y aún no la tiene, debe instalar Miniconda (recomendado) o Anaconda primero.
Tenga en cuenta que, al momento de escribir, Pytorch actualmente solo admite oficialmente las versiones de Python 3.8 a 3.11.
#create our test environment
(base) $ conda create -n pytorch_test python=3.11 -y
Ahora active su nuevo entorno.
(base) $ conda activate pytorch_test
Ahora necesitamos obtener el comando apropiado de instalación de Conda para Pytorch. Esto dependerá de su sistema operativo, lenguaje de programación elegido, administrador de paquetes preferidos y versión CUDA.
Afortunadamente, Pytorch proporciona una interfaz web útil que hace que esto sea fácil de configurar. Entonces, para comenzar, dirígete al sitio web de Pytorch en …
Haga clic en el Get Started Enlace cerca de la parte superior de la pantalla. Desde allí, desplácese hacia abajo un poco hasta que vea esto,
Haga clic en cada cuadro en la posición apropiada para su sistema y especificaciones. Mientras lo hace, verá que el comando en el Run this Command El campo de salida cambia dinámicamente. Cuando haya terminado de tomar sus elecciones, copie el texto del comando final que se muestra y escriba en su solicitud de ventana de comando.
Para mí, esto fue:-
(pytorch_test) $ conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia -y
Instalaremos Jupyter, Pandas y Matplotlib para permitirnos ejecutar nuestro código Python en un cuaderno con nuestro código de ejemplo.
(pytroch_test) $ conda install pandas matplotlib jupyter -y
Ahora escriba jupyter notebook en su símbolo del sistema. Debería ver un cuaderno Jupyter abierto en su navegador. Si eso no sucede automáticamente, es probable que vea una pantalla de información después del jupyter notebook dominio.
Cerca de la parte inferior, habrá una URL que debe copiar y pegar en su navegador para iniciar el cuaderno Jupyter.
Su URL será diferente a la mía, pero debería verse algo así:-
http://127.0.0.1:8888/tree?token=3b9f7bd07b6966b41b68e2350721b2d0b6f388d248cc69da
Probar nuestra configuración
Lo primero que haremos es probar nuestra configuración. Ingrese lo siguiente en una celda Jupyter y ejecutarla.
import torch
x = torch.rand(5, 3)
print(x)
Debería ver una salida similar a la siguiente.
tensor([[0.3715, 0.5503, 0.5783],
[0.8638, 0.5206, 0.8439],
[0.4664, 0.0557, 0.6280],
[0.5704, 0.0322, 0.6053],
[0.3416, 0.4090, 0.6366]])
Además, para verificar si su controlador de GPU y CUDA están habilitados y accesibles por Pytorch, ejecute los siguientes comandos:
import torch
torch.cuda.is_available()
Esto debería generar True Si todo está bien.
Si todo está bien, podemos proceder a nuestros ejemplos. Si no, regrese y verifique sus procesos de instalación.
NB En los tiempos a continuación, ejecuté cada uno de los procesos Numpy y Pytorch varias veces seguidas y me tomé el mejor tiempo para cada uno. Esto favorece que el Pytorch funciona un poco, ya que hay una pequeña sobrecarga en la primera invocación de cada carrera de Pytorch, pero, en general, creo que es una comparación más justa.
Ejemplo 1 – Una operación de matemática de matriz simple.
En este ejemplo, configuramos dos matrices unidimensionales grandes e idénticas y realizamos una adición simple a cada elemento de matriz.
import numpy as np
import torch as pt
from timeit import default_timer as timer
#func1 will run on the CPU
def func1(a):
a+= 1
#func2 will run on the GPU
def func2(a):
a+= 2
if __name__=="__main__":
n1 = 300000000
a1 = np.ones(n1, dtype = np.float64)
# had to make this array much smaller than
# the others due to slow loop processing on the GPU
n2 = 300000000
a2 = pt.ones(n2,dtype=pt.float64)
start = timer()
func1(a1)
print("Timing with CPU:numpy", timer()-start)
start = timer()
func2(a2)
#wait for all calcs on the GPU to complete
pt.cuda.synchronize()
print("Timing with GPU:pytorch", timer()-start)
print()
print("a1 = ",a1)
print("a2 = ",a2)
Timing with CPU:numpy 0.1334826999955112
Timing with GPU:pytorch 0.10177790001034737
a1 = [2. 2. 2. ... 2. 2. 2.]
a2 = tensor([3., 3., 3., ..., 3., 3., 3.], dtype=torch.float64)
Vemos una ligera mejora al usar Pytorch sobre Numpy, pero perdimos un punto crucial. No hemos usado la GPU porque nuestros datos de tensor Pytorch todavía están en la memoria de la CPU.
Para mover los datos a la memoria de GPU, necesitamos agregar el device='cuda' Directiva Al crear el tensor. Hagamos eso y veamos si hace la diferencia.
# Same code as above except
# to get the array data onto the GPU memory
# we changed
a2 = pt.ones(n2,dtype=pt.float64)
# to
a2 = pt.ones(n2,dtype=pt.float64,device='cuda')
Después de volver a correr con los cambios que obtenemos,
Timing with CPU:numpy 0.12852740001108032
Timing with GPU:pytorch 0.011292399998637848
a1 = [2. 2. 2. ... 2. 2. 2.]
a2 = tensor([3., 3., 3., ..., 3., 3., 3.], device='cuda:0', dtype=torch.float64)
Eso es más parecido, una velocidad más de 10 veces.
Ejemplo 2: una operación de matriz ligeramente más compleja.
Para este ejemplo, multiplicaremos matrices multidimensionales utilizando el incorporado matmul Operaciones disponibles en las bibliotecas Pytorch y Numpy. Cada matriz será de 10000 x 10000 y contendrá números de punto flotante aleatorios entre 1 y 100.
# NUMPY first
import numpy as np
from timeit import default_timer as timer
# Set the seed for reproducibility
np.random.seed(0)
# Generate two 10000x10000 arrays of random floating point numbers between 1 and 100
A = np.random.uniform(low=1.0, high=100.0, size=(10000, 10000)).astype(np.float32)
B = np.random.uniform(low=1.0, high=100.0, size=(10000, 10000)).astype(np.float32)
# Perform matrix multiplication
start = timer()
C = np.matmul(A, B)
# Due to the large size of the matrices, it's not practical to print them entirely.
# Instead, we print a small portion to verify.
print("A small portion of the result matrix:\n", C[:5, :5])
print("Without GPU:", timer()-start)
A small portion of the result matrix:
[[25461280. 25168352. 25212526. 25303304. 25277884.]
[25114760. 25197558. 25340074. 25341850. 25373122.]
[25381820. 25326522. 25438612. 25596932. 25538602.]
[25317282. 25223540. 25272242. 25551428. 25467986.]
[25327290. 25527838. 25499606. 25657218. 25527856.]]
Without GPU: 1.4450852000009036
Ahora para la versión de Pytorch.
import torch
from timeit import default_timer as timer
# Set the seed for reproducibility
torch.manual_seed(0)
# Use the GPU
device = 'cuda'
# Generate two 10000x10000 tensors of random floating point
# numbers between 1 and 100 and move them to the GPU
#
A = torch.FloatTensor(10000, 10000).uniform_(1, 100).to(device)
B = torch.FloatTensor(10000, 10000).uniform_(1, 100).to(device)
# Perform matrix multiplication
start = timer()
C = torch.matmul(A, B)
# Wait for all current GPU operations to complete (synchronize)
torch.cuda.synchronize()
# Due to the large size of the matrices, it's not practical to print them entirely.
# Instead, we print a small portion to verify.
print("A small portion of the result matrix:\n", C[:5, :5])
print("With GPU:", timer() - start)
A small portion of the result matrix:
[[25145748. 25495480. 25376196. 25446946. 25646938.]
[25357524. 25678558. 25675806. 25459324. 25619908.]
[25533988. 25632858. 25657696. 25616978. 25901294.]
[25159630. 25230138. 25450480. 25221246. 25589418.]
[24800246. 25145700. 25103040. 25012414. 25465890.]]
With GPU: 0.07081239999388345
La carrera de Pytorch fue 20 veces mejor esta vez que la carrera numpy. Grandes cosas.
Ejemplo 3 – Combinando el código de CPU y GPU.
A veces, no todo su procesamiento se puede hacer en una GPU. Un caso de uso diario para esto es gráficos de datos. Claro, puede manipular sus datos utilizando la GPU, pero a menudo el siguiente paso es ver cómo se ve su conjunto de datos final usando un diagrama.
No puede trazar datos si reside en la memoria de la GPU, por lo que debe volver a moverlo a la memoria de la CPU antes de llamar a sus funciones de trazado. ¿Vale la pena la sobrecarga de mover grandes fragmentos de datos de la GPU a la CPU? Averigüemos.
En este ejemplo, resolveremos esta ecuación polar para los valores de θ entre 0 y 2π en (x, y) coordinar los términos y luego trazarán el gráfico resultante.
No te cuelen demasiado en las matemáticas. Es solo una ecuación que, cuando se convierte para usar el sistema de coordenadas X, Y y resuelto, se ve bien cuando se traza.
Incluso para unos pocos millones de valores de X e Y, Numpy puede resolver esto en milisegundos, por lo que para hacerlo un poco más interesante, usaremos 100 millones (x, y) coordenadas.
Aquí está el código Numpy primero.
%%time
import numpy as np
import matplotlib.pyplot as plt
from time import time as timer
start = timer()
# create an array of 100M thetas between 0 and 2pi
theta = np.linspace(0, 2*np.pi, 100000000)
# our original polar formula
r = 1 + 3/4 * np.sin(3*theta)
# calculate the equivalent x and y's coordinates
# for each theta
x = r * np.cos(theta)
y = r * np.sin(theta)
# see how long the calc part took
print("Finished with calcs ", timer()-start)
# Now plot out the data
start = timer()
plt.plot(x,y)
# see how long the plotting part took
print("Finished with plot ", timer()-start)
Aquí está la salida. ¿Habrías adivinado de antemano que se vería así? ¡Seguro que no lo habría hecho!
Ahora, veamos cómo se ve la implementación equivalente de Pytorch y cuánto de aceleración obtenemos.
%%time
import torch as pt
import matplotlib.pyplot as plt
from time import time as timer
# Make sure PyTorch is using the GPU
device = 'cuda'
# Start the timer
start = timer()
# Creating the theta tensor on the GPU
theta = pt.linspace(0, 2 * pt.pi, 100000000, device=device)
# Calculating r, x, and y using PyTorch operations on the GPU
r = 1 + 3/4 * pt.sin(3 * theta)
x = r * pt.cos(theta)
y = r * pt.sin(theta)
# Moving the result back to CPU for plotting
x_cpu = x.cpu().numpy()
y_cpu = y.cpu().numpy()
pt.cuda.synchronize()
print("Finished with calcs", timer() - start)
# Plotting
start = timer()
plt.plot(x_cpu, y_cpu)
plt.show()
print("Finished with plot", timer() - start)
Y nuestra salida nuevamente.
La parte de cálculo fue aproximadamente 10 veces más que el cálculo numpy. El trazado de datos tomó casi al mismo tiempo usando las versiones Pytorch y Numpy, que se esperaba ya que los datos todavía estaban en la memoria de la CPU, y la GPU no jugó más parte en el procesamiento.
Pero, en general, afeitamos aproximadamente un 40% de descuento en el tiempo de ejecución total, lo cual es excelente.
Resumen
Este artículo ha demostrado cómo aprovechar una GPU NVIDIA usando Pytorch, una biblioteca de aprendizaje automático que se usa típicamente para aplicaciones de IA, para acelerar el código de pitón numérico no ML. Compara implementaciones Numpy estándar (basadas en CPU) con equivalentes de Pytorch acelerado por GPU para mostrar los beneficios de rendimiento de la ejecución de operaciones basadas en tensor en una GPU.
No necesita estar haciendo aprendizaje automático para beneficiarse de Pytorch. Si puede acceder a una GPU NVIDIA, Pytorch proporciona una forma simple y efectiva de acelerar significativamente las operaciones numéricas computacionalmente intensivas, incluso en el código Python de uso general.