Ir al contenido principal

Ajuste fino de Qwen3.6 con un conjunto de datos de preguntas y respuestas médicas

Aprende a ajustar Qwen3.6 en una GPU H100 NVL con SFT: desde la preparación del dataset y la carga en 4 bits, hasta el entrenamiento y la evaluación.
Actualizado 21 abr 2026  · 12 min leer

Qwen3.6-35B-A3B es un modelo Mixture-of-Experts ligero diseñado para tareas de código, razonamiento y contextos largos. Usa 35B parámetros totales con 3B activos por token, lo que lo hace más eficiente en ejecución sin perder rendimiento. Además, admite una ventana de contexto de 262K tokens, útil para bases de código grandes, documentos extensos y flujos de trabajo con múltiples pasos.

El modelo destaca especialmente en benchmarks de programación y estilo agente, con buenos resultados en SWE-bench, Terminal-Bench y Claw-Eval. También se mantiene competitivo en benchmarks de razonamiento y conocimiento más amplios como GPQA, AIME 2026, MMLU-Pro y C-Eval, lo que lo convierte en una base sólida para experimentos de ajuste fino que requieren tanto desempeño práctico en tareas como capacidad general de razonamiento.

En esta guía, configuraremos un entorno RunPod H100 NVL, prepararemos el proyecto, cargaremos y limpiaremos un dataset médico de preguntas y respuestas, ejecutaremos Qwen3.6 en cuantización de 4 bits, daremos formato a los prompts, construiremos utilidades de evaluación, probaremos el modelo base antes del ajuste fino, entrenaremos y guardaremos el adaptador, y por último compararemos el modelo tras el ajuste.

Aviso legal: este tutorial muestra un proceso técnico de ajuste fino con fines educativos. El modelo resultante no está pensado para usos médicos o clínicos y no debe utilizarse para diagnóstico, toma de decisiones ni atención a pacientes.

Paso 1: configura el entorno de entrenamiento

En la primera sección, lanzaremos una instancia H100 NVL en RunPod, instalaremos las librerías necesarias, nos conectaremos a Hugging Face y definiremos la configuración principal del proyecto.

Alquila una máquina con GPU H100 NVL

Empieza yendo a RunPod y seleccionando una máquina con GPU H100 NVL. Esta GPU incluye unos 94 GB de VRAM y 94 GB de RAM del sistema, suficiente para este flujo de ajuste fino de Qwen3.6.

Elige la última plantilla de PyTorch y edita la configuración del pod antes del despliegue. Define el disco del contenedor en 100 GB y el volumen en 200 GB. Así tendrás espacio para descargar el modelo, cachear dependencias y guardar localmente el adaptador ajustado.

Edición de la plantilla de Pytorch en Runpod.

También deberías añadir una variable de entorno HF_TOKEN en este paso. Te permitirá iniciar sesión en Hugging Face más tarde, mejorar el acceso a la descarga del modelo y facilitar el envío del modelo ajustado al Hub tras el entrenamiento.

Antes de lanzar la instancia, revisa el resumen del pod para comprobar el coste estimado. Cuando todo esté correcto, despliega el pod.

Resumen del pod de la máquina H100 NVL.

Instala las librerías necesarias

Cuando el pod esté listo, abre el enlace de Jupyter Notebook desde el panel de RunPod para iniciar JupyterLab. Crea un nuevo notebook de Python e instala las librerías necesarias para el ajuste fino cuantizado, la carga del dataset y el SFT con TRL. 

pip install -q -U transformers accelerate peft trl bitsandbytes datasets sentencepiece

Inicia sesión en Hugging Face

A continuación, inicia sesión en Hugging Face usando la variable de entorno HF_TOKEN que añadiste al crear el pod. Así evitas incrustar el token en el notebook y mantienes un flujo de trabajo más limpio. 

import os
from huggingface_hub import login

hf_token = os.getenv("HF_TOKEN")
login(token=hf_token)

Importa librerías y define la configuración

