Vai al contenuto principale

Fine-tuning di Phi-4 Reasoning: guida passo dopo passo

Una guida passo dopo passo per effettuare il fine-tuning del modello all’avanguardia Phi-4 Reasoning sul dataset di Q&A finanziarie in meno di 10 minuti.
· 9 min leggi

Scopri con l'IA

Phi-4-reasoning è un modello open-weight da 14 miliardi di parametri progettato per compiti di ragionamento complessi, in grado di rivaleggiare con modelli molto più grandi grazie a un fine-tuning accurato su dataset curati. Genera catene di ragionamento dettagliate e sfrutta il calcolo in fase di inferenza per ottenere prestazioni eccezionali. 

La sua versione potenziata, Phi-4-reasoning-plus, utilizza il reinforcement learning e 1,5 volte più token per una precisione ancora maggiore. Nonostante le dimensioni ridotte, entrambi i modelli superano controparti più grandi come DeepSeek-R1-Distill-70B e addirittura il modello da 671 miliardi di parametri DeepSeek-R1 nei benchmark.

In questo tutorial useremo il modello Phi-4-reasoning-plus ed effettueremo il fine-tuning sul dataset di ragionamento Financial Q&A. La guida include la configurazione dell’ambiente Runpod, il caricamento del modello, del tokenizer e del dataset, la preparazione dei dati per l’addestramento, la configurazione del modello, l’esecuzione di valutazioni e il salvataggio dell’adapter del modello fine-tunato.

Puoi imparare a conoscere Phi 4 e creare un tutor multimodale seguendo il tutorial Phi-4-Multimodal: guida con progetto demo.

Immagine di copertina del fine-tuning di Phi-4 Reasoning

Immagine dell’autore

1. Configurazione

In questo tutorial useremo RunPod come ambiente di calcolo. Segui questi passaggi per configurarlo:

1. Vai alla dashboard di RunPod e clicca su “Deploy On-Demand” per creare un nuovo pod.

2. Seleziona la GPU A100, sufficiente per caricare e fare fine-tuning del modello.

Configurazione di un pod in Runpod

3. Personalizza il deployment:

Modifica del Pod in Runpod

  • Aumenta lo storage a 150 GB.
  • Aggiungi il token di Hugging Face come variabile d’ambiente. Questo token è necessario per caricare modelli protetti e pubblicare modelli su Hugging Face Hub.

configurazione dello storage e delle variabili d’ambiente.

4. Una volta distribuito il pod, clicca su Connect per avviare l’istanza JupyterLab.

5. All’interno di JupyterLab, crea un nuovo notebook e installa tutti i pacchetti Python necessari. 

%%capture
%pip install -U transformers
%pip install -U datasets 
%pip install -U accelerate 
%pip install -U peft 
%pip install -U trl 
%pip install -U bitsandbytes
%pip install huggingface_hub[hf_xet]

6. Ora accedi alla CLI di Hugging Face usando il token salvato in precedenza. 

from huggingface_hub import login
import os

hf_token = os.environ.get("HF_TOKEN")
login(hf_token)

2. Caricamento del modello e del tokenizer

In questa guida caricheremo il modello completo microsoft/Phi-4-reasoning-plus da Hugging Face, insieme al relativo tokenizer.

Qui non usiamo la quantizzazione a 4 bit perché l’hardware gestisce comodamente pesi a piena precisione. Una A100 con 80 GB di VRAM lascia ampio margine, dato che il modello completo Phi-4-Reasoning occupa circa 28 GB: restano oltre 50 GB liberi per attivazioni, stati dell’ottimizzatore e altri overhead di training.

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

# Load tokenizer & model
model_dir = "microsoft/Phi-4-reasoning-plus"

tokenizer = AutoTokenizer.from_pretrained(model_dir, use_fast=True)

model = AutoModelForCausalLM.from_pretrained(
    model_dir, 
    device_map="auto",  
    torch_dtype=torch.bfloat16,
    trust_remote_code=True             
)

model.config.use_cache = False
model.config.pretraining_tp = 1

Download del modello Phi 4 reasoning

Dopo aver caricato il modello, puoi verificare quanta memoria GPU consuma eseguendo il comando seguente:

!nvidia-smi

Il modello completo usa circa 28 GB di VRAM. La GPU A100 ha 80 GB di VRAM, quindi hai ancora 55 GB di memoria libera per le attività di fine-tuning.

