Inicio / Artículos / Del modelo base al modelo de chat: ajuste SFT de un LLM indonesio de 102M

Del modelo base al modelo de chat: ajuste SFT de un LLM indonesio de 102M

Cómo un modelo base indonesio de 102M parámetros se convirtió en modelo de chat en dos etapas de SFT: formato ChatML, filtrado por longitud, acumulación de gradientes y un parche de identidad.

1980 palabras

Tras el preentrenamiento de NovAI Base 102M, el siguiente paso no fue escalarlo. Fue enseñarle a seguir instrucciones. El modelo base es un transformer solo decoder de 102 millones de parámetros, entrenado desde cero con unos 2.000 millones de tokens en indonesio, y funciona: escribe en indonesio, continúa un prompt y ha captado suficiente estructura estadística como para producir texto coherente. Pero no es realmente un chatbot. Hazle una pregunta y no hay garantía de respuesta, porque se le entrenó para predecir el siguiente token y no para reconocer este patrón de conversación:

User → question
Assistant → answer

No había GPU más grandes para un modelo mayor, así que el experimento mantuvo el mismo modelo y cambió el objetivo de entrenamiento y los datos. El método fue el Supervised Fine-Tuning (SFT).

Un modelo base todavía no es un asistente

Es fácil pasar por alto esa diferencia cuando usas ChatGPT, Claude, Gemini o herramientas parecidas, porque esos modelos han pasado por varias etapas de entrenamiento. Un modelo de lenguaje preentrenado parte de algo mucho más simple: predecir el siguiente token. Por ejemplo:

Indonesia adalah negara ...

Aprende qué es probable que venga después. Eso es fundamentalmente distinto de un intercambio como este:

User:
Apa itu machine learning?
Assistant:
Machine learning adalah ...

La segunda forma lleva un patrón de interacción: un rol, una instrucción y una respuesta esperada. El SFT es una técnica para enseñar ese comportamiento a un modelo preentrenado. El pipeline de este experimento fue así:

Indonesian Corpus
       ↓
Pretraining
       ↓
NovAI Base 102M
       ↓
Supervised Fine-Tuning
       ↓
NovAI Chat 102M

Lo llamativo es que la arquitectura no cambió. El número de parámetros siguió rondando los 102 millones. Lo que cambió fue qué se le entrenaba a imitar.

Punto de partida

El SFT arrancó desde el checkpoint preentrenado NovAI Base 102M V3. El preentrenamiento cubrió 2.000.027.648 tokens en 15.259 pasos, y los registros muestran BF16 sobre CUDA durante la ejecución en A100. Al final era un modelo de lenguaje utilizable, pero lo que necesitaba ahora eran datos completamente distintos. Más texto web no habría ayudado; ejemplos de las respuestas deseadas, sí.

El dataset de SFT

El dataset pasó por varias iteraciones. La segunda versión partió de un conjunto existente de 30.084 ejemplos de SFT y añadió 324 ejemplos de persona y preguntas factuales, hasta un total de 30.408. Ese añadido importaba. Los ejemplos nuevos no eran preguntas y respuestas generales. Algunos se escribieron específicamente para fijar hechos sobre el propio modelo: su identidad, su rol y respuestas relacionadas. Los datos se guardaron como conversaciones, conceptualmente así:

User:
Apa itu machine learning?
Assistant:
Machine learning adalah ...

Pero el modelo no ve conversaciones bien formateadas. Ve tokens, así que había que decidir cómo se serializan las conversaciones.

Enseñar el formato de conversación

Se usó una estructura al estilo ChatML con límites de conversación explícitos:

<|im_start|>
<|im_end|>

Una conversación queda aproximadamente así:

<|im_start|>user
Apa itu machine learning?
<|im_end|>
<|im_start|>assistant
Machine learning adalah ...
<|im_end|>

Eso le dice al modelo dónde empieza un mensaje, dónde termina y qué rol lo produjo. El tokenizador también se configuró con una plantilla de chat, para poder generar la misma estructura en código tanto en entrenamiento como en inferencia. Puede parecer un detalle menor de implementación, pero no lo es. Si el formato de entrenamiento y el de inferencia difieren, el modelo puede haber aprendido un protocolo de conversación y que luego se le pida trabajar con otro. Con un modelo pequeño, la prioridad era mantener esa interfaz simple y determinista.

Descartar las conversaciones largas

Luego tocaba la longitud de secuencia. Para esta ejecución, el máximo del tokenizador se fijó en 512 tokens. El notebook tokeniza cada conversación renderizada sin truncar y filtra los ejemplos que superan ese límite. La lógica del filtro era básicamente:

