logo

Dot CSV

Inteligencia Artificial, Tecnología, Ciencia y Futuro! Bienvenidos a la 4ª Revolución Industrial 🚀 Inteligencia Artificial, Tecnología, Ciencia y Futuro! Bienvenidos a la 4ª Revolución Industrial 🚀

Transcribed podcasts: 213
Time transcribed: 5d 22h 50m 3s

This graph shows how many times the word ______ has been mentioned throughout the history of the program.

No son pocas las veces que me habrán escuchado decir en el canal que entrenar a tus modelos
de deep learning sobre una GPU es mucho más rápido que hacerlo sobre una CPU.
También son muchas las ocasiones en las que habré dicho que uno de los elementos claves
que ha permitido que esta revolución de la inteligencia artificial surja ha sido el desarrollo
de la aceleración por hardware.
Pero por qué?
¿Por qué?
¿Qué diferencia hay entre una CPU o una GPU?
¿Cómo funcionan estos procesadores internamente y por qué podemos conseguir que nuestros
modelos de deep learning se entrenen más rápido si los utilizamos?
En el vídeo de hoy nos vamos a meter en el mundo del hardware para poder entender y desarrollar
una intuición sobre esto.
Como no puede ser de otra manera, este vídeo está patrocinado por NVIDIA, lo cual me hace
muchísima ilusión y claro, eso significa que vamos a estar hablando de tensor cores,
de precisión mixta, de CUDA y de todos estos elementos que hacen que podamos sacarle todo
el partido a nuestros modelos gracias al uso de las GPUs.
Hoy se viene un café de los fuertes, de los largos, de los densos, de los que les gusta
a muchos de los cafeteros, así que si tú estás listo, yo estoy listo, ¡comenzamos!
Pero antes quiero contaros una cosa que os va a interesar de la mano del sponsor de este
vídeo, que es NVIDIA.
Y es que eso es mucho lo que me escribís por redes sociales diciéndome, Carlos, voy
a empezar a estudiar informática o una ingeniería o arquitectura y necesito un equipo, un portátil
que me pueda llevar a la universidad y que me dé el rendimiento, la potencia para poder
trabajar con de manera cómoda o ahora que estamos entrando en septiembre por gente
que quiere aprender nuevas cosas, pues quiero aprender Inteligencia Artificial o quiero
aprender Data Science y necesitas un equipo que te dé todo ese rendimiento.
Ya saben que yo desde que comencé el canal de YouTube hasta hace muy poquito todo el
trabajo que hacía tanto de Inteligencia Artificial, Data Science como edición de vídeo para
YouTube, pues lo trabajaba todo con un portátil.
Para eso tienes que invertir en un buen procesador, en tener memoria lo suficiente y muy importante
en una buena tarjeta gráfica.
Y es por eso que te recomiendo que si vas a invertir en un portátil, pues le eches
un vistazo a la nueva campaña de envidia de portátiles GeForce.
¿Por qué?
Pues como ya hemos dicho, si tú vas a trabajar con Inteligencia Artificial, Machine Learning,
es muy importante contar con una buena tarjeta gráfica que sea capaz de acelerar todo el
entrenamiento de tus modelos.
O por ejemplo, si te vas a dedicar ahora a edición de fotografía o edición de vídeo,
es muy importante contar con una tarjeta gráfica que te dé el rendimiento que estás buscando.
Y en este caso, pues las tarjetas GeForce con la plataforma CUDA vienen muy bien integradas
con todo el software de edición para conseguir un rendimiento extra, que la verdad es que
vienen muy bien.
En este caso, para esta promo, Nvidia me ha prestado uno de estos portátiles que ofertan
a través de su página, un Aero 15GB, y lo he estado probando durante todos estos días
para poder hacer una valoración en condiciones y es una auténtica pasada, ¿vale?
Este bicho internamente tiene una tarjeta gráfica GeForce 2060, que no solo te va a
servir para trabajar en tus proyectos de Machine Learning o de diseño gráfico, sino que también
si lo quieres dedicar para jugar, para entretenerte, pues también te va a servir.
He probado por curiosidad a ver si este portátil era capaz de mover mi gafas de realidad virtual,
¿vale?
Ya saben que soy un fan de esta tecnología y ya saben que esta tecnología te exige
bastante rendimiento a nivel gráfico.
En este caso lo he probado y efectivamente este portátil tiene rendimiento de sobra
para poder mover esta gafas de realidad virtual.
Y no solo eso, sino que si además optas por un portátil de la serie RTX, pues podrás
acceder a la última tecnología en renderización de gráficos, que es el Ray Tracing en tiempo
real.
Es justamente esta tecnología que te permite jugar a los juegos con un nivel de realismo
gráfico increíble, ¿vale?
O sea, mirad cómo se ve el Minecraft con el Ray Tracing activado.
Una auténtica pasada.
Así que nada, si te estabas pensando pues invertir dinero en un nuevo portátil de cara
a septiembre, de cara a la vuelta al colegio, a la vuelta a la universidad, a la vuelta
al trabajo, pues que sepas que puedes consultar los diferentes portátiles GeForce que ofrece
Nvidia en su página en la nueva campaña que están sacando este septiembre.
Portátiles ligeros, de buen diseño y sobre todo potentes, ¿vale?
Tenéis el link abajo en la descripción.
Para el que ando un poco perdido, empecemos por lo básico.
Cuando hablamos de CPUs o de GPUs, a lo que nos estamos refiriendo son a los tipos de
procesadores que te puedes encontrar en tu ordenador convencional y cuya funcionalidad
básicamente es la de procesar, ejecutar las instrucciones que conforman el software.
Y claro, que es un procesador, puede dentro o fuera.
Si recuerdas tus clases de tecnología del colegio, recordarás que hay un elemento electrónico
que se llama el transistor, cuya funcionalidad una de ellas es la de permitir pasar la corriente
de este punto de aquí a este punto de aquí cuando se aplica un ligero pulso eléctrico
en este punto de aquí.
Básicamente actuaría algo así como un interruptor.
Esto conformaría una especie de unidad lógica que podemos utilizar, que podemos combinar,
para desarrollar lógicas más avanzadas.
Por ejemplo, si tenemos dos transistores los podemos combinar de esta manera para que así
este pulso de salida solamente se active cuando se activen estos pulsos de entrada.
Si te das cuenta lo que estaríamos conformando aquí sería el circuito electrónico de lo
que sería una puerta lógica AND.
De la misma manera podríamos conformar otro tipo de circuitos para tener puertas lógicas
OR, puertas lógicas SHOR, puertas lógicas NAND, todas las puertas lógicas.
Y bueno, con esto ya tendríamos puertas lógicas.
Pero esto solo es el principio, porque ahora si cogemos todas estas puertas lógicas las
podríamos combinar unas con otras para juntando operaciones lógicas desarrollar lógicas más
avanzadas.
Por ejemplo, juntando unas cuantas puertas lógicas AND y unas cuantas puertas lógicas
SHOR podemos conformar un circuito eléctrico que sea capaz de, por ejemplo, realizar una
operación de suma a nivel de bits.
Y continuamos, porque si tú ahora coges este circuito y lo combinas con otro circuito que
también pueda ser capaz de sumar, podemos generar un circuito que no solo sea capaz
de sumar bits, sino que sea capaz de sumar números de mayor magnitud.
Y si se fijan con esto ya habríamos creado un circuito eléctrico que es capaz de sumar
números.
Pero no nos quedemos solo con eso, también podríamos generar otro circuito eléctrico
que fuera capaz de realizar una resta, o otro que fuera capaz de realizar una comparación
entre números, o otro que fuera capaz de realizar el número complementario de un número.
Podemos realizar diferentes tipos de operaciones matemáticas.
Ahora, si cogemos todos estos circuitos y los integramos en un único módulo donde
yo pueda insertar dos datos de entrada, dos números, y pueda elegir que operación quiero
que se realice, por ejemplo la suma, la resta, la igualdad, con esto lo que estaríamos conformando
es un módulo que se conoce como una unidad aritmético lógica.
Podríamos decir que es algo así a nivel de circuitos como una calculadora, pero en realidad
si se fijan lo que tenemos ya aquí es una unidad de procesamiento que es capaz de realizar
diferentes tipos de instrucciones.
Pero no nos quedemos solo con eso, porque a ver, si volvemos de nuevo a los transistores,
podemos combinarlos de esta manera formando un bistable para poder generar un circuito
que tenga la funcionalidad de poder almacenar el estado de un bit, es decir, estaríamos
creando una especie de sistema de almacenamiento.
Con esto podríamos empezar a conformar cosas más grandes como podrían ser los registros
de memoria o la celda de memoria, cosa que va a ser interesante para continuar porque
podríamos utilizarlos para guardar datos, los números que estamos utilizando para hacer
estas operaciones matemáticas, pero no solo eso, porque también podríamos guardar el
tipo de operación que queremos que se ejecute en nuestra unidad aritmético lógica, en
nuestra ALU.
Por ejemplo podría tener en mi memoria dato número 1, dato número 2 y la operación
suma que quiero que se realice con estos dos datos, después dato número 3, dato número
4 y la operación resta que quiero que se realice con nuestros datos.
Si se fijan aquí ya estaríamos almacenando un tipo de programa, un tipo de software,
una serie de instrucciones que han de ser ejecutadas, que han de ser procesadas.
Pero oye, es que a lo mejor yo lo que quiero hacer es que en función del resultado de haber
ejecutado una instrucción, pues se ejecute un tipo de instrucción o otra, o que a lo
mejor saltemos alguna dirección de memoria diferente para guardar el resultado de la
operación que acabo de hacer.
Esto lo podríamos hacer en realidad de manera muy sencilla porque seguramente tengamos un
registro de memoria donde esté guardada la dirección de memoria en la que estamos leyendo
la instrucción a ejecutar ahora.
Claro, esta dirección de memoria no deja de ser un número que nosotros podríamos
pasar por nuestra unidad aritmética lógica para sumar o restar posiciones, con lo cual
no debería ser tan complicado generar un tipo de instrucción que podemos llamar go-to,
que te permita avanzar a diferentes puntos de la memoria.
Es decir, ya estaríamos empezando a implementar flujos de control más avanzados.
Y claro, te vas a mover a una posición de memoria porque vas a leer un dato que vas
a utilizar para otra operación o porque vas a guardar el resultado, vas a leer o vas
a escribir.
Al final te das cuenta de que necesitas generar también una serie de instrucciones que te
permitan controlar todos estos elementos, y esta lógica la puedes implementar y la
puedes integrar en lo que se conoce como una unidad de control.
Todos estos elementos que acabo de poner sobre la mesa, a grandes racos, son los elementos
que te podrías encontrar en un procesador convencional como el que funciona dentro de
tu ordenador.
Esta arquitectura, donde tenemos una unidad aritmética lógica, unidad de control, registros
y memoria, donde están almacenados tanto los datos como las instrucciones del programa
a ejecutar, conformarían lo que se conoce como una arquitectura de von Neumann.
Lo interesante aquí, si te fijas, es que esto no deja de ser una combinación de transistores,
de circuitos eléctricos y de módulos donde hay implementadas una serie de instrucciones
que nosotros hemos definido, es decir, tenemos una unidad aritmética lógica con una serie
de instrucciones como suma, resta, multiplicación a nivel de bits, que al final no dejan de
ser aquellas operaciones, aquellas instrucciones que yo he decidido que estén ahí dentro.
A todo este conjunto de instrucciones que emergen de la arquitectura, del diseño, del
procesador que hemos implementado, se le conoce como conjunto de instrucciones, tampoco tiene
un nombre muy extraño.
Y esto es muy interesante y muy bonito, porque es el diseño, de la arquitectura del procesador
que nosotros hemos definido, de donde surgen estas instrucciones, estos bloques individuales,
que al final es sobre lo que se cimienta todo el stack de software que normalmente acostumbramos
a utilizar en nuestro ordenador.
Ya sabes entonces cómo sería el funcionamiento de una CPU, un procesador, que te va a servir
tanto para hacer renderización de gráficos 3D, como para calcular números, como para
jugar al Fortnite, como para hacer que el sistema operativo de tu ordenador funcione,
un editor de texto, un editor de vídeo.
Todo esto funciona a partir de las instrucciones que vienen definidas en el procesador de tu
ordenador.
Miro para allá porque ahí donde está mi ordenador, ¿vale?
Esta capacidad que tiene nuestro procesador de poder ejecutar instrucciones para programas
tan diferentes es lo que hace que a un procesador como la CPU se le conozca como un procesador
de propósito general, es decir, que sirve para todo, tanto para un roto como para un
descosío.
Pero ojo, porque que un procesador sea de propósito general nos viene a hablar de
su versatilidad a la hora de ejecutar diferentes tipos de programas, tanto un programa de edición
3D, como el Fortnite, como la calculadora.
Pero esto no quiere decirte que este procesador esté especializado para ejecutar de la manera
más óptima cada uno de estos programas.
Por ponerte un ejemplo, imagínate que yo estoy haciendo un programa de análisis en
tiempo real de los datos que me llegan en un coche autónomo.
Buen momento para recordar que tienes pendiente de ver mi último vídeo sobre coches autónomos
que subí la semana pasada.
Pues vale, para hacer este análisis tengo que implementar una red neuronal que tiene
que estar muy optimizada, tiene que trabajar muy rápido para dar ese rendimiento en tiempo
real.
Para ello, pues podría aprovecharme de la aceleración por hardware y coger algunos
de estos puntos de la ejecución de la red neuronal que sean más repetidos, más críticos
y optimizarlos al máximo dentro de mi procesador.
Para eso voy a programarlo en bajo nivel y de hecho voy a bajar tanto que voy a trabajar
solamente con las instrucciones a nivel máquina, las instrucciones que me da el procesador.
Por ejemplo, voy a intentar optimizar mi función de activación relu, que como ya saben es
para un valor mayor de 0 devolvemos ese valor, para un valor menor de 0 devolvemos 0.
En este caso tendría que hacerlo con las instrucciones de mi procesador, lo cual sería
algo así como, voy a memoria, leo el valor de entrada, comparo si ese valor es mayor
o igual que 0, si es mayor que 0, voy a memoria y escribo el mismo valor o devuelvo el valor
donde lo había leído y si es menor que 0, pues tengo que devolver el valor de 0 ¿vale?
Estas son varias instrucciones que tienen que ser ejecutadas en diferentes ciclos de
reloj, pero ¿y si quisiera hacerlo más rápido todavía?
Y si no quisiera ejecutar todos estos ciclos de reloj, sino que fuera instantáneo, pues
una de las cosas que podría hacer sería rediseñar mi chip, rediseñar por ejemplo
mi unidad aritmética lógica para incluir un circuito, un circuito eléctrico juntando
transistores cuya lógica implementada sea la de la puerta lógica relu.
Esto significaría que ahora mi set de instrucciones, en mi conjunto de instrucciones que emerge
de la arquitectura de mi procesador, contaría con una nueva instrucción, la instrucción
relu, y esta instrucción relu la podría ejecutar en un único ciclo de reloj, con lo cual sería
mucho más rápido de la ejecución de toda la red neuronal.
A esto le llamaría un chip de tipo, no sé, Activation Function Chip y mi departamento
de marketing le llamaría Neural Tensor Chip 3000, vale, pero bueno, más o menos entender
la idea.
Con todo esto lo que quiero que entiendas es que al final, aunque tu CPU, tu procesador
de propósito general en tu ordenador, sea muy bueno para ejecutar cualquier tipo de
cosa, si nosotros intentáramos diseñar un chip que estuviera optimizado para resolver
una única tarea, pues podríamos conseguir un rendimiento superior al que notarían solamente
la CPU.
Esto es interesante porque es algo similar a lo que ocurre con las arquitecturas de redes
neuronales.
Tú al final, teóricamente, con un multilayer perceptron, con una red neuronal típica, puedes
resolver o aproximar la solución de cualquier tipo de problema.
Pero esto no quiere decir que tú no puedas encontrar una arquitectura mejor, por ejemplo,
para imágenes y vídeos, que esté más optimizada para ese tipo de datos y que por tanto, a
nivel teórico no, sino a nivel práctico, te pueda dar un mayor rendimiento, como sería
el caso de las redes neuronales convolucionales.
Y ahora que lo he dicho, oye, imágenes y vídeos, ¿tendríamos algún tipo de arquitectura
de chips que estuviera optimizado su diseño para el procesamiento de imágenes y vídeos?
Dentro cabecera.
Y es que sí, si la CPU de tu ordenador está pensado para ser un procesador de tipo general,
cuando hablamos de la GPU, de la Graphic Processing Unit, la unidad de procesamiento gráfico,
nos estamos refiriendo a un procesador de propósito específico, cuya tarea especializada,
cuya tarea óptima a resolver, sería la de procesar gráficos.
Y ahora sí nos metemos en materia, porque vamos a empezar a ver cuáles son las diferencias
entre estos dos procesadores.
Y si tuviéramos que decir en un único headline cuál es esta gran, gran, gran diferencia,
la encontraremos cuando lo miremos en un microscopio en el número de núcleo de procesamiento
que tiene cada una.
Y esto es muy importante lo que representa, porque si pensamos en un procesador tradicional,
una CPU de un único núcleo, lo que esto nos quiere decir es que este procesador solamente
va a tener esta combinación de unidad aritmético-lógica, unidad de control y registro de memoria, que
te va a permitir ejecutar instrucciones de manera secuencial.
Es decir, solamente va a poder ejecutar una instrucción por ciclo de reloj.
Por el contrario, si tienes un procesador con más de un núcleo, como es el caso de
la GPU, que tiene muchísimos, muchísimos núcleos, esto lo que te va a permitir es
poder ejecutar varias instrucciones al mismo tiempo, es decir, ejecución en paralelo.
Y a lo mejor estás pensando, pero Carlos, ¿cómo me dices que un procesador con un
único núcleo ejecuta todo de manera secuencial si el Pentium 1 de mi primo tiene un único
núcleo y, por ejemplo, yo lo he visto jugar al solitario y escuchar Spotify al mismo tiempo?
¿Qué dices, Carlos?
Pues yo te diría, a ver, esto no es una clase de sistema operativo de la carrera de informática,
o sea, ¿qué demonios me estás preguntando?
Además, dila a tu primo que cambie de ordenador porque eso ya está un poco, vale, quizás
un poco agresivo, ¿no?
Mejor te respondo, mira.
El hecho de que tu procesador solo tenga un único núcleo y que eso signifique que las
instrucciones se ejecutan de manera secuencial no quita que la CPU sea capaz de ejecutar
estas instrucciones secuenciales muy, muy, muy rápido.
Algo que hace que, por ejemplo, el sistema operativo de tu ordenador pueda trabajar y
trucar esta ejecución en serie para repartir los tiempos de ejecución de diferentes programas
y decirle ahora un poquito al solitario, ahora un poquito a Spotify, ahora un poquito
al solitario, ahora un poquito a esta página de internet que deberías de estar viendo
y con esto para el usuario final las sensaciones que estás trabajando con diferentes aplicaciones
en paralelo, aún cuando la ejecución sigue siendo en serie.
Y claro, esto pasa sí, sí, es como he dicho, un procesador de un único núcleo, pero la
tendencia es que en los últimos años hemos visto como han surgido en el mercado procesadores
con dual core, quad core, octa core.
Entonces al final esto lo que te permite es lo que he dicho, ejecutar de manera muy rápida
pues instrucciones en paralelo a más núcleos tienes, pues más instrucciones puedes ejecutar
al mismo tiempo, ¿vale?
Y esto es hablando de la CPU, pero una GPU ¿cuántos núcleos tiene?
Tiene 4, 8, 16, pues no amigos, aquí estamos hablando de un procesador que puede tener
en el orden de cientos y miles de núcleos.
Y vale, ahora mismo debería de estar haciéndote una pregunta en la cabeza, pero antes de que
te la responda, te voy a dejar con el típico vídeo que hay que poner en este momento de
cazadores de mitos donde se va a evidenciar por qué trabajar con muchos núcleos al mismo
tiempo, es una buena idea.
3, 2, 1.
Let me speed it up, ladies and gentlemen, Leonardo, can't point out, 6, 5, 4, 3, 2, 1.
Wow, un clásico de internet, ninguna explicación de GPU sin el vídeo de cazadores de mitos,
por favor.
Bueno, ¿qué?
¿Ya tienes la pregunta?
Sí, ¿no?
La pregunta sería algo así como, pero Carlos, ¿son los núcleos de la CPU y de la GPU iguales?
Pues la respuesta evidentemente es que no, si no nos preguntaremos por qué una CPU no
añade más núcleos como hace la GPU.
En este caso el núcleo que añade una GPU son a nivel de circuitos mucho más sencillos,
al final estos núcleos se tienen que especializar solamente en hacer operaciones matemáticas
muy rápidas, son ALUs, de las que hemos visto en un principio, que ejecutan unas pocas instrucciones,
como la idea de este chip es no ser de propósito general, pues no tiene que hacer un repertorio
de instrucciones tan amplio.
Además de esto, también el número de memoria, de registro de memoria que se dedica por núcleo
en una GPU es mucho menor que el que se hace en una CPU, con lo cual ya esto nos empieza
a plantear una serie de diferencias entre estos dos chips.
Así que en resumen, y toma nota porque esto va para examen, a nivel funcional la diferencia
entre estos dos chips es que la CPU, su obsesión es la de ejecutar muy rápidamente instrucciones
en serie que sean de propósito general, y por el contrario la GPU lo que busca es su
obsesión es la de ejecutar el mayor número de instrucciones al mismo tiempo, o dicho
de manera más técnica, fomentar el paralelismo.
Y si con el vídeo de cazadores de mitos todavía no te ha quedado claro por qué el paralelismo
nos interesa, déjame que te ponga otro ejemplo.
Imagínate una operación como multiplicar dos matrices.
Ya sabes, si te has enfrentado alguna vez a programarlo, cómo sería la multiplicación
de dos matrices, que esto implica hacer la suma acumulada del producto escalar de cada
uno de los elementos entre una fila y una columna de las matrices que estamos multiplicando.
Esta operación, si la trasladaras a código, implicaría recorrer todas las filas y columnas
de la matriz resultante y recorrer todos los elementos de las filas y las columnas, algo
que se traduciría en tres bucles for anidados y que a nivel computacional es desastroso,
tiene un orden computacional de n al cubo.
Esto significa que, a lo mejor para ejecutar una multiplicación de matrices pequeñas,
una CPU nos puede venir bien ejecutarlo secuencialmente, pero cuando el tamaño de las matrices se incrementa
la cosa se nos va de madres.
¿Cómo podríamos intentar acelerar esto?
Pues si nos aprovecháramos del paralelismo de la cantidad de núcleos que nos ofrece
una GPU, lo que podríamos hacer aquí es decir, oye, cada uno de los elementos resultantes
de la matriz final lo podríamos calcular de manera independiente, al final el valor
de la celda 1-1 no tiene nada que ver con el resultado de la celda 5-28, con lo cual
estos dos valores los podríamos calcular como si fueran operaciones diferentes, y puesto
que nuestro procesador, nuestra GPU nos permite tener hilos de ejecución muchísimos al
mismo tiempo, lo que podemos utilizar es el paralelismo para distribuir esta ejecución
y que cada uno se vaya calculando de manera simultánea y el orden de computación de
esta multiplicación de matrices se vería reducido drásticamente.
Y ojo porque he usado aquí la multiplicación matricial de manera no trivial, y es que al
final lo hemos dicho antes, esto es la GPU, la Graphic Processing Unit, y no sé si estarán
familiarizados con cuál es el pipeline de procesamiento de gráficos en un ordenador,
pero lo normal es que si tú tienes un modelado entre T, que puede ser esto de aquí, pues
esto se puede componer en diferentes triángulos cuyos vértices vienen representados por vectores
en un espacio tridimensional.
Estos vectores, si tú quieres aplicar una transformación de rotar, trasladar o escalar
el objeto, lo que tienes que hacer es multiplicarlos por unas matrices que apliquen estas transformaciones,
no sólo eso, sino que también si tú tienes este objeto en tridimensiones y lo quieres
convertir ya a una imagen bidimensional para mostrarlo en pantalla, tienes que proyectarlo
a través de la utilización de lo que se conoce como una Projection Matrix, además
cada uno de los vértices que conforman a nuestro modelo lo podemos procesar de manera
independiente aprovechándonos del paralelismo de nuestro procesador en cada uno de los núcleos,
y esto no para aquí, además estos vértices cuando los quieres convertir a píxeles, cada
uno de estos píxeles también de manera independiente los puedes procesar aplicando productos escalares
que en este caso van a calcular cuál es la iluminación, el sombreado, la textura que
tenemos que aplicar a cada uno de estos píxeles.
Si te fijas todo este Pipeline es una sucesión de matrices y productos escalares, que por
cierto, ¿a qué me sonará eso?
Bueno, al final un conjunto de tareas que por su naturaleza están muy preparadas para
sacarle todo el partido a este procesamiento multi y lo que nos ofrece la tarjeta gráfica,
de ahí que esta tarjeta gráfica pues su propósito específico sea el de procesar gráficos
en el ordenador.
Y bueno, he de confesar algo, ¿vale?
Pues les he mentido, antes he dicho que sí, que la tarjeta gráfica su propósito específico
es el de procesar gráficos, pero esto ya no es del todo cierto y es que la tendencia
en los últimos años ha sido la de decir, oye, vale, perfecto, esta tarjeta su paralelismo
a lo mejor viene muy bien para procesar gráficos, sí, pero es que a lo mejor también hay otra
serie de problemas que también pueden ser descompuestos y ser beneficiados por la utilización
de este paralelismo.
Hablamos de problemas como la computación de dinámicas de fluidos y de partículas,
de modelado climático para finanzas computacionales, para criptografía, minado de criptomonedas,
renderizaciones de imágenes médicas, modelado de moléculas y al final muchas otras tareas
y campo de estudio que se puede haber beneficiado el paralelismo al dividir la tarea en diferentes
subtareas independientes que pueden ser ejecutadas en diferentes núcleos.
Estamos entrando aquí en un campo súper interesante que es el uso de las tarjetas
de gráficas con propósito general, un campo que en inglés se conoce como General Purpose
Graphic Processing Unit o por las siglas GPGPU, y que bueno, donde todas estas tareas tienen
cabida y por supuesto, como no, también entra el Machine Learning.
Efectivamente es el Machine Learning, el Deep Learning, uno de estos campos donde tenemos
por ejemplo muy utilizadas las redes neuronales, que no dejan de ser una tarea que se puede
ver muy beneficiada como ya sabemos por el uso de las GPUs.
Por eso cuando surge esta nueva tendencia de la utilización de las tarjetas gráficas
con propósito general, pues empezamos a pensar si podemos combinar estas dos tecnologías
para sacar un mayor rendimiento.
Inicialmente el uso de propósito general de las tarjetas gráficas se veía muy limitado
por el diseño muy orientado a computar gráficos que tenían estas tarjetas, y no fue hasta
2007 que Nvidia sacó una plataforma que te permitía poder utilizar estas tarjetas gráficas
programarlas para resolver otro tipo de tareas.
Como te puedes dar cuenta, esto es una de las piedras angulares para poder empezar a
utilizar con propósito general esta tecnología.
A lo mejor todo esto del uso de propósito general de las tarjetas gráficas te sonaba
o no, pero lo que tengo claro es que el nombre de esta plataforma si te lo conoces, y es
CUDA.
Es con el lanzamiento de CUDA o CUDA con el que se empieza a generalizar el uso del paralelismo
pues para resolver otro tipo de tareas, y no tardo mucho en el momento en el que el
noviazgo con el deep learning surgió.
Es dos años más tarde, en 2009, cuando se publica el siguiente paper donde se presenta
los resultados de entrenar a una red haciendo uso del paralelismo que ofrece una tarjeta
gráfica en comparación con una CPU.
Los resultados que arroja en este artículo era que el uso de la GPU daba una velocidad
de entrenamiento de 70 veces más rápido que hacerlo sobre una CPU multinúcleos, algo
que creo que todos podemos coincidir en que es bastante revolucionario.
Estamos hablando de que un modelo de deep learning pase a estar varias semanas entrenándose
a solamente un día, algo que favorece notablemente el poder experimentar con este tipo de arquitecturas.
Y al final aquí está la clave de todo, es el poder entrenar a tus modelos de manera
mucho más rápida, el poder iterar en diferentes tipos de arquitectura, el poder experimentar
uno de esos elementos clave que permitió que durante la década de 2009 a 2019 hayamos
tenido toda esta explosión de diferentes arquitecturas y avances y progresos en el campo
del deep learning, algo que sin duda tenemos que agradecer a la aceleración por hardware.
Y creo que ya podemos responder a esta pregunta, ¿por qué se consigue esta aceleración por
hardware?
¿Por qué las redes neuronales es un tipo de problema que se puede ver favorecido por
el uso del paralelismo?
Pues bien, si te fijas el entrenamiento de una red neuronal es una tarea altamente paralelizable.
Tú ya sabes que cuando entrenas a una red neuronal que está compuesta por muchas capas
con diferentes neuronas, cada una de estas neuronas está realizando un cálculo, una
computación que realmente es independiente de cada una de las otras neuronas.
Este cálculo no solamente es independiente, sino que es el mismo en cada una de las neuronas
que conforman la misma capa.
Y este es uno de los ingredientes básicos que hace falta para poder distribuir esta
tarea en los diferentes hilos de procesamiento que tiene nuestra GPU.
Pero no solo eso, sino que tú también sabes que cuando estás entrenando a una red neuronal,
tú realizas un forward pass donde los datos de entrada pasan por toda la red, generan
un resultado, se computa una función de error y luego se realiza el proceso de back propagation.
¿Qué pasa? que esta función de error tú no la calculas solamente para un único dato
de entrada, sino que normalmente entrenas por lotes.
Por lo tanto, cada uno de los datos de entrada que conforman a tu lote también pueden ser
procesados de manera independiente.
Imaginemos que estás trabajando con un batch size, un tamaño de lote de 16 imágenes.
Pues cada una de estas 16 imágenes pueden ser procesadas en el forward pass de manera
independiente en cada uno de los hilos de procesamiento que tenga nuestro procesador,
nuestra GPU.
De nuevo, sujeto al paralelismo.
Como paralelismo, paralelismo, no paro de repetir paralelismo, pero es que al final
este es uno de los elementos clave que hace que las GPU sean tan buenas para poder entrenar
redes neuronales.
Pero ojo porque no es el único elemento que entra en juego aquí.
Hay más elementos que diferencian a una CPU de una GPU, por ejemplo el ancho de banda
de la memoria o lo que viene a ser la cantidad de información que podemos traer o llevar
a la memoria por unidad de tiempo.
En este caso, si lo que queremos es ejecutar el mayor número de instrucciones gracias
al paralelismo, al mismo tiempo, es muy importante que cada uno de los núcleos esté alimentado
con aquellos datos que hagan falta al mismo ritmo que se va a ejecutar este procesamiento.
De no contar con el ancho de banda de memoria suficiente, esto se podría convertir en un
cuello de botella que podría limitar el procesamiento de la GPU, dejándola sin los datos necesarios
para poder trabajar.
Está feo, ¿eh?
El comprarte una 2080 Ti para luego tenerla acapada por una mala gestión de datos, not
cool.
Pero espera, ¿por qué hay más?
Porque al final aquí solamente estamos hablando de aquellos elementos comunes en las arquitecturas
de las tarjetas gráficas que las hacen potencialmente interesante para resolver problemas, por ejemplo,
de deep learning.
Hemos hablado del ancho de banda de la memoria, del paralelismo, de cómo si las quieres utilizar
con propósito general las puedes programar con plataformas como CUDA.
Pero sabiendo esto y el interés de un mercado que demanda más flops para poder entrenar
clasificadores de perretes y gatetes a máxima velocidad, nadie impide a los fabricantes
de chips como NVIDIA el intentar buscar nuevas arquitecturas que incluyan nuevos trucos,
nuevas técnicas que puedan especializar más todavía la arquitectura de estos chips de
cara al mercado de, por ejemplo, el deep learning.
Y es por eso que vamos a hablar de dos técnicas que han surgido los últimos años, que son
los tensor cores y la precisión mixta.
Ok, tenemos a nuestra GPU que ya sabemos que a nivel de arquitectura está compuesta por
múltiples y múltiples núcleos.
Comercialmente a estos núcleos se le llaman CUDA cores, núcleos CUDA, pero en nuestro
caso ya sabemos que esto no deja de ser unidades aritmético-lógicas como las que mencionamos
en un principio, es decir, circuitos que son capaces de resolver diferentes instrucciones
de operaciones matemáticas y lógicas.
Estas unidades, como vimos antes, son capaces de resolver operaciones matemáticas con números
enteros, pero si quisiéramos también podríamos diseñarlas para que operaran, por ejemplo,
con números racionales, números con decimales.
Y ya saben que en informática para representar números racionales es una movida, porque
al final tenemos que utilizar el tipo de datos de punto flotante, donde dedicamos una cantidad
de bits para poder representar hasta cierta precisión el número decimal que queramos.
De manera estándar se utiliza el tipo de datos FP32, que utiliza una cadena de 32 bits
para representar al número racional.
Claro, si tú quisieras ganar en precisión podrías utilizar un tipo de datos que usara
un mayor número de bits, por ejemplo FP64, pero a costa de utilizar una mayor cantidad
de memoria para representar a ese número.
Y también podrías hacer lo contrario, podrías utilizar una cadena de 16 bits, FP16, para
representar con menos precisión a un número decimal, pero eso sí ahorrándote espacio
en memoria.
Y claro, para ciertos problemas esta pérdida de precisión de los números decimales puede
ser bastante crítica, pero lo que se ha comprobado es que para el deep learning esta pérdida
de precisión de los números decimales, que en este caso pues serían los parámetros
de nuestro modelo, o las activaciones que salen de una capa a otra, pues no afecta tanto
al rendimiento, siempre que se apliquen una serie de truquitos donde estamos mezclando
pues tipo de datos FP16 y tipo de datos FP32.
De ahí a que este tipo de entrenamiento se le conozca como entrenamiento de precisión
mixta, porque estamos mezclando tipos de datos.
Y claro, ¿por qué nos podría interesar utilizar esta representación en 16 bits?
Como ya lo hemos dicho antes, al final el guardar un numerito con solamente 16 bits
te permite ahorrarte espacio en memoria, lo cual, traducido a nuestro problema, significaría
poder almacenar redes neuronales más grandes o poder entrenarlas con un mayor número de
datos al mismo tiempo.
Y no solo eso, sino que a de ser más pequeño también te va a permitir reducir el ancho
de banda de la memoria y también las unidades aritmético-lógicas que van a ser el procesamiento
son más sencillas, con lo cual te pueden aportar más rendimiento.
Es decir, son ventajas por todos los lados y esto significa una mejora sustancial en
el entrenamiento de nuestras redes neuronales.
Y continuamos para bingo porque también quiero hablarte de lo que son los tensor cores.
Al final lo hemos dicho antes, en las redes neuronales hay un tipo de operación que
no para de repetirse en todo momento y es la multiplicación de matrices.
Es por eso que en las últimas GPUs Nvidia ha ido modificando sus arquitecturas para
incorporar un nuevo tipo de núcleos que se van a encargar de resolver una tarea diferente
a los CUDA cores que hemos hablado antes.
Estamos hablando de los tensor cores, un tipo de núcleos que se van a encargar de resolver
una única instrucción, una única tarea, multiplicar dos matrices de tamaño 4x4 y
agregarla a otra matriz.
Wow.
Es decir, si antes por ejemplo estamos hablando que para multiplicar dos matrices tenías
que preocuparte de paralelizar su procesamiento, mandarlo a diferentes hilos, en este caso
no.
En este caso si tú tienes dos matrices y otra más, pues puedes hacer toda esta operación
en un único ciclo de reloj.
Y esto es muy interesante porque a lo mejor tú estarás pensando, pero Carlos, es que
una matriz 4x4 no resuelve mi problema de una red neuronal donde mi matriz de parámetros
es a lo mejor de 1000x1000.
Pero es que en este caso la operación, la instrucción de la que estamos hablando, es
la multiplicación de dos matrices y su suma, un tipo de operación que puede ser utilizada
para atacar la multiplicación de matrices más grandes al dividirla en diferentes bloques
y calcular el subproducto de ellas.
Para hacer esto, pues nos podemos aprovechar de estos tensor cores para que se encarguen
de hacer de manera muy especializada esta operación, consiguiendo un rendimiento superior,
muy superior a lo que se podía conseguir con arquitecturas anteriores.
Estos núcleos, los tensor cores, están disponibles para ser utilizados por cualquier programador
a través de CUDA que sea capaz de descomponer su problema que quiera resolver en un problema
que se ha resuelto con una instrucción de 4x4 más 4.
Pero claro, qué pasa, esto no puede servir para todos los problemas, ¿por qué?
Pues bueno, porque estos tensor cores, para poder operar de manera óptima, de manera
eficiente, hacen uso de la técnica que hemos dicho antes de precisión mixta.
Y claro, como hemos dicho, pues hay una serie de problemas como los de deep learning que
esta precisión mixta no compromete su resultado, pero para otro tipo de problemas esto puede
ser muy crítico.
Aquí lo interesante es que tú has entendido toda esta explicación que te acabo de dar,
y eso significa que ya estás desarrollando una intuición en cómo funcionan todas estas
tecnologías, lo cual de cara a futuros proyectos, si te dedicas a trabajar en deep learning creo
que te va a resultar bastante valioso.
Si te preguntas cómo tú puedes hacer uso de todo esto, la buena noticia es que posiblemente
ya lo estés utilizando, y es que si cuentas con una tarjeta de NVIDIA que tenga CUDA instalado
y trabajas con librerías como PyTorch, TensorFlow o FastAI, muchas de sus rutinas ya están
utilizando el código de CUDA para poder sacar todo el partido extra que te permite la aceleración
por hardware.
Te acuerdas en el vídeo de 3 maneras de programar una red neuronal te conté un poco cómo funcionaban
estas librerías y cómo poder generar redes neuronales en diferentes niveles de abstracción.
Hoy en ese stack hemos bajado otro nivel, nos hemos acercado al metal para ganar un
poco de intuición en cómo funcionan todas estas tecnologías que han permitido el desarrollo
y el avance del campo del deep learning.
Hemos dejado muchos detalles fuera y en realidad mi intención con este vídeo no es que te
conviertas en un experto del hardware de la noche al día, aquí mi intención es que
la próxima vez que estés trabajando con una librería de deep learning como la de
FastAI y utilizas una función como esta, sepas que si hay una mejora del rendimiento del
200% a la hora de entrenar a tu modelo, esto se está produciendo porque hay una serie
de rutinas CUDA que están llamando a la aceleración por hardware de tu GPU para coger estas multiplicaciones
matriciales y ejecutarlas sobre núcleos de tensores que lo que están haciendo es aprovechar
el paralelismo de todos estos núcleos para hacerlo de manera eficiente y que estos núcleos
no dejan de ser unidades aritmético-lógicas conformadas por muchas puertas lógicas y transistores
colocados de manera correcta.
Nos vemos en el próximo vídeo.
Muchas gracias por aguantar hasta el final, ya sabes que si este vídeo te ha gustado
pues puedes dejar tu like aquí abajo y comentar y también que si valoras este tipo de contenido
en YouTube, formación gratuita, pues puedes sumarte a apoyar el canal financieramente
a través de mi Patreon, podéis seguirme en Instagram, en Twitter, donde comparto cosas
que también les pueden interesar y nos vemos la próxima semana, muchas gracias.