// M4 · lección 01
Fine-tuning: qué es y cuántas maneras hay
La lección de apertura del módulo: qué es afinar un modelo y en qué se diferencia de entrenarlo, las cuatro maneras de hacerlo —SFT completo, condicionado por token, LoRA y QLoRA— y cuándo se elige cada una; qué cabe por un cuello de rango ocho, qué gana el proyecto entrando en el ecosistema de Hugging Face, y qué demuestra, y qué no, afinar un modelo general con QLoRA.
// el recorrido · dónde estás
- M0 El tallerEntorno reproducible(hecho)
- M1 Datos y tokensDatos y tokenización(hecho)
- M2 El decoderPreentrenar un GPT(hecho)
- M3 El encoderEmbeddings y clasificar(hecho)
- M4 Fine-tuningSFT y LoRA(estás aquí)
- M5 AlineamientoPreferencias: DPO, GRPO(por delante)
- M6 Evaluar y publicarMedir y desplegar(por delante)
- FASE 2 El entrenadorAgentes, RAG, MCP
M4, lección a lección
- 01Fine-tuning: qué es y cuántas maneras hay
- 02Los tokens de Elo y el corpus que faltaba
- 03LoRA escrita a mano
- 04Estilo y texto PGN: los dos cortes del corpus
- 05QLoRA con `transformers`: el decoder entra en el ecosistema
- 06Adaptadores y publicación: 1,6 MB que llegan al navegador
- 07Diversidad y barridos: dos veredictos en vez de uno
- 08El selector en la demo: seis condiciones y 1,6 MB
- 09Lo que salió: cinco mediciones y cuatro criterios
- 10Los labs del afinado
entra
- medium-v4 preentrenado
- partidas por tramo de Elo
se construye
- corpus equilibrado
- LoRA a mano
- adaptadores de estilo
- QLoRA de Qwen3
sale
- medium-elo
- adaptadores de 1,6 MB
- selector en la demo
el aprendiz, ahora
Clases particulares: jugar como un 1500 o como los maestros. Con LoRA no se reescribe su libro; se le pegan notas adhesivas encima.
por qué este paso · fuera del ajedrez
Adaptar un modelo que ya existe es lo que harás casi siempre en la práctica: barato, rápido y reversible.
Lección 1 de 10 del módulo «Fine-tuning e instrucción». Es la única sin código: aquí se decide qué se va a construir y por qué, y las ocho siguientes lo construyen fichero a fichero. Sigue en «Los tokens de Elo y el corpus que faltaba».
Qué vas a construir
El aprendiz ya ha leído millones de partidas y sabe adivinar la siguiente jugada (M2), y ha aprendido a mirar una posición entera y juzgarla (M3). Ahora le tocan clases particulares: que juegue como alguien de 1500 cuando se lo pidas, que pase una temporada estudiando solo partidas de maestros, que abra siempre con el peón de rey. En cualquier LLM esta es la etapa que viene después del preentrenamiento, el afinadoFine-tuningSeguir entrenando un modelo ya entrenado sobre datos más específicos o con un objetivo distinto. Puede tocar todos los pesos (completo) o solo unos pocos (parcial, LoRA). En Rukh, el decoder base se afina con partidas de maestros y con tokens de Elo objetivo., y aquí se hace de las cuatro maneras con las que se afinan hoy los modelos de texto.
Al terminar, el proyecto tiene una familia: el mismo decoder de M2, con sus 115 120 128 parámetros, en versiones que se diferencian en lo que se les enseñó después de aprender a jugar.
medium-eloobedece una instrucción que viaja en la propia entrada: «juega como alguien de 1500».medium-mastersha pasado una última mano de partidas de élite.lora-e4ylora-d4son ficheros de 1,6 MB que se enganchan encima del modelo y le cambian la apertura.qwen3-pgn-qloraes un Qwen3 de 600 millones de parámetros afinado con las mismas partidas, para contestar una pregunta pendiente desde M1: ¿merecía la pena escribir un modelo propio?
La pregunta del módulo es qué cambia un afinado y qué no, y la respuesta, medida por cinco caminos,
es que cambia el comportamiento y no la competencia. Un adaptador de 1,6 MB, el 0,34 % del modelo,
sube la primera jugada 1. e4 del 59,64 % al 99,85 % sin costar una décima de legalidad, de
exactitud ni de puzles, y ninguno de los dos afinados completos mueve el Elo de forma demostrable.
Predecir el siguiente token sobre partidas humanas enseña qué se juega a cada nivel; nunca premia
calcular mejor. Mover la fuerza será trabajo de M5.
El código se reparte así:
| Lección | Qué construye |
|---|---|
| 2 · Los tokens de Elo | el corpus que faltaba, el muestreo plano por bandas y el afinado desde unos pesos |
| 3 · LoRA a mano | models/lora.py y los catorce tests que la definen |
| 4 · Estilo y texto PGN | los cortes por primera jugada y las mismas partidas escritas como PGN |
5 · QLoRA con transformers |
el decoder vestido de PreTrainedModel, Qwen3 y su evaluación |
| 6 · Adaptadores y publicación | los factores de LoRA como entradas del grafo ONNX y las cards de un adaptador |
| 7 · Diversidad y barridos | la entropía de aperturas y el barrido que mide el Elo por condición |
| 8 · El selector en la demo | «juega como…» y el cambio de estilo sin descargar otro modelo |
| 9 · Lo que salió | las cinco mediciones y los cuatro criterios del hito |
| 10 · Los labs | labs/m4/ entero y los ejercicios |
Teoría justa
Qué es afinar un modelo
Afinar es seguir entrenando un modelo ya entrenado, con otros datos y durante mucho menos tiempo,
para que se comporte de otra manera. El bucle es el de M2 (lotes, entropía cruzada sobre el
siguiente token, AdamW) y lo ejecuta el mismo rukh train con otro YAML. Cambian cuatro números y un
fichero:
Preentrenamiento (medium-v4, M2) |
Afinado (medium-elo, M4) |
|
|---|---|---|
| De dónde se parte | pesos al azar | checkpoints/medium-v4-<fecha>/best.pt, ya entrenado |
| Datos | 18,9 M de partidas, 1 681 M tokens | 1 219 423 partidas, 95,7 M tokens |
| Pasos | 48 000 | 3 800 |
| Tasa de aprendizaje | 4,5e-4 | 1,0e-4 |
| Cuánto tarda | 4 h 3 min | ~20 min |
<fecha> es la marca de tiempo de la carpeta que escribió tu entrenamiento
(ls -td checkpoints/medium-v4-*/ | head -1 te da la última en Git Bash;
(Get-ChildItem -Directory checkpoints/medium-v4-* | Sort-Object LastWriteTime)[-1].Name en PowerShell).
Todo es entre diez y veinte veces más pequeño, y la tasa de aprendizaje, cuatro veces y media menor: el modelo ya sabe jugar, y un paso grande le borra tanto como le enseña.
Un cocinero formado en España que se va a trabajar a Japón no vuelve a aprender a cocinar. Corta, sazona y controla el fuego igual; lo que cambia es la despensa, y con la despensa cambian los platos que salen. Afinar es eso: el mismo oficio con otros ingredientes.
Con los LLM, afinar se confunde a diario con otras cosas:
- Un prompt no cambia los pesos; se escribe y se paga en tokens en cada petición. Un afinado cambia el modelo una vez.
- Con RAGRAGRetrieval-Augmented Generation: antes de generar, se recuperan fragmentos relevantes (posiciones parecidas, párrafos de un libro) por similitud de embeddings y se le dan al modelo como contexto. Permite citar fuentes y actualizar conocimiento sin reentrenar. se le da al modelo información que no tiene pegando documentos en el contexto. Un afinado cambia su comportamiento, cómo responde. Si el modelo no conoce un dato, afinar no lo arregla.
- Tampoco es entrenar de cero: se hereda todo lo aprendido, incluido lo que uno preferiría que olvidara.
Cuatro maneras de afinar, y cuándo se usa cada una
Las cuatro se diferencian en qué pesos se tocan, y son las mismas de cualquier guía para afinar un LLM de texto. Este módulo las hace todas sobre el mismo modelo de partida, y eso es lo que permite compararlas.
| Método | Qué modifica | Qué queda congelado | Coste | Cuándo se elige |
|---|---|---|---|---|
SFTSFT (supervised fine-tuning)Seguir entrenando un modelo ya preentrenado con el mismo objetivo —predecir el siguiente token— pero sobre un corpus elegido. No cambia la pérdida ni la arquitectura: cambia de qué distribución salen los ejemplos, y eso es todo lo que hace falta para mover el comportamiento. En Rukh son los dos afinados del hito: medium-masters sobre partidas de élite y medium-elo sobre un corpus plano en Elo. completo |
los 115 M de parámetros | nada | ~20 min, 461 MB por versión | Cuando el dominio nuevo es grande y quieres que el modelo entero se mueva. Aquí: medium-masters. |
| Condicionado por token | los 115 M, con una orden en la entrada | nada | igual que el SFT | Cuando quieres un solo modelo que haga varias cosas según lo que le pidas. Aquí: medium-elo. |
LoRALoRALow-Rank Adaptation: en vez de modificar una matriz de pesos W, se aprende una corrección B·A de rango r (A de r × d, B de d × r, con B inicializada a cero para que el modelo adaptado sea el base en el paso 0) y se suma a W escalada por alpha/r. Entrena pocos parámetros —en medium, 393 216 con r = 8 sobre consulta y valor, el 0,34 %—, se guarda como un adaptador de 1,6 MB y se puede cambiar en caliente. QLoRA hace lo mismo sobre un modelo base cuantizado a 4 bits. |
dos matrices pequeñas junto a cada atención | los 115 M originales | ~7 min, 1,6 MB por versión | Cuando quieres muchas variantes baratas, intercambiables en caliente. Aquí: los dos de estilo. |
| QLoRAQLoRALoRA sobre un modelo base cuantizado a 4 bits: los pesos congelados ocupan una cuarta parte y los adaptadores siguen entrenándose en 16 bits, así que cabe en la GPU un modelo que en fp16 no cabría. El ahorro se nota cuando el peso está en las capas lineales; a 0,6 B parámetros, con una tabla de embeddings que es el 26 % del modelo y no se cuantiza, el ahorro medido fueron 341 MB de pesos (29 %) y 7 MB del pico de memoria (0,4 %). El nombre promete más de lo que da a esa escala, y decirlo forma parte de usarlo bien. | lo mismo que LoRA | la base, además cuantizada a 4 bits | cabe en una GPU que no aguantaría el modelo entero | Cuando el modelo no te cabe en la tarjeta. Aquí: Qwen3-0.6B. |
Publicar cinco estilos con SFT son cinco ficheros de 461 MB; con LoRA, cinco de 1,6 MB y un solo modelo base.
Afinar cambia la distribución, no el objetivo
medium-elo se entrena con entropía cruzada sobre el siguiente token, igual que medium-v4. Solo
cambia de dónde salen los lotes, y eso tiene dos consecuencias.
La primera: el afinado puede deshacer cosas que nadie le pidió deshacer. Es el olvido catastróficoOlvido catastróficoCuando afinar un modelo con datos nuevos le hace perder lo que sabía. No avisa: la pérdida sobre los datos nuevos baja mientras la capacidad vieja se deshace. La defensa no es una técnica sino una medición: se afina con tasa baja, pocos pasos, y se vuelve a medir contra el listón anterior. En M4 el listón es el Elo del modelo base; si el afinado por Elo lo hundiera, el eje habría salido caro., y la defensa es aburrida y funciona: tasa
baja, pocos pasos y volver a medir el listón anterior (los 1504 de Elo de medium-v4).
La segunda: la pérdida de validación deja de ser el objetivo. Validamos sobre partidas de 1800+, y el afinado entrena sobre un corpus donde el 49 % está por debajo de 1800, así que esa pérdida debe subir. Leer la subida como «va mal», o publicar el checkpoint de menor pérdida, sería un error; la lección 2 cuenta cómo estuvo a punto de pasar.
Condicionar con un token: qué puede y qué no
<w1500> es una orden. No hay parser ni plantilla: la orden es el contexto, y el contexto es lo
único que un decoder causal tiene. En rigor no es instruction tuningInstruction tuningAfinado supervisado (SFT) sobre pares instrucción → respuesta, para que un LLM obedezca lo que se le pide en la propia entrada en vez de limitarse a continuar texto. Rukh no lo hace tal cual: sus dos tokens de cabecera <wXXXX> <bXXXX> son condicionamiento con tokens de control, el pariente más pequeño de un prompt de sistema. Comparten la lección práctica: una instrucción solo funciona si el entrenamiento vio ejemplos de ella, y los tramos por debajo de 1800 no los vio hasta M4. (SFT sobre pares de instrucción y respuesta, lo que convierte un modelo base en uno que
sigue órdenes), sino condicionamiento con un token de control, el pariente más pequeño de un prompt
de sistema. Comparten lo esencial: la orden viaja en la entrada.
Si el corpus contiene partidas de 1500 precedidas de <w1500> y partidas de 2400 precedidas de
<w2400>, el modelo aprende dos distribuciones sobre jugadas y elige según el prefijo. Es imitación
condicionada, lo mismo que hace Maia, salvo que Maia entrena un modelo por nivel y aquí hay uno solo
con un interruptor.
Lo que no puede hacer es inventar fuerza. Pedir <w2600> le pide imitar las elecciones de un 2600, y
esas elecciones sin el cálculo que las produjo son las de un 2600 perdiendo partidas por táctica. El
eje sirve sobre todo para bajar, y la pregunta es si baja de forma medible y ordenada.
LoRA: notas adhesivas en vez de reimprimir el libro
Con afinado completo, un proyecto que quiera cinco estilos acaba con cinco copias casi iguales del
modelo. Tres tamaños del mismo modelo salen por todo el módulo, en megabytes decimales: 460 MB los
pesos en float32 (el model.onnx de medium-v4), 231 MB el model-fp16.onnx que descarga la demo
(la interfaz muestra 221 MB porque cuenta en MiB) y 1,39 GB el best.pt de un entrenamiento, que
guarda además los dos momentos de Adam.
La LoRALoRALow-Rank Adaptation: en vez de modificar una matriz de pesos W, se aprende una corrección B·A de rango r (A de r × d, B de d × r, con B inicializada a cero para que el modelo adaptado sea el base en el paso 0) y se suma a W escalada por alpha/r. Entrena pocos parámetros —en medium, 393 216 con r = 8 sobre consulta y valor, el 0,34 %—, se guarda como un adaptador de 1,6 MB y se puede cambiar en caliente. QLoRA hace lo mismo sobre un modelo base cuantizado a 4 bits. plantea el afinado de otra manera: en vez de mover una matriz de pesos
W, aprende una corrección B·A cuyo rangoRango (de una matriz)Cuántas direcciones independientes describe una matriz. Una de 768×768 puede tener hasta 768; el producto B·A de LoRA con r=8 tiene como mucho 8, y ahí está todo: es lo que lo abarata (12 288 números en vez de 589 824) y también lo que lo limita, porque cualquier corrección que el adaptador quiera aprender tiene que caber en esas ocho direcciones. es tan pequeño que los dos
factores juntos no pesan nada al lado de W, y la suma a la salida.
Es la diferencia entre reimprimir un manual con las correcciones de un profesor y pegarle notas adhesivas en las páginas que importan. El libro es el mismo para todos, cada alumno lleva su taco de notas, y para volver al original basta con despegarlas.
Las cuentas: una proyección de consulta de medium es una matriz de 768 × 768 = 589 824 números. Su
adaptador con r = 8 son dos matrices, 8 × 768 y 768 × 8: 12 288 números, el 2,08 %. Sobre las
proyecciones de consulta y valor de las dieciséis capas, 393 216 parámetros entrenables de
115 120 128, el 0,34 %, que en float32 son 1 572 864 bytes: 1,6 MB.
Todo lo que el adaptador aprenda tiene que pasar por ocho dimensiones por matriz. Por eso es barato, y ese es también su techo: sirve para inclinar un modelo hacia un estilo, un dominio o un formato, no para enseñarle algo que no sabe. Una nota adhesiva corrige un párrafo; no añade un capítulo.
// Ejercicio 01Cuenta el adaptador que se le dio a Qwen, pero sobre nuestro decoder
La lección 5 afina Qwen3 con r = 16 sobre las cuatro proyecciones de atención. Calcula cuántos
parámetros tendría ese mismo adaptador —r = 16 sobre q, k, v y attn.proj— en medium
(dieciséis capas, d_model 768), qué fracción del modelo es y cuánto pesa en float32. Después
di por qué la fórmula del lab 4, n_capas × n_objetivos × 2 × r × d_model, vale para esas cuatro
matrices y no valdría para mlp.fc.
// SoluciónVer la solución
Cada matriz adaptada recibe una A de r × 768 y una B de 768 × r: 2 × 16 × 768 = 24 576 números. Cuatro matrices por capa son 98 304, y dieciséis capas, 1 572 864
parámetros. Sobre 115 120 128 es el 1,37 %, cuatro veces el adaptador de estilo (r = 8 sobre
dos matrices: 393 216, el 0,34 %), y en float32 son 6 291 456 bytes, 6,3 MB. Fíjate en la
coincidencia: 1 572 864 es el número de bytes del adaptador de estilo, porque duplicar el
rango y duplicar las matrices multiplica por cuatro, igual que pasar de contar números a contar
bytes.
La fórmula vale porque las cuatro matrices tienen entrada y salida de 768: q, k y v son
tres franjas de 768 filas de la misma qkv de 768 × 2304, y attn.proj es una 768 × 768.
mlp.fc es 768 × 3072: su A seguiría siendo r × 768, pero su B sería 3072 × r, y el
término 2 × r × d_model ya no describe las dos. Es el mismo hecho por el que el exportador de
la lección 6 admite q, k y v en el grafo y no admite el MLP: los factores viajan como un
tensor por rango y tienen que medir todos lo mismo.
Que la corrección tenga rango 8 se comprueba con álgebra. Los valores singularesValores singularesLas longitudes de las direcciones por las que una matriz puede empujar, que salen de su descomposición en valores singulares (SVD). Cuántos son distintos de cero es el rango de la matriz, y eso convierte la afirmación central de LoRA en una línea de álgebra comprobable: la corrección (alpha/r)·B·A de un adaptador de rango 8 tiene exactamente ocho valores singulares distintos de cero, y el noveno es cero de verdad (2,8·10⁻⁷ frente a 0,18 del octavo) y no «pequeño». Sobre la matriz de consulta del primer bloque de medium, que tiene 768 y ninguno nulo, la corrección pesa el 4,41 % en norma de Frobenius. de una matriz miden cuánto empuja en cada una de sus direcciones independientes, y los del adaptador entrenado son ocho: el noveno es cero salvo redondeo de float32 (2,8·10⁻⁷ frente a 0,18 del octavo).
labs/m4/lora_spectrum.py.La matriz de consulta del primer bloque tiene 768 valores singulares y ninguno es cero; su
corrección tiene 8, y en norma de FrobeniusNorma de FrobeniusEl tamaño de una matriz en un solo número: la raíz cuadrada de la suma de los cuadrados de todos sus elementos, como la longitud de un vector que tuviera todos sus números en fila. Sirve para decir cuánto pesa una matriz al lado de otra. En M4, ‖ΔW‖ / ‖W‖ compara la corrección de un adaptador con la matriz que corrige: sobre la consulta del primer bloque de medium, la de lora-e4 es el 4,41 %, y aun así cambia del todo la primera jugada. (la raíz de la suma de los cuadrados de todos sus números: el tamaño de una matriz en una cifra) pesa el 4,41 % de la matriz que corrige. Pequeña y, aun
así, capaz de cambiar del todo una decisión.
Dos detalles importan para lo que viene. B empieza en cero, así que las notas empiezan en
blanco y el modelo adaptado es el modelo base en el paso 0. Y la atención de este decoder usa una
proyección qkv fusionada, una sola nn.Linear de 768 × 2304: adaptar «consulta y valor» es adaptar
dos franjas de su salida, cada una con su A y su B. La lección 3 lo escribe entero.
Cómo se comprueba que lo que escribiste es LoRA
Escribir a mano una técnica publicada tiene un riesgo: que se parezca a la técnica y no lo sea. Un
alpha en el sitio equivocado o A y B intercambiadas dan un modelo que entrena, converge y
produce números creíbles, y no es LoRA.
La comprobación es ponerla al lado de la implementación de referencia, peftpeftLa librería de Hugging Face que implementa los métodos de afinado con pocos parámetros —LoRA entre ellos— sobre cualquier PreTrainedModel. En Rukh no se usa para entrenar los adaptadores propios, que están escritos a mano en src/rukh/models/lora.py: se usa como implementación de referencia. La misma configuración sobre los mismos pesos base, con la misma A copiada de un lado a otro, tiene que optimizar el mismo número paso a paso; en tests/unit/test_hf_wrapper.py sale idéntica hasta el último bit durante seis pasos. Sí es la que entrena el QLoRA de Qwen3, donde el objetivo es recorrer el camino del ecosistema.:
dos modelos sobre los mismos pesos base, uno con nuestra apply_lora y otro con peft, los mismos
pasos de SGD sobre el mismo lote. Si la regla de actualización difiriera en algo, las pérdidas se
separarían en el primer paso; salen idénticas hasta el último bit durante seis pasos. El test está en
la lección 5. Fundir el adaptador en los pesos también se comprueba: la jugada elegida no cambia en
ninguna posición.
Vestir el decoder de PreTrainedModel
El decoder de este curso es PyTorch a secas, a propósito: leerlo no debería exigir conocer el
framework de nadie. Pero el resto del ecosistema habla PreTrainedModelPreTrainedModelLa clase base de transformers que define el contrato que habla todo el ecosistema de Hugging Face: from_pretrained, save_pretrained, una PretrainedConfig serializable y un forward que devuelve un objeto con logits y loss. Rukh no la hereda en su decoder: escribe una envoltura de cien líneas (src/rukh/models/hf.py) que contiene un MoveDecoder, así que hay una sola definición del paso hacia delante en el repositorio y el test de que las dos dan los mismos logits comprueba que el envoltorio es fiel. Lo que eso compra es concreto: peft, trl y el Hub funcionan sobre el decoder sin reimplementar ninguno., y escribir una envoltura es barato. Es un adaptador de enchufe: no cambia el aparato,
cambia la clavija para que entre en la pared de otro país.
La envoltura contiene un MoveDecoder: sigue habiendo una sola definición del paso hacia
delante, y a cambio se abren peft, trl y el Hub. Es el mismo trabajo que harías para meter una
arquitectura propia en cualquier herramienta que espera un modelo de transformers; la lección 5 lo
hace con el código delante.
QLoRA a 0,6 B: qué demuestra y qué no
La QLoRAQLoRALoRA sobre un modelo base cuantizado a 4 bits: los pesos congelados ocupan una cuarta parte y los adaptadores siguen entrenándose en 16 bits, así que cabe en la GPU un modelo que en fp16 no cabría. El ahorro se nota cuando el peso está en las capas lineales; a 0,6 B parámetros, con una tabla de embeddings que es el 26 % del modelo y no se cuantiza, el ahorro medido fueron 341 MB de pesos (29 %) y 7 MB del pico de memoria (0,4 %). El nombre promete más de lo que da a esa escala, y decirlo forma parte de usarlo bien. es LoRA sobre un modelo base cuantizadoCuantizaciónGuardar y calcular los pesos con menos bits de los que se entrenaron: fp16 (la mitad de tamaño, sin pérdida apreciable) o int8 dinámico (una cuarta parte, con algo de error). En Rukh se cuantizan solo las matrices (MatMul y Gemm) y cada fichero pasa una prueba de paridad contra PyTorch antes de subirse. A 4 bits, la receta de QLoRA en M4, cada peso es uno de 16 niveles: NF4 los coloca donde se amontonan los pesos de una red entrenada (una campana centrada en cero, con un cero exacto), cada bloque de 64 pesos guarda su escala, y esas escalas se cuantizan a su vez a 8 bits (doble cuantización). Para multiplicar, cada bloque se descuantiza al vuelo a bf16: ahorra memoria, no cálculo, y solo sirve para una base congelada; el adaptador que se entrena encima sigue en bf16. a 4 bits. Existe por la memoria: permite afinar un LLM de 7 B en una tarjeta de consumo, o uno
de 65 B en una sola GPU de 48 GB.
A 0,6 B parámetros en una tarjeta de 32 GB no hay nada que ahorrar: el modelo en bf16 son 1,2 GB. Lo
honesto es intentarlo, medir el pico de memoria con y sin cuantizar y decir qué pasó. Si
bitsandbytes no carga, la ejecución cae a bf16 y lo escribe en su informe.
Lo que sí se demuestra con Qwen es si merecía la pena escribir un modelo propio. Para eso, las
dos mitades comparten partidas (mismo mes, misma validación) y métricas (el Qwen afinado entra en el
harness de M2 como un jugador más). Solo cambia la representación: el decoder lee un token por jugada
legal; Qwen lee 1. e4 e5 2. Nf3 partido en los trozos que decida su BPE, y nada le impide escribir
una jugada que no existe.
Un decoder cuyo vocabulario es el conjunto de jugadas solo puede fallar de una manera: una jugada
legal en la posición equivocada. Un modelo que escribe SANSANNotación algebraica estándar, la de los libros: Nf3, O-O, exd5+. Es compacta para humanos pero ambigua sin el tablero (hay que saber qué caballo puede ir a f3). Lichess publica las partidas en SAN; Rukh las convierte a UCI con python-chess. tiene tres maneras
más:
| Fallo | Qué significa | ¿Puede el decoder? |
|---|---|---|
| ilegal aquí | jugada bien formada que esta posición no permite | sí |
| no es una jugada | Nf9, O-O-O-O, una frase |
no |
| SAN ambigua | dos piezas podrían hacerla y no desambiguó (Nd2 en vez de Nbd2) |
no |
| nada | no escribió ningún token | no |
Es la elección que tienes con cualquier LLM al que le pides una salida estructurada: dejarle escribir texto libre y validarlo después, o restringir lo que puede generar. El decoder de Rukh es el caso extremo de lo segundo: no sabe escribir nada que no sea una jugada.
Qué has aprendido
Afinar es el mismo bucle de siempre con otros datos, menos pasos y una tasa más baja; hay cuatro maneras, dos que producen un modelo entero y dos que producen un parche de pocos megabytes. Y la comparación con Qwen solo vale si lo único distinto es la representación.
Antes de afinar nada hay que arreglar el terreno: la mitad del eje de Elo no se había entrenado nunca. Sigue en «Los tokens de Elo y el corpus que faltaba».