def filter_long_sequences(example):
    text = tokenizer.apply_chat_template(
        example["messages"],
        tokenize=False
    )
    tokens = tokenizer(
        text,
        truncation=False
    )    return len(tokens["input_ids"]) <= 512

¿Por qué no truncar sin más? Porque truncar puede cortar justo la parte que importa. Partir un ejemplo de entrenamiento en mitad de la respuesta del asistente entrenaría al modelo con una respuesta incompleta. Se optó por descartar las secuencias demasiado largas en vez de cambiar su significado en silencio. Es una decisión de ingeniería pequeña, pero en los modelos pequeños estas decisiones pesan.

La ejecución del entrenamiento

El entrenamiento usó el stack de Hugging Face: PyTorch, Transformers, TRL y su SFTTrainer. El trainer se montó directamente sobre el modelo preentrenado y el dataset de SFT combinado. El tamaño de batch efectivo sale así:

16 × 2 = 32

Se usó acumulación de gradientes porque el objetivo no era meter todo el batch efectivo en la memoria de la GPU de una vez. Funciona así:

Batch 1
   ↓
forward / backward
Batch 2
   ↓
forward / backward
   ↓
optimizer update

El resultado es un batch efectivo de 32 sin que la GPU tenga que contener los 32 ejemplos a la vez. En el notebook, max_steps se deriva del tamaño del dataset y del batch efectivo, así que la ejecución principal equivale más o menos a una pasada por los datos y no a un número arbitrario y grande de épocas.

El primer resultado no fue el final

Tras la primera etapa de SFT había un modelo nuevo, NovAI Chat 102M, que se podía usar en formato de chat. Las pruebas mostraron un comportamiento que no se esperaba del todo. El modelo respondía, pero no siempre de forma limpia. En una prueba interactiva se saltó una respuesta concisa y derivó hacia texto sin relación, hasta acabar reproduciendo fragmentos como:

<Kata-kata yang menggambarkan kehidupan seorang wanita...

y más tarde empezó a emitir un token especial incompleto:

<|im_

Ese no era el comportamiento buscado, y muestra por qué son útiles los experimentos con modelos pequeños. Una salida extraña de un modelo de frontera es fácil de achacar a otros. Con un modelo que has construido tú, tienes que averiguar qué parte del pipeline la causó. ¿El dataset? ¿La plantilla de chat? ¿El objetivo de entrenamiento? ¿Los ajustes de muestreo? ¿La capacidad limitada del modelo? ¿Muy pocos ejemplos del comportamiento esperado? No tenía por qué haber una única respuesta, pero sí se podía atacar un problema concreto.

El problema de la identidad

El modelo debería tener una identidad coherente. Si alguien pregunta:

Siapa kamu?

la respuesta debería ser predecible y hablar del propio modelo. Parece trivial, pero no para un modelo que absorbió sobre todo patrones generales del lenguaje. Si los datos de entrenamiento están llenos de asistentes de IA, nombres, personas y estilos de conversación diversos, un modelo pequeño aprende con facilidad asociaciones incoherentes.

Reentrenar desde cero habría sido excesivo, así que el arreglo fue mucho más pequeño: un dataset de identidad dedicado y otra etapa de SFT que funciona como parche de identidad. El notebook marca la etapa con el mensaje PATCH TRAINING IDENTITAS (LLM-NOVA) DIMULAI («comienza el entrenamiento del parche de identidad»). No pretendía enseñar conocimientos generales nuevos. El objetivo era acotado: reforzar la identidad y el comportamiento que el modelo debe asociar consigo mismo.

Una segunda etapa de SFT

La segunda etapa partió del modelo de chat ya ajustado y no del modelo base original. El pipeline quedó así:

NovAI Base 102M
       │
       │ General SFT
       ▼
NovAI Chat 102M
       │
       │ Identity SFT
       ▼
NovAI Chat 102M + Identity

La etapa de identidad usó una configuración más conservadora, a propósito. El SFT general intenta cambiar el comportamiento conversacional del modelo en todos los frentes. La etapa de identidad es mucho más acotada, y el objetivo era un pequeño ajuste de comportamiento, no otra reescritura agresiva.

¿Por qué no incluirlo en la primera ejecución?

Una pregunta lógica es por qué los ejemplos de identidad no se añadieron sin más al dataset principal para entrenarlo todo junto. Hay argumentos razonables a favor. Sin embargo, las etapas separadas permiten aislar el efecto. En lugar de esto:

