Large Language Model (LLM) from scratch. Transformers.

En la publicación pasada entendimos la estructura general de un LLM. Este paso es vital, puesto que nos da una visión panorámica sobre la morfología de estos modelos. Con esto en mente, ahora podemos estudiar la arquitectura de aquellos.

Existen múltiples arquitecturas para diseñar un LLM, no obstante, en esta entrada vamos a estudiar transformers, puesto que es la que emplean los LLM más famosos, tales como GPT, BERT, T5, LLaMa, etc.

Al igual que en la entrada anterior, usaremos como referencia a Jurafsky y Martin (2026), como es usual. Cualquier error es mi responsabilidad.

Atención

En el contexto específico de transformers, el concepto de atención fue introducido por Vaswani et al. (2017). Este artículo, y concepto, son importantes por varias razones, no obstante, en este momento se me ocurren dos muy obvias: la atención está presente en los LLMs más famosos, tales como GPT, BERT, etc., y además, el artículo tiene más de 250 mil citas (a mayo de 2026).

En términos formales, la atención es un mecanismo que permite construir una nueva representación contextual de un token combinando información de otros tokens de la secuencia.

Pero, ¿Por qué necesitamos una nueva representación contextual?. Primero que todo, recordemos algunos conceptos expuestos en la entrada sobre embeddings. En esta publicación, representábamos el significado de una palabra de forma vectorial y estática, es decir, los valores de los componentes del vector de la palabra no dependen del contexto en el que se encuentre. Esto es problemático porque muchas veces la idoneidad de la palabra es definida por el contexto. Por ejemplo:

El coche no siguió avanzando por el camino porque este estaba mojado.

El coche no siguió avanzando por el camino porque este estaba averiado.

En este caso, en la primera oración, el coche no avanzó porque el camino estaba mojado, y en la segunda, porque el coche estaba averiado. Como podemos ver, el contexto determina el significado de la palabra «este».

Entonces, respondiendo a la pregunta, sobre si una nueva representación conceptual es necesaria, si, es absolutamente necesaria, puesto que ayuda al modelo a distinguir este tipo de sutilidades.

Ahora que sabemos por qué una representación contextual es necesaria, estamos en condiciones de explicar el concepto de atención. Formalmente, se podría definir como el mecanismo mediante el cual el transformer toma xix_i, es decir, la representación vectorial del token que está en la posición ii de la secuencia, y produce aia_i, una nueva representación del mismo token, pero enriquecida con información de los tokens anteriores. En un modelo causal, como GPT, esta información sólo puede venir del propio token y de los tokens que aparecen antes de él, no de los tokens futuros.

Para entender esta idea, usaremos una versión simplificada de lo que realmente ocurre, tal y como lo muestra Jurafsky y Martin (2026). Para esto, asumiremos la siguiente estructura:

ai=jiαijxja_{i} = \sum_{j\leq i}\alpha_{ij}x_{j}

Es decir, en la posición ii de la secuencia, tomamos la representación vectorial de los tokens anteriores (incluyendo ii), y los ponderamos por αij\alpha_{ij}. Luego, para obtener este último, calculamos el score, y lo pasamos por la softmax:

score(xi,xj)=xixjscore(x_{i},x_{j}) = x_{i} \cdot x_{j}

αij=softmax(score(xi,xj)),ji\alpha_{ij} = softmax(score(x_{i},x_{j}) ), \forall j\leq i

¿Por qué calculamos el producto punto? Recordemos que este se puede interpretar como una medida de similitud entre dos vectores, entonces, si el vector de un token previo xjx_j​ es parecido al vector del token actual xix_i, entonces probablemente contiene información útil para interpretar o representar mejor a xix_i. En términos prácticos, supongamos el siguiente ejemplo:

El gato es buena mascota. Este felino…

Supongamos que en esta frase estamos analizando xfelinox_{felino}. Es de esperar, que la similitud entre xfelinox_{felino} y xgatox_{gato}, sea mayor a la similitud entre xfelinox_{felino}y xbuenax_{buena}. Lo anterior implica que αfelino,gato>αfelino,buena\alpha_{felino,gato} >\alpha_{felino,buena}. Por tanto, el vector contextualizado del token felino, afelinoa_{felino}, contendrá más información de gato que del token buena.