Ahora importa las librerías necesarias y define los valores de configuración que usaremos en todo el tutorial. Incluye el ID del modelo, el nombre del dataset, tamaños de muestra, directorio de salida y semilla aleatoria. 

import os
import random
import re

import torch
from datasets import load_dataset
from peft import LoraConfig, prepare_model_for_kbit_training
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    set_seed,
)
from trl import SFTConfig, SFTTrainer

MODEL_ID = "Qwen/Qwen3.6-35B-A3B"
DATASET_ID = "keivalya/MedQuad-MedicalQnADataset"
SAMPLE_SIZE = 240
EVAL_SIZE = 24
COMPARISON_SAMPLE_COUNT = 3
MAX_SEQ_LENGTH = 768
OUTPUT_DIR = "qwen36-medquad-quick"
SEED = 42

set_seed(SEED)
random.seed(SEED)


def supports_bf16() -> bool:
    return torch.cuda.is_available() and torch.cuda.get_device_capability(0)[0] >= 8


print(f"PyTorch: {torch.__version__}")
print(f"CUDA disponible: {torch.cuda.is_available()}")
if torch.cuda.is_available():
    print(f"GPU: {torch.cuda.get_device_name(0)}")
    print(
        f"Memoria de la GPU (GB): {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f}"
    )

Al ejecutar esta celda, deberías ver que CUDA está disponible y que el notebook está conectado a una GPU NVIDIA H100 NVL. 

PyTorch: 2.8.0+cu128
CUDA available: True
GPU: NVIDIA H100 NVL
GPU memory (GB): 93.1

Ingeniero Asociado de IA para Científicos de Datos

Entrena y afina los últimos modelos de IA para producción, incluidos los LLM como Llama 3. ¡Comienza hoy tu viaje para convertirte en Ingeniero de IA!
Explora la pista

Paso 2: carga y prepara el dataset

En esta parte cargaremos el dataset médico de preguntas y respuestas MedQuad, limpiaremos los campos de texto, filtraremos ejemplos de menor calidad y crearemos particiones pequeñas de entrenamiento y evaluación para un ajuste fino rápido.

Carga el dataset MedQuad

Comenzamos cargando la partición de entrenamiento del dataset MedQuad desde Hugging Face. Tras la carga, inspeccionamos la estructura, los nombres de columnas y el primer ejemplo para entender el tipo de pares pregunta-respuesta con los que trabajaremos.

raw_dataset = load_dataset(DATASET_ID, split="train")
print(raw_dataset)
print("First example:", raw_dataset[0])

Esto muestra que el dataset MedQuad contiene 16.407 ejemplos de preguntas y respuestas médicas distribuidos en tres campos: qtype, Question y Answer.

Dataset({
features: ['qtype', 'Question', 'Answer'],
num_rows: 16407
})
First example: {'qtype': 'susceptibility', 'Question': 'Who is at risk for Lymphocytic Choriomeningitis (LCM)? ?', 'Answer': 'LCMV infections can occur after exposure to fresh urine, droppings, saliva, or nesting materials from infected rodents. Transmission may also occur when these materials are directly introduced into broken skin, the nose, the eyes, or the mouth, or presumably, via the bite of an infected rodent. Person-to-person transmission has not been reported, with the exception of vertical transmission from infected mother to fetus, and rarely, through organ transplantation.'}

Limpia los campos de texto

A continuación, definimos una pequeña función de limpieza de texto y una regla de filtrado para eliminar muestras de baja calidad. La limpieza normaliza espacios en blanco y el filtrado descarta ejemplos con preguntas o respuestas demasiado cortas, respuestas excesivamente largas o plantillas genéricas poco útiles para el entrenamiento. 

def clean_text(text):
    text = re.sub(r"\s+", " ", str(text)).strip()
    return text


BAD_ANSWER_PREFIXES = (
    "These resources address",
    "These resources from MedlinePlus",
    "For more information, go to",
    "For more information go to",
)


