Curso
Qwen3.5 Small es un modelo muy práctico para ajustes finos ligeros: está pensado para prototipado, adaptación a tareas concretas y flujos de trabajo de investigación. Combinado con QLoRA, podemos ajustarlo sobre un modelo base cuantizado en 4 bits y congelado, haciendo el proceso mucho más eficiente en memoria y accesible en hardware limitado.
En este tutorial, configuraremos el entorno, cargaremos el modelo y el conjunto de datos AG News, evaluaremos el modelo base y después ajustaremos Qwen3.5 Small para clasificación de noticias. Cerraremos comparando los resultados antes y después del entrenamiento, y por último guardaremos y publicaremos el adaptador LoRA en Hugging Face para reutilizarlo.
Te recomiendo realizar el itinerario de aprendizaje Hugging Face Fundamentals si aún no estás familiarizado con su funcionamiento.
¿Qué es Qwen3.5 Small?
Antes de entrar en el código, conviene entender el modelo con el que vamos a trabajar. Qwen3.5 es una familia de modelos fundacionales y la serie "Small" hace referencia a su línea ligera, en concreto los modelos con menos de 10.000 millones de parámetros. Para este tutorial usaremos la versión de 0,8B.
Aun teniendo solo 800 millones de parámetros, Qwen3.5-0.8B es muy capaz y extremadamente eficiente. Cargado en precisión de 4 bits, requiere menos de 2 GB de VRAM. Esto lo hace ideal para portátiles de consumo, entornos gratuitos en la nube como Kaggle y despliegues ligeros en el borde.
Además, la arquitectura Qwen3.5 admite una ventana de contexto enorme de 262K y multimodalidad nativa. Aunque aquí nos centramos estrictamente en clasificación de texto, los conceptos fundamentales pueden ampliarse fácilmente a tareas de visión más adelante. El modelo también incluye un modo de razonamiento híbrido, pero lo mantendremos desactivado para maximizar la velocidad de inferencia en esta tarea sencilla de etiquetado.
En conjunto, Qwen3.5-0.8B es una opción muy práctica para prototipar. Permite probar flujos de ajuste fino de forma rápida y a bajo coste, manteniendo un buen rendimiento aguas abajo.
1. Configura el entorno en Kaggle
En este tutorial usaremos un cuaderno de Kaggle como entorno de entrenamiento. Kaggle nos da acceso a GPUs, lo que lo convierte en una plataforma cómoda para entrenar, ajustar y evaluar el modelo. Antes de ejecutar el cuaderno, crea uno nuevo, abre los ajustes de Session y selecciona GPU P100 como acelerador.

A continuación, configuraremos el entorno, añadiremos un token de Hugging Face a Kaggle e importaremos las librerías de Python que usaremos en todo el tutorial.
Crea un token de acceso de Hugging Face
Antes de ejecutar el cuaderno, crea un token de acceso de Hugging Face desde los ajustes de tu cuenta. Ve a Settings, abre Access Tokens y genera uno nuevo. Si quieres publicar el adaptador LoRA ajustado en el Hub de Hugging Face al final, asegúrate de que el token tenga permisos de escritura.

Añade el token a los secrets de Kaggle
Tras generar el token, abre tu cuaderno de Kaggle y añádelo como secreto. En el editor del cuaderno, ve a Add-ons, luego Secrets y crea un nuevo secreto llamado HUGGINGFACE_TOKEN. Pega ahí el valor de tu token para que el cuaderno pueda acceder a él de forma segura sin exponerlo en el código.

