Vai al contenuto principale

Fine-tuning di GPT-OSS

Una guida passo passo al fine-tuning del modello OpenAI GPT-OSS 20B sul dataset di domande e risposte mediche, sia per l’accuratezza sia per l’adozione dello stile.
· 6 min leggi

Scopri con l'IA

Il GPT-OSS 20B è un modello linguistico a pesi aperti della serie gpt-oss di OpenAI, progettato per bassa latenza e casi d’uso specializzati. Ha 21 miliardi di parametri (3,6B attivi) ed è ottimizzato per funzionare in modo efficiente su hardware consumer, richiedendo circa 16 GB di memoria grazie alla quantizzazione MXFP4.

Pur essendo più piccolo del GPT-OSS 120B, supporta comunque funzionalità avanzate come livelli di ragionamento configurabili (basso, medio, alto), accesso completo alla chain-of-thought per il debug e la trasparenza e capacità agentiche come function calling, navigazione web ed esecuzione di codice.

In questa guida ti accompagno nell’intero processo di setup e fine-tuning di GPT-OSS 20B. Questo include:

  1. Installare le dipendenze e preparare l’ambiente
  2. Caricare il modello e il tokenizer
  3. Creare lo stile del prompt con il nuovo pacchetto Python OpenAI Harmony
  4. Caricare il dataset e applicare la formattazione Harmony
  5. Eseguire una semplice inferenza del modello prima del fine-tuning
  6. Configurare il modello per l’addestramento
  7. Eseguire il fine-tuning del modello su un dataset di Q&A mediche
  8. Eseguire inferenza dopo il fine-tuning per valutare i miglioramenti

Ricorda che questo tutorial è a scopo di ricerca e formazione e non per diagnosi o trattamenti dei pazienti.

Immagine dell’autore

1. Configurare l’ambiente

Sebbene il modello GPT-OSS abbia 21 miliardi di parametri, richiede comunque una quantità significativa di memoria GPU: almeno 80 GB di VRAM per gestirlo comodamente. Un modo rapido per iniziare è avviare un’istanza RunPod H100 o un ambiente GPU con molta memoria simile.

RunPod H100SXM machine

Una volta pronto l’ambiente, installa tutti i pacchetti Python necessari per l’elaborazione dei dati, il fine-tuning del modello e il tracciamento degli esperimenti:

%%capture
%pip install -U accelerate 
%pip install -U peft 
%pip install -U trl 
%pip install -U bitsandbytes
%pip install -U transformers
%pip install -U trackio
%pip install -U openai-harmony
%pip install -U tiktoken
%pip install -U pyctcdecode

Infine, effettua l’accesso a Hugging Face Hub usando la tua API key. Questo ti permetterà di salvare e condividere direttamente sul tuo account Hugging Face il modello e il tokenizer sottoposti a fine-tuning:

from huggingface_hub import notebook_login

notebook_login()

2. Caricare il modello e il tokenizer GPT-OSS

Ora carichiamo GPT-OSS 20B con quantizzazione MXFP4 ma dequantizziamo immediatamente i pesi in bfloat16 al caricamento, usando l’attenzione eager, cache disabilitata e assegnazione automatica dei dispositivi.

import torch
from transformers import AutoModelForCausalLM, Mxfp4Config

quantization_config = Mxfp4Config(dequantize=True)
model_kwargs = dict(
    attn_implementation="eager",
    torch_dtype=torch.bfloat16,
    quantization_config=quantization_config,
    use_cache=False,
    device_map="auto",
)

model = AutoModelForCausalLM.from_pretrained("openai/gpt-oss-20b", **model_kwargs)

Oltre al modello, ci serve anche il tokenizer dallo stesso repository. Il tokenizer converte il testo in token (ID) che il modello può elaborare e decodifica poi gli output del modello in testo leggibile.

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("openai/gpt-oss-20b")

3. Creare lo stile del prompt con OpenAI Harmony

OpenAI Harmony è un formato di risposta specializzato usato dai modelli GPT-OSS per strutturare conversazioni, output di ragionamento e chiamate a funzioni in modo coerente. 