Utilizzo della memoria GPU

3. Caricamento e preparazione del dataset

Prima di caricare il dataset, definiremo uno stile di prompt per l’addestramento. Questo prompt include:

  • Istruzioni di sistema: indicazioni per il modello su come rispondere.
  • Domande finanziarie aperte: domande fornite dall’utente che richiedono ragionamento.
  • Risposta dell’assistente con parte di “thinking”: una catena di ragionamento passo dopo passo seguita dalla risposta.

Questo stile di prompt è pensato per incoraggiare il modello a pensare in modo critico e a produrre un processo di ragionamento insieme alla risposta finale.

train_prompt_style="""
<|im_start|>system<|im_sep|>
Below is an instruction that describes a task, paired with an input that provides further context. 
Write a response that appropriately completes the request. 
Before answering, think carefully about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response.
<|im_end|>
<|im_start|>user<|im_sep|>
{}<|im_end|>
<|im_start|>assistant<|im_sep|>
<think>
{}
</think>
{}
"""

Creeremo una funzione Python per applicare le colonne del dataset (domande, ragionamenti e risposte) al template del prompt. Questa funzione genererà una nuova colonna chiamata “text”, che include tutti i componenti del prompt.

EOS_TOKEN = tokenizer.eos_token  # Must add EOS_TOKEN

def formatting_prompts_func(examples):
    inputs = examples["Open-ended Verifiable Question"]
    complex_cots = examples["Complex_CoT"]
    outputs = examples["Response"]
    texts = []
    for input, cot, output in zip(inputs,complex_cots,outputs):
        text = train_prompt_style.format(input,cot,output) + EOS_TOKEN
        texts.append(text)
    return {
        "text": texts,
    }

Ora scaricheremo il dataset (TheFinAI/Fino1_Reasoning_Path_FinQA), caricheremo 1000 campioni, applicheremo la funzione di formattazione e creeremo la nuova colonna “text”.

from datasets import load_dataset

dataset = load_dataset(
    "TheFinAI/Fino1_Reasoning_Path_FinQA", split="train[0:1000]", trust_remote_code=True
)
dataset = dataset.map(
    formatting_prompts_func,
    batched=True,
)
dataset["text"][20]

La colonna "text" del dataset include le istruzioni di sistema, la domanda dell’utente, una catena di ragionamento passo dopo passo e la risposta finale. Questo formato strutturato assicura che il modello comprenda il compito e generi risposte di alta qualità.

Panoramica del dataset processato.

La nuova funzione SFT trainer non accetta direttamente un tokenizer. Dobbiamo quindi convertirlo in un data collator usando DataCollatorForLanguageModeling di Transformers.

from transformers import DataCollatorForLanguageModeling

data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False
)

4. Inferenza del modello prima del fine-tuning

Prima del fine-tuning eseguiremo l’inferenza su una domanda del dataset per confrontare le prestazioni del modello dopo il fine-tuning. Lo stile del prompt di inferenza è simile a quello di training, ma esclude la parte di ragionamento e la risposta.

inference_prompt_style = """
<|im_start|>system<|im_sep|>
Below is an instruction that describes a task, paired with an input that provides further context. 
Write a response that appropriately completes the request. 
Before answering, think carefully about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response.
<|im_end|>
<|im_start|>user<|im_sep|>
{}<|im_end|>
<|im_start|>assistant<|im_sep|>
<think>
{}
"""

Selezioneremo la 21ª domanda del dataset, la formatteremo e la tokenizzeremo per l’inferenza:

question = dataset[20]['Open-ended Verifiable Question']
inputs = tokenizer(
    [inference_prompt_style.format(question, "") + tokenizer.eos_token],
    return_tensors="pt"
).to("cuda")

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=1200,
    eos_token_id=tokenizer.eos_token_id,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs)
print(response[0].split("<|im_start|>assistant<|im_sep|>")[1])

Il modello genera una lunga parte di ragionamento ma non fornisce una risposta chiara. Aumentare max_new_tokens a 2000 potrebbe produrre una risposta, ma è un approccio inefficiente e inaffidabile. Il ragionamento è frammentario e la conclusione imprecisa.

Risposta generata prima del fine-tuning

5. Configurazione del modello

In questa sezione implementeremo LoRA (Low-Rank Adaptation), una tecnica di fine-tuning efficiente in termini di parametri, progettata per adattare i Large Language Model (LLM) a nuovi compiti. LoRA funziona congelando la maggior parte dei parametri del modello e introducendo un piccolo set di parametri addestrabili sotto forma di matrici a rango ridotto. Queste matrici vengono aggiunte al modello in una decomposizione a basso rango, consentendo l’adattamento senza modificare o memorizzare l’intero set di pesi.

Usando LoRA possiamo ridurre significativamente il consumo di VRAM e i tempi di training, mantenendo un’accuratezza paragonabile al fine-tuning completo.

from peft import LoraConfig, get_peft_model

# LoRA config
peft_config = LoraConfig(
    lora_alpha=16,                           # Scaling factor for LoRA
    lora_dropout=0.05,                       # Add slight dropout for regularization
    r=64,                                    # Rank of the LoRA update matrices
    bias="none",                             # No bias reparameterization
    task_type="CAUSAL_LM",                   # Task type: Causal Language Modeling
    target_modules=[
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj",
        "gate_proj",
        "up_proj",
        "down_proj",
    ],  # Target modules for LoRA
)

model = get_peft_model(model, peft_config)

Ora imposteremo l’SFTTrainer, che semplifica il processo di fine-tuning combinando in un unico flusso dataset, modello, data collator, argomenti di training e configurazione LoRA. Questo approccio rende il fine-tuning efficiente, intuitivo e altamente personalizzabile.

from trl import SFTTrainer
from transformers import TrainingArguments


# Training Arguments
training_arguments = TrainingArguments(
    output_dir="output",
    per_device_train_batch_size=1,
    per_device_eval_batch_size=1,
    gradient_accumulation_steps=2,
    optim="paged_adamw_32bit",
    num_train_epochs=1,
    logging_steps=0.2,
    warmup_steps=10,
    logging_strategy="steps",
    learning_rate=2e-4,
    fp16=False,
    bf16=False,
    group_by_length=True,
    report_to="none"
)

# Initialize the Trainer
trainer = SFTTrainer(
    model=model,
    args=training_arguments,
    train_dataset=dataset,
    peft_config=peft_config,
    data_collator=data_collator,
)

6. Addestramento del modello

Prima di iniziare l’addestramento, libereremo RAM e VRAM della GPU per evitare errori di memoria insufficiente (OOM). Questo garantisce un’esecuzione fluida durante il training.

import gc, torch
gc.collect()
torch.cuda.empty_cache()
model.config.use_cache = False
trainer.train()

Durante il training, il processo utilizza circa il 54% della memoria e raggiunge il 99% di utilizzo della GPU, garantendo un uso efficiente delle risorse.

Panoramica dell’utilizzo di memoria GPU del pod

Puoi osservare che la training loss diminuisce a ogni step, a indicare che il modello sta apprendendo e si sta fine-tunando efficacemente. 

Training loss

7. Inferenza del modello dopo il fine-tuning

Ora testeremo il modello fine-tunato confrontando le sue risposte con il dataset. Usando la stessa domanda della sezione precedente, generiamo una risposta:

question = dataset[20]['Open-ended Verifiable Question']
inputs = tokenizer(
    [inference_prompt_style.format(question, "") + tokenizer.eos_token],
    return_tensors="pt"
).to("cuda")

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=1200,
    eos_token_id=tokenizer.eos_token_id,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs)
print(response[0].split("<|im_start|>assistant<|im_sep|>")[1])

La parte di ragionamento è dettagliata e accurata e la risposta corrisponde da vicino al dataset. 

<think><|im_end|>First, I need to figure out what the employee contributions were for the years 2002 and 2003. Okay, looking at the numbers, I see that the company contributed $979,000 in 2002 and $825,000 in 2003. So, those are my two key figures. 

Next, I'll calculate the change in the contribution amount. To find this, I subtract the 2003 contribution from the 2002 contribution. Let me do that: $979,000 minus $825,000 equals $154,000. So, there's a decrease of $154,000 from 2002 to 2003.

Now, I need to understand what this decrease means in terms of percentage. Percentage change is usually calculated by taking the change, dividing it by the original number, and then multiplying by 100. So, I'll take that $154,000 change and divide it by the 2002 amount, $979,000. 

Let me do that math: $154,000 divided by $979,000... Hmm, this gives me a value of approximately 0.1573. 

Finally, I multiply that by 100 to convert it to a percentage. That gives me about 15.73%. 