Esta metodología fue desarrollada por Jurafsky y Martin (2026) con fines pedagógicos, no obstante, el funcionamiento de la atención es más compleja. Esta, tiene conceptos adicionales como attention head, query, key y value. A continuación veremos cada uno, y cómo se relacionan.

Attention head, query y value

Una attention head es un componente de atención que toma las representaciones vectoriales de los tokens y las proyecta en tres espacios distintos: queries, keys y values. La query corresponde a la representación del token actual en su rol de búsqueda; las keys corresponden a las representaciones de los tokens del contexto en su rol de comparación; y los values corresponden a la información que finalmente será ponderada y sumada. De esta forma, la cabeza de atención compara la query del token actual con las keys de los tokens disponibles en el contexto —en un transformer causal, el propio token y los tokens anteriores—, transforma esas comparaciones en pesos de atención y usa esos pesos para combinar los values. El resultado es una nueva representación del token actual, enriquecida con información contextual. En la siguiente imagen, podemos ver con más claridad qué ocurre:

En el ejemplo de la ilustración, tenemos tres tokens de entrada que vienen en forma de embeddings: x1x_{1}, x2x_{2}y x3x_{3}, siendo este último, el token que estamos analizando. Nuestro objetivo es calcular a3a_{3}, es decir, una nueva representación contextualizada de ese token. Para ello, primero proyectamos cada vector de entrada en tres representaciones distintas: key, query y value. Estas proyecciones se obtienen mediante tres matrices aprendidas por el modelo:

qi=xiWQq_{i} = x_{i}W^{Q}

ki=xiWKk_{i} = x_{i}W^{K}

vi=xiWVv_{i} = x_{i}W^{V}

donde qiq_{i} es la query, kik_{i}es la key y viv_{i}es el value correspondiente al token en la posición ii.

Como queremos calcular la salida de atención para x3x_3, usamos la query de x3x_3, es decir, q3q_3, y la comparamos con las keys de los tokens disponibles en el contexto. Puesto que estamos en un modelo causal, x3x_3 puede atender a x1x_1, x2x_2 y a sí mismo, pero no a tokens futuros. Por lo tanto, calculamos los siguientes scores:

x3contra x1:score(x3,x1)=q3k1dkx_{3} \text{contra } x_{1} : score(x_{3},x_{1}) = \frac{q_{3}\cdot k_{1}}{\sqrt{d_{k}}}

x3contra x2:score(x3,x2)=q3k2dkx_{3} \text{contra } x_{2} : score(x_{3},x_{2}) = \frac{q_{3}\cdot k_{2}}{\sqrt{d_{k}}}

x3contra x1:score(x3,x3)=q3k3dkx_{3} \text{contra } x_{1} : score(x_{3},x_{3}) = \frac{q_{3}\cdot k_{3}}{\sqrt{d_{k}}}

El producto punto q3kjq_3 \cdot k_j mide la compatibilidad entre la query del token actual y la key de cada token del contexto. Luego, dividimos por dk\sqrt{d_k} para evitar que los scores crezcan demasiado cuando la dimensión de las queries y keys es alta.

Posteriormente, pasamos estos scores por una softmax. Esto nos permite convertir los scores en pesos de atención:

α31,α32,α33\alpha_{31}, \alpha_{32}, \alpha_{33}

Formalmente:

α3j=escore(x3,xj)m3escore(x3,xm)\alpha_{3j} = \frac{e^{score(x_{3},x_{j})}}{\sum_{m\leq3}e^{score(x_{3},x_{m})}}

para j{1,2,3}j \in \{1,2,3\}. De esta forma, los pesos de atención son números positivos que suman 1:

α31+α32+α33=1\alpha_{31} + \alpha_{32} + \alpha_{33} =1

Cada α3j\alpha_{3j}​ indica cuánta información del token xjx_j será incorporada en la nueva representación de x3x_3.

