Entrenar un mini-GPT en NumPy

Entrenar un mini-GPT en NumPy

30 min read

Un mini-GPT recién inicializado reparte su apuesta casi por igual entre las 512 entradas del vocabulario, y la pérdida de la lección sobre el modelo de lenguaje causal le cobra por eso ln⁡512≈6.24\ln 512 \approx 6.24 en cada posición. El que cargan las celdas de este bloque paga 3.23.2 sobre páginas que nunca vio. Los dos leen los ids del tokenizador de la lección sobre BPE y son la misma red, con 136 448136\,448 números cada uno. Lo único que los separa es el valor de esos números.

Entre uno y otro hay 2 0002\,000 pasos de entrenamiento, y esta lección escribe el bucle que los dio. Es el del curso anterior (la pérdida, su gradiente, un paso en contra, repetir) con tres cambios, los mismos que llevan todos los modelos grandes. Cada paso mira unas pocas ventanas sacadas al azar, no el corpus entero. El gradiente no va tal cual al paso: pasa antes por AdamW. Y η\eta sube al principio y baja al final. El primero lo derivamos; los otros dos quedan enunciados, con sus razones.

El primero es una cuestión de coste. La parte del corpus con la que se entrenó el checkpoint tiene 125 789125\,789 tokens, y una ventana de 6565 tokens puede empezar en casi cualquiera de ellos: unas 125 700125\,700 ventanas solapadas. El gradiente exacto de su pérdida media exige la ida y la vuelta de todas, unos ocho millones de tokens: en el navegador, a algo menos de un milisegundo por token, casi dos horas por paso. Un paso del checkpoint miraba 3232 ventanas de 6464 tokens sacadas al azar, casi cuatro mil veces menos.

Es una encuesta: no le preguntas a todo el país sino a mil personas al azar, y la respuesta sale con ruido pero sin sesgo, con un ruido que baja al crecer la muestra, aunque despacio. La primera sección demuestra las tres cosas.

El segundo cambio lo anunció el curso anterior sin llegar a hacerlo. En su lección sobre el descenso de gradiente, una sola η\eta no podía servir a la vez para una dirección empinada y otra casi plana, y los arreglos, momentum y Adam, quedaron fuera. En un batch del mini-GPT hay coordenadas del gradiente miles de veces más pequeñas que otras. AdamW le da a cada parámetro su propia escala, de modo que ninguno se mueve mucho más que η\eta por paso, sea cual sea el tamaño de su gradiente.

El tercero, la η\eta que sube y baja, se ve en el entrenamiento del checkpoint.

Dos gráficas apiladas que comparten el eje horizontal, los 2 000 pasos del entrenamiento del checkpoint. Arriba, la tasa de aprendizaje: sube en línea recta desde cero hasta 0.003 en el paso 200 y después baja en una curva suave, media onda de coseno, hasta 0.0003 en el paso 2 000. Abajo, dos pérdidas. La de cada batch, en gris, arranca en 6.23, cae deprisa hasta 4 hacia el paso 200 y sigue bajando cada vez más despacio hasta 2.5. La del texto reservado, en verde, medida cada 250 pasos, baja con ella al principio, se separa por encima hacia el paso 750 y se queda casi plana en 3.2 desde el paso 1 500. Una línea vertical discontinua en el paso 1 750 marca el checkpoint, el punto verde más bajo.
Los 2 000 pasos del checkpoint. La tasa sube en línea recta durante 200 pasos y baja en coseno hasta la décima parte, y casi toda la pérdida se gana mientras está alta. El checkpoint es el paso 1 750, el de menor pérdida sobre el texto reservado, que desde el 1 500 apenas se mueve.

Un batch al azar apunta, de media, adonde apunta el corpus

Fijemos la parte de entrenamiento del corpus y llamemos Li(θ)\mathcal{L}_i(\theta) a la pérdida de la lección sobre el modelo causal en la ventana de T+1T + 1 tokens que empieza en la posición ii: la media de sus TT valores de −log⁡pθ-\log p_\theta. La pérdida que entrenar quiere bajar es la media de todas,

Lcorpus(θ)=Ei[Li(θ)],\mathcal{L}_{\text{corpus}}(\theta) = \mathbb{E}_i\left[\mathcal{L}_i(\theta)\right],

donde ii se elige al azar, con la misma probabilidad para cada posición en la que cabe una ventana, y E\mathbb{E} es la media sobre ese azar.

