- Un experimento en el que se diseñaron manualmente solo los pesos de un transformer solo decodificador similar a GPT-2, sin entrenamiento, para predecir el patrón
(aab)* - Como para predecir
"aabaabaabaab..."hay que mirar los dos tokens anteriores, esto permite mostrar mejor el funcionamiento de la atención que un patrón alternado simple - El modelo se construyó pequeño, con
N_CTX=5,N_VOCAB=2,N_EMBED=8, y usa tokenizacióna=0,b=1junto con embeddings one-hot - Un único head de atención reparte 0.5 de atención a cada uno de los dos tokens más recientes, y calcula el siguiente token usando la cancelación de la codificación
a=1,b=-1 - En contextos no ambiguos mostró una precisión de 100.0% (27/27), pero requiere unas 4,000 FLOPs para una sola predicción con contexto de 5 tokens, por lo que es mucho menos eficiente que una regla implementada directamente
Mini GPT-2 con pesos fijados a mano, sin entrenamiento
- El objetivo es entender de forma intuitiva qué hace realmente cada componente de un transformer y de la atención
- El modelo no se entrena ni usa pesos preentrenados; fue construido asignando manualmente cada peso en una sola tarde
- La estructura es un transformer solo decodificador similar a GPT-2, y la implementación se simplificó a partir de la implementación picoGPT de jaymody
- se eliminó layer norm
- se usó un solo head en lugar de multi-head attention
- se eliminó la capa feed-forward
mlpdel bloque transformer
Por qué se eligió la secuencia (aab)*
- Al principio se intentó predecir una secuencia como
"ababababab", pero como el transformer predice una secuencia desplazada, la tarea resultó demasiado fácil- basta con predecir
bsi esa, y si no,a, así que no hace falta usar embeddings posicionales
- basta con predecir
- La tarea final quedó definida como la predicción de la secuencia
"aabaabaabaab...", es decir,(aab)*- si los dos tokens anteriores son
aboba, el siguiente token esa - si los dos tokens anteriores son
aa, el siguiente token esb bbse trata como un caso fuera del alcance de la tarea
- si los dos tokens anteriores son
- La tokenización es un esquema simple que solo maneja dos símbolos
aes0bes1
Dimensiones del modelo y flujo de cálculo
- Los parámetros elegidos del modelo son tres
N_CTX = 5: longitud máxima de contexto que el modelo ve de una vezN_VOCAB = 2: los dos tokensa,bN_EMBED = 8: tamaño del embedding que contiene espacio para tokens, posiciones y cálculo
- Aunque la tarea real solo necesita los dos tokens anteriores, se fijó
N_CTX=5para incluir la situación en la que hay que ignorar tokens irrelevantes - La función
gptfunciona en este orden- suma embeddings de token y de posición con
wte[inputs] + wpe[range(len(inputs))] - pasa por un bloque transformer
- al final crea logits en el espacio de vocabulario con
x @ wte.T
- suma embeddings de token y de posición con
Embeddings que guardan posición y token como one-hot
wpees el embedding posicional, y usa las primeras 5 dimensiones del embedding como one-hot posicional- la posición 0 es
[1, 0, 0, 0, 0] - la posición 4 es
[0, 0, 0, 0, 1]
- la posición 0 es
wtees el embedding de tokens, y usa las siguientes 2 dimensiones como one-hot de token- el token
aes[1, 0]en esas dimensiones de token - el token
bes[0, 1]
- el token
- La octava posición del embedding no se usa al principio y se reserva como scratch space dentro del bloque transformer
- Por ejemplo,
"aabaa"se representa como una matriz de embeddings5 x 8, y cada fila contiene tanto el one-hot posicional como el one-hot de token
Cómo el head de atención selecciona los dos tokens más recientes
- El bloque transformer está compuesto por un solo head de atención y una capa lineal
c_projque devuelve el resultado de la atención al espacio de embeddings c_attnes una capa lineal de tamañoembed_size x (embed_size * 3)que convierte el embedding de entrada en una matrizqkv, y luego la divide enq,k,vksepara el embedding posicional y representa la información de posición de cada tokenqrepresenta el rango de posiciones que cada posición quiere buscar, y medianteq @ k.Tforma la matriz de scores de atención- Después del softmax y la máscara causal, la matriz de atención tiene estas propiedades
- la primera fila pone 100% de atención solo en el primer token
- las filas siguientes ponen
0.5de atención en cada uno de los dos tokens accesibles más recientes
- La máscara causal impide ver tokens futuros sumando un valor muy pequeño a esas posiciones, como
-1e10en el código real- este modelo hecho a mano no está diseñado para espiar el futuro, pero la máscara se mantuvo para conservar una estructura más cercana a GPT-2
- La escala al dividir por
np.sqrt(q.shape[-1])ayuda a mejorar los gradientes en entrenamiento real, pero no afecta a este modelo artesanal
Codificación de v y predicción mediante suma con cancelación
vconvierte el one-hot del token a la codificacióna=1,b=-1- Como el resultado de la atención promedia con peso 0.5 los dos tokens más recientes, esta codificación calcula la siguiente regla
a, b→0.5 * 1 + 0.5 * (-1) = 0b, a→0.5 * (-1) + 0.5 * 1 = 0a, a→0.5 * 1 + 0.5 * 1 = 1
- Como resultado, en la séptima posición de la fila aparece el siguiente valor
0cuando se debe predecira1cuando se debe predecirb
- En la entrada
"aabaa", la primera fila puede producir una predicciónbpor falta de información, pero las predicciones posteriores sí coinciden con la regla de(aab)*
Cómo se devuelve la predicción al espacio de vocabulario
c_projconvierte el valor de la séptima posición del resultado de atención de vuelta al formato one-hot de token- No genera simplemente
[..., 1, 0, ...]o[..., 0, 1, ...], sino un one-hot escalado por1024embedding[row, 5] = 1024 + (-1024) * predictionembedding[row, 6] = 0 + 1024 * prediction
- El bloque transformer tiene una conexión residual, así que
x = x + causal_self_attention(...)suma el embedding original - Como esa señal residual queda ahí aunque no haga falta, se usa la escala
1024para dominarla - Por último se calcula
x @ wte.Tpara obtener los logits y luego se aplica softmax- en el contexto
"aabaa", la fila final de predicción apunta ab - durante el entrenamiento sirven las predicciones de todas las filas, pero en inferencia solo hace falta la última
- en el contexto
Resultados de generación y precisión
- La función
completemete en el modelo los últimos 5 tokens como máximo y elige el siguiente token conargmaxsobre la última fila del resultado de softmax - Ejemplos de generación
complete("a")→a :: baabaabaabcomplete("ba")→ba :: abaabaabaacomplete("abaab")→abaab :: aabaabaaba
- Incluso con entradas fuera del alcance, a veces recupera el patrón repetitivo
complete("ababa")→ababa :: abaabaabaacomplete("bbbbb")→bbbbb :: aabaabaaba
- En la prueba
"aab" * 10, si se evalúan solo contextos no ambiguos, la precisión es de 100.0% (27/27)
La diferencia entre 4,000 FLOPs y 8 instrucciones
- Al usar todo el contexto de 5 tokens, este modelo necesita alrededor de 4,000 operaciones de punto flotante para predecir un solo token
- la mayoría se usan en el cálculo de la atención
- se puede reducir con una ventana de contexto más pequeña, fused multiply-add, kv caching, etc.
- aun así, para predecir un solo token siguen haciendo falta cientos de instrucciones de máquina
- Un ensamblador x64 escrito directamente para la misma regla
(aab)*calcula el siguiente token en 8 instrucciones - Queda abierta la pregunta de si se puede entrenar un modelo de lenguaje 1000 veces más eficiente que los modelos actuales para generación de lenguaje natural
1 comentarios
Opiniones en Hacker News
Un trabajo relacionado es "Thinking Like Transformers".
Presenta un lenguaje de programación primitivo llamado RASP, compuesto por operaciones que pueden modelarse con componentes de Transformer, y muestra que se pueden escribir programas como histogramas u ordenamientos.
También hay una excelente entrada de blog de Sasha Rush y Gail Weiss, y trabajos posteriores mostraron que los programas tipo RASP pueden compilarse en pesos de modelo reales sin entrenamiento.
[1] https://arxiv.org/abs/2106.06981
[2] https://srush.github.io/raspy/
[3] https://arxiv.org/abs/2301.05062
Si este campo te parece divertido, también vale la pena echarle un vistazo a mi trabajo HandCrafted Transformers, donde elegí manualmente los pesos de un modelo Transformer para que hiciera sumas largas de forma parecida a como las personas aprenden en la primaria.
[1] https://colab.research.google.com/github/newhouseb/handcraft...
La dirección de ir de redes neuronales a código también parece muy interesante desde el punto de vista de la explicabilidad.
Pensaba que entendía bien los Transformers, pero nunca había implementado uno por mi cuenta.
Un día probé implementarlo yo mismo y no funcionaba ni entrenaba tan bien como el Transformer estándar de PyTorch; al final me di cuenta de que la causa era que había ignorado el dropout.
Lo entrené para sumar números, y como nunca le mostraba el mismo par dos veces, pensaba que el sobreajuste era imposible, pero el papel del dropout fue mucho más grande de lo que esperaba.
En resumen, conviene simplemente implementar un Transformer uno mismo, y cuanto más desde cero, mejor.
Todos los que lo hicieron aprendieron algo inesperado, y lo que entendió cada persona fue distinto: desde la paralelización del entrenamiento a nivel de tokens hasta cómo funciona realmente la retropropagación.
También me gustan los materiales de Karpathy, pero el video que finalmente hizo que entendiera los Transformers fue este: https://youtu.be/kWLed8o5M2Y?si=SJT5_lCJ0hSR7Z_k
He tenido una idea similar desde hace un tiempo.
¿No se podría crear una interfaz intuitiva sobre los pesos del modelo que permita a un experto de dominio ajustarlos manualmente para acelerar el entrenamiento?
Por ejemplo, en un modelo de visión, al detectar conos de tráfico, subir un conjunto de pesos correspondiente a "lo anaranjado".
Así una persona podría acelerarlo, en vez de exigir miles o millones de ejemplos adicionales para calibrar correctamente "lo anaranjado".
Claro que la dificultad está en que esta interfaz tendría que mapearse a conjuntos de pesos con significados diferentes, y me pregunto si hay alguna razón técnica por la que eso sea imposible.
[1] https://www.youtube.com/watch?v=8SF_h3xF3cE&t=1358s
En pocas palabras, las formas de ayuda humana en IA casi siempre son menos rentables que simplemente correr más poder de cómputo.
Mientras una persona calibra una capa de pesos para detectar conos de tráfico anaranjados, un clúster de GPU ya habrá entrenado a la IA para detectar conos de tráfico, semáforos, árboles, otros autos e incluso conos de tráfico de un naranja ligeramente distinto.
Aunque ajustes la imagen para que vea mejor el naranja, si no puedes monitorear al mismo tiempo la precisión para todos los demás colores, es probable que sin darte cuenta generes problemas con otros colores.
No es que un clúster específico de neuronas corresponda a un concepto específico; más bien, en general todo hace un poco de todo.
Hay demasiados parámetros involucrados.
El paper de los Transformers era tan técnico que siempre quise entenderlo aunque fuera superficialmente, pero me resultaba difícil.
Este artículo me ayudó muchísimo a entender cómo funciona, y al menos los ejemplos eran muy claros.
Gracias a eso también pude recordar las matrices que había aprendido en la universidad.
¿No es una especie de máquina abstracta, como una máquina de Turing o una máquina que parsea expresiones regulares?
Primero se define una lista de tokens; por ejemplo, digamos que son 24 caracteres para hacerlo fácil.
Esta máquina toma una secuencia de tokens de entrada, realiza operaciones matriciales deterministas y luego devuelve una lista de probabilidades para todos los tokens.
El "entrenamiento" no es más que el proceso de fijar algunos de los números dentro de las matrices usadas en esas operaciones.
Vale la pena notar que en el código final hay un solo
if, y ese es para evaluar la precisión del resultado.Toda la "lógica" sale del resultado de las operaciones matriciales.
En las redes neuronales, en general todo es un poco difuso y casi no hay cosas como
if/else, aunque sí hay casos, como en los ejemplos de Transformers, donde los valores se "enmascaran" con 0 o -∞.La salida también casi siempre es un conjunto de puntajes o probabilidades; si un modelo que distingue fotos de gatos y perros devuelve algo como
dog:0.95 cat:0.05, decimos que predijo perro porque el puntaje de perro es más alto.El mecanismo de atención, que es el núcleo de Transformer, se basa en una especie de operación de búsqueda suave.
En un sistema no difuso, se recorrería cada token de la secuencia, se verificaría si está relacionado con el token actual y, si lo está, se haría alguna acción; en Transformer, en cambio, la relación no es una decisión binaria.
En su lugar, calcula puntajes continuos de relación entre todos los pares de tokens de la secuencia y usa esos puntajes para decidir la siguiente acción.
Pero algunas cosas no son fáciles de generalizar directamente desde un sistema basado en decisiones binarias.
Por ejemplo, esos puntajes de relación se usan como pesos para calcular un promedio ponderado sobre los tokens del vocabulario y obtener un "token promedio" para la posición actual.
No parece haber una forma sencilla de interpretar esto como una extensión de un proceso basado en lógica de ramificación.
Vale la pena ver este paper, que explica que los Linear Transformers en realidad son Fast Weight Programmers: https://arxiv.org/abs/2102.11174
Si se configuran los pesos con cuidado, se puede hacer que realicen cualquier cálculo.
Aunque estaría bueno tener un compilador que no se base en aproximaciones.
Me pregunto para qué serviría eso de "quizá te den ganas de crear tu propio modelo", aparte de como ejercicio de aprendizaje para satisfacer la curiosidad.
Empieza a dar la impresión de que los modelos de machine learning complejos son poco realistas para alguien que lee blogs en casa.
Genera muchas palabras de inglés antiguo que parecen plausibles, y aprende las bases de la gramática inglesa y el formato de las obras de teatro, entre otras cosas.
Me sorprendió bastante que llegara tan lejos en tan poco tiempo.
Entrenar varios modelos localmente hasta un nivel de fidelidad tipo Shakespeare-from-Wish.com podría servir para evaluar si encontraste una buena arquitectura y si ya es momento de intentar escalarla.
Dice que quería entender mejor Transformer y la atención, y que aunque había leído The Illustrated Transformer, no le quedaba intuitivamente claro qué hacía en la práctica cada una de las piezas de la atención.
Cosas como la diferencia entre
qyk, y ni hablar dev.Más allá de satisfacer la curiosidad, ayuda a construir y profundizar la comprensión.
Estaría bueno que el título pudiera incluir alguna expresión como neural network.
Esto trata sobre la arquitectura "Transformer" de machine learning, no sobre un conjunto de bobinas que acoplan electromagnéticamente dos circuitos.