Una vez obtenidos los pesos de atención, podemos calcular la salida de la cabeza de atención para el token x3x_3. Para ello, hacemos una suma ponderada de los value vectors:

head3=α31v1+α32v2+α33v3head_{3} = \alpha_{31}v_{1} + \alpha_{32}v_{2} + \alpha_{33}v_{3}

El vector head3head_3​ tiene dimensión:

1×dv1\times d_{v}

Sin embargo, la arquitectura del transformer necesita que la salida de la atención tenga la misma dimensión que la representación original del token, es decir:

1×dmodel1\times d_{model}

Por esta razón, multiplicamos head3head_3 por una matriz de salida WOW^O:

a3=head3Woa_{3} = head_{3}W^{o}

donde:

WOdv×dmodelW^{O} \in \mathbb{R}^{d_{v}\times d_{model}}

Así, la operación tiene la siguiente forma:

[1×dv][dv×dmodel]=[1×dmodel][1\times d_{v}]\cdot [d_{v}\times d_{model}] = [1\times d_{model}]

El resultado final es:

a31×dmodela_{3} \in \mathbb{R}^{1\times d_{model}}

Es decir, a3a_3 es una nueva representación del token x3x_3​, enriquecida con información de x1x_1, x2x_2 y del propio x3x_3​. Esta representación mantiene la misma dimensionalidad que la entrada, lo que permite integrarla al flujo residual del transformer y pasarla a las siguientes capas.

Para verlo con un ejemplo pequeño, supongamos que:

dv=3   y   dmodel=2d_{v} = 3 \ \ \ \text{y} \ \ \ d_{model}=2

Entonces head3head_3 podría ser un vector de tres dimensiones:

headi=[h1 h2 h3]head_{i} = [h_{1} \ h_{2} \ h_{3}]

y WOW^{O} tendría dimensión 3×23\times 2:

WO=[w11w12w21w22w31w32]W^{O} = \begin{bmatrix} w_{11} & w_{12} \\ w_{21} & w_{22} \\ w_{31} & w_{32} \end{bmatrix}

La multiplicación sería:

a3=[h1h2h3][w11w12w21w22w31w32]a_{3} = \begin{bmatrix} h_{1} & h_{2} & h_{3} \end{bmatrix} \cdot \begin{bmatrix} w_{11} & w_{12} \\ w_{21} & w_{22} \\ w_{31} & w_{32} \end{bmatrix}

Por lo tanto:

a3=[h1w11+h2w21+h3w31h1w12+h2w22+h3w32]a_{3} = \begin{bmatrix} h_{1}w_{11} + h_{2}w_{21} + h_{3}w_{31} & h_{1}w_{12} + h_{2}w_{22} + h_{3}w_{32} \end{bmatrix}

Así, WOW^O aprende cómo transformar y mezclar la información contenida en head3head_3 para devolverla al espacio general del modelo.

Dicho esto, en términos formales, el conjunto de ecuaciones para obtener aia_{i}vendría dado por:

qi=xiWQ  ;  kj=xjWK  ;  vj=xjWVq_{i} = x_{i}W^{Q} \ \ ; \ \ k_{j} = x_{j}W^{K} \ \ ; \ \ v_{j} = x_{j}W^{V}

score(xi,xj)=qikjdkscore(x_{i},x_{j}) = \frac{q_{i}k_{j}}{\sqrt{d_{k}}}

αij=softmax(score(xi,xj))  ji\alpha_{ij} = softmax(score(x_{i},x_{j})) \ \ \forall j \leq i

headi=jiαijvjhead_{i} = \sum_{j\leq i} \alpha_{ij}v_{j}

ai=headiWoa_{i} = head_{i}W^{o}

Múltiples attention heads

En la sección anterior mostramos el funcionamiento de una sola attention head. Sin embargo, los transformers no suelen usar una única cabeza de atención, sino varias trabajando en paralelo. La idea es que cada cabeza pueda aprender una forma distinta de mirar el mismo contexto. Por ejemplo, una cabeza podría especializarse en ciertas relaciones gramaticales, otra en relaciones semánticas, otra en repeticiones o dependencias de largo alcance. En este sentido, multi-head attention permite que el modelo construya varias representaciones contextuales complementarias a partir de los mismos tokens de entrada.