Un batch de tamaño BB son BB posiciones i1,…,iBi_1, \dots, i_B sacadas así, cada una por su cuenta, y su pérdida es la media de las suyas:

L^(θ)=1B∑j=1BLij(θ).\hat{\mathcal{L}}(\theta) = \frac{1}{B}\sum_{j=1}^{B}\mathcal{L}_{i_j}(\theta).

En el código, ventanas(ids, B, T, rng) devuelve X e Y, dos matrices de ids de forma B×TB \times T, la segunda corrida una posición, y la red devuelve logits de forma B×T×∣V∣B \times T \times \lvert V \rvert. El gradiente de L^\hat{\mathcal{L}} es el de siempre, el softmax menos el one-hot en cada fila y después la vuelta de la lección sobre backpropagation, y perdida devuelve las dos cosas, con los gradientes en un diccionario de las mismas claves y formas que los pesos.

Dos propiedades hacen que L^\hat{\mathcal{L}} sirva. La primera es que no tiene sesgo. Cada iji_j sale del mismo sorteo que ii, así que la media de Lij(θ)\mathcal{L}_{i_j}(\theta) es Lcorpus(θ)\mathcal{L}_{\text{corpus}}(\theta) para cada jj, y la media de BB números con esa media tiene esa media. El gradiente es lineal y atraviesa la media igual que atraviesa una suma:

E[∇θL^(θ)]=∇θLcorpus(θ).\mathbb{E}\left[\nabla_\theta \hat{\mathcal{L}}(\theta)\right] = \nabla_\theta \mathcal{L}_{\text{corpus}}(\theta).

Llamemos g\mathbf{g} al lado derecho, el gradiente que costaría dos horas, y g^\hat{\mathbf{g}} al de dentro de la media, el del batch: dos vectores con una coordenada por parámetro. Un paso con g^\hat{\mathbf{g}} va, de media, adonde iría un paso con g\mathbf{g}.

La segunda es que su ruido baja como 1/B1/B. Lo que g^\hat{\mathbf{g}} se aleja de g\mathbf{g}, medido como la media del cuadrado de la distancia, es

E[∥g^−g∥2]=1B E[∥∇θLi−g∥2],\mathbb{E}\left[\lVert \hat{\mathbf{g}} - \mathbf{g} \rVert^{2}\right] = \frac{1}{B}\,\mathbb{E}\left[\lVert \nabla_\theta \mathcal{L}_i - \mathbf{g} \rVert^{2}\right],

el ruido de una sola ventana dividido entre BB.

Ver de dónde sale la división entre B

g^−g\hat{\mathbf{g}} - \mathbf{g} es la media de las BB desviaciones ∇θLij−g\nabla_\theta \mathcal{L}_{i_j} - \mathbf{g}, y el cuadrado de su norma es

∥g^−g∥2=1B2∑j=1B∑k=1B(∇θLij−g)⊤(∇θLik−g).\lVert \hat{\mathbf{g}} - \mathbf{g} \rVert^{2} = \frac{1}{B^{2}}\sum_{j=1}^{B}\sum_{k=1}^{B}\left(\nabla_\theta \mathcal{L}_{i_j} - \mathbf{g}\right)^{\top}\left(\nabla_\theta \mathcal{L}_{i_k} - \mathbf{g}\right).

Toma la media término a término. Si j≠kj \neq k, las dos posiciones se sortearon por separado, así que la media del producto es el producto de las medias, y cada una de ellas vale E[∇θLi]−g=0\mathbb{E}\left[\nabla_\theta \mathcal{L}_i\right] - \mathbf{g} = \mathbf{0}. Los B2−BB^{2} - B términos cruzados se anulan, que es la misma cuenta con la que la lección sobre el producto interno escalado sumaba productos independientes. Quedan los BB términos con j=kj = k, cada uno de media E[∥∇θLi−g∥2]\mathbb{E}\left[\lVert \nabla_\theta \mathcal{L}_i - \mathbf{g} \rVert^{2}\right], divididos entre B2B^{2}.

El precio está en la raíz. El tamaño típico del error baja como 1/B1/\sqrt{B} y el coste del paso crece como BB: la mitad de ruido cuesta cuatro veces más tokens. Y las ventanas se sacan al azar, no en orden como en el modelo de lenguaje de caracteres, porque las dos cuentas necesitan que cada iji_j salga del mismo sorteo que ii. En orden, el gradiente de cada paso sería el de una página de la novela, y el modelo iría detrás del argumento.

Un paso de AdamW, a la tasa que toca