def keep_example(example):
    question = clean_text(example["Question"])
    answer = clean_text(example["Answer"])

    if len(question) < 12 or len(answer) < 40:
        return False
    if len(answer) > 900:
        return False
    if any(answer.startswith(prefix) for prefix in BAD_ANSWER_PREFIXES):
        return False
    return True


filtered_dataset = raw_dataset.filter(keep_example)

Crea la partición de entrenamiento y evaluación

Tras el filtrado, barajamos el dataset, seleccionamos un subconjunto pequeño para este tutorial y lo dividimos en entrenamiento y evaluación. Así mantenemos una ejecución ligera pero comparable antes y después del ajuste fino. 

subset = filtered_dataset.shuffle(seed=SEED).select(range(SAMPLE_SIZE))
split_dataset = subset.train_test_split(test_size=EVAL_SIZE, seed=SEED)

train_dataset = split_dataset["train"]
eval_dataset = split_dataset["test"]

print(f"Filtered dataset size: {len(filtered_dataset)}")
print(f"Train size: {len(train_dataset)}")
print(f"Eval size: {len(eval_dataset)}")

Después del filtrado, quedan 7.355 ejemplos de mayor calidad, de los que muestreamos 216 para entrenamiento y 24 para evaluación en esta ejecución rápida.

Filtered dataset size: 7355
Train size: 216
Eval size: 24

Paso 3: carga Qwen3.6 con cuantización en 4 bits

En este paso configuraremos la cuantización en 4 bits con BitsAndBytes, cargaremos el tokenizer de Qwen3.6 y luego el modelo base para ejecutarlo de forma más eficiente en la H100 NVL.

Configura BitsAndBytes para carga en 4 bits

Qwen3.6 sigue siendo un modelo grande, por lo que cargarlo en precisión completa consumiría mucha más memoria. Para que el ajuste fino sea más eficiente, lo cargamos en 4 bits con BitsAndBytes. Esto reduce el uso de memoria manteniendo el modelo práctico para ajuste fino con QLoRA.

Aquí usamos nf4 cuantización, activamos la doble cuantización y establecemos el dtype de cómputo a bfloat16 cuando la GPU lo soporte.

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_use_double_quant=True,
    bnb_4bit_compute_dtype=torch.bfloat16 if supports_bf16() else torch.float16,
)

Carga el tokenizer

Ahora cargamos el tokenizer de Qwen3.6. También comprobamos si ya hay definido un token de relleno. Si falta, reutilizamos el token de fin de secuencia como padding para que el batching funcione bien en el entrenamiento y la generación. 

tokenizer = AutoTokenizer.from_pretrained(
    MODEL_ID,
    trust_remote_code=True,
    use_fast=False,
)

if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

Carga el modelo base

Ahora cargamos el modelo base Qwen3.6 con la configuración de cuantización en 4 bits definida antes. Usamos device_map="auto" para ubicar el modelo automáticamente en la GPU disponible y de nuevo bfloat16 cuando esté soportado para un cómputo más eficiente.

Tras cargar el modelo, establecemos el ID del token de relleno y desactivamos la caché. Esto es importante porque la caché ayuda en inferencia, pero durante el ajuste fino puede interferir con el entrenamiento y aumentar el uso de memoria.

model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID,
    quantization_config=bnb_config,
    device_map="auto",
    dtype=torch.bfloat16 if supports_bf16() else torch.float16,
    trust_remote_code=True,
)

model.config.pad_token_id = tokenizer.pad_token_id
model.config.use_cache = False

Paso 4: da formato a los prompts y crea la utilidad de evaluación

En este paso definiremos la estructura del prompt para Qwen3.6, limpiaremos las salidas del modelo, crearemos funciones de ayuda para entrenamiento e inferencia y prepararemos algunas muestras de vista previa para comparar el modelo antes y después del ajuste fino. 

Define el prompt del sistema

Empezamos con un prompt de sistema breve que indica al modelo cómo responder. Dado que es una tarea médica de preguntas y respuestas, queremos respuestas directas, objetivas y concisas.

SYSTEM_PROMPT = "Answer the medical question directly in 2-4 factual sentences."