Siguiendo la notación de Jurafsky y Martin (2026), si tenemos AA cabezas de atención, cada cabeza cc tiene su propio conjunto de matrices aprendidas:WQc,WKc,WVcW^{Q_c}, \quad W^{K_c}, \quad W^{V_c}

Estas matrices proyectan los vectores de entrada en queries, keys y values específicos para cada cabeza:

qic=xiWQcq_i^c = x_i W^{Q_c}kjc=xjWKck_j^c = x_j W^{K_c}vjc=xjWVcv_j^c = x_j W^{V_c}

para cada cabeza cc, con 1cA1 \leq c \leq A. Luego, para calcular la atención del token en posición ii, la cabeza cc compara la query qicq_i^c​ con las keys kjck_j^c​ de los tokens disponibles en el contexto:

scorec(xi,xj)=qickjcdkscore^c(x_i,x_j) = \frac{q_i^c \cdot k_j^c}{\sqrt{d_k}}

Estos scores se normalizan mediante una softmax, produciendo los pesos de atención:

αijc=softmax(scorec(xi,xj))\alpha_{ij}^c = \text{softmax}(score^c(x_i,x_j))

A partir de estos pesos, cada cabeza calcula su propia suma ponderada de los value vectors:

headic=jiαijcvjchead_i^c = \sum_{j \leq i} \alpha_{ij}^c v_j^c

Finalmente, las salidas de todas las cabezas se concatenan y se proyectan mediante una matriz WOW^O:

ai=(headi1headi2headiA)WOa_i = (head_i^1 \oplus head_i^2 \oplus \dots \oplus head_i^A)W^O

Así, la función de multi-head attention puede expresarse como:

MultiHeadAttention(xi,[x1,,xi])=aiMultiHeadAttention(x_i, [x_1, \dots, x_i]) = a_i

Vale la pena destacar que la salida de cada cabeza headichead_i^c tiene dimensión:

[1×dv][1 \times d_v]

Por lo tanto, si tenemos AA cabezas, la concatenación de todas ellas produce un vector de dimensión:

[1×Adv][1 \times A d_v]

Luego, la matriz WOW^O proyecta esta concatenación de vuelta al espacio del modelo:

WOAdv×dmodelW^{O} \in \mathbb{R}^{Ad_{v}\times d_{model}}

De esta forma, la salida final de multi-head attention conserva la dimensión esperada:

ai1×dmodela_{i} \in \mathbb{R}^{1\times d_{model}}

Esto es importante porque permite que la salida de la atención pueda integrarse al flujo residual del transformer y pasar a las siguientes capas.

Transformer Blocks

Multi-head attention es uno de los subcomponentes de un transformer block. Lo hemos explicado en un apartado especial porque es, probablemente, el componente más característico y complejo de la arquitectura. Sin embargo, un transformer block moderno incluye también otros elementos importantes, como LayerNorm, conexiones residuales y una feedforward network.

El objetivo de esta sección es entender cómo interactúan estos subcomponentes dentro del bloque. Para ello, resulta útil introducir la idea de residual stream. En un transformer moderno, la representación de cada token no se reemplaza por completo cada vez que pasa por un subcomponente. Más bien, existe un flujo principal de información —el residual stream— que transporta la representación del token a través del bloque. Los distintos módulos leen desde ese flujo, calculan nueva información y luego suman su resultado de vuelta mediante conexiones residuales. En particular, la multi-head attention incorpora información contextual proveniente de otros tokens, mientras que la feedforward network transforma la representación de cada token de forma independiente. Este principio se ilustra en la siguiente figura:

Como se puede ver, cada token tiene su propio flujo vertical de información. En la figura, el token principal es xix_i. Ese vector sube por el bloque, mientras algunos módulos leen desde él, calculan nueva información y la suman de vuelta al flujo principal.

En la sección pasada explicamos el módulo de multi-head attention, sin embargo, nos falta explicar feedforward, y layernorm.

Capa feedforward

