NVIDIA (I): arquitecturas de uso general

Rubén Rodríguez Abril

Las unidades de procesamiento gráfico (GPU) nacieron como aceleradores para videojuegos y simulaciones, pero su capacidad de cálculo en paralelo reveló su potencial en la computación de alto rendimiento. En apenas una década, NVIDIA evolucionó de desarrollar chips gráficos a arquitecturas GPU de uso general como Fermi y Pascal, vitales para avances en inteligencia artificial y aprendizaje profundo.

Las unidades de procesamiento gráfico (GPU) surgieron originariamente como aceleradores (chips auxiliares) destinados a calcular texturas y describir el movimiento de los cuerpos en el espacio, siendo utilizados fundamentalmente en el ámbito de los videojuegos y la simulación. Sin embargo, su extraordinaria capacidad de cálculo en paralelo pronto reveló su potencial para la realización de tareas de computación de alto rendimiento (high-performance computing, HPC), en campos como la biología (secuenciación de ADN y simulación de proteínas), la meteorología (virtualización de tormentas) o incluso la cosmología (simulación de choques entre agujeros negros). De ahí que en apenas una década, la compañía NVIDIA pasara de desarrollar chips de uso puramente gráfico (como las series 7800) a diseñar su primera arquitectura GPU para uso general, Fermi, que vino seguido por otras como Ampère, Volta o Hopper.

En este artículo, examinaremos brevemente la estructura básica de las GPUs de uso general de NVIDIA. En el siguiente, nos detendremos en dos de los chips de NVIDIA más utilizados en los modelos de lenguaje más potentes de la actualidad: V100, perteneciente a la arquitectura Volta y utilizado en el entrenamiento y la inferencia de GPT-1 y GPT-2, y A100, encuadrado en la arquitectura Ampère y que es el cerebro cibernético subyacente a GPT-3, Claude, Mistral y Llama.

Computación en paralelo y Deep Learning

A diferencia de las CPUs, que están especializadas en ejecutar un hilo de instrucciones con la mayor rapidez y la menor latencia posibles, las GPUs están orientadas a realizar la mayor cantidad de cálculo posible, a través de centenares (o incluso miles) de hilos.

En la computación en paralelo, un mismo algoritmo, llamado kernel, se ejecuta varias veces simultáneamente con diferentes datos de entrada cada vez (single instruction, multiple data, SIMD). Cada copia del algoritmo se denomina hilo.

Este paralelismo es adecuado, entre otras cosas, para computar el movimiento de cuerpos en el espacio. Así, durante un movimiento de rotación, todos las aristas de la superficie del cuerpo se desplazan con arreglo a la misma ecuación, si bien cada punto tiene unas coordenadas iniciales diferentes. Para calcular las nuevas coordenadas se crean tantos hilos como aristas en la superficie haya. Cada hilo ejecuta el mismo juego de instrucciones aritméticas.

Dentro del Aprendizaje Profundo sucede algo parecido. El cálculo de los valores de activación de las unidades de cada capa es un procedimiento extraordinariamente repetitivo y que por esa misma razón se ejecuta con mayor eficiencia cuando es paralelizado. Esto es lo que ha motivado que desde el año 2009 las GPUs de uso general se hayan transformado en el hardware de uso favorito por parte de los investigadores en IA, disparando los ingresos de las compañías del sector. Si en el entrenamiento de AlexNet fueron usados tan sólo dos procesadores gráficos, en la actualidad los nuevos modelos emplean centenares de ellos.

Arquitectura de una GPU de uso general

Las GPUs de uso general se dividen en varias partes:

SMs (Streaming Multiprocessors), que son las áreas del chip donde se ejecutan las instrucciones. Cada SM se encarga de la ejecución de uno o varios bloques de hilos.

GigaThread Engine: Distribuye los kernels y sus hilos entre los diferentes SMs del chip.