Aplica la plantilla de chat

Qwen3.6 usa un formato de entrada tipo chat, así que necesitamos una función auxiliar que convierta nuestros mensajes en la estructura correcta. Esta función aplica la plantilla de chat del tokenizer y desactiva la salida de razonamiento para que el modelo se centre en la respuesta final. 

def apply_chat_template(messages, add_generation_prompt=False):
    kwargs = {
        "tokenize": False,
        "add_generation_prompt": add_generation_prompt,
    }
    try:
        return tokenizer.apply_chat_template(
            messages,
            enable_thinking=False,
            **kwargs,
        )
    except TypeError:
        return tokenizer.apply_chat_template(
            messages,
            chat_template_kwargs={"enable_thinking": False},
            **kwargs,
        )

Limpia la salida del modelo

Definimos una función de limpieza para normalizar el texto generado. Elimina espacios extra y suprime cualquier prefijo answer: si apareciera en la salida. 

def clean_answer_text(answer):
    answer = clean_text(answer)
    answer = re.sub(r"^answer\s*:\s*", "", answer, flags=re.IGNORECASE)
    return answer

Construye el prompt de usuario

Creamos también una función auxiliar para el prompt de usuario. Mantiene el formato de la pregunta consistente en entrenamiento y evaluación e indica al modelo que devuelva solo la respuesta sin repetir la pregunta. 

def make_user_prompt(question):
    return (
        f"Question: {clean_text(question)}\n\n"
        "Respond with only the answer. Do not restate the question."
    )

Da formato a los ejemplos para SFT

Ahora podemos transformar cada ejemplo del dataset en una conversación estilo chat con un mensaje del sistema, una pregunta del usuario y la respuesta esperada del asistente. Esta es la estructura que usaremos para el ajuste fino supervisado. 

def format_training_example(example):
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {
            "role": "user",
            "content": make_user_prompt(example["Question"]),
        },
        {
            "role": "assistant",
            "content": clean_answer_text(example["Answer"]),
        },
    ]
    return {"text": apply_chat_template(messages, add_generation_prompt=False)}

Genera respuestas de referencia inicial

Antes de entrenar, necesitamos una función auxiliar que genere respuestas con el modelo base. La usaremos para comparar las respuestas antes y después del ajuste fino.

La función siguiente construye el prompt, lo tokeniza, ejecuta la generación y después elimina trazas de <think> y el formato extra de la salida.

@torch.inference_mode()
def generate_answer(question, max_new_tokens=160):
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {
            "role": "user",
            "content": make_user_prompt(question),
        },
    ]
    prompt = apply_chat_template(messages, add_generation_prompt=True)
    model_inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

    generated = model.generate(
        **model_inputs,
        max_new_tokens=max_new_tokens,
        do_sample=False,
        pad_token_id=tokenizer.pad_token_id,
        eos_token_id=tokenizer.eos_token_id,
    )

    new_tokens = generated[0][model_inputs["input_ids"].shape[1] :]
    text = tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
    return clean_answer_text(re.sub(r"<think>.*?</think>", " ", text, flags=re.DOTALL))

Añadimos también una función auxiliar para acortar salidas largas al imprimir ejemplos de vista previa. 

def clip_text(text, max_chars=600):
    text = re.sub(r"<think>.*?</think>", " ", text, flags=re.DOTALL)
    text = re.sub(r"\s+", " ", text).strip()
    return text if len(text) <= max_chars else text[:max_chars] + "..."

Crea muestras de vista previa para comparar

Por último, construimos una función auxiliar que crea algunas filas de vista previa en paralelo. Cada fila contiene la pregunta, la predicción del modelo y la respuesta de referencia. Así podemos inspeccionar fácilmente el rendimiento antes y después del ajuste. 

def generate_preview_rows(dataset, sample_count=COMPARISON_SAMPLE_COUNT):
    sample_count = min(sample_count, len(dataset))
    subset = dataset.select(range(sample_count))
    previews = []

    for row in subset:
        previews.append(
            {
                "question": row["Question"],
                "prediction": generate_answer(row["Question"]),
                "reference": clean_answer_text(row["Answer"]),
            }
        )

    return previews