La capa feedforward de esta arquitectura-cuyo concepto fue explicado en esta entrada– es una red neuronal completamente conectada de dos capas, cuya capa oculta (dffd_{ff}) suele tener una dimensión más grande que la del modelo (dmodeld_{model}). Conceptualmente, la red hace algo similar a esto:

[1×dmodel][1×dff][1×dmodel][1\times d_{model}] \to [1\times d_{ff}] \to [1\times d_{model}]

En este caso, llevamos al vector de entrada a un espacio más grande (de dmodeld_{model} a dffd_{ff}), porque así la red tiene más “lugar” para representar combinaciones, patrones y transformaciones no lineales. Después, comprime esa información de vuelta a la dimensión del modelo (dmodeld_{model}). Esta compresión es necesaria para que sigamos trabajando con el vector en el flujo residual, de otra forma, dejaríamos el vector de salida en una dimensión que no es la que espera el subcomponente siguiente.

Dicho esto, en términos matemáticos, este paso se puede expresar de la siguiente forma:

FFN(xi)=ReLU(xiW1+b1)W2+b2FFN(x_{i}) = ReLU(x_{i}W_{1} + b_{1} ) W_{2} + b_{2}

donde xi1×dmodelx_{i} \in \mathbb{R}^{1\times d_{model}}, W1dmodel×dffW_{1} \in \mathbb{R}^{d_{model}\times d_{ff}} y W2dff×dmodelW_{2} \in \mathbb{R}^{d_{ff}\times d_{model}} (Tarea: chequea que FFN(xi)1×dmodelFFN(x_{i}) \in \mathbb{R}^{1\times d_{model}}).

Capa LayerNorm

Layer norm (de Layer Normalization) normaliza el vector de representación de cada token para que sus valores no crezcan, se desplacen o se vuelvan demasiado extremos a medida que pasan por muchas capas.

Si pensamos en xi1×dmodelx_{i} \in \mathbb{R}^{1\times d_{model}}, layer norm toma ese vector y calcula la media y la desviación estándar de sus propias dimensiones internas. Es decir, no normaliza toda la capa completa ni todos los tokens juntos, sino el vector individual de un token. En términos matemáticos, primero hace

μ=1dmodelr=1dmodelxir\mu = \frac{1}{d_{model}}\sum_{r=1}^{d_{model}}x_{i}^{r}

σ=1dmodelr=1dmodel(xirμ)2\sigma = \sqrt{\frac{1}{d_{model}}\sum_{r=1}^{d_{model}}(x_{i}^{r}-\mu)^{2}}

Luego, normaliza:

x^i=xiμσ\hat{x}_{i} = \frac{x_{i}-\mu}{\sigma}

y finalmente, aplica dos parámetros aprendibles (α\alpha y β\beta):

LayerNorm(x^i)=αxi^+β\text{LayerNorm}(\hat{x}_{i}) = \alpha \hat{x_{i}} + \beta

Bloque completo

Empleando la notación de Jurafsky y Martin (2026), podemos expresar los cálculos que hace un transformer block en el siguiente conjunto de ecauciones:

ti1=LayerNorm(xi)t_{i}^{1} = \text{LayerNorm}(x_{i})

ti2=MultiheadAttention(ti1,[t11,...tN1])t_{i}^{2} = \text{MultiheadAttention}(t_{i}^{1}, [t_{1}^{1},…t_{N}^{1}])

ti3=ti2+xit^{3}_{i} = t_{i}^{2} + x_{i}

ti4=LayerNorm(ti3)t_{i}^{4} = \text{LayerNorm}(t_{i}^{3})

ti5=FFN(ti4)t_{i}^{5} = \text{FFN}(t_{i}^{4})

hi=ti5+ti3h_{i} = t_{i}^{5} + t_{i}^{3}

Primero, en ti1t_i^1, se normaliza la representación de entrada del token xix_i. Luego, en ti2t_i^2​, esa representación normalizada entra al módulo de multi-head attention, que calcula una contribución contextual para el token ii, usando información del propio token y de los tokens disponibles en el contexto. Después, en ti3t_i^3, la salida de attention se suma al vector original xix_i, formando la primera conexión residual.

