Notas para contenedor de docker:
Comando de docker para ejecución de la nota de forma local:
nota: cambiar <ruta a mi directorio>
por la ruta de directorio que se desea mapear a /datos
dentro del contenedor de docker.
docker run --gpus all --rm -v <ruta a mi directorio>:/datos --name jupyterlab_nvidia_cupy_container -p 8888:8888 -d palmoreck/jupyterlab_nvidia_cupy:1.1.0_10.2
password para jupyterlab: qwerty
Detener el contenedor de docker:
docker stop jupyterlab_nvidia_cupy_container
Documentación de la imagen de docker palmoreck/jupyterlab_nvidia_cupy:1.1.0_10.2
en liga.
Nota: si se desean ejecutar los ejemplos que se presentan a continuación de forma local, es necesario tener una tarjeta gráfica NVIDIA.
Para ejecutar el notebook si están usando google colab:
Elegir en el menú las opciones Entorno de ejecución > Cambiar tipo de entorno de ejecución > Acelerador por Hardware > GPU Runtime > Change > Hardware Accelerator > GPU .
instalar cupy con:
!pip3 install --user cupy-cuda102
creando una celda del notebook.
Ver googlecolab github para información sobre google colab.
Extraído del github de CuPy: CuPy is an implementation of NumPy-compatible multi-dimensional array on CUDA. CuPy consists of the core multi-dimensional array class, cupy.ndarray, and many functions on it. It supports a subset of numpy.ndarray interface.
Lo siguiente se basa en Basics of CuPy y en 0_definiciones_generales.
Un subconjunto de funciones del paquete numpy de Python están implementadas en CuPy vía la clase cupy.ndarray la cual es compatible en la GPU con la clase numpy.ndarray utilizada en la CPU. Por ejemplo:
In [1]:
import cupy as cp
import numpy as np
In [4]:
import pprint
In [2]:
x_gpu = cp.array([1, 2, 3])
Y el array $1$-dimensional anterior está alojado en la GPU.
Podemos obtener información del array
anterior utilizando algunos métodos y atributos:
In [7]:
print('x_gpu.ndim:',x_gpu.ndim)
print('x_gpu.shape:',x_gpu.shape)
print('x_gpu.size:',x_gpu.size)
print('x_gpu.dtype:',x_gpu.dtype)
accedemos con corchetes a sus componentes:
In [8]:
print('primer elemento', x_gpu[0])
print('último elemento', x_gpu[-1])
print('segundo elemento', x_gpu[1])
print('penúltimo elemento', x_gpu[-2])
print('del primero al 2º elemento incluyendo este último', x_gpu[:2])
print('del 2º al último elemento sin incluir el 2º', x_gpu[2:])
y a diferencia de numpy que nos devuelve un error al hacer:
In [26]:
x_cpu = np.array([1,2,3])
In [27]:
x_cpu[[3]]
con cupy
se reciclan los índices:
In [22]:
x_gpu[[3]]
Out[22]:
In [31]:
pprint.pprint(cp.arange(3))
In [34]:
cp.random.seed(2020)
pprint.pprint(cp.random.rand(4))
Array's dos dimensionales:
In [35]:
A = cp.array([[1,2,3],[4,5,6]])
pprint.pprint(A)
In [36]:
print('A.ndim:', A.ndim)
print('A.shape:', A.shape)
print('A.size:', A.size)
print('A.dtype', A.dtype)
accedemos con corchetes a sus componentes
In [37]:
print('elemento en la posición (0,0):', A[0][0])
print('elemento en la posición (1,2):', A[1][2])
#también con la siguiente notación:
print('elemento en la posición (0,0):', A[0,0])
print('elemento en la posición (1,2):', A[1,2])
In [38]:
print('primer columna:', A[:,0])
print('tercer columna:', A[:,2])
print('segundo renglón:', A[1,:])
y con las funciones arange
o random
:
In [39]:
pprint.pprint(cp.arange(6).reshape(2,3))
pprint.pprint(cp.arange(0,1.2,.2).reshape(3,2))
In [43]:
cp.random.seed(2020)
pprint.pprint(cp.random.rand(2,4))
In [44]:
v1 = cp.array([6,-3,4])
v2 = cp.array([4,5,0])
escalar = -1/2
In [45]:
escalar*v1
Out[45]:
In [46]:
v1.dot(v2)
Out[46]:
In [47]:
v1+v2
Out[47]:
In [48]:
A = cp.array([[2,5,0],[3,6,6],[-6,4,-1],[5,4,9]])
A
Out[48]:
In [49]:
v = cp.array([-2,1,4])
v
Out[49]:
In [50]:
A*v
Out[50]:
In [51]:
A = cp.array([[2,5,0],[3,6,6],[-6,4,-1],[5,4,9]])
A
Out[51]:
Obsérvese que las clases de los objetos deben ser del mismo tipo
In [52]:
v = np.array([-2,1,4])
v
Out[52]:
In [53]:
A.dot(v)
In [54]:
v = cp.array([-2,1,4])
v
Out[54]:
In [55]:
A.dot(v)
Out[55]:
In [56]:
A@v
Out[56]:
In [58]:
v = cp.array([7,0,-3,2])
v
Out[58]:
In [59]:
v@A
Out[59]:
In [60]:
A = cp.array([[2,5,0],[3,6,6],[-6,4,-1],[5,4,9]])
A
Out[60]:
In [61]:
B = cp.array([[2,-2,3],[1,-1,5],[0,-2,1],[0,0,-3]])
B
Out[61]:
In [62]:
A+B
Out[62]:
In [63]:
A*B
Out[63]:
In [64]:
A = cp.array([[2,5,0],[3,6,6],[-6,4,-1],[5,4,9]])
A
Out[64]:
In [65]:
B = cp.array([[2,-2,3],[1,-1,5],[0,-2,1]])
B
Out[65]:
In [66]:
A@B
Out[66]:
In [69]:
v = cp.array([1,2,3])
v
Out[69]:
In [70]:
cp.linalg.norm(v)
Out[70]:
In [71]:
A = cp.array([[2,5,0],[3,6,6],[-6,4,-1]])
A
Out[71]:
In [72]:
cp.linalg.norm(A)
Out[72]:
In [73]:
A = cp.array([[8, -6, 2], [-4, 11, -7], [4, -7, 6]])
b = cp.array([28,-40,33])
print('A:')
pprint.pprint(A)
print('b:')
pprint.pprint(b)
In [74]:
x=cp.linalg.solve(A,b)
print('x:')
pprint.pprint(x)
In [75]:
print('Verificando resultado Ax = b')
print('b:')
pprint.pprint(b)
print('Ax:')
pprint.pprint(A@x)
In [76]:
x_cpu = np.array([1, 2, 3])
x_gpu = cp.asarray(x_cpu) # move the data to the current device.
In [77]:
x_gpu
Out[77]:
In [78]:
type(x_gpu)
Out[78]:
In [79]:
x_gpu = cp.array([1, 2, 3]) # create an array in the current device
x_cpu = cp.asnumpy(x_gpu) # move the array to the host.
Y estas funciones pueden utilizarse para realizar operaciones dependiendo del tipo de array
:
In [83]:
y_cpu = np.array([5,6,7])
In [84]:
x_gpu + y_cpu
In [85]:
x_gpu + cp.asarray(y_cpu)
Out[85]:
In [86]:
cp.asnumpy(x_gpu) + y_cpu
Out[86]:
Es posible ejecutar una función dependiendo de sus argumentos con el módulo get_array_module
In [80]:
def fun(x):
xp = cp.get_array_module(x)
return xp.exp(-x) + xp.cos(xp.sin(-abs(x)))
In [81]:
fun(x_gpu)
Out[81]:
In [82]:
fun(x_cpu)
Out[82]:
Es posible escribir kernels con CuPy. Ver por ejemplo: User-Defined Kernels.
Referencias:
Otro paquete para uso de Python+GPU para cómputo matricial es:
Un paquete para uso de pandas+GPU:
Ver optional-libraries para librerías que pueden ser utilizadas con CuPy.