Definisce come sono organizzati i messaggi tra ruoli (system, developer, user, assistant) e abilita il modello a generare output su più canali—ad esempio separando il ragionamento chain-of-thought, i preamboli per l’uso di strumenti e le risposte finali rivolte all’utente.

In questa sezione creeremo una funzione che prende una domanda e una risposta e le formatta in un prompt di conversazione in stile Harmony. Usa la libreria openai_harmony per codificare la conversazione in modo strutturato.

from openai_harmony import (
    load_harmony_encoding, HarmonyEncodingName,
    Conversation, Message, Role,
    SystemContent, DeveloperContent, ReasoningEffort
)

enc = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)

def render_pair_harmony(question, answer):
    convo = Conversation.from_messages([
        Message.from_role_and_content(
            Role.DEVELOPER,
            DeveloperContent.new().with_instructions(
                "You are a medical expert with advanced knowledge in clinical reasoning and diagnostics. "
                "Respond with ONLY the final diagnosis/cause in ≤5 words."
            )
        ),
        Message.from_role_and_content(Role.USER, question.strip()),
        Message.from_role_and_content(Role.ASSISTANT, answer.strip()),
    ])
    tokens = enc.render_conversation(convo)
    text = enc.decode(tokens)
    return text

4. Caricare e formattare il dataset

Ora caricheremo il dataset di Q&A mediche (FreedomIntelligence/medical-o1-verifiable-problem) e applicheremo la funzione prompt_style_harmony a ciascun esempio. Il testo trasformato viene salvato in una nuova colonna chiamata "text", offrendo un formato pulito, orientato alle istruzioni, allineato allo stile del prompt del developer.

from datasets import load_dataset

def prompt_style_harmony(examples):
    qs = examples["Open-ended Verifiable Question"]
    ans = examples["Ground-True Answer"]
    outputs = {"text": []}
    for q, a in zip(qs, ans):
        rendered = render_pair_harmony(q, a)
        outputs["text"].append(rendered)
    return outputs

dataset = load_dataset(
    "FreedomIntelligence/medical-o1-verifiable-problem",
    split="train[0:1000]"
)
dataset = dataset.map(prompt_style_harmony, batched=True)
print(dataset.column_names)

print(dataset[0]["text"])

Il dataset ora contiene una nuova colonna "text", che include la conversazione formattata in Harmony. Questa comprende:

  • Istruzioni del developer (es. rispondi in modo conciso con una diagnosi)
  • Messaggio dell’utente (la domanda medica)
  • Messaggio dell’assistente (la risposta ground-truth)
['Open-ended Verifiable Question', 'Ground-True Answer', 'text']
<|start|>developer<|message|># Instructions

You are a medical expert with advanced knowledge in clinical reasoning and diagnostics. Respond with ONLY the final diagnosis/cause in ≤5 words.<|end|><|start|>user<|message|>An 88-year-old woman with osteoarthritis is experiencing mild epigastric discomfort and has vomited material resembling coffee grounds multiple times. Considering her use of naproxen, what is the most likely cause of her gastrointestinal blood loss?<|end|><|start|>assistant<|message|>Gastric ulcer<|end|>

5. Inferenza del modello prima del fine-tuning

Prima di addestrare il modello, è importante valutarne le prestazioni di base per un confronto dopo il fine-tuning. Per farlo, definiamo una funzione di supporto, render_inference_harmony, che prepara un prompt in stile Harmony contenente le istruzioni strette del developer e la domanda dell’utente, ma lascia vuota la risposta dell’assistente per permettere al modello di completarla.

def render_inference_harmony(question):
    convo = Conversation.from_messages([
        Message.from_role_and_content(
            Role.DEVELOPER,
            DeveloperContent.new().with_instructions(
                "You are a medical expert with advanced knowledge in clinical reasoning and diagnostics. "
                "Respond with ONLY the final diagnosis/cause in ≤5 words."
            )
        ),
        Message.from_role_and_content(Role.USER, question.strip()),
    ])
    tokens = enc.render_conversation_for_completion(convo, Role.ASSISTANT)
    text = enc.decode(tokens)
    return text

Poi prendiamo il primo campione del dataset, lo formattiamo con questa funzione, lo tokenizziamo e lo passiamo al modello per generare una risposta:

question = dataset[0]["Open-ended Verifiable Question"]