A continuación, en ti4t_i^4​, se normaliza esta representación actualizada ti3t_i^3​. Luego, en ti5t_i^5, la representación normalizada pasa por la red feedforward, que transforma el vector del token de forma independiente respecto de los demás tokens. Finalmente, la salida de la feedforward se suma nuevamente a ti3t_i^3, produciendo hih_i, la salida final del transformer block para el token en la posición ii.

Sobre la entrada xix_{i}

Hasta este punto, hemos discutido lo que hacemos con el vector xi1×dmodelx_{i} \in \mathbb{R}^{1\times d_{model}} , no obstante, no hemos dicho mucho sobre él. En esta sección veremos que no sólo es necesario el embedding, si no también, la posición de dicho vector en la secuencia.

Supongamos que tenemos una secuencia de N tokens, donde N es el largo del contexto. El input del transformer se podría escribir como XN×dmodelX \in \mathbb{R}^{N\times d_{model}} , donde dmodeld_{model} es el largo del embedding. Para ilustrar esta matriz, a modo de ejemplo, asumiremos que cada token es una palabra, y que nuestro contexto viene dado por la siguiente frase (N=7):

Esto me an buelto myos enemigos malos

Dicha secuencia deberá tener 7 embeddings, que vienen dados por:

  • Esto = [x11,...,xdmodel1x_{1}^{1},…,x_{d_{model}}^{1}]
  • me = [x12,...,xdmodel2x_{1}^{2},…,x_{d_{model}}^{2}]
  • an = [x13,...,xdmodel3x_{1}^{3},…,x_{d_{model}}^{3}]
  • malos = [x17,...,xdmodel7x_{1}^{7},…,x_{d_{model}}^{7}]

Por lo que la matriz X vendría dada por:

X=[x11...xdmodel1.........x17...xdmodel7]X = \begin{bmatrix} x_{1}^{1} & … & x_{d_{model}}^{1} \\ … & … & … \\ x_{1}^{7} & … & x_{d_{model}}^{7} \end{bmatrix}

En este punto, es importante entender que los embeddings obtenidos no registran ningún tipo de posición en el texto -en esta entrada podrás entender por qué-. No obstante, como podrás intuir después de leer el apartado de atención, la posición importa. En la siguiente imagen ilustramos por qué importa:

En este caso, podemos ver que el significado de la oración cambia levemente al intercambiar el orden de las palabras. Entonces, para que el transformer enriquezca los vectores según el contexto de forma correcta, es necesario que reciba el token bajo análisis, más los tokens que lo preceden en el orden original en que aparecieron en la secuencia.

Para llevar a cabo esta tarea, el modelo usa positional embeddings. Estos embeddings son vectores asociados a las posiciones de la secuencia. Por ejemplo, si tenemos una secuencia de N tokens, tendríamos N vectores:

  • P11×dmodelP_{1} \in \mathbb{R}^{1\times d_{model}}
  • P21×dmodelP_{2} \in \mathbb{R}^{1\times d_{model}}
  • PN1×dmodelP_{N} \in \mathbb{R}^{1\times d_{model}}

Luego, para construir la representación final de la entrada del token en la posición ii, llevamos a cabo la siguiente suma:

xi=E[tokeni]+Pix_{i} = E[token_{i}] + P_{i}

donde E|V|×dmodelE \in \mathbb{R}^{|V|\times d_{model}}, es el vector de embeddings, y |V||V| es el tamaño del vocabulario.

¿Cómo se aprende el modelo PiP_{i}? Al inicio del entrenamiento, los vectores P1,,PNP_1,\dots,P_N​ suelen estar inicializados con valores aleatorios. Durante el entrenamiento, el modelo intenta predecir correctamente los siguientes tokens. Si la información de posición ayuda a reducir el error, los gradientes van ajustando esos vectores posicionales. Con muchas frases, el modelo aprende patrones como: qué suele pasar al comienzo de una oración, qué roles suelen tener ciertas posiciones, cómo afecta el orden a la interpretación, etc.

The Language Modeling Head