formatted_train_dataset = train_dataset.map(
    format_training_example,
    remove_columns=train_dataset.column_names,
)

comparison_examples = eval_dataset.shuffle(seed=SEED).select(
    range(COMPARISON_SAMPLE_COUNT)
)

print(formatted_train_dataset[0])

Al imprimir el primer ejemplo formateado, verás una muestra en formato chat con el prompt del sistema, la pregunta del usuario y la respuesta del asistente ya envueltos en la plantilla de Qwen. 

{'text': '<|im_start|>system\nAnswer the medical question directly in 2-4 factual sentences.<|im_end|>\n<|im_start|>user\nQuestion: What is (are) COPD ?\n\nRespond with only the answer. Do not restate the question.<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\nMore information on COPD is available at: What is COPD? and at the Learn More, Breathe Better Campaign For information on quitting smoking, visit http://www.surgeongeneral.gov/tobacco/ or Smokefree.gov. For information on the H1N1 flu and COPD, go to The Centers for Disease Control and Prevention.<|im_end|>\n'}

Paso 5: evalúa el modelo base antes del ajuste fino

Probaremos el modelo base Qwen3.6 en algunos ejemplos de evaluación antes de entrenar. Esto nos da una línea base para comparar cómo cambia el modelo tras el ajuste fino.

Comenzamos generando respuestas de vista previa para algunas muestras de la partición de evaluación. 

Cada vista previa incluye la pregunta, la respuesta del modelo antes del ajuste y la respuesta de referencia del dataset. 

baseline_previews = generate_preview_rows(comparison_examples)

for idx, sample in enumerate(baseline_previews, start=1):
    print(f"\n--- Preview {idx} ---")
    print("Question:", sample["question"])
    print("Model answer (before fine-tuning):", clip_text(sample["prediction"]))
    print("Reference answer:", clip_text(sample["reference"]))

Aquí tienes comparaciones en paralelo entre la respuesta actual del modelo y la respuesta de referencia. Estos ejemplos nos ayudan a entender el punto de partida del modelo antes de cualquier entrenamiento específico.

Evalúa el modelo base antes del ajuste fino

En estos ejemplos ya se ve que el modelo base es capaz de producir respuestas médicas relevantes y, por lo general, correctas. 

Sin embargo, sus respuestas no siempre coinciden con el estilo, nivel de detalle o redacción de las respuestas de referencia del dataset. En algunos casos, el modelo da una explicación más amplia o genérica, mientras que la referencia es más específica y está mejor alineada con el formato objetivo.

Paso 6: configura QLoRA para el ajuste fino

En esta parte definiremos la configuración del adaptador LoRA, estableceremos los argumentos de ajuste fino supervisado e inicializaremos el entrenador que gestionará el proceso con QLoRA.

Define la configuración de LoRA

En lugar de actualizar todos los parámetros del modelo, QLoRA ajusta un conjunto mucho menor de pesos entrenables del adaptador. Esto hace que el entrenamiento sea mucho más eficiente en memoria, manteniendo la capacidad de adaptación a la nueva tarea.

Aquí definimos una configuración LoRA con rango 8, factor de escala 16 y un dropout pequeño. También apuntamos a todas las capas lineales para aplicar adaptadores de forma amplia en el modelo.

lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules="all-linear",
)

Configura los argumentos del trainer 

Después, definimos la configuración de entrenamiento con SFTConfig. Estos ajustes controlan el comportamiento del ajuste fino supervisado: tamaño de lote, acumulación de gradientes, tasa de aprendizaje, frecuencia de logs y modo de precisión.

Este tutorial usa una configuración ligera con una época de entrenamiento y un batch pequeño para mantenerlo práctico. También activamos gradient checkpointing y AdamW paginado en 8 bits para reducir el uso de memoria durante el entrenamiento.

