Cuando conté cómo nació LayaFT dije que estaba trabajando para publicarlo en PyPI. Ya está: laya-finetune va en la 0.1.2, con tests en cada cambio y la misma idea de siempre, que entrenar Laya se sienta como entrenar un YOLO con Ultralytics.

pip install laya-finetune

Una tarea es un YAML

Todo empieza por describir la decisión: qué lee Laya (el estado), qué tiene que responder y con qué opciones. Hay tres tipos de pregunta: choice para elegir una opción, score para un nivel en una escala y noul para un sí o no con su probabilidad.

name: invoices
fields: {vendor: {}, body: {min_words: 40}}
state: {invoice: "{vendor}: {body}"}
questions:
  expense_type:
    type: choice
    instructions: Which expense category is the `invoice`?
    options: {travel: "travel: flights, hotels, taxis", software: "software: licences, SaaS, cloud", hardware: "hardware: laptops, screens, peripherals"}
  urgency: {type: score, instructions: "How soon must it be paid?", levels: {low: "no date", high: "due this week"}}
  duplicate: {type: noul, instructions: "Does the `invoice` say it was already paid?"}
data: {test: invoices_test.jsonl}

La única regla que no se negocia: el test lo escribe una persona y el modelo nunca lo ve al entrenar.

Cuatro comandos

layaft generate task=invoices backend=ollama n=216 context=all   # un LLM escribe casos ya etiquetados
layaft verify task=invoices llm=gemma4:31b                       # otro LLM los responde a ciegas
layaft train task=invoices profile=full ctx=16k                  # RLCD y calibración → runs/invoices-16k
layaft val task=invoices model=runs/invoices-16k ctx=16k         # contra el test escrito a mano

generate funciona con Ollama, OpenRouter, OpenAI o cualquier servidor compatible. verify le pasa cada caso a un modelo de otra familia, y si no lee en el texto la respuesta que se pidió, el caso se va. Lo mismo se hace desde Python con LayaFT("multilingual") y sus métodos generate, verify, train, val y predict.

Detalle técnico: el resultado es un checkpoint normal de Laya: carga con el laya.load(path) oficial y sirve el mismo contrato. Generar datos no necesita GPU; para entrenar, profile=test cabe en 3 GB y prueba todo el ciclo en unos minutos antes de una corrida completa.

Elegir de un catálogo que cambia

Hay decisiones donde las opciones no son fijas: herramientas, documentos, productos. Para esas, LayaFT hace una pregunta de sí o no por cada candidato, con el candidato dentro de la pregunta. generate escribe peticiones que sí lo necesitan y pair agrega los negativos: los candidatos más parecidos según embeddings, que son los difíciles, y otros al azar. Así entrené el modelo de claude-decide, del que va el próximo post.

De 1k a 32k de contexto

Laya lee 1,024 tokens de fábrica. Con ctx=8k LayaFT la entrena con estados largos, y más allá de 8k extiende sus posiciones antes de entrenar. Como escribir miles de documentos de 32k con un LLM no escala, los estados largos se arman envolviendo cada caso corto en relleno neutro: hilos resueltos, notificaciones, logs. En la RTX 4050 de mi laptop (6 GB), con tres preguntas:

  • 1k: el checkpoint tal cual, unos 25 ms.
  • 8k: 2.1 GB y 1.1 s.
  • 16k: 2.7 GB y 2.8 s.
  • 32k: ya no cabe en 6 GB.

Detalle técnico: mmBERT alterna una capa de atención global con dos locales de 128 tokens, así que YaRN se aplica solo a las globales; las locales nunca ven más de 128 posiciones. Arriba de 8k el encoder pasa de sdpa a flex_attention, porque sdpa arma una máscara densa y se quedó sin memoria a 16k en 6 GB.

Tres tareas

Lo probé en tres tareas, cada una con su test escrito a mano: la mesa de ayuda del post anterior, enrutar herramientas y prefiltrar contexto. Contando solo los casos con todas las respuestas bien, pasa de 10 a 55 %, de 4 a 72 % y de 41 a 90 %.

Casos de prueba con todas las respuestas bien, tal como viene y ajustada con LayaFT: mesa de ayuda 10 y 55 %, enrutar herramientas 4 y 72 %, prefiltrar contexto 41 y 90 %
Casos con todas las respuestas bien, cada tarea con su test actual

El código está en GitHub con licencia MIT. Si lo prueban con una tarea suya, me encantaría saber cómo les fue: nuevos backends, tipos de pregunta, tareas que rompan algún supuesto o resultados en otras GPUs son bienvenidos. Y si se preguntan para qué sirve todo esto en el día a día, claude-decide es la respuesta que yo me di; de él va el próximo post.