Numeremos los pasos s=1,2,…,Ss = 1, 2, \dots, S y llamemos θs\theta_s a los parámetros después del paso ss (el curso anterior escribía θt\theta_t, pero aquí tt ya es la posición dentro de la ventana). El paso ss saca un batch, calcula su gradiente g^s\hat{\mathbf{g}}_s y hace con él tres cosas, en este orden.

Primero, el recorte de la lección sobre el gradiente que se desvanece: si ∥g^s∥>1\lVert \hat{\mathbf{g}}_s \rVert > 1, lo dividimos entre su norma. Aquí la norma es la de las 136 448136\,448 coordenadas juntas y no la de cada grupo de pesos, así que el recorte encoge el paso sin torcer su dirección.

Después, AdamW. Guarda dos medias móviles, una del gradiente y otra de su cuadrado coordenada a coordenada, que empiezan en cero y en cada paso le dan un peso 1−ρ1 - \rho al gradiente nuevo y ρ\rho a lo que ya llevaban:

ms=ρ1 ms−1+(1−ρ1) g^s,vs=ρ2 vs−1+(1−ρ2) g^s⊙g^s,\mathbf{m}_s = \rho_1\,\mathbf{m}_{s-1} + (1 - \rho_1)\,\hat{\mathbf{g}}_s, \qquad \mathbf{v}_s = \rho_2\,\mathbf{v}_{s-1} + (1 - \rho_2)\,\hat{\mathbf{g}}_s \odot \hat{\mathbf{g}}_s,

con m0=v0=0\mathbf{m}_0 = \mathbf{v}_0 = \mathbf{0}. (El artículo de Adam y minigpt.py llaman β1\beta_1 y β2\beta_2, beta1 y beta2, a lo que aquí es ρ1\rho_1 y ρ2\rho_2: la β\beta queda reservada para el bloque 2.) Con ellas mueve los parámetros, coordenada a coordenada:

θs=θs−1−ηs(ms/(1−ρ1s)vs/(1−ρ2s)+ε+λ θs−1).\theta_s = \theta_{s-1} - \eta_s\left(\frac{\mathbf{m}_s / (1 - \rho_1^{s})}{\sqrt{\mathbf{v}_s / (1 - \rho_2^{s})} + \varepsilon} + \lambda\,\theta_{s-1}\right).

El mini-GPT usa ρ1=0.9\rho_1 = 0.9, ρ2=0.999\rho_2 = 0.999 y ε=10−8\varepsilon = 10^{-8}, que sólo evita dividir entre cero. Y λ=0\lambda = 0: he entrenado el checkpoint sin decaimiento de pesos, así que su Adam es AdamW con la W apagada. Escribirla es el reto de esta lección.

La fórmula queda enunciada (el artículo sobre AdamW de «Para profundizar», al pie, la deriva), pero tres cosas se leen en ella. La división entre 1−ρs1 - \rho^{s} deshace el arranque en cero: en el primer paso, m1/(1−ρ1)\mathbf{m}_1 / (1 - \rho_1) es g^1\hat{\mathbf{g}}_1 y v1/(1−ρ2)\mathbf{v}_1 / (1 - \rho_2) es su cuadrado, así que el cociente vale el signo del gradiente, coordenada a coordenada. El primer paso mueve η1\eta_1 entero cada parámetro cuyo gradiente no sea cero, sea éste de 10−410^{-4} o de 1010.

Después, el cociente compara la media reciente del gradiente con la raíz de la media reciente de su cuadrado. Si el gradiente insiste en un signo, las dos se parecen y el cociente ronda ±1\pm 1; si su signo es ruido, se acerca a 00. Cada parámetro se mueve, como mucho, del orden de ηs\eta_s por paso: la η\eta única del curso anterior, con una escala propia para cada coordenada.

Y λ θs−1\lambda\,\theta_{s-1} va fuera del cociente: cada peso se encoge la misma fracción, ηsλ\eta_s \lambda, tenga la historia de gradientes que tenga. Eso es el decaimiento de pesos desacoplado, la W (weight decay) que Loshchilov y Hutter le añadieron a Adam. Sumado a g^s\hat{\mathbf{g}}_s, pasaría por la división y encogería menos a los pesos que más se mueven.

Queda ηs\eta_s. El checkpoint la saca de un calendario de dos tramos, un calentamiento (warm-up) lineal durante ScalS_{\text{cal}} pasos y un descenso en coseno hasta la décima parte,

