rukh · lab

// 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.

  • tiempo de trabajo35 min
  • nivel medio
  • actualizado el23 de septiembre de 2026

// el recorrido · dónde estás

  1. M0 El tallerEntorno reproducible(hecho)
  2. M1 Datos y tokensDatos y tokenización(hecho)
  3. M2 El decoderPreentrenar un GPT(hecho)
  4. M3 El encoderEmbeddings y clasificar(hecho)
  5. M4 Fine-tuningSFT y LoRA(estás aquí)
  6. M5 AlineamientoPreferencias: DPO, GRPO(por delante)
  7. M6 Evaluar y publicarMedir y desplegar(por delante)
  8. FASE 2 El entrenadorAgentes, RAG, MCP

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-elo obedece una instrucción que viaja en la propia entrada: «juega como alguien de 1500».
  • medium-masters ha pasado una última mano de partidas de élite.
  • lora-e4 y lora-d4 son ficheros de 1,6 MB que se enganchan encima del modelo y le cambian la apertura.
  • qwen3-pgn-qlora es 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.

SFT completobloque 4bloque 3bloque 2bloque 1embeddingse2e4e7e5…modelo entero461 MB por versiónCondicionadobloque 4bloque 3bloque 2bloque 1embeddings<w1500>e2e4…modelo entero461 MB, varias órdenesLoRAbloque 4bloque 3bloque 2bloque 1embeddingse2e4e7e5…parche1,6 MB por versiónQLoRAbloque 4bloque 3bloque 2bloque 1embeddings1.e4…parchebase en 4 bitsse entrenacongeladocongelado y en 4 bitstoken de orden
fig. 01Qué deja tocar al gradiente cada método. Las dos columnas de la izquierda entrenan el modelo entero y publican uno nuevo; las dos de la derecha lo congelan y publican una pieza pequeña que se monta al lado. La línea discontinua es la división que decide el coste.
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.

Rukh · tokens de control<bos><w1800><b1900>e2e4e7e5…Un LLM de chat · plantilla<|im_start|>systemEres…<|im_end|><|im_start|>user…Few-shot · los ejemplos van dentrotexto→etiquetatexto→etiquetatexto→tokens de controlcontenidolo que se pide completar
fig. 02La misma idea con tres disfraces. Los dos tokens de cabecera de Rukh, la plantilla de chat de un LLM y los ejemplos de un prompt few-shot son una sola secuencia plana: unos pocos tokens acordados condicionan lo que viene después. Cuando la lección 9 lea que «juega como 1200» cambia el repertorio, estará leyendo un prompt de sistema de un token.

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.

entrada768W · congelada768 × 768589 824 númerosB · A · el adaptadorA8 × 768r = 8768 → 8 → 768: la señal se aplasta y vuelve a abrirseB768 × 812 288 números · el 2,1 % de W+salida768
La corrección no puede ser cualquier cosa: tiene que caber por ocho dimensiones, y en el cuello la señal se aplasta hasta pasar. Eso es lo que la abarata y también lo que la limita.

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).

W768 × 768 · 768 valores singulares, ninguno cero(cada fila a su escala)ΔW = (α/r)·B·A· 8 valores singulares · 4,4 % de la norma de Wa partir de aquí son cero, no pequeños15101520índice
El rango no es una metáfora: son ocho direcciones, y la novena es cero salvo redondeo de float32 (2,8·10⁻⁷ frente a 0,18 de la octava). Cada fila va a su propia escala, porque en norma la corrección es una vigésima quinta parte de la matriz y a una sola escala no se vería. Medido sobre el adaptador publicado con 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».