text = render_inference_harmony(question)

inputs = tokenizer(
    [text + tokenizer.eos_token], return_tensors="pt"
).to("cuda")
outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=20,
    eos_token_id=tokenizer.eos_token_id,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs)
print(response[0])

In questo test, l’output del modello è visibilmente più lungo e descrittivo del richiesto, nonostante le istruzioni del developer chiedano esplicitamente una diagnosi concisa (≤5 parole). 

<|start|>developer<|message|># Instructions

You are a medical expert with advanced knowledge in clinical reasoning and diagnostics. Respond with ONLY the final diagnosis/cause in ≤5 words.<|end|><|start|>user<|message|>An 88-year-old woman with osteoarthritis is experiencing mild epigastric discomfort and has vomited material resembling coffee grounds multiple times. Considering her use of naproxen, what is the most likely cause of her gastrointestinal blood loss?<|end|><|start|>assistant<|return|>We have an old woman with NSAID use causing ulcer bleed. The question: "An 88

Invece di rispondere semplicemente “Gastric ulcer” (l’etichetta ground-truth), il modello ha prodotto una spiegazione prolissa sull’uso di FANS e il sanguinamento da ulcera.

dataset[0]["Ground-True Answer"]
'Gastric ulcer'

6. Preparare il modello per l’addestramento

Per eseguire il fine-tuning in modo efficiente usiamo LoRA (Low-Rank Adaptation), che consente di addestrare solo un piccolo sottoinsieme di parametri mantenendo congelato il modello base. Questo riduce drasticamente l’uso di memoria e il costo di training.

Iniziamo definendo una LoraConfig che specifica il rank, il fattore di scala e i layer di destinazione in cui inserire gli adapter LoRA.

from peft import LoraConfig, get_peft_model

peft_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules="all-linear",
    target_parameters=[
        "7.mlp.experts.gate_up_proj",
        "7.mlp.experts.down_proj",
        "15.mlp.experts.gate_up_proj",
        "15.mlp.experts.down_proj",
        "23.mlp.experts.gate_up_proj",
        "23.mlp.experts.down_proj",
    ],
)
peft_model = get_peft_model(model, peft_config)
peft_model.print_trainable_parameters()

Ci sono solo ~15M di parametri (≈0,07% del modello completo) addestrabili, e la dimensione dell’adapter è circa 16 MB: un enorme guadagno di efficienza rispetto al fine-tuning completo.

trainable params: 15,040,512 || all params: 20,929,797,696 || trainable%: 0.0719

Poi configuriamo il setup di training usando SFTConfig dalla libreria trl. Include iperparametri come learning rate, batch size, accumulo dei gradienti, warmup ratio e tipo di scheduler:

from trl import SFTConfig

training_args = SFTConfig(
    learning_rate=2e-4,
    gradient_checkpointing=True,
    num_train_epochs=1,
    logging_steps=10,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    max_length=2048,
    warmup_ratio=0.03,
    lr_scheduler_type="cosine_with_min_lr",
    lr_scheduler_kwargs={"min_lr_rate": 0.1},
    output_dir="gpt-oss-20b-medical-qa",
    report_to="trackio",
    push_to_hub=True,
)

7. Addestrare il modello

Con il dataset pronto, l’adapter LoRA collegato e gli argomenti di training configurati, possiamo avviare il processo di fine-tuning. Usiamo lo SFTTrainer della libreria trl, che semplifica il supervised fine-tuning gestendo modello, tokenizer, dataset e training loop in un unico punto:

from trl import SFTTrainer

trainer = SFTTrainer(
    model=peft_model,
    args=training_args,
    train_dataset=dataset,
    processing_class=tokenizer,
)
trainer.train()

Durante l’addestramento, la loss diminuisce gradualmente, segno che il modello sta imparando ad allineare le proprie risposte al dataset di Q&A mediche formattato in Harmony.

Model training loss

Una volta completato l’addestramento, salviamo localmente il modello fine-tunato e poi lo pubblichiamo sull’Hugging Face Hub. Importante: vengono caricati solo i pesi degli adapter LoRA (non l’intero modello 20B), rendendo il repository leggero e facile da condividere:

trainer.save_model(training_args.output_dir)
trainer.push_to_hub(dataset_name="kingabzpro/gpt-oss-20b-medical-qa")