Hasta este punto ya sabemos lo que hace la capa del transformer. En términos coloquiales, en dicha capa entra un bloque completo de tokens como una matriz, y el transformer procesa todas las posiciones en paralelo. Gracias a la máscara causal, cada posición sólo puede usar información de los tokens anteriores y de sí misma. En términos matemáticos, una capa de transformer mapea una ventana completa de vectores de entrada (x) en vectores de salida contextualizados (h), es decir,

(x1,x2,...,xN)(h1,h2,...,hN)(x_{1},x_{2},…,x_{N}) \to (h_{1},h_{2},…,h_{N})

La siguiente ilustración resume parte de lo que hemos explicado si entrase la secuencia «A diestro dexan a Sant Esteuan, mas cae aluen.»

Con estos vectores ya enriquecidos, estamos en condiciones de predecir la siguiente palabra (como lo hicimos en esta entrada). Este proceso es la función principal de un language modeling head. La siguiente imagen explica esta función en detalle:

La figura muestra el flujo final de un transformer usado como modelo de lenguaje. Primero, una secuencia de tokens de entrada w1,w2,,wNw_1, w_2, \dots, w_N​ atraviesa el último bloque transformer, produciendo una representación contextualizada para cada posición: h1L,h2L,,hNLh_1^L, h_2^L, \dots, h_N^L​. Durante la generación, normalmente nos interesa el último vector contextualizado, hNLh_N^L​, porque contiene información del contexto disponible hasta el token NN.

Luego, el vector hNLh_N^L entra en la language modeling head. En esta etapa, se proyecta desde el espacio interno del modelo hacia el espacio del vocabulario mediante una capa de unembedding, usualmente representada como U=ETU = E^T, donde URd×VU \in \mathbb{R}^{d \times |V|}. Matemáticamente, esta operación se expresa como u=hNLUu = h_N^L U, con uR1×Vu \in \mathbb{R}^{1 \times |V|}. El vector resultante uu contiene los logits, es decir, un puntaje no normalizado para cada token posible del vocabulario. Finalmente, una función softmax transforma esos logits en una distribución de probabilidad yR1×Vy \in \mathbb{R}^{1 \times |V|}. Cada componente de yy indica la probabilidad que el modelo asigna a un posible siguiente token.

Fase de entrenamiento

En el entrenamiento de un transformer causal, el modelo aprende a predecir el siguiente token en cada posición de una secuencia, es decir,

(aluen.|A diestro dexan a Sant Esteuan, mas cae aluen).\mathbb{P}(\text{aluen.} | \text{A diestro dexan a Sant Esteuan, mas cae aluen}).

Para ello, produce una distribución de probabilidad sobre el vocabulario y se compara esa distribución con el token correcto. La pérdida usada es la cross-entropy loss, que penaliza al modelo cuando asigna baja probabilidad al token real siguiente (si has seguido esta serie de publicaciones, esto te resultaría familiar, en caso contrario, visita esta entrada). Luego, mediante gradient descent, los pesos del modelo se ajustan para reducir esa pérdida promedio.

Es importante precisar que, durante el entrenamiento, el modelo no procesa todo el corpus completo de una sola vez. En la práctica, se define una ventana de contexto —por ejemplo, de algunos miles de tokens— y el corpus se organiza en segmentos o chunks que caben dentro de esa ventana. Si un documento es más corto que la ventana, pueden empaquetarse varios documentos en un mismo chunk, normalmente separados por un token especial. Cada chunk se procesa como una secuencia de entrenamiento: el modelo recibe sus tokens y aprende a predecir el siguiente token en cada posición, respetando la máscara causal. De esta manera, puede entrenarse sobre corpus muy grandes, pero siempre mediante fragmentos de tamaño limitado por su ventana de contexto.

Notas finales, y próxima entrada…

En esta entrada vimos el concepto de transformers con mayor profundidad. Esta idea ha sido fundamental en la construcción de los LLM’s más famosos, por tanto, su lectura es obligatoria.

En la próxima publicación veremos prácticas asociadas al post-entrenamiento, tales como instuction tuning y alignment. Stay tuned!

José Miguel Muñoz Urra – jmunozu@pulki.es