trainer_config = SFTConfig(
    output_dir=OUTPUT_DIR,
    dataset_text_field="text",
    max_length=MAX_SEQ_LENGTH,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=2,
    num_train_epochs=1,
    learning_rate=1e-4,
    warmup_steps=5,
    logging_steps=5,
    save_strategy="epoch",
    report_to="none",
    packing=False,
    gradient_checkpointing=True,
    optim="paged_adamw_8bit",
    bf16=supports_bf16(),
    fp16=not supports_bf16(),
)

Inicializa el SFTTrainer

Con los ajustes del adaptador y los argumentos de entrenamiento definidos, ya podemos inicializar el SFTTrainer. Este entrenador reúne el modelo base, el dataset formateado, la configuración LoRA y el tokenizer para dejar el modelo listo para el ajuste fino supervisado.  

trainer = SFTTrainer(
    model=model,
    train_dataset=formatted_train_dataset,
    peft_config=lora_config,
    args=trainer_config,
    processing_class=tokenizer,
)

Antes de comenzar el entrenamiento, conviene confirmar cuántos parámetros se actualizarán realmente. Una de las grandes ventajas de QLoRA es que solo una fracción mínima del modelo es entrenable. 

trainer.model.print_trainable_parameters()

Verás que solo alrededor del 0,03% de los parámetros del modelo completo se entrenan. Por eso QLoRA resulta tan práctico para ajustar LLMs en hardware limitado.

trainable params: 11,238,720 || all params: 34,671,849,408 || trainable%: 0.0324

Paso 7: entrena el modelo y guarda el adaptador

En esta sección iniciaremos el ajuste fino supervisado, guardaremos el adaptador entrenado localmente y lo subiremos a Hugging Face para reutilizarlo más adelante.

Inicia el ajuste fino

Con el modelo, el dataset y el entrenador listos, podemos comenzar el proceso de ajuste. El trainer recorrerá el conjunto de entrenamiento formateado y actualizará solo los pesos del adaptador LoRA, no el modelo completo. 

train_result = trainer.train()
train_result

Durante el entrenamiento, deberías ver cómo la pérdida desciende gradualmente, señal de que el modelo aprende el formato de la tarea y se adapta a los datos médicos de preguntas y respuestas. 

La pérdida de entrenamiento se reduce paso a paso.

Guarda los pesos ajustados

Al terminar, guarda localmente el adaptador ajustado y el tokenizer. Obtendrás un checkpoint reutilizable que podrás cargar más adelante sin repetir todo el entrenamiento.

trainer.model.save_pretrained(OUTPUT_DIR)
tokenizer.save_pretrained(OUTPUT_DIR)

Sube el modelo a Hugging Face

Una vez guardado el adaptador, puedes subirlo a Hugging Face. Así será más fácil compartirlo, versionarlo y recargarlo en futuros notebooks o flujos de despliegue. 

HF_REPO_ID = "kingabzpro/qwen36-medquad-quick"

trainer.model.push_to_hub(HF_REPO_ID)
tokenizer.push_to_hub(HF_REPO_ID)

Adaptador del modelo ajustado subido al repositorio de Hugging Face.Al finalizar la subida, el adaptador ajustado estará disponible en el repositorio.

Repositorio del modelo ajustado en Hugging Face.

Fuente: kingabzpro/qwen36-medquad-quick

Paso 8: evalúa el modelo tras el ajuste fino

Con el entrenamiento completo, podemos evaluar el modelo ajustado en los mismos ejemplos de comparación que usamos antes. Así obtenemos una comparativa directa antes/después sobre las mismas preguntas. 

model.eval()

fine_tuned_previews = generate_preview_rows(comparison_examples)

for idx, (before, after) in enumerate(
    zip(baseline_previews, fine_tuned_previews), start=1
):
    print(f"\n{'=' * 80}")
    print(f"Sample {idx}")
    print(f"{'=' * 80}")
    print("Question:", before["question"])
    print("\nBefore fine-tuning:")
    print(clip_text(before["prediction"]))
    print("\nAfter fine-tuning:")
    print(clip_text(after["prediction"]))
    print("\nReference answer:")
    print(clip_text(before["reference"]))