30K+ examples
      ↓
one opaque training run

puedes razonar sobre dos etapas:

General instruction data
        ↓
Chat behavior
thenIdentity examples
        ↓
Identity behavior

Separar las etapas hace el experimento más fácil de leer. También refleja una lección que gana peso con los modelos más pequeños: no todo problema necesita otra gran ejecución de entrenamiento. A veces la respuesta correcta es una intervención dirigida.

El número de parámetros nunca se movió

Cuenta los parámetros en cada paso. Antes del SFT: 102M. Después del SFT: 102M. Tras la pasada de identidad: siguen siendo 102M. La arquitectura no se volvió más potente ni aparecieron miles de millones de parámetros. Lo que cambió fueron los pesos y aquello con lo que se entrenaron. El preentrenamiento optimizó el modelado del lenguaje. El SFT optimizó la imitación de ejemplos que siguen instrucciones. La etapa de identidad empujó luego al modelo hacia un patrón de comportamiento concreto.

Qué muestra el experimento

No conviene sobreinterpretar el resultado. Un modelo de 102M no puede competir con modelos de frontera de clase GPT, y nada aquí sugiere lo contrario. Tampoco demuestra que el SFT mejore mágicamente el razonamiento de un modelo pequeño, porque no lo hace. El resultado es más práctico: un modelo de lenguaje indonesio preentrenado puede pasar por otra etapa de entrenamiento que le enseñe explícitamente la forma de una conversación, cómo responder a una instrucción, cómo es una buena respuesta de asistente y, mediante ejemplos dirigidos, cómo describirse a sí mismo. Es una capacidad muy distinta de la simple predicción del siguiente token.

El SFT tiene límites

El SFT aporta mucho, pero no es la etapa final. Por sí solo no elimina las alucinaciones, no garantiza la exactitud factual, no crea una capacidad de razonamiento ilimitada ni da al modelo acceso a información fuera de sus parámetros. Y no enseña qué respuesta prefiere la gente cuando hay varias posibles. Esas carencias piden otras técnicas:

Pretraining
     ↓
SFT
     ↓
Preference Optimization
     ↓
RAG / External Knowledge
     ↓
Tools / Agents

Cada capa resuelve una limitación distinta, y por eso construir un LLM deja de parecer una sola ejecución de entrenamiento y empieza a parecer un pipeline.

Por qué interesan los modelos pequeños

Lo interesante de este trabajo no es que se entrenara un modelo de 102M. Es que todo el pipeline se puede inspeccionar, del corpus al tokenizador y más allá:

Corpus
  ↓
Tokenizer
  ↓
Architecture
  ↓
Pretraining
  ↓
Checkpoint
  ↓
SFT Dataset
  ↓
Chat Template
  ↓
SFT
  ↓
Identity Patch
  ↓
Inference

Cuando algo sale mal, puedes volver atrás y examinar la capa responsable, algo muy distinto de llamar a una API. Construir desde cero compensa justo ahí.

Primero construir, luego mejorar

La serie empezó con la pregunta de si Indonesia podría construir un modelo fundacional propio. Un modelo de 102M queda muy por debajo de la escala de frontera, pero responde a una versión reducida de esa pregunta: un modelo de lenguaje pequeño sí se puede construir y entrenar localmente. La siguiente pregunta era si se podía convertir en algo que se comporte como un asistente. El SFT ofreció un camino, y el experimento de identidad añadió una lección: a un modelo que sabe hablar todavía hay que enseñarle cómo debe comportarse.

Lo que sigue abierto

Quedan varias cosas por probar:

  • ¿Un conjunto de instrucciones en indonesio más sólido mejoraría claramente los resultados?
  • ¿Cuenta más la calidad de los datos que su volumen, y cuánto más?
  • ¿La optimización por preferencias afinaría las respuestas?
  • ¿Podría un especialista acotado a un solo dominio resultar sorprendentemente útil a este tamaño, en vez de un generalista que intenta saberlo todo?
  • ¿Dónde está el techo de un modelo indonesio pequeño cuando sus datos, su objetivo y su evaluación están diseñados en torno a casos de uso indonesios?

Nadie tiene aún esas respuestas, y por eso el trabajo continúa. Nada de esto sostiene que un modelo de este tamaño vaya a desplazar a la IA de frontera. El objetivo es entender qué ocurre de verdad cuando todo el pipeline se construye a mano, etapa tras etapa. El preentrenamiento fue el primer hito y el SFT, el segundo.