Caché L1: es una pequeña memoria, construida con circuitos biestables, de la que dispone cada uno de los SMs. En el caché se almacenan las variables sobre las que se van a realizar operaciones de una manera inmediata, como los valores de activación de la capa anterior, así como los pesos sinápticos de la capa actual.

Caché L2: es una memoria compartida por todos los SMs del chip. En ella se guardan los valores de activación de la capa anterior.

HBM2: es una memoria DRAM externa con un tamaño de 40 GB, presente a partir de la arquitectura Pascal (2016). Está situada en el exterior del chip y se comunica con éste a través de un interposer de silicio, tal y como se muestra más abajo en este mismo artículo. En ella se almacenan todos (o buena parte de) los pesos sinápticos del modelo (si éste es lo suficientemente pequeño).

Figura 1: Esquema de la estructura de una GPU de uso general. En azul-verde están dibujados los SM, encargados de ejecutar las instrucciones de los hilos. Las unidades de memoria se colorean de verde claro. Se distingue entre una memoria externa DRAM situada fuera del circuitaje de la propia GPU, y dos memorias internas integradas dentro del chip, los cachés L1, privativos de cada SM, y el caché L2, común a todos ellos. Fuente: NVIDIA.

A menudo, los modelos de lenguaje son tan grandes que es necesario utilizar decenas o incluso centenares de GPUs durante sus fases de entrenamiento e inferencia. En tales casos, las GPUs se comunican entre sí a través de PCI Express, un interfaz estándar para comunicación entre la GPU, la CPU y otros componentes de ua placa base, o a través de VLink, un sistema de comunicación específica para GPUs diseñado por NVIDIA.

SM (Streaming Multiprocessors, Multiprocesadores de transmisión)

Los SM son el elemento central de las GPUs de uso general (GPGPUs). Es el lugar donde se ejecutan las instrucciones y se realizan los cálculos matemáticos. Como ya se señaló con anterioridad, en CUDA, las funciones se llaman kernels y las diferentes instancias de las mismas se llaman hilos (threads), que a su vez se agrupan en bloques (blocks). Los hilos son “copias” de una misma función, que se ejecutan paralelamente, pero con diferentes datos de entrada. Cada SM procesa uno o varios bloques de hilos. El programa determina cuántos bloques se lanzan por cada función y de cuántos hilos se compone cada bloque.

Figura 2: Esquema de una SM de arquitectura Fermi. Los circuitos de control (en naranja) envían instrucciones a las unidades aritméticas (en verde) para que sean ejecutadas allí. Entre las unidades aritméticas se distingue a los núcleos CUDA (“CUDA cores”), que realizan una operación en enteros (INT) o en coma flotante (FP) por ciclo del reloj, las unidades de carga y descarga (LD/ST), que como su propio nombre indica cargan y descargan operandos y resultados en las áreas aritméticas, y las unidades de funciones especiales (SFU), que calculan funciones como el seno y el coseno. Los núcleos y las unidades de carga y descarga pueden realizar una operación por ciclo del reloj. Las SFUs, cuatro. En azul, por su parte, se describen las diferentes áreas de memoria.

Dentro de un SM se distinguen las siguientes secciones:

Núcleos CUDA, que cumplen el papel de la unidad aritmético-lógica de una CPU, ejecutando operaciones sobre enteros (INT8, INT16) o sobre números en coma flotante (FP).

Unidades de funciones especiales (SFU), que calculan funciones trigonométricas y exponenciales.

Unidades de carga y descarga (LD/ST), que realizan operaciones de transferencia de datos (generalmente, operandos y resultados) entre los registros y el caché.

Tensor Cores (Núcleos tensoriales), introducidos a partir de la arquitectura Volta (2017) y que realizan operaciones de multiplicación matricial. Dado que en la imagen se describe la arquitectura Fermi (2010), no están dibujados en la misma.

Caché L1, que guarda los datos sobre sobre los que se van a realizar operaciones matemáticas de un modo inmediato.

Caché L0 o caché de instrucciones, que almacena las instrucciones a ejecutar inmediatamente.