Instala las librerías necesarias
Ahora podemos instalar las librerías principales para este flujo. Estos paquetes cubren carga de modelos, cuantización, ajuste eficiente en parámetros, manejo de datos y evaluación.
!pip install -q -U transformers accelerate
!pip install -q peft trl bitsandbytes datasets scikit-learn
Autentícate con Hugging Face
Después, cargamos el token de Hugging Face desde los secrets de Kaggle y lo usamos para autenticarnos. Esto da al cuaderno acceso al modelo y nos permitirá subir más tarde el adaptador entrenado.
from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()
login(token=user_secrets.get_secret("HUGGINGFACE_TOKEN"))
Importa las librerías de Python
Por último, importamos las librerías de Python que usaremos para cargar el modelo, entrenar, procesar el conjunto de datos y evaluar.
import torch
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer, SFTConfig
from transformers import TrainingArguments
from sklearn.metrics import accuracy_score, f1_score
from tqdm.notebook import tqdm
2. Carga el modelo Qwen3.5 y el conjunto de datos AG News
Con el entorno listo, ya podemos cargar el modelo base y preparar el conjunto de datos para el ajuste fino.
En esta sección cargaremos Qwen3.5-0.8B en precisión de 4 bits para que el entrenamiento sea más eficiente en memoria, configuraremos el tokenizador y cargaremos un subconjunto pequeño de AG News para entrenamiento y evaluación.
Carga Qwen3.5-0.8B con cuantización de 4 bits
Empezamos cargando Qwen3.5-0.8B con cuantización de 4 bits usando BitsAndBytes. Esto reduce el uso de memoria y hace el modelo más manejable en un cuaderno de Kaggle.
model_id = "Qwen/Qwen3.5-0.8B"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=bnb_config,
device_map="auto",
dtype=torch.bfloat16
)
Carga el tokenizador
A continuación cargamos el tokenizador del mismo modelo. También establecemos el token de relleno como el de fin de secuencia para que el modelo gestione correctamente el padding durante el entrenamiento y la generación.
tokenizer = AutoTokenizer.from_pretrained(model_id)
tokenizer.pad_token = tokenizer.eos_token
model.config.pad_token_id = tokenizer.eos_token_id
model.generation_config.pad_token_id = tokenizer.eos_token_id
Carga el conjunto de datos AG News
Por último, cargamos el conjunto de datos AG News y creamos divisiones de entrenamiento y prueba más pequeñas para este tutorial. Usar un subconjunto acelera el flujo de trabajo y aun así nos da datos suficientes para evaluar el efecto del ajuste fino.
dataset = load_dataset("ag_news")
train_dataset = dataset["train"].select(range(2500))
test_dataset = dataset["test"].select(range(200))
3. Formato del prompt y evaluación de referencia
Antes de ajustar, debemos definir cómo verá el modelo la tarea y medir su rendimiento en estado original.
En esta sección crearemos un prompt con instrucciones para clasificar noticias, extraeremos predicciones de la salida del modelo, haremos una inferencia de muestra y evaluaremos el modelo base en el conjunto de prueba antes del entrenamiento.
Crea el prompt de instrucciones
Empezamos definiendo los nombres de las etiquetas y construyendo un prompt que pida al modelo clasificar un artículo. El prompt está redactado para que el modelo devuelva solo la etiqueta numérica, lo que facilita el análisis de la salida en la evaluación.
labels = ['World','Sports','Business','Sci/Tech']
def build_prompt(text):
return f"""
Classify the news article.
Article:
{text}
Return ONLY the number of the correct label.
0 = World
1 = Sports
2 = Business
3 = Sci/Tech
Answer:
"""
Extrae predicciones de la salida del modelo
Creamos después una pequeña función auxiliar para extraer la etiqueta predicha del texto generado. Esta función elimina cualquier token de razonamiento adicional y devuelve la clase final como entero.
import re
def extract_label(text):
text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
matches = re.findall(r"[0-3]", text)
if matches:
return int(matches[-1])
return -1
Ejecuta una inferencia de ejemplo
Antes de evaluar el modelo en todo el conjunto de prueba, lo probamos con un único ejemplo. Así podemos inspeccionar el artículo, comprobar la etiqueta correcta y ver qué predice el modelo antes del ajuste fino.
sample = train_dataset[10]
sample_text = sample["text"]
sample_label = sample["label"]
print("Ground truth label:", sample_label)
print("Sample text:")
print(sample_text[:300])
La salida muestra que la etiqueta real es 2, que corresponde a Business.
Ground truth label: 2
Sample text:
Oil and Economy Cloud Stocks' Outlook NEW YORK (Reuters) - Soaring crude prices plus worries about the economy
and the outlook for earnings are expected to hang over the stock market next week during the depth of the summer doldrums.
Después, formateamos el prompt, se lo pasamos al modelo y generamos una predicción.
prompt = build_prompt(sample_text)
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
with torch.inference_mode():
outputs = model.generate(
**inputs,
max_new_tokens=5,
do_sample=False,
pad_token_id=tokenizer.eos_token_id
)
prediction = tokenizer.decode(outputs[0], skip_special_tokens=True)
print("Model prediction (before training):")
print(extract_label(prediction))
El modelo también predice 2, así que clasifica correctamente este ejemplo. Esto nos confirma rápidamente que el formato del prompt y la canalización de predicción funcionan como esperamos.
Model prediction (before training):
2
Evalúa el modelo base antes del ajuste fino
Ahora evaluamos el modelo base en el conjunto de prueba para establecer una referencia. Esto nos da un punto de comparación antes y después del ajuste fino.
def evaluate_model(dataset):
preds = []
refs = []
model.eval()
for example in tqdm(dataset):
text = example["text"]
label = example["label"]
prompt = build_prompt(text)
inputs = tokenizer(prompt, return_tensors="pt", truncation=True)
inputs = {k: v.to("cuda") for k, v in inputs.items()}
with torch.inference_mode():
outputs = model.generate(
**inputs,
max_new_tokens=5,
do_sample=False,
pad_token_id=tokenizer.eos_token_id
)
prediction = tokenizer.decode(outputs[0], skip_special_tokens=True)
pred_label = extract_label(prediction)
preds.append(pred_label)
refs.append(label)
acc = accuracy_score(refs, preds)
f1 = f1_score(refs, preds, average="weighted")
print("Accuracy:", acc)
print("F1:", f1)
print('Pre-fine-tuning evaluation')
evaluate_model(test_dataset)
La salida informa de una exactitud de 0.52 y un F1 ponderado de 0.4589. Estos resultados de referencia muestran que el modelo ya tiene cierta capacidad para la tarea, aunque hay margen claro de mejora mediante ajuste fino.
Pre-fine-tuning evaluation
Accuracy: 0.52
F1: 0.45889163289429247
4. Ajusta Qwen3.5 con QLoRA
Con la referencia establecida, podemos ajustar el modelo a la tarea de clasificación de noticias. En esta sección daremos formato a los datos de entrenamiento, configuraremos los adaptadores LoRA, definiremos la configuración de entrenamiento, entrenaremos el modelo y guardaremos el adaptador final para su uso posterior.
Prepara los datos de entrenamiento
Primero, formateamos cada ejemplo de entrenamiento para que siga la misma estructura de prompt usada en inferencia. Añadimos la etiqueta correcta al final del prompt para que el modelo aprenda a generar la respuesta adecuada durante el ajuste fino.
def format_train(example):
prompt = build_prompt(example['text'])
answer = str(example['label'])
return {'text': prompt + ' ' + answer}
train_dataset = train_dataset.map(format_train)
Configura los adaptadores LoRA
Después definimos la configuración de LoRA. Esto indica al modelo qué capas adaptar durante el ajuste fino, permitiéndonos entrenar un pequeño conjunto de parámetros adicionales en lugar de actualizar todo el modelo.
lora_config = LoraConfig(
r=16,
lora_alpha=16,
lora_dropout=0,
bias='none',
task_type='CAUSAL_LM',
target_modules=[
'q_proj','k_proj','v_proj','o_proj',
'gate_proj','up_proj','down_proj'
]
)
Define la configuración de entrenamiento
A continuación, establecemos los argumentos de entrenamiento para el ajuste supervisado. Estos parámetros controlan cuántos ejemplos se procesan a la vez, cómo se acumulan los gradientes, cuánto dura el entrenamiento y dónde se guardan los archivos de salida.
training_args = SFTConfig(
output_dir="./qwen35-lora",
per_device_train_batch_size=8,
gradient_accumulation_steps=2,
learning_rate=2e-4,
num_train_epochs=1,
logging_steps=10,
bf16=True,
dataset_text_field="text",
packing=False,
)
Entrena el modelo
Ahora inicializamos el entrenador con el modelo, el conjunto de datos, la configuración de LoRA, los ajustes de entrenamiento y el tokenizador. Luego iniciamos el ajuste fino sobre el conjunto de entrenamiento formateado de AG News.
trainer = SFTTrainer(
model=model,
train_dataset=train_dataset,
peft_config=lora_config,
args=training_args,
processing_class=tokenizer
)
trainer.train()
Salida:
TrainOutput(global_step=157, training_loss=1.9094241804378047, metrics={'train_runtime': 616.477, 'train_samples_per_second': 4.055, 'train_steps_per_second': 0.255, 'total_flos': 1041693973114368.0, 'train_loss': 1.9094241804378047})
Guarda el adaptador entrenado
Cuando termine el entrenamiento, guardamos el adaptador LoRA y el tokenizador. Así podremos recargar el adaptador ajustado más tarde para evaluar, compartir o publicar en Hugging Face.
trainer.model.save_pretrained("qwen35-small-news-class")
tokenizer.save_pretrained("qwen35-small-news-class")
5. Evalúa el modelo ajustado
Tras el entrenamiento, recargamos el modelo base y acoplamos el adaptador LoRA guardado. Así podemos ejecutar la evaluación con los pesos ajustados y comparar los resultados con la referencia anterior.
Recarga el modelo base y acopla el adaptador LoRA
Primero cargamos de nuevo el modelo base Qwen3.5 y luego acoplamos el adaptador LoRA ajustado que guardamos en el directorio de trabajo de Kaggle. Después, ponemos el modelo en modo de evaluación e inspeccionamos el resumen de parámetros entrenables.
from transformers import AutoModelForCausalLM
base_model = AutoModelForCausalLM.from_pretrained(
model_id,
device_map="auto",
dtype=torch.bfloat16
)
from peft import PeftModel
model = PeftModel.from_pretrained(
base_model,
"/kaggle/working/qwen35-small-news-class"
)
model.eval()
model.print_trainable_parameters()
La salida muestra que no hay parámetros entrenables en esta fase porque el entrenamiento ya ha concluido y ahora usamos el adaptador guardado para inferencia y evaluación.
trainable params: 0 || all params: 758,782,784 || trainable%: 0.0000
Evalúa el modelo ajustado
A continuación ejecutamos de nuevo la misma función de evaluación sobre el conjunto de prueba. Así medimos cuánto ha mejorado el modelo tras el ajuste fino.
print('Post-fine-tuning evaluation')
evaluate_model(test_dataset)
La salida informa de una exactitud de 0.865 y un F1 ponderado de 0.8661. Comparado con la referencia, se aprecia una gran mejora tras el ajuste fino.
Post-fine-tuning evaluation
Accuracy: 0.865
F1: 0.8660926258753361
Compara los resultados
Ya podemos comparar el rendimiento antes y después del ajuste fino. Tanto la exactitud como el F1 mejoran de forma sustancial, lo que muestra que el ajuste con QLoRA ayuda al modelo a adaptarse muy bien a la tarea de clasificación de noticias.
|
Métrica |
Antes del entrenamiento |
Después del entrenamiento |
|
Exactitud |
0.52 |
0.865 |
|
F1 |
0.4589 |
0.866 |
Como ves, ajustar Qwen3.5 Small con QLoRA mejora de forma notable el rendimiento en clasificación.
6. Publica el modelo en Hugging Face
Una vez completada la evaluación, podemos subir el adaptador ajustado y el tokenizador a Hugging Face. Así será más fácil reutilizarlo, compartirlo y cargarlo en futuros proyectos sin repetir todo el entrenamiento.
model.push_to_hub("kingabzpro/qwen35-small-news-class")
tokenizer.push_to_hub("kingabzpro/qwen35-small-news-class")
Cuando termine la subida, el modelo estará disponible en:
Model:
https://huggingface.co/kingabzpro/qwen35-small-news-class

