Quien haya tenido que trabajar con librerías de Inteligencia Artificial y Deep Learning, como lo son TensorFlow y Keras, sabrá que tarde o temprano va a necesitar sacarle el máximo provecho a su equipo si quiere acelerar las cosas.

Entrenar tus modelos con un la CPU, por potente que esta sea, puede llevar incluso días, y por supuesto no queremos eso. Por suerte, hasta en un portátil normalito como el mio, es posible acelerar mucho las cosas (y dejarlo en unas horas) si entrenamos esos mismos modelos usando la capacidad de cómputo de nuestra GPU.

Conseguirlo puede no ser una tarea fácil, y es que aunque estas librerías de Artificial Intelligence vienen perfectamente preparadas para ello, pelearse con entornos de Python, pip, Conda, NVIDIA, CUDA, cuDNN y Windows puede convertirse en una auténtica pesadilla. Tenemos la opción de contenerizarlo todo y tener entornos limpios, aislados y versionados, gracias a Docker, que además se ve muy potenciado si utilizamos WSL (Subsistema de Windows para Linux) en nuestra máquina Windows.

Si necesitas aprovechar la potencia de tu GPU para TensorFlow y Keras en tus cuadernos de Jypter, en un entorno Windows, aquí tienes los pasos que seguí yo, con la esperanza de que te sirvan de ayuda.

Paso a paso

Requisitos

Paso 1. Instalación de CUDA en Windows

En primer lugar necesitamos instalar los drivers adecuados para WSL y nuestra versión de Windows, que son los que nos permiten enviar procesos a la GPU para aprovechar su potencia de cómputo.

Puedes descargarlos en este enlace a CUDA WSL, yo instalé la versión Studio. Después instalalos normalmente siguiendo las instrucciones del propio instalador.

Paso 2. Instalación de WSL2 (Subsistema de Windows para Linux)

En PowerShell, ejecutar:

wsl –install

Si ya lo tenias instalado, o si quieres asegurarte, puedes probar a actualizarlo:

wsl –update

Establece la versión 2 como predeterminada para evitar problemas despues con las IPTABLES en Docker.

wsl –set-default-version 2

En este punto te recomiendo reiniciar tu máquina, aunque arriesgarte a seguir sin hacerlo como hice yo jaja.

Paso 3. Instalación de Ubuntu en Windows con WSL

Vamos a instalar una distribución de Linux en Windows usando WSL2, en concreto yo instalé Ubuntu-20.04. De nuevo en PowerShell:

wsl –install -d Ubuntu-20.04

Asegurate de que tu distribución se ha instalado con la versión 2 de WSL, lo puedes comprobar con el siguiente comando:

wsl -l -v

Paso 4. Iniciar sesión en Ubuntu

El siguiente comando abrirá una consola conectada a nuestro nuevo entorno Linux.

wsl -d Ubuntu-20.04

La primera vez necesitarás crear un usuario y contraseña. ¡Trata de no olvidarlos!

Paso 5. Instalación de dependencias en Linux

En nuestra sesión de Linux (que acabamos de iniciar), vamos a instalar las dependencias que necesitamos. En primer lugar, Instalaremos Docker:

curl https://get.docker.com sh

Una vez haya terminado, instalaremos también dentro de Linux, el NVIDIA Container toolkit:

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)

curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -

curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

sudo apt-get update

sudo apt-get install -y nvidia-docker2

Paso 6. Comprobar el servicio de Docker

Con el siguiente comando podemos comprobar que docker está correctamente instalado y en ejecución:

sudo service docker status

Si está en ejecución, perfecto, puedes pasar al Paso 7. si no fuese así, puede deberse a varias razones:

Posibilidad 2: El servicio no se ha iniciado automaticamente

Parece ser que en WSL a veces el servicio no arranca automaticamente, puedes probar reiniciando el demonio manualmente

sudo dockerd