Circuitos de Control, que extraen las instrucciones del caché L0, las decodifican y las envían a los núcleos para su ejecución. En la organización de la computación paralela de hilos goza de una importancia fundamental el concepto de warp, que es una agrupación de 32 hilos. En cada SM, los diferentes warps de un bloque se van “turnando” para su ejecución. A cada ciclo del reloj los circuitos de control transmite las instrucciones aritméticas de código máquina de las que se componen los hilos del warp a los núcleos CUDA para que procedan a su ejecución. Cada núcleo ejecuta una instrucción (en un hilo) por ciclo del reloj. Cuando concluye la ejecución de todas las instrucciones de todos los hilos del warp, se pasa al warp siguiente. Los circuitos de control se estructuran en dos: el programador de warps (warp programmer) y la unidad de envío (dispatch unit).

Registros, que es son ternas de números naturales (32 bits) que describen el estado de máquina de cada hilo. En la arquitectura Fermi, hay 63 registros por cada hilo.

Figura 3: Los hilos de un warp, en ejecución. El programador de warps (warp scheduler) va turnando a cada uno de ellos dentro del SM para su ejecución. Durante esta última, las unidades de envío (dispatch units) transmiten las instrucciones (FADD, FFMA, etc…) a las diferentes unidades aritméticas para que sean ejecutadas. La dimensión temporal se representa verticalmente en el diagrama. Cada rectángulo representa una instrucción. Su anchura horizontal (en cuadritos) representa el número de unidades en las que se ejecutan, su anchura vertical, la cantidad de ciclos del reloj invertidos. Así, para ejecutar la función recíproca (RCP) o la función seno (SIN) es necesario que transcurran ocho ciclos del reloj (“ocho cuadritos”). Para instrucciones de suma (FADD) o carga, sólo son necesarios dos.

Tipos de memoria

En una GPU están presentes los dos grandes tipos de memoria:

Memoria SRAM, formada a partir de circuitos construidos con transistores entrelazados entre sí, y en los que la corriente eléctrica transcurre en forma de lemniscata. Los valores binarios son representados por la presencia o ausencia de corriente. Este tipo de memoria tiene una menor latencia, pero energéticamente es mucho más cara. Es la materia de la que están hechas los dos cachés (L1 y L2) del chip.

Memoria DRAM, formada por filas gigantescas de condensadores controlados por transistores. Los valores binarios son representados por la presencia o ausencia de carga almacenada. Dado que los condensadores deben ser recargados periódicamente, se dice que la memoria es dinámica. Tiene una mayor latencia, aunque energéticamente es más barata. En una GPU, es externa al circuitaje del chip. Desde la arquitectura Pascal (2016), los sistemas especializados en IA suelen usar el sistema HBM (high band memory, memoria de banda ancha), que se muestra esquemáticamente en la imagen.

Figura 4: La memoria HBM aparece descrita en la esquina superior derecha. Chips de silicio llenos de condensadores se apilan sobre el controlador HBM. Un interposer de silicio comunica a la memoria con la GPU a través de 1024 pistas de datos. Fuente: Wikipedia.

Conclusiones

Las GPUs de uso general de NVIDIA, y particularmente los pertenecientes a las arquitecturas Fermi y Pascal, fueron el verdadero cerebro digital de los modelos de Visión Artificial y de Procesamiento de Lenguaje Natural surgidos entre los años 2010 y 2017: AlexNet, GoogLeNet, los sistemas de traducción seq2seq o incluso experimentos oníricos como DeepDream son ejemplos de ellos. De hecho, la GPU NVIDIA P100 fue la que sirvió de base al modelo primigenio de transformer pergeñado en el trabajo “Attention is All You Need”.

Sin embargo, los diseñadores de NVIDIA consideraron que era necesario crear unidades especiales dentro de los Sms que fuesen capaces de ejecutar. Para ello fueron creados los Tensor Cores, presentes en las arquitectura Volta y Ampere, objeto del siguiente artículo de esta serie.

SERIES

Deep Learning

Mistral AI