Deberías ver, para cada muestra, la respuesta base, la respuesta ajustada y la respuesta de referencia. Esto facilita revisar cómo cambió el estilo y el contenido tras aplicar el adaptador.

Comparación de la respuesta base, la respuesta ajustada y la referencia para cada muestra.

Estos ejemplos muestran que el ajuste fino ayudó al modelo a alinearse mejor con el estilo del dataset objetivo, especialmente en preguntas médicas breves y factuales. 

A la vez, también confirman que ha sido una ejecución pequeña sobre un subconjunto limitado, por lo que no se garantiza la mejora en todos los casos. Algunas respuestas se alinean mejor con la distribución de entrenamiento, mientras que otras pueden perder detalle útil o volverse demasiado estrechas.

Para obtener mejores resultados, el siguiente paso sería entrenar con el dataset completo en lugar de un subconjunto y ejecutar al menos 3 épocas. También podrías aumentar el rango de LoRA para dar más capacidad de aprendizaje al adaptador y afinar el prompt del sistema para que el modelo siga de forma más consistente el estilo de respuesta médica deseado. 

Estos cambios probablemente producirán mejoras más estables en un abanico más amplio de preguntas.

Reflexiones finales 

Ajustar un modelo de lenguaje moderno no es tan simple como puede parecer al principio. 

Aunque Qwen3.6-35B-A3B es un modelo Mixture-of-Experts eficiente con 35.000 millones de parámetros, sigue requiriendo cómputo serio solo para cargarlo en modo de 4 bits y prepararlo para entrenar. El entrenamiento en sí también es costoso, especialmente cuando pasas de un subconjunto pequeño experimental a un dataset mayor o varias épocas.

El hardware importa mucho. En este tutorial usamos una H100 NVL para mantener el flujo práctico. En una GPU más antigua, el tiempo de entrenamiento puede aumentar considerablemente. Incluso con una A100, una configuración similar puede llevar cerca de una hora, y en GPUs más débiles, aún más. 

Así que, aunque QLoRA y la carga en 4 bits hacen que el ajuste fino sea más accesible, no lo convierten en algo ligero en el día a día. También merece la pena preguntarse si el ajuste fino es necesario. Para muchas tareas básicas, no recomiendo lanzarse directamente a ajustar. 

Si dedicas tiempo a mejorar el prompt, estructurar mejor el flujo y usar herramientas, MCPs o métodos de recuperación, a menudo podrás acercarte mucho al comportamiento objetivo sin entrenar. En muchos casos, eso te acerca al 80% del resultado que quieres, siendo mucho más rápido, barato y fácil de iterar.

El ajuste fino cobra más valor cuando necesitas que el modelo aprenda de forma consistente un estilo de respuesta muy específico, un comportamiento de dominio o un formato institucional. Ahí empieza a tener sentido. Si quieres que el modelo responda exactamente como lo haría un hospital, un sistema sanitario o un flujo de trabajo especializado, entonces el ajuste fino es el mejor camino. 

En resumen: primero prueba con prompting y diseño de flujos, y recurre al ajuste fino cuando realmente necesites que el modelo interiorice una forma especializada de responder.


Abid Ali Awan's photo
Author
Abid Ali Awan
LinkedIn
Twitter

Soy un científico de datos certificado que disfruta creando aplicaciones de aprendizaje automático y escribiendo blogs sobre ciencia de datos. Actualmente me centro en la creación de contenidos, la edición y el trabajo con grandes modelos lingüísticos.

Temas

Los mejores cursos de IA

programa

Associate AI Engineer para desarrolladores

26 h
Aprende a integrar IA en aplicaciones de software usando APIs y bibliotecas de código abierto. ¡Empieza hoy tu camino para convertirte en AI Engineer!
Ver detallesRight Arrow
Iniciar curso
Ver másRight Arrow