ηs=ηmax⁡⋅min⁡(1,sScal)⋅(0.1+0.9⋅1+cos⁡(πs/S)2),\eta_s = \eta_{\max} \cdot \min\left(1, \frac{s}{S_{\text{cal}}}\right) \cdot \left(0.1 + 0.9 \cdot \frac{1 + \cos(\pi s / S)}{2}\right),

con ηmax⁡=3⋅10−3\eta_{\max} = 3 \cdot 10^{-3}, Scal=200S_{\text{cal}} = 200 y S=2 000S = 2\,000: la curva de arriba de la figura.

El calentamiento sale de la primera lectura de la fórmula. Mientras ms\mathbf{m}_s y vs\mathbf{v}_s llevan uno o dos gradientes, cada parámetro da un paso de casi ηs\eta_s en la dirección del signo de un gradiente lleno de ruido: 136 448136\,448 pasos a ciegas del mismo tamaño, que sólo son pequeños si ηs\eta_s lo es.

El descenso sale de la primera sección. Cerca del final, con la pérdida casi plana, g\mathbf{g} es pequeño pero el ruido del batch no se va, y el gradiente de cada paso es casi todo ruido. Con η\eta fija, el modelo tiembla alrededor del mínimo a una distancia que crece con η\eta; bajarla al final lo deja asentarse.

El mini-GPT, entrenado delante de ti

Las tres celdas corren sobre el mini-GPT: minigpt.py, el fichero de NumPy que produjo el checkpoint, y minigpt.json, sus pesos. Es la columna del proyecto del Transformer en pequeño (dos bloques, cuatro cabezas, dmodel=64d_{\text{model}} = 64) más perdida, que hace la ida, la pérdida y la vuelta, y Adam.

La primera carga el tokenizador, el modelo y el corpus, aparta el último 10 %, el texto reservado que el checkpoint no vio nunca, y saca un batch de cuatro ventanas de ocho tokens para que veas las formas. Después mide sobre él el checkpoint y la misma red con los pesos del paso 0.

import json
import numpy as np
from pyodide.http import open_url

exec(open_url("/courses/llm-agents/bpe.py").read()) # codificar, vocabulario, quitar_cabecera
exec(open_url("/courses/llm-agents/minigpt.py").read()) # MiniGPT, ventanas, Adam
F = [tuple(par) for par in json.load(open_url("/courses/llm-agents/bpe-merges.json"))]
V = vocabulario(F)
ids = np.array(codificar(quitar_cabecera(open_url("/courses/llm-agents/corpus.txt").read()), F))
n_val = len(ids) // 10 # el último 10 %: el checkpoint no lo vio
entren, val = ids[:-n_val], ids[-n_val:]
print(len(entren), "tokens de entrenamiento y", len(val), "reservados")

X, Y = ventanas(entren, 4, 8, np.random.default_rng(0)) # B = 4 ventanas de T = 8 tokens, y uno más
print("X", X.shape, " Y", Y.shape)
print("X[0] =", [V[i].decode("utf-8", "replace") for i in X[0]])
print("Y[0] =", [V[i].decode("utf-8", "replace") for i in Y[0]])

pesos = open_url("/courses/llm-agents/minigpt.json").read()
modelo = MiniGPT.cargar(pesos)
nuevo = MiniGPT(semilla=0) # la misma red, con los pesos del paso 0
Xv, Yv = ventanas(val, 8, 64, np.random.default_rng(1)) # ocho ventanas del texto reservado
print("%d parámetros ln 512 = %.3f" % (modelo.n_parametros(), np.log(512)))
print("pérdida sin entrenar: %.3f" % nuevo.perdida(Xv, Yv)[0])
print("pérdida del checkpoint: %.3f" % modelo.perdida(Xv, Yv)[0])
numpy

The first run downloads the Python interpreter (~15 MB). After that it stays in the browser cache and is reused across every lesson.

La primera fila de X es Anduvieron b en ocho tokens, y la de Y, la misma corrida uno, acaba en re. La red sin entrenar paga 6.2356.235, casi ln⁡512=6.238\ln 512 = 6.238: con pesos de tamaño 0.020.02 los logits salen casi iguales y el softmax reparte por igual. El checkpoint paga 3.1803.180. Entre los dos números están los 2 0002\,000 pasos de la figura.

La segunda pone a prueba el 1/B1/B. Para B=1B = 1, 44 y 1616 ventanas de ocho tokens, saca ocho batches independientes y mide cuánto se alejan sus gradientes de la media de los ocho: una estimación de E[∥g^−g∥2]\mathbb{E}\left[\lVert \hat{\mathbf{g}} - \mathbf{g} \rVert^{2}\right]. Si la sección tiene razón, BB por el ruido no cambia.

