Corso
Qwen3.5 Small è un modello pratico per il fine-tuning leggero perché è pensato per il prototyping, l’adattamento specifico al compito e i flussi di lavoro di ricerca. Insieme a QLoRA, possiamo effettuare il fine-tuning partendo da un modello base quantizzato a 4 bit mantenuto congelato, rendendo il processo molto più efficiente in memoria e accessibile su hardware limitato.
In questo tutorial imposteremo l’ambiente, caricheremo il modello e il dataset AG News, valuteremo il modello base e poi faremo il fine-tuning di Qwen3.5 small per la classificazione delle notizie. Concluderemo confrontando i risultati prima e dopo l’addestramento, quindi salveremo e pubblicheremo l’adapter LoRA su Hugging Face per il riuso.
Ti consiglio di seguire il percorso di competenze Hugging Face Fundamentals se non hai già familiarità con la piattaforma.
Che cos’è Qwen3.5 Small?
Prima di tuffarci nel codice, è utile capire il modello con cui lavoriamo. Qwen3.5 è una famiglia di foundation model, e la serie "Small" indica la linea più leggera, in particolare i modelli sotto i 10 miliardi di parametri. Per questo tutorial useremo la versione 0.8B.
Nonostante abbia solo 800 milioni di parametri, Qwen3.5-0.8B è molto capace ed estremamente efficiente. Caricato in precisione a 4 bit, richiede meno di 2 GB di VRAM. Questo lo rende ideale per laptop consumer, ambienti cloud gratuiti come Kaggle e deployment leggeri all’edge.
Inoltre, l’architettura Qwen3.5 supporta una finestra di contesto enorme da 262K e la multimodalità nativa. Anche se in questa guida ci concentriamo strettamente sulla classificazione di testo, i concetti di base trattati possono essere facilmente estesi a compiti di visione in seguito. Il modello offre anche una modalità di ragionamento ibrida, che però lasceremo disattivata per massimizzare la velocità di inferenza nel nostro semplice task di etichettatura.
Nel complesso, Qwen3.5-0.8B è un’opzione altamente pratica per il prototyping. Ci offre un modo rapido ed economico per testare i workflow di fine-tuning, garantendo comunque solidi risultati a valle.
1. Configura l’ambiente Kaggle
In questo tutorial useremo un notebook Kaggle come ambiente di training. Kaggle ci fornisce l’accesso alle GPU, rendendolo una piattaforma comoda per addestrare, effettuare il fine-tuning e valutare il nostro modello. Prima di eseguire il notebook, avvia un nuovo notebook Kaggle, apri le impostazioni della Sessione e seleziona GPU P100 come acceleratore.

Successivamente, configureremo l’ambiente, aggiungeremo un token Hugging Face a Kaggle e importeremo le librerie Python utilizzate nel tutorial.
Crea un token di accesso Hugging Face
Prima di eseguire il notebook, crea un token di accesso Hugging Face dalle impostazioni del tuo account. Vai su Settings, apri Access Tokens e genera un nuovo token. Se vuoi pubblicare l’adapter LoRA fine-tunato su Hugging Face Hub alla fine del tutorial, assicurati che il token abbia i permessi di scrittura.

Aggiungi il token ai secrets di Kaggle
Dopo aver generato il token, apri il tuo notebook Kaggle e aggiungilo come secret. Nell’editor del notebook, vai su Add-ons, poi Secrets, e crea un nuovo secret chiamato HUGGINGFACE_TOKEN. Incolla lì il valore del tuo token, così il notebook potrà accedervi in modo sicuro senza esporlo nel codice.