O, como hice yo, iniciar una nueva ventana en la misma instalación de linux lanzando en PowerShell de nuevo el comando “wsl -d Ubuntu-20.04”, y parando y relanzando el servicio desde ahí:

sudo service docker stop
sudo service docker start

Posibilidad 2: Reiniciar

En serio, prueba a reiniciar y luego vuelve a ejecutar el service docker start anterior. A mi siempre me ha bastado con esto.

Posibilidad 3: El servicio no se inicia por un problema con las IPTABLES y la red

Mira el Paso 3 y usa el comando que aparece ahí para asegurarte de que estás usando la versión 2 de WSL.

wsl -l -v

Posibilidad 4: Alguna otra razón

Pueden pasar muchas cosas, como que se esté intentando ocupar una IP que en tu red esté ya en uso y eso impida que el servicio se inicie. WSL tiene algunos problemas en la forma en que maneja la red, y eso da bastante guerra al usar docker. En este punto lo unico que puedes es comprobar el log de docker para tirar del hilo:

sudo cat /var/log/docker.log

Mucho ánimo, ya casi lo tienes!

Paso 7. Comprobar la instalación

Si todo está en orden, podemos descargar y ejecutar un contenedor de docker que utilice las GPUs disponibles para hacer un benchmark de rendimiento.

sudo docker run –gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark

Si todo funciona correctamente, habremos conseguido ejecutar un contenedor de docker dentro de linux dentro de windows, que accede a nuestra GPU!. ¿Maravilloso no? El resultado debería ser similar a este, donde se muestra nuestro dispositivo y la velocidad alcanzada:

> Compute 6.1 CUDA device: [NVIDIA GeForce GTX 1050 Ti]
6144 bodies, total time for 10 iterations: 5.173 ms
= 72.969 billion interactions per second
= 1459.382 single-precision GFLOP/s at 20 flops per interaction

También probé con otro portatil con una gráfica más potente y aquí puede verse la diferencia.

> Compute 7.5 CUDA device: [NVIDIA GeForce GTX 1650]
16384 bodies, total time for 10 iterations: 25.335 ms
= 105.955 billion interactions per second
= 2119.095 single-precision GFLOP/s at 20 flops per interaction

Si escribimos el comando:

nvidia-smi

Podemos obtener un resumen con la versión del Driver, la versión de CUDA, y los procesos en ejecución en la GPU.

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 515.75       Driver Version: 517.40       CUDA Version: 11.7     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA GeForce ...  On   | 00000000:01:00.0 Off |                  N/A |
| N/A   43C    P0    N/A /  N/A |      0MiB /  4096MiB |      1%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

Paso 8. Lanzar el Notebook de Jupyter desde el contenedor de Docker

Al igual que hemos hecho con el benchmark, también podemos descargar y ejecutar un contenedor de docker con TensorFlow y Jupyter preinstalados.

sudo docker run -it –gpus all -p 8888:8888 tensorflow/tensorflow:latest-gpu-py3-jupyter

Al final de la ejecución, la traza nos mostrará un enlace a localhost que si abrimos en un navegador de nuestra máquina, nos permitirá acceder a nuestro Jupyter Notebook.

Servidor preparado

Por defecto Jupyter se ejecuta en el puerto 8888, pero si ya tuviesemos ese puerto ocupado en nuestra máquina, podemos mapearlo en otro cambiando el 8888:8888 por un nuevo puerto, por ejemplo 9080:8888.

Ahí podemos ejecutar el siguiente código de Python para comprobar que TensorFlow puede acceder a la GPU:

import tensorflow as tf
print('GPU name: ', tf.config.experimental.list_physical_devices('GPU'))

Jupyter Notebook

Paso 9 (Opcional). Compartir ficheros de nuestra máquina con Jupyter.

La máquina host (Windows) comparte los ficheros con WSL (Linux) mediante una unidad de red. La forma más facil de abrir esta carpeta compartida para mover ahí algunos ficheros y tenerlos disponibles en Linux es escribiendo el siguiente comando en nuestra consola Linux.