Fuente: kingabzpro/qwen35-small-news-class
El cuaderno de Kaggle de este tutorial también está disponible. Te recomiendo usarlo solo si te surge algún problema al reproducir los pasos anteriores. También puedes clonarlo y ejecutarlo en tu propio sistema, pero recuerda actualizar el nombre de usuario y el token de Hugging Face para que coincidan con tu cuenta.
7. Conclusión
En este tutorial, ajustamos Qwen3.5-0.8B con QLoRA para clasificación de noticias y logramos un gran resultado con una configuración muy ligera. Tras solo una época y un subconjunto pequeño de datos, la exactitud pasó de 0.52 a 0.865 y el F1 ponderado pasó de 0.4589 a 0.8661.
Para un modelo tan pequeño, eso es lo más sorprendente: no necesitamos un modelo enorme, ni un dataset inmenso, ni entrenamientos largos para lograr buenos resultados en tareas posteriores.
Lo mejor es lo práctica que resulta esta configuración. Qwen describe el modelo de 0.8B como idóneo para prototipado y ajuste fino específico de tareas, y QLoRA mantiene bajo el uso de memoria al entrenar adaptadores sobre un modelo base de 4 bits congelado. Eso significa que no es solo un flujo en la nube: también es realista para probar en GPUs de portátiles y equipos de consumo.
Otro motivo por el que este modelo me entusiasma es que la familia Qwen3.5 admite ajuste fino de texto y de visión. Así que, aunque aquí lo usamos para clasificación de texto, la misma idea puede extenderse a clasificación de imágenes, de imagen a texto y de texto a texto. Esto convierte a un modelo pequeño como este en una opción realmente práctica cuando quieres probar ideas rápido, ajustar a bajo coste y mantener un despliegue ligero.
También te recomiendo echar un vistazo a otros recursos, como nuestra guía de Qwen3.5 y el tutorial para ejecutar Qwen3.5 en local con una sola GPU.
FAQs
¿Por qué usar QLoRA (cuantización en 4 bits) en lugar de ajuste completo para un modelo de 0.8B?
Aunque 0.8B se considera "pequeño" en términos de LLM, actualizar por completo los 800 millones de parámetros sigue exigiendo mucha VRAM y cómputo. QLoRA congela el modelo base en precisión de 4 bits y solo entrena un conjunto muy pequeño de pesos de adaptador encima. Esto reduce drásticamente la huella de memoria, permitiéndote entrenar cómodamente en una GPU P100 gratuita de Kaggle o en un portátil estándar sin quedarte sin memoria.
Qwen3.5 admite un nuevo modo "thinking". ¿Debería usarlo para tareas de clasificación?
Qwen3.5-0.8B admite de forma nativa razonamiento híbrido, pero funciona en modo "no-thinking" por defecto. Aunque puedes activar sus capacidades de razonamiento, tareas sencillas de clasificación como AG News no requieren un razonamiento complejo paso a paso. Mantener desactivado el modo de razonamiento es mucho más rápido y eficiente en tokens para un etiquetado directo.
¿Cómo afecta la enorme ventana de contexto de 262K de Qwen3.5 a este script de ajuste fino?
Aunque el modelo base admite hasta 262.144 tokens, ajustar con documentos masivos agotará rápidamente la memoria de la GPU de Kaggle. Para clasificación de noticias, el texto es corto, así que no supone un problema. Si planeas adaptar este script a tareas con documentos largos (como resumir libros enteros), tendrás que activar el checkpointing de gradientes y reducir el tamaño de lote.
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.