Questo crea un nuovo repository di modello su Hugging Face all’indirizzo: kingabzpro/gpt-oss-20b-medical-qa.

Fonte: kingabzpro/gpt-oss-20b-medical-qa

8. Inferenza del modello dopo il fine-tuning

Dopo il fine-tuning, possiamo testare il modello per verificare se ora segue più da vicino le istruzioni del developer. Per farlo, carichiamo prima il modello base GPT-OSS 20B e poi colleghiamo gli adapter LoRA fine-tunati.

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

# Load the tokenizer
tokenizer = AutoTokenizer.from_pretrained("openai/gpt-oss-20b")

# Load the original model first
model_kwargs = dict(attn_implementation="eager", torch_dtype="auto", use_cache=True, device_map="auto")
base_model = AutoModelForCausalLM.from_pretrained("openai/gpt-oss-20b", **model_kwargs).cuda()

# Merge fine-tuned weights with the base model
peft_model_id = "gpt-oss-20b-medical-qa"
model = PeftModel.from_pretrained(base_model, peft_model_id)
model = model.merge_and_unload()

Successivamente, eseguiamo l’inferenza su un campione del dataset. La domanda è formattata in stile Harmony, tokenizzata, passata attraverso il modello e decodificata nuovamente in testo:

question = dataset[0]["Open-ended Verifiable Question"]

text = render_inference_harmony(question)

inputs = tokenizer(
    [text + tokenizer.eos_token], return_tensors="pt"
).to("cuda")
outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=20,
    eos_token_id=tokenizer.eos_token_id,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs)
print(response[0])

Il modello fine-tunato ora produce una risposta breve e accurata. È molto più vicina all’etichetta ground-truth (“Gastric ulcer”) e rispetta il vincolo delle ≤5 parole.

<|start|>developer<|message|># Instructions

You are a medical expert with advanced knowledge in clinical reasoning and diagnostics. Respond with ONLY the final diagnosis/cause in ≤5 words.<|end|><|start|>user<|message|>An 88-year-old woman with osteoarthritis is experiencing mild epigastric discomfort and has vomited material resembling coffee grounds multiple times. Considering her use of naproxen, what is the most likely cause of her gastrointestinal blood loss?<|end|><|start|>assistant<|return|><|message|>Stomach ulcer<|end|><|return|>

Possiamo testare anche un altro campione per confermare la coerenza:

question = dataset[10]["Open-ended Verifiable Question"]

text = render_inference_harmony(question)

inputs = tokenizer(
    [text + tokenizer.eos_token], return_tensors="pt"
).to("cuda")
outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=20,
    eos_token_id=tokenizer.eos_token_id,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs)
print(response[0])

Perfetto. Il modello fine-tunato ha generato risposte concise, accurate e aderenti alle istruzioni.

<|start|>developer<|message|># Instructions

You are a medical expert with advanced knowledge in clinical reasoning and diagnostics. Respond with ONLY the final diagnosis/cause in ≤5 words.<|end|><|start|>user<|message|>What substance is primarily filtered in the renal tubules with minimal secretion or re-absorption?<|end|><|start|>assistant<|return|><|message|>creatinine<|end|><|return|>

Conclusione

Sono tempi entusiasmanti per la comunità dell’AI open source. Anche se i nuovi modelli GPT-OSS potrebbero non essere ancora in cima alle classifiche, mostrano grande potenziale, soprattutto nelle rispettive classi di parametri, dove modelli più piccoli possono comunque offrire risultati notevoli.

In questo tutorial, abbiamo eseguito il fine-tuning del modello GPT-OSS 20B per il question answering medico usando adapter LoRA e lo stile di prompt Harmony. I risultati sono incoraggianti: il modello fine-tunato produce risposte concise e accurate, allineate ai compiti di ragionamento clinico. Con più epoche di training, dataset più grandi e prompt di sistema più raffinati, le prestazioni possono migliorare ulteriormente.

Se cerchi un corso pratico per familiarizzare con il fine-tuning, dai un’occhiata a Fine-Tuning With Llama 3.

Abid Ali Awan

Autore

Abid Ali Awan

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.

Argomenti

Correlato