# Necesita la celda anterior: modelo, entren, ventanas.
def gradiente(B, rng, T=8):
"""El gradiente de la pérdida de un batch de B ventanas: sus 136 448 números, en fila."""
X, Y = ventanas(entren, B, T, rng)
_, g = modelo.perdida(X, Y)
return np.concatenate([v.ravel() for v in g.values()])

rng = np.random.default_rng(2)
for B in [1, 4, 16]:
G = np.array([gradiente(B, rng) for _ in range(8)]) # ocho batches independientes, uno por fila
# estima E||g_batch - g||^2; ddof=1 porque la media también sale de estos ocho
ruido = G.var(axis=0, ddof=1).sum()
print("B = %2d ruido %6.1f B · ruido %6.1f" % (B, ruido, B * ruido))
numpy

The first run downloads the Python interpreter (~15 MB). After that it stays in the browser cache and is reused across every lesson.

El ruido cae de 540540 a 155155 y a 3838, catorce veces menos con dieciséis veces más ventanas, y BB por el ruido se queda entre 540540 y 620620. Las estimaciones tienen su propio ruido; cambia la semilla y la constante sigue ahí.

La tercera sigue entrenando, sobre una copia del checkpoint y con un Adam nuevo: el checkpoint guarda θ\theta pero no m\mathbf{m} ni v\mathbf{v}, así que el calentamiento vuelve a hacer falta. Entrena sobre los últimos 3 0003\,000 tokens del texto reservado, las últimas páginas de Marianela, con una sola ventana de 1616 tokens por paso: el batch más ruidoso, pero el que cabe en el navegador. El calendario es el del checkpoint en pequeño, con ηmax⁡=5⋅10−4\eta_{\max} = 5 \cdot 10^{-4}, más o menos donde el suyo lo dejó (4⋅10−44 \cdot 10^{-4} en el paso 1 750). En un dispositivo lento, la celda se detiene antes de que el navegador la corte, y dice dónde.

# Necesita la primera celda: pesos, val, ventanas, MiniGPT, Adam.
import time

eta_max, S_cal, S = 5e-4, 50, 200

def tasa(s):
"""El calendario del checkpoint en pequeño: calentamiento lineal y coseno hasta la décima parte."""
return eta_max * min(1, s / S_cal) * (0.1 + 0.9 * (1 + np.cos(np.pi * s / S)) / 2)

alumno = MiniGPT.cargar(pesos) # una copia: el checkpoint no se toca
opt = Adam(alumno.p) # m y v en cero: el checkpoint no los guarda
cola = val[-3000:] # las últimas páginas, que no vio nunca
Xp, Yp = ventanas(cola, 8, 16, np.random.default_rng(1)) # ocho ventanas fijas de esas páginas
rng, t0 = np.random.default_rng(0), time.time()
print("paso 0 pérdida %.3f" % alumno.perdida(Xp, Yp)[0])
for s in range(1, S + 1):
X, Y = ventanas(cola, 1, 16, rng) # B = 1: lo que cabe en el navegador
_, g = alumno.perdida(X, Y)
norma = np.sqrt(sum((v ** 2).sum() for v in g.values()))
if norma > 1: # el recorte, sobre las 136 448 a la vez
g = {k: v / norma for k, v in g.items()}
opt.paso(alumno.p, g, tasa(s))
if s % 25 == 0:
print("paso %3d pérdida %.3f eta %.1e" % (s, alumno.perdida(Xp, Yp)[0], tasa(s)))
if time.time() - t0 > 8: # el navegador corta a los 10 s
print("tiempo agotado en el paso", s)
break
numpy

The first run downloads the Python interpreter (~15 MB). After that it stays in the browser cache and is reused across every lesson.

La pérdida sobre esas páginas baja de 3.4733.473 a 3.1043.104, pero no en cada medida: sube en el paso 50, y otra vez en el 100 y el 125. Con una ventana por paso, cada g^s\hat{\mathbf{g}}_s es casi todo ruido y el modelo avanza dando tumbos. Y lo que baja es la pérdida sobre las páginas con las que entrena: si eso es aprender español o aprenderse esas páginas lo mide la lección sobre perplejidad, más adelante en el bloque.