explorer.exe .

Esto abrira un explorador de ficheros (No olvides el punto al final del comando que indica que quieres abrir la carpeta actual). Ahí puedes copiar los ficheros que quieras tener accesibles en Linux. Suponiendo que no hayas cambiado de ruta, seguirás en el directorio de ${HOME}. Puedes conocer tu carpeta de trabajo actual escribiendo:

pwd

Ahora puedes crear un volumen en docker que mapea tu carpeta de Linux con la carpeta de trabajo de Jupyter dentro del contenedor de Docker (/tf/notebooks). Por ejemplo, imagina que en tu ${HOME} has creado una subcarpeta “notebooks” y en ella has puesto tus cuadernos de Jypter. Pues podrías mapearlo así:

sudo docker run -it –gpus all -v ${HOME}/notebooks:/tf/notebooks -p 8888:8888 tensorflow/tensorflow:latest-gpu-py3-jupyter

Ahora, al abrir Jupyter en el navegador, encontrarás que puedes acceder a los cuadernos que hayas copiado.

Nota: es posible que falle si ya tenias el puerto ocupado, por ejemplo porque hayas ejecutado otro contenedor antes y no lo has detenido antes de lanzar este. No me voy a enredar con como manejar docker porque eso daría para otro artículo, pero siempre puedes recurrir al Ctrl+C para interrumpirlo :). Aquí te dejo una referencia rápida de comandos de Docker.

Paso 10 (Opcional). Crear un contenedor Docker con nuestras propias dependencias y paquetes de Python.

Si además de TensorFlow necesitas otras librerías o paquetes, como Scikit-learn, Numpy, Pandas, o Keras, puedes instalar los paquetes en el contenedor que acabamos de descargar, o lo que yo recomendaría: crear tu propia imagen de Docker con las dependencias que necesites. Así podrás tener un contendor limpio y preparado para trabajar siempre que te haga falta.

Para ello, abre de nuevo la carpeta de Linux con

explorer.exe .

Y crea en ella un documento de definición de imagen de Docker o Dockerfile. El nombre debe ser “Dockerfile” sin extensión. Editalo a tu antojo para que incluya las dependencias que necesites. Dejo aquí el mio de ejemplo.

# Imagen base
FROM tensorflow/tensorflow:2.9.2-gpu-jupyter

## instalar tus propias dependencias

RUN apt-get update
RUN apt install graphviz -y
RUN pip install scikit-learn
RUN pip install torch
RUN pip install theano
RUN pip install pandas
RUN pip install keras
RUN pip install numpy
RUN pip install matplotlib
RUN pip install scipy
RUN pip install summary

Una vez guardado el fichero, vuelve a Linux y, en la ruta donde está el fichero Dockerfile, compila la imagen y dale un nombre:

docker build -t minombre/miimagen:mitag .

Finalmente, puedes ejecutar tu flamante contenedor personalizado con un comando similar al que veniamos usando, pero empleando tu imagen en su lugar:

sudo docker run -it –gpus all -v ${HOME}/notebooks:/tf/notebooks -p 8888:8888 minombre/miimagen:mitag

Si quieres más información, incluso acerca de como publicar tu nueva imagen en Dockerhub para compartirla, este artículo puede serte de gran ayuda.

Conclusión

Espero que este tutorial paso a paso te sirva de ayuda. A mi me llevo mucho tiempo en su primer momento para conseguir una instalación que funcionase, ya que pensaba que podría utilizar CUDA desde Windows con Docker instalado de manera nativa, pero al parecer no era así. Si al final de esta guía has conseguido tener un entorno funcionando, notarás una gran diferencia en el rendimiento cuando entrenes tus modelos de IA usando la GPU.

Dejo por aquí algunos enlaces y referencias que me fueron de gran ayuda durante mi propia instalación y que espero ahorraros.

Referencias principales

Otros enlaces interesantes: