rukh · lab

// M2 · lección 11

Más datos, no más red: de small a medium-v4

La lección del módulo que no estaba en el plan: por qué small se quedaba corto, qué se probó primero y qué se probó después, la escalera de Elo que estaba mal calibrada y el modelo de 115 millones de parámetros sobre 1 681 millones de tokens del que parten los dos módulos siguientes.

  • tiempo de trabajo45 min
  • además, ejecución sin supervisión+ 368 min de GPU y red
  • nivel base
  • actualizado el22 de septiembre de 2026

Última lección del módulo. Viene de «Los labs del decoder» y cierra M2 con el modelo que M4 y M5 van a afinar.

Qué vas a construir

Al terminar esta lección tendrás medium-v4: el decoder de 115 120 128 parámetros entrenado sobre 1 681 millones de tokens, con 1504 de Elo estimado (IC 95 % 1446-1558) y un 99,8 % de jugadas legales sin máscara. Es el modelo del que parten el afinado por Elo y los adaptadores de M4 y los tres modelos alineados de M5, y es el que se publica como chorcat/rukh-medium.

Pero el objeto de esta lección no es el modelo: es cómo se llegó a él, porque el camino contradice lo que las diez lecciones anteriores daban por sentado. M2 cerró diciendo que a small le faltaba entrenamiento y que el siguiente paso era una red más grande. Las dos cosas estaban mal, y lo que las desmintió fue una división y una escalera.

El diagnóstico cabe en una división

«Exportar a ONNX» terminó con el hueco entre la pérdida de entrenamiento y la de validación de small creciendo —0,021, 0,046, 0,060— mientras el top-1 se aplanaba. Antes de tocar nada, la cuenta que había que hacer:

data/tokens/uci/train/meta.json: 2 949 514 partidas, 240 068 954 tokens
parámetros de small: 38 971 392
tokens únicos por parámetro: 240 M / 39 M = 6,2
pasadas sobre el corpus: 20 000 pasos × 256 secuencias × 200 tokens = 1 024 M / 240 M = 4,3

Seis tokens por parámetro, cuando la relación que la literatura de escalado da como razonable ronda los veinte. Y cuatro pasadas y pico sobre el mismo material. El hueco que crecía no era «algo de sobreajuste, normal»: era la firma de repetir material, y explica también por qué medium, con el triple de parámetros y los mismos datos, solo había comprado 84 puntos de Elo —1091 (IC 990-1194) frente a los 1007 (920-1101) de small, medidos los dos en la escalera de entonces (D-054)— con los intervalos solapados. Más red sobre el mismo libro recita mejor el libro.

Y había 238 millones de tokens parados: el mes de febrero entero era validación, para un bucle que evalúa leyendo 640 000. Esa es la primera palanca, y cuesta cero descargas.

Tres corridas, una palanca cada una

// Antes de empezarQué cuesta cada lab, y cuál puedes saltarte

LabRutaRelojDejaAtajo
Corrida v2Más datos con la misma red pequeñacuesta máquina~45 mincheckpoints/small-v2/best.ptno hay
Corrida v3La Elite de 44 meses, tokenizada y entrenadacuesta máquina~1,5 h de descarga + 5 min de tokenizar + ~45 de entrenardata/elite/games.parquet, checkpoints/small-v3/best.ptrukh pull rukh-games-elite, y rukh pull small para el modelo
Corrida v4medium-v4: la capacidad, cuando por fin está justificadacuesta máquina4 h 3 min + ~30 de evaluacióncheckpoints/medium-v4/best.pt, la base de M4 y M5rukh pull medium-v4
cuesta máquina
Tiempo real de GPU, red o motor. El reloj es el de la RTX 5090 de referencia.

Las tres corridas comparten planificador, semilla y la misma validación congelada: las 100 000 primeras partidas de 2025-02, que desde aquí es lo único que no entrena (D-062). Cada una cambia una sola cosa respecto a la anterior, para que la diferencia sea atribuible.

v2: devolver el mes de validación al entrenamiento. configs/data/pipeline-v2.yaml deja val_games: 100000 y manda el resto de febrero al corpus: 240 M → 471 M tokens únicos, 6,2 → 12,1 por parámetro. La misma receta de small, 28 000 pasos en vez de 20 000.

Terminal
uv run rukh data tokenize --config configs/data/pipeline-v2.yaml --scheme uci --pack
uv run rukh train --config configs/train/small-v2.yaml

v3: cambiar de qué está hecho el corpus. La Lichess Elite Database publica en database.nikonoel.fr las partidas de 2500+ contra 2300+, sin bullet, por meses. Veinticuatro meses son unos 6,5 millones de partidas, y son a la vez más datos y otra distribución: el corpus de 1800+ tiene un 3,4 % de partidas con las blancas en 2400 o más, la Elite un 93 %. configs/data/pipeline-v3.yaml los añade con extra_train_parquets y deja la validación donde estaba, para que la pérdida siga siendo comparable.

Terminal
uv run rukh data elite --config configs/data/pipeline-elite44.yaml # o: uv run rukh pull rukh-games-elite
uv run rukh data tokenize --config configs/data/pipeline-v3.yaml --scheme uci --pack
uv run rukh train --config configs/train/small-v3.yaml

(pipeline-elite44.yaml descarga los 44 meses que usa la corrida final; v3 se hizo con los primeros 24 y la config tokeniza lo que haya en data/elite/games.parquet. Si sigues el orden de esta página tu v3 llevará 44, que es más de lo que tuvo la de referencia.)

Salida real de las dos, con v1 de M2 al lado. «General» es la validación congelada; «fuerte» es la misma pérdida sobre partidas de 2200+:

Modelo Par. Tokens únicos general top-1 fuerte 2200+ top-1 hueco train/val
small v1 39 M 240 M 1,5197 51,20 % 1,5391 50,84 % +0,0603
medium v1 115 M 240 M 1,4782 52,31 % 1,4880 52,06 %
small v2 39 M 471 M 1,4703 52,49 % 1,4652 52,69 % +0,0242
small v3 39 M 1 095 M 1,4770 52,19 % 1,4537 53,02 %

Dos lecturas, en el orden en que se hicieron:

  1. Recuperar el mes vale −0,049 nats y el hueco cae a la mitad. small v2, con 39 millones de parámetros, bate al medium de 115 millones entrenado con los datos viejos. El cuello eran los datos; ahora está medido y no inferido. El tiempo de entrenamiento fue 58 minutos.
  2. La composición mueve otra cosa que la cantidad. v3 cede 0,007 en juego promedio —su corpus se parece menos a la validación— y gana 0,012 sobre juego fuerte. Y mueve el eje de Elo: la divergencia entre las distribuciones condicionadas a <2800> y a <1800> pasa de 0,047 nats en v1 y 0,049 en v2 a 0,115 en v3. Duplicar los datos (v2) no había movido ese eje; cambiar de qué están hechos (v3) sí. Sin élite, el «juega como 1500 / 2000 / 2400» de M4 no tenía de dónde salir.

La escalera estaba torcida

Mientras se medían esas corridas apareció una contradicción que ningún modelo puede resolver: small v2 puntuaba 0,725 contra un Stockfish con UCI_Elo 1320 y perdía veinte a cero contra skill-3, un rival etiquetado con 1250. Un modelo no puede ganar a uno de 1320 y perder siempre contra uno de 1250. Lo que se puede medir es el rival, y el descubrimiento entero —motor contra motor, la tabla de los cuatro escalones medidos entre 1381 y 1678 en vez de entre 800 y 1250, y el control de uci-1500 a +179 sobre uci-1320 que valida el método— está contado en «El Elo y la suite», al lado del informe que lo destapó. Aquí solo lo que esa lección no podía saber todavía: qué le hizo la corrección a todo lo publicado hasta ese día.

Etapa Publicado Corregido IC 95 %
small v1 1007 1359 1293-1429
small v2 1070 1407 1344-1462
small v3 1095 1425 1367-1485

El listón de 1200 nunca se falló. small v1 estaba publicado en Hugging Face con «Elo bar not met» y estaba en 1359. Hubo que corregir once model cards, dos planes y las lecciones, explicando el porqué y no solo la cifra. Lo relativo no cambia: la corrección sube a todos por igual, y el trabajo de datos vale +66 Elo en la escala corregida igual que valía +88 en la torcida.

medium-v4: capacidad, cuando por fin está justificada

Con 1 681 millones de tokens únicos —enero, el resto de febrero y 44 meses de Elite, 18 942 740 partidas— la división sale a 14,6 tokens únicos por parámetro para 115 millones (1 681 M / 115 M), siete veces más que los 2,1 de medium v1 y a la altura de los 12,1 de small v2, así que medium deja de ser una red grande sobre un libro pequeño. configs/train/medium-v4.yaml: 48 000 pasos, 2 457 millones de tokens vistos —21 por parámetro, la cifra que engaña si se confunde con la de únicos—, 1,46 pasadas.

Terminal
uv run rukh data tokenize --config configs/data/pipeline-v4.yaml --scheme uci --pack
uv run rukh train --config configs/train/medium-v4.yaml
uv run rukh eval --model checkpoints/medium-v4/best.pt --config configs/eval/greedy.yaml --stage medium-v4-greedy

Tokenizar tarda cinco minutos y deja 3,3 GB en data/tokens-v4/uci/. El entrenamiento duró 4 h 3 min y es el trabajo más largo de la primera fase; se lanza al final de una sesión y se recoge en la siguiente. La evaluación canónica —greedy.yaml, temperatura 0,05, la cabecera fijada en 1800, la escalera corregida— tarda media hora.

Modelo Par. Tokens únicos val/loss Elo IC 95 % legal argmax top-1 puzles
small v1 39 M 240 M 1,5197 1359 1293-1429 99,40 % 51,10 % 22,07 %
medium v1 115 M 240 M 1,4782 99,40 % 52,90 % 23,93 %
small v2 39 M 471 M 1,4703 1407 1344-1462 99,30 % 51,80 % 26,93 %
small v3 39 M 1 095 M 1,4770 1425 1367-1485 99,10 % 52,40 % 26,73 %
medium-v4 115 M 1 681 M 1,3733 1504 1446-1558 99,80 % 54,40 % 37,50 %

Las columnas de Elo, legalidad, top-1 y puzles son las de la evaluación canónica (greedy.yaml), que no es la misma medida que el top-1 de validación de la tabla anterior: por eso medium v1 lee 52,90 % aquí y 52,31 % allí. Su Elo va en blanco porque solo se midió en la escalera torcida —1091 (990-1194)— y nunca se reevaluó en la corregida; ponerle un número a esa celda sería inventarlo.

Tres cosas de esa fila:

  • No era la arquitectura. El mismo medium, con la misma receta, pasa de 1,4782 con 240 M de tokens repetidos 4,3 veces a 1,3733 con siete veces más material único y 1,46 pasadas.
  • La legalidad no era una tendencia. La serie 99,40 → 99,30 → 99,10 % de las tres small se había leído como algo a vigilar. Era un modelo de 39 M estirándose sobre un corpus creciente; con capacidad suficiente, fuerza y legalidad suben juntas, y 99,80 % es la más alta del proyecto.
  • Los puzles son donde más se ve. De 22,07 % a 37,50 %, un 70 % relativo. Un puzle es la jugada única que gana, no la que jugó un humano, y es la métrica que menos premia imitar.

El reparto de los +145 Elo entre small v1 y medium-v4, palanca a palanca y en la escala corregida: recuperar el mes de validación +48, la Elite +18, la capacidad ya justificada +79. El condicionamiento por Elo, cero: esa historia es la de M4.

Lo que se publica y lo que se mide después

chorcat/rukh-small es small v3 —la mejor small de la serie—, y chorcat/rukh-medium es medium-v4. La demo sirve small porque 79 MB en fp16 caben en un móvil y 231 no; medium-v4 es para correrlo en local y para lo que viene.

Aquí es donde esos dos modelos publicados empiezan a ganarse el sueldo, y no es para que juegues contra ellos: es para comparar. Exporta tu medium-v4 y cárgalo en la demo con «Tu propio modelo»; después elige Rukh medium (fp16) en el desplegable de etapa y juega la misma apertura. Si tu corrida salió bien, las dos partidas deberían parecerse; si la tuya juega notablemente peor, tienes una pista de que algo del entrenamiento no fue como el de referencia, y la tienes antes de gastar media hora de evaluación en averiguarlo.

Sus cards llevan los números de la evaluación canónica del día en que se publicaron, y ahí aparece un detalle que conviene tener presente al comparar: small v3 midió 1425 (1367-1485) el 19 de septiembre y 1365 (1293-1423) el 20, con la misma config, la misma semilla y los mismos pesos. El rival juega por tiempo y con UCI_LimitStrength, que aleatoriza a propósito, así que la escalera se repite a sí misma con un suelo de unos 40 puntos (D-107). Es una báscula de baño que te da 82 y 84 al subirte dos veces seguidas: no está rota, pero con ella no se puede afirmar que has adelgazado un kilo, y una diferencia entre dos pesadas solo empieza a decir algo cuando supera con holgura esos dos kilos de oscilación. M5 va a construir un instrumento distinto por esto. De momento, la regla: dos Elo de la escalera se comparan dentro de una misma tirada, no entre tiradas. Las cifras de esta lección son las del 19 y 20 de septiembre; la tabla única las volvió a medir el 21 en una sola tirada (small v1 1355 (1297-1420) y medium-v4 1535 (1476-1599)) y M6 explica la diferencia.

// Ejercicio 01Haz la división antes de tu siguiente corrida

Toma meta.json del corpus con el que entrenaste small en la parte 2 y calcula tokens únicos por parámetro y pasadas para esa corrida. Después haz lo mismo para tiny (6 000 pasos de 128 × 2 × 200 tokens sobre el mismo corpus). ¿Cuál de las dos estaba más lejos de los veinte? ¿Cuál repitió más veces el material?

// SoluciónVer la solución

small: 240 M / 39 M ≈ 6,2 tokens por parámetro y 1 024 M / 240 M ≈ 4,3 pasadas. tiny: 240 M / 5,3 M ≈ 45 por parámetro, y 6 000 × 256 × 200 = 307 M tokens vistos, 1,3 pasadas. tiny estaba sobrado de datos y corto de red; small, al revés. Por eso la palanca correcta para small era material y para tiny habría sido capacidad —que es, dicho de otro modo, lo que small es respecto a tiny.

// Ejercicio 02Predice el Elo desde la pérdida, y comprueba cuánto te fías

Con small v1 (1,5197 → 1359) y medium-v4 (1,3733 → 1504) traza una recta pérdida-Elo y predice el Elo de small v2 (1,4703). Compáralo con el medido, 1407 (1344-1462). ¿Sirve la recta como criterio de aceptación?

// SoluciónVer la solución

La pendiente sale de (1504 − 1359) / (1,5197 − 1,3733) ≈ 990 Elo por nat, y la predicción para v2 es 1359 + 990 × 0,0494 ≈ 1408: clavado. Y aun así no sirve como criterio, por dos razones que el módulo ya ha enseñado. La recta se ajustó sobre modelos entrenados con la distribución de la validación, y v3 —con élite— cae fuera de ella (1,4770 predice 1401 y midió 1425). Y la escalera tiene un suelo de reproducibilidad de unos 40 puntos, así que una predicción a diez puntos es más precisa que el instrumento que la contrastaría. Sirve para descartar un experimento en 40 minutos; el Elo se mide siempre.

Qué has aprendido, cómo se mide

Que la primera pregunta ante un modelo que se queda corto no es cuántos parámetros tiene sino cuántos tokens distintos ha visto por cada uno, y que el hueco entre entrenamiento y validación es el instrumento que responde antes de gastar una GPU. Que cantidad y composición del corpus son dos palancas distintas, y que solo la segunda mueve un eje que no estaba en los datos. Que una escala contra la que mides tiene que haberse medido a sí misma, y que un control con respuesta conocida es lo que te dice si puedes fiarte de ella. Y que escalar la red es la decisión correcta exactamente cuando la división lo permite, y la equivocada un momento antes.

Lo medido: medium-v4, 1504 de Elo (1446-1558) y 99,80 % de legalidad sin máscara, los dos criterios del hito con el intervalo entero por encima; 37,50 % de puzles; y la historia completa en D-062 a D-073 del registro de decisiones.

Lo siguiente es M3, el encoder, que va a pasar por la misma corrección: el que se publicó también se reentrenó a 39 M sobre este corpus, y la última lección de M3 cuenta qué compró eso y qué no. La cheatsheet del módulo está justo debajo.

// cheatsheet M2

Once preguntas para llevarte

01Explica la atención en tres frases.
Cada posición proyecta su vector en una consulta (Q), una clave (K) y un valor (V). El producto escalar entre la consulta de una posición y las claves de todas las demás, dividido por la raíz de la dimensión de la cabeza y pasado por softmax, da un reparto de pesos que suma 1. La salida es la media de los valores ponderada por esos pesos: softmax(QKᵀ/√d)·V. La escala 1/√d existe porque el producto escalar de d dimensiones tiene varianza d y sin ella el softmax saturaría y el gradiente se anularía.
02¿Qué es la máscara causal y qué pasa si falta?
Poner a menos infinito, antes del softmax, el peso de toda clave posterior a la consulta, de modo que la matriz de atención queda triangular inferior. Sin ella la posición t puede leer el token t+1, que es justo el que tiene que predecir: la tarea pasa de predecir a copiar, la pérdida se desploma y el top-1 se dispara, pero al generar (donde no hay futuro) el modelo juega al azar. Se detecta porque la legalidad sin máscara se hunde, y se verifica con un test: cambiar un token futuro no debe mover los logits pasados ni un decimal.
03¿Por qué pre-norm y no post-norm?
Pre-norm pone el LayerNorm dentro de la rama, antes de la subcapa (x = x + attn(ln(x))), así que la conexión residual queda libre: la derivada de cada bloque es la identidad más la de la subcapa y el gradiente llega íntegro desde la pérdida hasta el embedding. Post-norm normaliza después de la suma, mete la normalización en el camino residual y hace que una pila profunda solo entrene con warmup largo e inicialización muy cuidada. Es un cambio de dos caracteres que convierte doce capas entrenables con suerte en doce capas entrenables.
04¿Qué hace el warmup y qué pasa sin él?
Sube la tasa de aprendizaje linealmente desde cero durante los primeros pasos (1 000 en rukh-small) antes de empezar el coseno. En los primeros pasos, Adam estima el segundo momento con un puñado de gradientes y su paso normalizado puede mover un parámetro tanto como la tasa entera; sobre una red recién inicializada eso satura la atención y el modelo cae en predecir siempre las jugadas más frecuentes. No suele explotar con un NaN: se queda en una pérdida mediocre que ya no baja, que es peor porque no se nota.
05Temperatura y top-k: ¿en qué se diferencian?
La temperatura divide los logits antes del softmax y reescala toda la distribución: por debajo de 1 la concentra (con 0 es el argmax), por encima la aplana. El top-k no reescala, recorta: deja solo los k tokens de mayor probabilidad y pone el resto a cero. Son complementarios, no alternativas. Con 2 030 jugadas, la cola de mil opciones improbables suma un porcentaje apreciable de disparates; el top-k la elimina y la temperatura decide cuánto arriesga el modelo dentro de lo que queda. Rukh usa temperatura 0,6 y k = 20.
06¿Qué mide la legalidad sin máscara, por qué hay dos cifras y por qué no vale medirla con máscara?
Mide el porcentaje de jugadas legales que propone el modelo cuando se le deja emitir el token que quiera, sobre 1 000 posiciones de validación. Se publica dos veces: legality_argmax toma el token más probable (sin temperatura ni top-k) y legality_sampled saca uno como lo saca la demo (temperatura 0,6 y top-k 20), que es siempre la menor de las dos porque muestrear mete cola. El listón de ≥ 99 % para rukh-small es el argmax, porque habla de lo que saben los pesos y no de un ajuste del muestreador; la muestreada es lo que experimenta un jugador y va al lado con su definición. Con la máscara de legalidad puesta el 100 % está garantizado por construcción, así que estarías midiendo a python-chess y no al modelo.
07¿Por qué bf16 y no fp16?
Los dos ocupan 16 bits, pero reparten distinto. fp16 tiene 5 bits de exponente y 10 de mantisa: mucha precisión y poco rango, con el mínimo normal en torno a 6e-5, justo donde viven los gradientes de una red profunda, así que se van a cero y hace falta un GradScaler con escalado dinámico de la pérdida. bf16 tiene 8 bits de exponente (el mismo rango que fp32) y 7 de mantisa: menos precisión por número y nada que se desborde por abajo, así que entrena sin escalar nada. En una GPU moderna es nativo, así que la simplicidad sale gratis.
08¿Qué es un checkpoint y qué tiene que llevar dentro?
Una fotografía del entrenamiento en disco. No basta con los pesos: lleva el paso alcanzado, el estado del optimizador (los momentos de Adam), el estado de los generadores aleatorios, la configuración del modelo y de la tirada y la procedencia (hash del vocabulario, hash del manifiesto de datos y SHA de git). Con eso se puede reanudar exactamente donde se cortó, evaluar el modelo meses después y decir con qué datos exactos se entrenó. Rukh escribe uno cada 1 000 pasos más best.pt por pérdida de validación.
09¿Qué es la perplejidad?
La exponencial de la entropía cruzada media. Se lee como entre cuántas opciones equiprobables duda el modelo: con un vocabulario de 2 030 jugadas, un modelo sin entrenar tiene perplejidad 2 030 y pérdida ln(2030) ≈ 7,6; una pérdida de 3,0 es perplejidad 20, es decir, acierta como si eligiera al azar entre veinte jugadas. Como en una posición típica hay unas treinta legales, esa cifra ya implica bastante aprendizaje. No es una métrica aparte: es la misma pérdida en otra escala.
10¿Qué significa un intervalo de confianza del Elo y por qué importa?
El Elo estimado es el valor que mejor explica los resultados contra rivales de fuerza conocida según la fórmula logística P = 1/(1+10^((rival−modelo)/400)); el intervalo del 95 % dice cuánto podría moverse ese valor si se repitieran las partidas, y se calcula por bootstrap remuestreando los resultados y reajustando mil veces. La suite juega 20 partidas contra cada uno de ocho escalones, 160 en total: a un 50 % de rendimiento la desviación típica sería de unos 27 puntos (±54), y el bootstrap sobre las partidas reales de small da ±68 (1359, IC 1293-1429), porque contra la mayoría de escalones el modelo está lejos del 50 % y cada partida informa menos. Es decir: una mejora de 10, 30 o 50 puntos de Elo medida con una escalera de 160 partidas no es una mejora, es ruido, y presumir de ella es el error clásico de los benchmarks.
11¿Cuándo se escala la red? Tokens únicos por parámetro.
Antes de subir de talla, divide los tokens únicos del corpus (los de meta.json, no los vistos por el bucle: repetir material no lo convierte en material nuevo) entre los parámetros. small v1 iba a 240 M / 39 M = 6,2, con 4,3 pasadas sobre el mismo corpus, y su hueco train/val crecía (+0,060) mientras el top-1 se aplanaba: la firma de material agotado, y triplicar la red (medium v1, mismos datos) solo compró 84 Elo con los intervalos solapados. Devolver el mes de validación al corpus (471 M, 12,1 por parámetro) bajó la pérdida del mismo small de 1,5197 a 1,4703 y batió a medium. medium-v4, con 1 681 M de tokens únicos (14,6 por parámetro, 1,46 pasadas), da 1504 de Elo y 99,80 % de legalidad. Hueco creciendo y métrica plana: faltan datos; hueco estable y métrica subiendo: hay margen en pasos o en capacidad.
Todas las cheatsheets, imprimibles →