GPU: el cerebro de la IA
Rubén Rodríguez Abril
La GPU (Unidad de Procesamiento Gráfico) es un acelerador (chip especializado) en el cómputo en paralelo. Aunque su propósito originario eran operaciones de naturaleza gráfica (como la rasterización o el sombreado), en la actualidad son ampliamente utilizadas para implementar sistemas de aprendizaje profundo. Son los verdaderos cerebros de la IA.
De la implementación a la simulación
Aunque la primera red neuronal artificial de la historia, el perceptrón de Rosenblatt, fue implementada por un hardware construido ex profeso, en el que los pesos sinápticos eran implementados por potenciómetros, pronto se abrió paso la idea de simular estas redes en ordenadores, que tenían una creciente capacidad de computación (cada 18 meses duplicaban su densidad en transistores). No sería necesario, pues, construir físicamente neuronas artificiales por medio de componentes y circuitos integrados. Serían los propios ordenadores los encargados de simular la existencia de las mismas. Y de computar en cada momento sus patrones de excitación.
Hasta mediados de los años 2000, era común que los sistemas de aprendizaje profundo fuesen implementados directamente por la unidad central de procesos del ordenador, la CPU. Sin embargo, a partir de la segunda mitad de la década de los años 2000 se generalizó el uso de las unidades de procesamiento gráfico (GPUs), que debido a sus amplias capacidades de procesamiento en paralelo, han encontrado aplicaciones como la química cuántica o la simulación de las redes neuronales artificiales.
Computación en serie y en paralelo
Dentro de la computación, existen dos grandes paradigmas: la computación en serie y la computación en paralelo. En la primera de ellas, que es típica de las aplicaciones de usuario, un único proceso se encarga de ejecutar rápidamente las reglas predeterminadas por un algoritmo (“programa de ordenador”). Es típica de las aplicaciones de usuario En la segunda, varios procesos son los que se encargan simultáneamente de ejecutar un mismo algoritmo, aunque con diferentes datos de entrada en cada caso. Es como si se ejecutaran a la vez varias copias de un mismo programa de ordenador, aunque con diferentes argumentos en cada una de ellas.
La computación paralela es de gran utilidad en el ámbito de los juegos de ordenador, dado que para rotar un cuerpo es necesario mover todos los puntos de dicho cuerpo a la vez, con arreglo a una misma ecuación matemática. Es un cálculo que puede ser paralelizado, y que es mucho más efectivo si se realiza en una GPU que en una CPU
Computación en Paralelo y Redes Neuronales
Dentro de las redes neuronales, una misma estructura algorítmica, la neurona artificial, también se repite a lo largo de las diversas capas del sistema, por lo que también en este ámbito la computación en paralelo es mucho más eficiente que la serial.
Podría decirse que la fortaleza de la computación en serie es que sus algoritmos pueden ser más complejos, y ejecutarse con mayor rapidez. En cambio, la computación paralela puede concebirse como un mecanismo de resolución de problemas mediante el uso masivo de cálculos simultáneos.
CPU frente a GPU
Estructura interna del núcleo
Los procesadores, CPU y GPU por igual, se estructuran en núcleos, que son aquellas áreas del chip donde se realizan las verdaderas tareas de computación. Cada uno de estos núcleos se compone a su vez de circuitos de control, unidad aritmético-lógica y caché.
Los circuitos de control son el elemento director del núcleo, dado que se encargan de descodificar y de ejecutar las instrucciones de código máquina, así como de comunicarse con los dispositivos de entrada y salida del ordenador.
La caché es una memoria local de cada núcleo, en el que éste almacena los datos y trozos de código más utilizados, para de este modo evitar tener que consultar con frecuencia las tarjetas de memoria DRAM situadas en la placa base (un procedimiento mucho más lento, y con una mayor latencia -retardo-). Se diferencia de la memoria DRAM en que esta última almacena los bits en condensadores, mientras que la memoria caché lo hace en circuitos biestables (transistores entrelazados entre sí).
En tercer lugar, la unidad aritmético-lógica, como su propio nombre indica, es la parte del núcleo encargado de realizar las operaciones de cálculo aritmético (ADD, SUB, MUL, DIV, etc.) y lógico (AND, NOT, etc.).
Diferencias en los procesadores
En general, una CPU tan sólo dispone de unos pocos núcleos (normalmente de 2 a 8) mientras que una GPU puede llegar a disponer de cientos o miles de ellos. Y en cada uno de estos últimos el espacio asignado a los circuitos de control y la caché se acorta a costa del incremento de la unidad aritmético-lógica. La CPU, además, tiene acceso a la memoria de la placa base y a todos los dispositivos periféricos (teclado, impresora, ratón, etc…).
Una GPU puede llegar a tener más de 1.000 núcleos, mientras que las CPUs, por lo general, no sobrepasan los 10.
Dos modelos de ejecución
En resumidas cuentas, como consecuencia del mayor tamaño de sus circuitos de control, la CPU puede ejecutar con mayor eficiencia instrucciones de código máquina (particularmente, las susceptibles de crear bucles y ramificaciones –if…then– en el algoritmo), funciona a una menor latencia y a una mayor frecuencia de reloj, puede acceder a la memoria general del ordenador, administrar los dispositivos de entrada y salida y gestionar las interrupciones (los “avisos”) emitidas por estos últimos.
Como vemos, en las GPUs la ALU (unidad aritmético-lógica) es ampliada a costa del caché y de los circuitos de control. Esto tiene como consecuencia que el juego de instrucciones que puede ejecutar una GPU es mucho menos versátil que el de una CPU. A cambio, las capacidades de cálculos de la primera son muy superiores a las de la segunda.
La GPU no puede en cambio realizar ninguna de estas tareas, y funciona a menor frecuencia del reloj. A cambio, el mayor tamaño de su unidad aritmético-lógica le permite realizar cálculos en paralelo a una velocidad mucho mayor de lo que podría realizar la CPU. En general, podría decirse que una CPU, gracias a su mayor caché y mayor eficiencia en la ejecución de instrucciones singulares, es capaz de ejecutar en menor tiempo algoritmos más complicados, y con más ramificaciones y bucles que la GPU. El poder de esta última, sin embargo, reside en su capacidad de ejecutar masivamente operaciones de cálculo (insertadas, eso sí, en algoritmos más simples).
Algoritmos más simples y ejecutados a una menor frecuencia, a costa de un mayor poder global de cálculo: esa es la divisa de la GPU.
Aplicación en GPU
En la actualidad, la programación de las GPUs suele realizarse en el lenguaje de programación C (o C++), por medio de APIs (interfaces de programación, una especie de suplemento al lenguaje en cuestión). Una de las más importantes es CUDA, desarrollada por el fabricante de tarjetas gráficas NVIDIA.
En CUDA el programador ha de especificar, por medio de etiquetas, qué funciones concretas van a ser ejecutadas por la CPU (host) y cuáles van a ser ejecutadas en paralelo por la GPU (device). Estas últimas se denominan funciones globales (global functions) o también funciones de dispositivo (device functions). Antes de que comience la ejecución de cualquier programa, el código máquina de estas últimas es transferido a la tarjeta gráfica, y queda a la espera de que se solicite su ejecución desde la CPU.
Una vez que el programa principal comienza a ejecutarse en la CPU, cada vez que sea necesario ejecutar un cálculo en paralelo en la GPU, desde la CPU se enviarán a la tarjeta gráfica los datos de entrada necesarios, que quedarán grabados en su memoria. Tras ello, se invocará la función encargada de realizar el cálculo, especificando cuántos hilos (procesos que se agrupan en bloques) en paralelo van ponerse en marcha. Una vez que concluya la ejecución de los hilos, los resultados serán transferidos a la placa base.
GPU y Redes Neuronales
El esquema que hemos reseñado puede aplicarse también a las redes neuronales artificiales. Supongamos que tenemos una red neuronal compuesta de una capa de entrada de 20 neuronas, otra capa de salida de 10, y tres capas interiores de 25 neuronas cada una. Serían necesario configurar unos 2.080 pesos sinápticos diferentes (incluyendo los sesgos), que se guardarían en el disco duro de una computadora.
Cada vez que se quiera poner en marcha la red neuronal, será necesario transferir dichos pesos sinápticos y el código máquina encargado de los cómputos a la memoria de la tarjeta gráfica. Desde la CPU se pondrá en marcha los algoritmos paralelos que calculen el valor de las funciones excitación y de activación en cada una de las neuronas. Los resultados de cada capa servirán como datos de entrada para el cálculo de los valores de la capa siguiente. Una vez que se calculen los valores de la última capa (la salida), éstos serán transmitidos por la GPU a la CPU, que los guardará en la memoria RAM de la placa base.
Evidentemente, el funcionamiento interno de las GPUs y su aplicación al ámbito del aprendizaje profundo tienen una complejidad mucho mayor de la expuesta. En este artículo hemos pretendido ofrecer una pequeña introducción sobre estos temas al lector. Le remitimos a la lectura de bibliografía especializada en el caso de que quiera profundizar en esta materia.