Ga naar hoofdinhoud

Phi-4 Reasoning finetunen: een stapsgewijze gids

Een stapsgewijze gids om het state-of-the-art Phi-4 Reasoning-model in minder dan 10 minuten te finetunen op de finance Q&A-dataset.
· 9 min lezen

Verken met AI

Phi-4-reasoning is een open-weight model met 14 miljard parameters, ontworpen voor complexe redeneertaken. Het kan zich meten met veel grotere modellen dankzij zorgvuldig finetunen op gecureerde datasets. Het genereert gedetailleerde redeneerketens en benut rekencapaciteit tijdens inferentie voor uitzonderlijke prestaties. 

De verbeterde versie, Phi-4-reasoning-plus, gebruikt reinforcement learning en 1,5x meer tokens voor nog grotere nauwkeurigheid. Ondanks hun kleinere omvang presteren beide modellen beter dan grotere tegenhangers zoals DeepSeek-R1-Distill-70B en overtreffen ze zelfs het model met 671 miljard parameters, DeepSeek-R1, op benchmarks.

In deze tutorial gebruiken we het Phi-4-reasoning-plus-model en finetunen we het op de Financial Q&A reasoning-dataset. Deze gids omvat het inrichten van de Runpod-omgeving, het laden van het model, de tokenizer en dataset, het voorbereiden van de data voor training, het configureren van het model, het uitvoeren van evaluaties en het opslaan van de gefinetunede modeladapter.

Leer meer over Phi 4 en bouw een multimodale taaldocent met de tutorial Phi-4-Multimodal: A Guide With Demo Project.

Fine-tuning Phi-4 Reasoning feature image

Afbeelding door auteur

1. Installatie 

In deze tutorial gebruiken we RunPod als rekenomgeving. Volg deze stappen om je omgeving in te richten:

1. Ga naar het RunPod-dashboard en klik op “Deploy On-Demand” om een nieuwe pod te maken.

2. Selecteer de A100-GPU; die is voldoende om het model te laden en te finetunen.

Een pod instellen in Runpod

3. Pas je deployment aan:

De pod bewerken in Runpod

  • Verhoog de opslag naar 150 GB.
  • Voeg de Hugging Face-token toe als omgevingsvariabele. Deze token is vereist om afgeschermde modellen te laden en modellen naar de Hugging Face Hub te pushen.

opslag en omgevingsvariabelen instellen.

4. Zodra de pod is gedeployed, klik je op Connect om de JupyterLab-instantie te starten.

5. Maak in JupyterLab een nieuw notebook en installeer alle benodigde Python-pakketten. 

%%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. Log vervolgens in op de Hugging Face CLI met de token die je eerder hebt opgeslagen. 

from huggingface_hub import login
import os

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

2. Het model en de tokenizer laden

In deze gids laden we het volledige microsoft/Phi-4-reasoning-plus-model vanaf Hugging Face, samen met de tokenizer.

We gebruiken hier geen 4-bit kwantisatie, omdat de hardware full-precision gewichten prima aankan. Een A100 met 80 GB VRAM heeft voldoende marge, aangezien het volledige Phi-4-Reasoning-model slechts ~28 GB inneemt. Er blijft dus ruim 50 GB over voor activaties, optimizer-states en andere trainingsoverhead.

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

Het Phi 4 reasoning-model downloaden

Na het laden kun je met het volgende commando controleren hoeveel GPU-geheugen het model gebruikt:

!nvidia-smi

Het volledige model gebruikt ongeveer 28 GB VRAM. De A100-GPU heeft 80 GB VRAM, dus je houdt nog zo’n 55 GB vrij voor finetuning-taken.

GPU-geheugengebruik

3. De dataset laden en verwerken

Voordat we de dataset laden, definiëren we een trainingsprompt-stijl. Deze prompt bevat:

  • Systeembeschrijving: Richtlijnen voor hoe het model moet antwoorden.
  • Open financiële vragen: Door de gebruiker aangeleverde vragen die redeneren vereisen.
  • Antwoord van de assistant met denkstappen: Een stapsgewijze redeneerketen gevolgd door het antwoord.

Deze prompt is bedoeld om het model aan te moedigen kritisch te denken en naast het uiteindelijke antwoord ook het redeneerproces te produceren.

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>
{}
"""

We maken een Python-functie die de kolommen van de dataset (vragen, redeneringen en antwoorden) toepast op de prompttemplate. Deze functie maakt een nieuwe kolom “text” met alle promptonderdelen.

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,
    }

We downloaden nu de dataset (TheFinAI/Fino1_Reasoning_Path_FinQA), laden 1000 samples, passen de opmaakfunctie toe en maken de nieuwe kolom “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]

De kolom "text" bevat systeembeschrijvingen, de vraag van de gebruiker, een stapsgewijze redeneerketen en het uiteindelijke antwoord. Deze structuur zorgt ervoor dat het model de taak begrijpt en antwoorden van hoge kwaliteit genereert.

Overzicht van de verwerkte dataset.

De nieuwe STF-trainerfunctie accepteert niet rechtstreeks een tokenizer. Daarom moeten we de tokenizer omzetten naar een data-collator met DataCollatorForLanguageModeling uit Transformers.

from transformers import DataCollatorForLanguageModeling

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

4. Inferentie met het model vóór finetunen

Voordat we finetunen, voeren we inferentie uit op een vraag uit de dataset om de prestaties na het finetunen te kunnen vergelijken. De inferentieprompt lijkt op de trainingsprompt, maar sluit de redeneergedeelten en het antwoord uit.

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>
{}
"""

We kiezen de 21e vraag uit de dataset, formatteren deze en tokenizen voor inferentie:

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])

Het model genereert een lang redeneergedeelte maar geeft geen duidelijk antwoord. max_new_tokens verhogen naar 2000 kan een antwoord opleveren, maar dat is inefficiënt en onbetrouwbaar. De redenering is versnipperd en de conclusie onnauwkeurig.

Gegenereerde reactie vóór finetunen

5. Het model instellen

In dit onderdeel implementeren we LoRA (Low-Rank Adaptation), een parameter-efficiënte finetuningtechniek om LLM’s aan nieuwe taken aan te passen. LoRA bevriest het merendeel van de modelparameters en voegt een kleine set trainbare parameters toe in de vorm van low-rank-matrices. Deze worden als low-rank-decompositie toegevoegd, zodat het model kan aanpassen zonder de volledige modelgewichten te wijzigen of op te slaan.

Met LoRA verlagen we VRAM-gebruik en trainingstijd aanzienlijk, terwijl we nauwkeurigheid behouden die vergelijkbaar is met volledige finetuning.

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)

We richten nu de SFTTrainer in, die het finetunen vereenvoudigt door dataset, model, data-collator, trainingsargumenten en LoRA-config te bundelen in één gestroomlijnde workflow. Dit maakt finetunen efficiënt, gebruiksvriendelijk en goed aanpasbaar.

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. Modeltraining

Voordat we starten met trainen, maken we RAM en GPU-VRAM vrij om out-of-memory (OOM)-fouten te voorkomen. Dit zorgt voor een soepele uitvoering tijdens training.

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

Tijdens het trainen wordt ongeveer 54% van het geheugen gebruikt en is de GPU-belasting 99%, wat de efficiëntie ten goede komt.

Overzicht van GPU-geheugengebruik van de pod

Je ziet dat de trainingsloss bij elke stap daalt, wat aangeeft dat het model effectief leert en finetunet. 

Training loss

7. Inferentie na finetunen

We testen nu het gefinetunede model door de reacties te vergelijken met de dataset. Met dezelfde vraag als in het vorige onderdeel genereren we een antwoord:

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])

Het redeneergedeelte is gedetailleerd en nauwkeurig, en het antwoord komt sterk overeen met de 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|>

Laten we nog een voorbeeld testen:

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])

Het resultaat is overtuigend: het model genereert correcte redeneringen en antwoorden in de stijl van de 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. Het model opslaan

Om het gefinetunede model later (en door anderen) te kunnen gebruiken, slaan we de modeladapter en tokenizer op en pushen we ze naar de 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)

Het model is nu openbaar beschikbaar op: kingabzpro/Phi-4-Reasoning-Plus-FinQA-COT. 

Gefinetunede modeladapter gepusht naar de Hugging Face-hub

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

Krijg je problemen met het finetunen, raadpleeg dan het bijbehorende notebook: fine-tuning-phi-4-reasoning.ipynb.

Tot slot

Het Phi-4 Reasoning-model is een belangrijke stap vooruit voor de open-source gemeenschap. In plaats van te vertrouwen op enorme modellen met 685B parameters, behaalt Phi-4 Reasoning betere resultaten met een kleiner, efficiënter ontwerp. Dit laat zien dat focus op hoogwaardige datasets en reinforcement learning tot betere prestaties kan leiden in kleinere modellen.

Een van de grootste voordelen van het finetunen van Phi-4 Reasoning is de betaalbaarheid en efficiëntie. Het finetunen van dit model op 20K samples of meer kost je minder dan $10 en duurt slechts een paar uur, dankzij het kleinere formaat en de geoptimaliseerde architectuur.

Kleinere modellen zoals Phi-4 Reasoning verlagen niet alleen de finetune-kosten, maar besparen ook op cloudkosten voor inferentie. Daarmee is het een uitstekende keuze voor projecten die hoge prestaties nodig hebben zonder de overhead van gigantische modellen.

Meer weten? Bekijk onze gidsen voor het finetunen van verschillende large language models:

  1. Fine-Tuning Llama 4: A Guide With Demo Project
  2. Fine-Tuning Qwen3: A Step-by-Step Guide
  3. Fine-Tune Gemma 3: A Step-by-Step Guide With Financial Q&A Dataset 
  4. Fine-Tuning DeepSeek R1 (Reasoning Model)
Abid Ali Awan

Auteur

Abid Ali Awan

Als gecertificeerd data scientist haal ik met passie het maximale uit de nieuwste technologie om innovatieve machinelearning-toepassingen te bouwen. Met een sterke achtergrond in spraakherkenning, data-analyse en -rapportage, MLOps, conversationele AI en NLP heb ik mijn vaardigheden aangescherpt in het ontwikkelen van intelligente systemen die echt impact maken. Naast mijn technische expertise ben ik ook een sterke communicator met een talent om complexe concepten terug te brengen tot heldere, beknopte taal. Daardoor ben ik uitgegroeid tot een veelgelezen blogger over data science, waar ik mijn inzichten en ervaringen deel met een groeiende community van data-professionals. Op dit moment richt ik me op contentcreatie en redactie, waarbij ik met large language models werk aan krachtige en aansprekende content die zowel bedrijven als individuen helpt het beste uit hun data te halen.

Onderwerpen

Gerelateerd