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:

  1. 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 .

  2. instalar cupy con:

!pip3 install --user cupy-cuda102

creando una celda del notebook.

Ver googlecolab github para información sobre google colab.

CuPy : NumPy-like API accelerated with CUDA

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)


x_gpu.ndim: 1
x_gpu.shape: (3,)
x_gpu.size: 3
x_gpu.dtype: int64

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:])


primer elemento 1
último elemento 3
segundo elemento 2
penúltimo elemento 2
del primero al 2º elemento incluyendo este último [1 2]
del 2º al último elemento sin incluir el 2º [3]

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]]


---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-27-b81a25cb0295> in <module>
----> 1 x_cpu[[3]]

IndexError: index 3 is out of bounds for axis 0 with size 3

con cupy se reciclan los índices:


In [22]:
x_gpu[[3]]


Out[22]:
array([1])

Otra forma de generar arrays en numpy es con la función arange o random para un array pseudo aleatorio:


In [31]:
pprint.pprint(cp.arange(3))


array([0, 1, 2])

In [34]:
cp.random.seed(2020)
pprint.pprint(cp.random.rand(4))


array([0.24879282, 0.84020173, 0.58404578, 0.68705705])

Array's dos dimensionales:


In [35]:
A = cp.array([[1,2,3],[4,5,6]])
pprint.pprint(A)


array([[1, 2, 3],
       [4, 5, 6]])

In [36]:
print('A.ndim:', A.ndim)
print('A.shape:', A.shape)
print('A.size:', A.size)
print('A.dtype', A.dtype)


A.ndim: 2
A.shape: (2, 3)
A.size: 6
A.dtype int64

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])


elemento en la posición (0,0): 1
elemento en la posición (1,2): 6
elemento en la posición (0,0): 1
elemento en la posición (1,2): 6

In [38]:
print('primer columna:', A[:,0])
print('tercer columna:', A[:,2])
print('segundo renglón:', A[1,:])


primer columna: [1 4]
tercer columna: [3 6]
segundo renglón: [4 5 6]

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))


array([[0, 1, 2],
       [3, 4, 5]])
array([[0. , 0.2],
       [0.4, 0.6],
       [0.8, 1. ]])

In [43]:
cp.random.seed(2020)
pprint.pprint(cp.random.rand(2,4))


array([[0.24879282, 0.84020173, 0.58404578, 0.68705705],
       [0.24259164, 0.45069515, 0.4845656 , 0.94929182]])

Operaciones en el álgebra lineal con CuPy

Producto escalar-vector, suma y punto entre vectores


In [44]:
v1 = cp.array([6,-3,4])
v2 = cp.array([4,5,0])
escalar = -1/2

In [45]:
escalar*v1


Out[45]:
array([-3. ,  1.5, -2. ])

In [46]:
v1.dot(v2)


Out[46]:
array(9)

In [47]:
v1+v2


Out[47]:
array([10,  2,  4])

Producto matriz vector point-wise


In [48]:
A = cp.array([[2,5,0],[3,6,6],[-6,4,-1],[5,4,9]])
A


Out[48]:
array([[ 2,  5,  0],
       [ 3,  6,  6],
       [-6,  4, -1],
       [ 5,  4,  9]])

In [49]:
v = cp.array([-2,1,4])
v


Out[49]:
array([-2,  1,  4])

In [50]:
A*v


Out[50]:
array([[ -4,   5,   0],
       [ -6,   6,  24],
       [ 12,   4,  -4],
       [-10,   4,  36]])

Producto matriz-vector


In [51]:
A = cp.array([[2,5,0],[3,6,6],[-6,4,-1],[5,4,9]])
A


Out[51]:
array([[ 2,  5,  0],
       [ 3,  6,  6],
       [-6,  4, -1],
       [ 5,  4,  9]])

Obsérvese que las clases de los objetos deben ser del mismo tipo


In [52]:
v = np.array([-2,1,4])
v


Out[52]:
array([-2,  1,  4])

In [53]:
A.dot(v)


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-1cc4310ff569> in <module>
----> 1 A.dot(v)

TypeError: Argument 'b' has incorrect type (expected cupy.core.core.ndarray, got numpy.ndarray)

In [54]:
v = cp.array([-2,1,4])
v


Out[54]:
array([-2,  1,  4])

In [55]:
A.dot(v)


Out[55]:
array([ 1, 24, 12, 30])

In [56]:
A@v


Out[56]:
array([ 1, 24, 12, 30])

In [58]:
v = cp.array([7,0,-3,2])
v


Out[58]:
array([ 7,  0, -3,  2])

In [59]:
v@A


Out[59]:
array([42, 31, 21])

Suma y producto matriz-matriz pointwise


In [60]:
A = cp.array([[2,5,0],[3,6,6],[-6,4,-1],[5,4,9]])
A


Out[60]:
array([[ 2,  5,  0],
       [ 3,  6,  6],
       [-6,  4, -1],
       [ 5,  4,  9]])

In [61]:
B = cp.array([[2,-2,3],[1,-1,5],[0,-2,1],[0,0,-3]])
B


Out[61]:
array([[ 2, -2,  3],
       [ 1, -1,  5],
       [ 0, -2,  1],
       [ 0,  0, -3]])

In [62]:
A+B


Out[62]:
array([[ 4,  3,  3],
       [ 4,  5, 11],
       [-6,  2,  0],
       [ 5,  4,  6]])

In [63]:
A*B


Out[63]:
array([[  4, -10,   0],
       [  3,  -6,  30],
       [  0,  -8,  -1],
       [  0,   0, -27]])

Producto matriz-matriz


In [64]:
A = cp.array([[2,5,0],[3,6,6],[-6,4,-1],[5,4,9]])
A


Out[64]:
array([[ 2,  5,  0],
       [ 3,  6,  6],
       [-6,  4, -1],
       [ 5,  4,  9]])

In [65]:
B = cp.array([[2,-2,3],[1,-1,5],[0,-2,1]])
B


Out[65]:
array([[ 2, -2,  3],
       [ 1, -1,  5],
       [ 0, -2,  1]])

In [66]:
A@B


Out[66]:
array([[  9,  -9,  31],
       [ 12, -24,  45],
       [ -8,  10,   1],
       [ 14, -32,  44]])

Algunas operaciones básicas del álgebra lineal

Norma de vectores


In [69]:
v = cp.array([1,2,3])
v


Out[69]:
array([1, 2, 3])

In [70]:
cp.linalg.norm(v)


Out[70]:
array(3.74165739)

Norma de matrices


In [71]:
A = cp.array([[2,5,0],[3,6,6],[-6,4,-1]])
A


Out[71]:
array([[ 2,  5,  0],
       [ 3,  6,  6],
       [-6,  4, -1]])

In [72]:
cp.linalg.norm(A)


Out[72]:
array(12.76714533)

Resolver sistema de ecuaciones lineales


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)


A:
array([[ 8, -6,  2],
       [-4, 11, -7],
       [ 4, -7,  6]])
b:
array([ 28, -40,  33])

In [74]:
x=cp.linalg.solve(A,b)
print('x:')
pprint.pprint(x)


x:
array([ 2., -1.,  3.])

In [75]:
print('Verificando resultado Ax = b')
print('b:')
pprint.pprint(b)
print('Ax:')
pprint.pprint(A@x)


Verificando resultado Ax = b
b:
array([ 28, -40,  33])
Ax:
array([ 28., -40.,  33.])

Transferencia de datos del host al device o viceversa


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]:
array([1, 2, 3])

In [78]:
type(x_gpu)


Out[78]:
cupy.core.core.ndarray

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


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-84-a5a0137f3f92> in <module>
----> 1 x_gpu + y_cpu

cupy/core/core.pyx in cupy.core.core.ndarray.__add__()

cupy/core/_kernel.pyx in cupy.core._kernel.ufunc.__call__()

cupy/core/_kernel.pyx in cupy.core._kernel._preprocess_args()

TypeError: Unsupported type <class 'numpy.ndarray'>

In [85]:
x_gpu + cp.asarray(y_cpu)


Out[85]:
array([ 6,  8, 10])

In [86]:
cp.asnumpy(x_gpu) + y_cpu


Out[86]:
array([ 6,  8, 10])

Función ejecutada dependiendo de que sean array's de numpy o CuPy

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]:
array([1.03424619, 0.74963557, 1.03984615])

In [82]:
fun(x_cpu)


Out[82]:
array([1.03424619, 0.74963557, 1.03984615])

Definición de kernels en CuPY

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.