Oh, wait, let me make sure everything makes sense. I calculated the change in contributions correctly by subtracting the 2003 amount from the 2002 amount. Then I used the original amount from 2002 as the base for the percentage calculation. 

Yes, that all seems right. So, the employee contribution decreased by approximately 15.73% from 2002 to 2003.</think>The percentage decrease in the employee contribution from 2002 to 2003 was approximately 15.73%.<|im_end|>

Testiamo un altro esempio:

question = dataset[200]['Open-ended Verifiable Question']
inputs = tokenizer(
    [inference_prompt_style.format(question, "") + tokenizer.eos_token],
    return_tensors="pt"
).to("cuda")

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=1200,
    eos_token_id=tokenizer.eos_token_id,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs)
print(response[0].split("<|im_start|>assistant<|im_sep|>")[1])

Il risultato è notevole: il modello genera con successo ragionamenti accurati e risposte nello stile del dataset:

<think><|im_end|>Okay, let's figure out how much of the long-term debt should be considered current liabilities. So, the total long-term debt at the end of 2011 was $1859 million. Now, I remember that there's some information about the timing of cash payments related to this debt. In particular, there's a chunk of $29 million that is due in 2012. Hmm, that's the part that's likely to become a current liability. 

Let’s check the table again to make sure. It says total long-term debt is $1859 million, and the breakdown of estimated cash payments includes $29 million for 2012. Yep, that seems to align. So, this $29 million should be classified as a current liability because it's due within the next year. 

But wait, let me double-check. The table is pretty straightforward: long-term debt is $1859 million and the breakdown clearly shows $29 million due in 2012. So, that $29 million is the part that should be on the current liabilities section of the balance sheet. 

Alright, so I'm confident that the portion of the long-term debt that's included in current liabilities as of December 31, 2011, is $29 million. That's it!</think>The portion of the long-term debt included in the current liabilities on the balance sheet as of December 31, 2011, is $29 million. This $29 million represents the amount of debt due within the next year, making it a current liability.<|im_end|>

8. Salvataggio del modello

Per rendere il modello fine-tunato disponibile per usi futuri e per il download da parte di altri, salveremo l’adapter del modello e il tokenizer e li pubblicheremo su Hugging Face Hub:

new_model_name = "Phi-4-Reasoning-Plus-FinQA-COT"
model.push_to_hub(new_model_name)
tokenizer.push_to_hub(new_model_name)

Il modello è ora disponibile pubblicamente su: kingabzpro/Phi-4-Reasoning-Plus-FinQA-COT. 

Adapter del modello fine-tunato pubblicato su Hugging Face Hub

Fonte: kingabzpro/Phi-4-Reasoning-Plus-FinQA-COT · Hugging Face

Se riscontri problemi nell’esecuzione dello script di fine-tuning, consulta il notebook di accompagnamento: fine-tuning-phi-4-reasoning.ipynb.

Considerazioni finali

Il modello Phi-4 Reasoning rappresenta un passo avanti significativo per la community open-source. Invece di fare affidamento su modelli enormi da 685B parametri, Phi-4 Reasoning ottiene risultati migliori con un’architettura più piccola ed efficiente. Dimostra che concentrarsi su dataset di alta qualità e sul reinforcement learning può portare a prestazioni ancora superiori in modelli più compatti.

Uno degli aspetti migliori del fine-tuning di Phi-4 Reasoning è l’accessibilità in termini di costi ed efficienza. Effettuare il fine-tuning di questo modello su 20.000 campioni o più ti costerà meno di 10 $ e richiederà solo poche ore, grazie alle dimensioni ridotte e all’architettura ottimizzata.

Modelli più piccoli come Phi-4 Reasoning non solo riducono i costi di fine-tuning, ma fanno risparmiare anche sulla spesa di infrastruttura cloud per l’inferenza del modello. È quindi un’ottima scelta per progetti che richiedono alte prestazioni senza l’overhead dei modelli giganti.

Per maggiori informazioni, consulta le nostre guide al fine-tuning di vari Large Language Model:

  1. Fine-tuning di Llama 4: guida con progetto demo
  2. Fine-tuning di Qwen3: guida passo dopo passo
  3. Fine-tuning di Gemma 3: guida passo dopo passo con dataset di Q&A finanziarie 
  4. Fine-tuning di DeepSeek R1 (Reasoning Model)
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