Installa le librerie necessarie
Ora possiamo installare le principali librerie necessarie per questo workflow. Questi pacchetti coprono il caricamento del modello, la quantizzazione, il fine-tuning efficiente in termini di parametri, la gestione dei dataset e la valutazione.
!pip install -q -U transformers accelerate
!pip install -q peft trl bitsandbytes datasets scikit-learn
Autenticati con Hugging Face
Successivamente, carichiamo il token Hugging Face dai secrets di Kaggle e lo usiamo per l’autenticazione. Questo dà al notebook accesso al modello e ci permette di caricare l’adapter addestrato in seguito.
from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()
login(token=user_secrets.get_secret("HUGGINGFACE_TOKEN"))
Importa le librerie Python
Infine, importiamo le librerie Python usate nel tutorial per il caricamento del modello, l’addestramento, l’elaborazione del dataset e la valutazione.
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. Carica il modello Qwen3.5 e il dataset AG News
Ora che l’ambiente è pronto, possiamo caricare il modello base e preparare il dataset per il fine-tuning.
In questa sezione caricheremo Qwen3.5-0.8B in precisione a 4 bit per rendere l’addestramento più efficiente in memoria, imposteremo il tokenizer e caricheremo un piccolo sottoinsieme del dataset AG News per training e valutazione.
Carica Qwen3.5-0.8B con quantizzazione a 4 bit
Iniziamo caricando Qwen3.5-0.8B con quantizzazione a 4 bit usando BitsAndBytes. Questo riduce l’uso di memoria e rende il modello più pratico da usare in un notebook 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
)
Carica il tokenizer
Poi carichiamo il tokenizer per lo stesso modello. Impostiamo anche il token di padding come token di fine sequenza, così il modello gestisce correttamente gli input con padding durante training e generazione.
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
Carica il dataset AG News
Infine, carichiamo il dataset AG News e creiamo split di training e test più piccoli per questo tutorial. Usare un sottoinsieme mantiene il workflow più veloce, pur fornendo abbastanza dati per valutare l’effetto del fine-tuning.
dataset = load_dataset("ag_news")
train_dataset = dataset["train"].select(range(2500))
test_dataset = dataset["test"].select(range(200))
3. Formattazione del prompt e valutazione di baseline
Prima del fine-tuning, dobbiamo definire come il modello vedrà il compito e misurare come si comporta nella sua forma originale.
In questa sezione creeremo un prompt in stile istruzioni per la classificazione delle notizie, estrarremo le previsioni dall’output del modello, eseguiremo un’inferenza di esempio e valuteremo il modello base sul set di test prima dell’addestramento.
Crea il prompt di istruzioni
Iniziamo definendo i nomi delle etichette e costruendo un prompt che chieda al modello di classificare un articolo di notizie. Il prompt è scritto affinché il modello restituisca solo l’etichetta numerica, semplificando l’analisi dell’output in fase di valutazione.
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:
"""
Estrai le previsioni dall’output del modello
Creiamo quindi una piccola funzione di supporto per estrarre l’etichetta prevista dal testo generato. Questa funzione rimuove eventuali token di ragionamento extra e restituisce l’etichetta finale come intero.
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
Esegui un’inferenza di esempio
Prima di valutare il modello sull’intero set di test, proviamolo su un singolo esempio. Questo ci aiuta a ispezionare l’articolo, verificare l’etichetta corretta e vedere cosa prevede il modello prima del fine-tuning.
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])
L’output mostra che la vera etichetta è 2, che corrisponde 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.
Poi formattiamo il prompt, lo passiamo al modello e generiamo una previsione.
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))
Il modello prevede anche 2, quindi classifica correttamente questo esempio. Questo ci dà una verifica rapida che il formato del prompt e la pipeline di previsione funzionano come previsto.
Model prediction (before training):
2
Valuta il modello base prima del fine-tuning
Ora valutiamo il modello base sul set di test per stabilire una baseline. Questo ci fornisce un punto di riferimento per confrontare le prestazioni prima e dopo il fine-tuning.
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)
L’output riporta un’accuratezza di 0.52 e un F1 score pesato di 0.4589. Questi risultati di baseline mostrano che il modello ha già una certa capacità di svolgere il compito, pur lasciando ampio margine di miglioramento tramite fine-tuning.
Pre-fine-tuning evaluation
Accuracy: 0.52
F1: 0.45889163289429247
4. Fine-tuning di Qwen3.5 con QLoRA
Ora che abbiamo stabilito una baseline, possiamo fare il fine-tuning del modello sul task di classificazione delle notizie. In questa sezione formatteremo i dati di training, configureremo gli adapter LoRA, definiremo le impostazioni di training, alleneremo il modello e salveremo l’adapter finale per usi futuri.
Prepara i dati di training
Per prima cosa, formattiamo ogni esempio di training in modo che corrisponda alla stessa struttura di prompt usata in inferenza. Aggiungiamo l’etichetta corretta alla fine del prompt, così il modello può imparare a generare la risposta giusta durante il fine-tuning.
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 gli adapter LoRA
Successivamente definiamo la configurazione di LoRA. Questo indica al modello quali layer adattare durante il fine-tuning, permettendoci di addestrare un piccolo set di parametri aggiuntivi invece di aggiornare l’intero modello.
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'
]
)
Definisci la configurazione di training
Impostiamo quindi gli argomenti di training per il supervised fine-tuning. Queste impostazioni controllano quante istanze vengono elaborate alla volta, come si accumulano i gradienti, quanto dura l’addestramento e dove salvare i file di output.
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,
)
Allena il modello
Ora inizializziamo il trainer con il modello, il dataset, la configurazione LoRA, le impostazioni di training e il tokenizer. Poi avviamo il fine-tuning del modello sul training set formattato di AG News.
trainer = SFTTrainer(
model=model,
train_dataset=train_dataset,
peft_config=lora_config,
args=training_args,
processing_class=tokenizer
)
trainer.train()
Output:
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})
Salva l’adapter addestrato
Al termine dell’addestramento, salviamo l’adapter LoRA e il tokenizer. In questo modo possiamo ricaricare l’adapter fine-tunato in seguito per valutazione, condivisione o pubblicazione su Hugging Face.
trainer.model.save_pretrained("qwen35-small-news-class")
tokenizer.save_pretrained("qwen35-small-news-class")
5. Valuta il modello fine-tunato
Dopo l’addestramento, ricarichiamo il modello base e colleghiamo l’adapter LoRA salvato. Questo ci consente di eseguire la valutazione usando i pesi fine-tunati e confrontare i risultati con la baseline precedente.
Ricarica il modello base e collega l’adapter LoRA
Per prima cosa carichiamo di nuovo il modello Qwen3.5 base, quindi colleghiamo l’adapter LoRA fine-tunato salvato nella working directory di Kaggle. Dopo, passiamo il modello in modalità valutazione e ispezioniamo il riepilogo dei parametri addestrabili.
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()
L’output mostra che in questa fase non ci sono parametri addestrabili perché l’addestramento è terminato e ora stiamo usando l’adapter salvato per inferenza e valutazione.
trainable params: 0 || all params: 758,782,784 || trainable%: 0.0000
Valuta il modello fine-tunato
Ora eseguiamo di nuovo la stessa funzione di valutazione sul set di test. Questo ci permette di misurare quanto il modello è migliorato dopo il fine-tuning.
print('Post-fine-tuning evaluation')
evaluate_model(test_dataset)
L’output riporta un’accuratezza di 0.865 e un F1 score pesato di 0.8661. Rispetto ai risultati di baseline, questo mostra un grande miglioramento dopo il fine-tuning.
Post-fine-tuning evaluation
Accuracy: 0.865
F1: 0.8660926258753361
Confronta i risultati
Ora possiamo confrontare le prestazioni del modello prima e dopo il fine-tuning. Sia l’accuratezza sia l’F1 score migliorano in modo sostanziale, mostrando che il fine-tuning con QLoRA aiuta il modello ad adattarsi bene al task di classificazione delle notizie.
|
Metrica |
Prima del training |
Dopo il training |
|
Accuracy |
0.52 |
0.865 |
|
F1 Score |
0.4589 |
0.866 |
Come possiamo vedere, il fine-tuning di Qwen3.5 Small con QLoRA migliora significativamente le prestazioni di classificazione.
6. Pubblica il modello su Hugging Face
Una volta completata la valutazione, possiamo caricare l’adapter fine-tunato e il tokenizer su Hugging Face. Questo rende il modello più semplice da riutilizzare, condividere e caricare in progetti futuri senza ripetere l’intero processo di training.
model.push_to_hub("kingabzpro/qwen35-small-news-class")
tokenizer.push_to_hub("kingabzpro/qwen35-small-news-class")
Dopo il caricamento, il modello sarà disponibile qui:
Model:
https://huggingface.co/kingabzpro/qwen35-small-news-class

Fonte: kingabzpro/qwen35-small-news-class
È disponibile anche il notebook Kaggle per questo tutorial. Ti consiglio di usarlo solo se incontri problemi nel riprodurre i passaggi sopra. Puoi anche clonare il notebook ed eseguirlo sul tuo sistema, ma ricordati di aggiornare username e token di Hugging Face in base al tuo account.
7. Conclusione
In questo tutorial, abbiamo effettuato il fine-tuning di Qwen3.5-0.8B con QLoRA per la classificazione di notizie e abbiamo ottenuto un risultato solido con una configurazione molto leggera. Dopo una sola epoca su un piccolo sottoinsieme di dati, l’accuratezza è passata da 0.52 a 0.865 e l’F1 pesato da 0.4589 a 0.8661.
Per un modello così piccolo, questa è onestamente la parte più sorprendente: non servono un modello enorme, un dataset gigantesco o un addestramento lungo per ottenere buoni risultati a valle.
Ciò che rende il tutto ancora più utile è la praticità della configurazione. Qwen descrive il modello da 0.8B come adatto a prototyping e fine-tuning specifico per il task, e QLoRA mantiene basso l’uso di memoria addestrando adapter sopra un modello base a 4 bit congelato. Questo significa che non è solo un workflow da notebook nel cloud: è realistico anche per test su GPU consumer e laptop più piccole.
Un altro motivo per cui trovo questo modello interessante è che la famiglia Qwen3.5 supporta sia il fine-tuning su testo che su visione. Quindi, anche se qui lo abbiamo usato per la classificazione di testo, la stessa idea può estendersi a classificazione di immagini, image-to-text e text-to-text. Questo rende un modello piccolo come questo un’opzione davvero pratica quando vuoi testare rapidamente idee, fare fine-tuning a basso costo e mantenere il deployment leggero.
Ti consiglio anche di dare un’occhiata alle nostre altre risorse, tra cui la guida a Qwen3.5 e il nostro tutorial su come eseguire Qwen3.5 in locale su una singola GPU.
FAQs
Perché usare QLoRA (quantizzazione a 4 bit) invece del fine-tuning completo per un modello da 0.8B?
Anche se 0.8B è considerato "small" secondo gli standard degli LLM, aggiornare completamente tutti gli 800 milioni di parametri richiede comunque molta VRAM e potenza di calcolo. QLoRA congela il modello base in precisione a 4 bit e allena solo un piccolo set di nuovi pesi di adapter sopra di esso. Questo riduce drasticamente l’impronta in memoria, permettendoti di addestrare comodamente su una GPU P100 gratuita di Kaggle o su un normale laptop consumer senza esaurire la memoria.
Qwen3.5 supporta una nuova modalità "thinking". Dovrei usarla per i task di classificazione?
Qwen3.5-0.8B supporta nativamente il ragionamento ibrido ma opera in modalità "non-thinking" per impostazione predefinita. Anche se puoi abilitare le sue capacità di ragionamento, compiti semplici di classificazione come AG News non richiedono logiche complesse passo dopo passo. Tenere disabilitata la thinking mode è molto più veloce e più efficiente in termini di token per un’etichettatura diretta.
In che modo la finestra di contesto enorme da 262K di Qwen3.5 influisce su questo script di fine-tuning?
Sebbene il modello base supporti nativamente fino a 262.144 token, il fine-tuning su documenti enormi esaurirà rapidamente la memoria della GPU su Kaggle. Per la classificazione di notizie, i testi sono brevi, quindi non è un problema. Se vuoi adattare questo script a task su documenti lunghi (come riassumere interi libri), dovrai abilitare il gradient checkpointing e ridurre la dimensione del batch.
In quanto data scientist certificato, sono appassionato di sfruttare tecnologie all’avanguardia per creare applicazioni di machine learning innovative. Con una solida esperienza in riconoscimento vocale, analisi e reportistica dei dati, MLOps, AI conversazionale e NLP, ho affinato le mie competenze nello sviluppo di sistemi intelligenti in grado di avere un impatto concreto. Oltre alla mia expertise tecnica, sono anche un comunicatore efficace, con il talento di rendere chiari e sintetici concetti complessi. Di conseguenza, sono diventato un blogger molto seguito in ambito data science, condividendo idee ed esperienze con una community in crescita di professionisti dei dati. Attualmente mi concentro sulla creazione e sull’editing di contenuti, lavorando con large language model per sviluppare contenuti potenti e coinvolgenti che possano aiutare aziende e singoli a valorizzare al meglio i propri dati.