Ahora quítale el calentamiento: pon eta_max, S_cal, S = 3e-3, 1, 200, la tasa máxima del checkpoint sin calentar, y vuelve a ejecutarla. En 25 pasos la pérdida salta de 3.473.47 a 4.894.89, porque el primer paso mueve cada parámetro 3⋅10−33 \cdot 10^{-3} en la dirección de un solo gradiente lleno de ruido. Con S_cal = 50 el salto se retrasa, no desaparece: llega hacia el paso 75, a 4.354.35, justo después de que la tasa toque su máximo. El calentamiento protege el arranque y nada más. El checkpoint salió del final de su calendario, con la tasa en la décima parte, y reanudarlo a la máxima lo saca de donde estaba.

Comprueba tu intuición

Cuatro preguntas sobre el batch y el primer paso de Adam, y un reto: escribir AdamW.

Con ids un array largo de ids, como el corpus de la primera celda, y ventanas la de minigpt.py, ¿qué imprime esto?

X, Y = ventanas(ids, 4, 16, np.random.default_rng(0))
print(X.shape, Y.shape, (X[:, 1:] == Y[:, :-1]).all())
 

Pasas de B=8B = 8 a B=32B = 32 ventanas por paso, con la misma TT. ¿Qué les pasa al tamaño típico del error del gradiente, E[∥g^−g∥2]\sqrt{\mathbb{E}\left[\lVert \hat{\mathbf{g}} - \mathbf{g} \rVert^{2}\right]}, y al coste de cada paso?

El checkpoint dio S=2 000S = 2\,000 pasos con batches de B=32B = 32 ventanas de T=64T = 64 tokens, sobre los 125 789125\,789 tokens de entrenamiento. ¿Cuántas veces, de media, le tocó predecir cada token? Redondea a un decimal.

A margin of ±0.3 is accepted.

Cargas el checkpoint, creas un Adam nuevo y das un solo paso con η=3⋅10−3\eta = 3 \cdot 10^{-3}, sin calentamiento. ¿Qué le pasa a un parámetro cuyo gradiente, en ese paso, no es cero?

Escribe el método paso de AdamW: un paso de la fórmula de la sección, coordenada a coordenada, sobre un diccionario de pesos como modelo.p. En cada llamada suma uno a self.s, actualiza las dos medias móviles de cada peso, self.m[k] y self.v[k], con su gradiente g[k], y resta a p[k], en su sitio, lo que dice la fórmula: con la eta de esa llamada y el decaimiento self.lam fuera del cociente. Con lam = 0 tiene que dar lo mismo que el Adam de minigpt.py.

The first run downloads the Python interpreter (~15 MB); after that it stays in the browser cache. This challenge is much easier to solve on a physical keyboard: on a phone, read it and come back later.


El mini-GPT ya se entrena, y lo que sale de él es lo mismo que salía en la tabla a mano de la lección sobre el modelo de lenguaje causal: en cada posición, una distribución sobre las 512 entradas. La pérdida sólo le pregunta cuánto apostó por el token que de verdad venía; nunca le pide que elija uno. Para escribir texto hay que elegir, token a token, y el modelo no dice cómo.

Ése es el asunto de la lección siguiente, sobre el muestreo: quedarse siempre con la entrada más probable, que en el mini-GPT acaba repitiendo no, no, no, o sortear entre las posibles con una temperatura, un top-k o un top-p, y qué parte de la distribución tira cada uno.

Was this helpful?
Further reading3 sources · 2 papers, 1 article

Where this lesson comes from, and where to go next. None of it is needed to carry on with the course.

  • Por qué AdamW para entrenar modelos de lenguaje, y qué está cambiando
    articleGustavo Torres, 2026gustavoai.devES

    El artículo de este sitio que deriva lo que la lección enuncia: de dónde salen las dos medias de Adam, por qué la W va fuera del cociente y qué justifica el calentamiento y el descenso de la tasa.

  • Decoupled Weight Decay Regularization
    paperLoshchilov y Hutter, 2019ICLR 2019 · arXiv:1711.05101EN

    El artículo de la W: por qué, con Adam, sumar el decaimiento al gradiente no es lo mismo que aplicarlo fuera. Su algoritmo 2 pone las dos versiones una al lado de la otra, y la segunda es el reto de esta lección.

  • Language Models are Few-Shot Learners
    paperBrown et al., 2020arXiv:2005.14165EN

    El artículo de GPT-3. Su apéndice B es la receta de esta lección a escala: AdamW, recorte a norma 1, calentamiento lineal y coseno hasta el 10 % de la tasa, para un modelo de 175 000 millones de parámetros.