Ga naar hoofdinhoud

Gradient Checkpointing: efficiënt fine-tunen met Unsloth en NVIDIA

Leer hoe Unsloth's door NVIDIA geoptimaliseerde gradient checkpointing en QLoRA het VRAM-gebruik verlagen bij het fine-tunen van Qwen3.5 4B voor medische document-OCR.
Bijgewerkt 14 aug 2026  · 12 min lezen

Verkennen met AI

ChatGPTClaudePerplexity

De samenwerking van Unsloth met NVIDIA richt zich op sneller fine-tunen door verborgen trainingsoverhead te verminderen. In plaats van alleen te vertrouwen op grotere GPU’s of kleinere modellen, pakken de verbeteringen knelpunten aan in het trainingsproces zelf, zoals herhaalde constructie van metadata, vertragingen bij het opnieuw laden van activaties en inefficiënte tokenrouting. Voor gebruikers betekent dit snellere training en een soepelere fine-tuningworkflow op ondersteunde NVIDIA GPU’s.

In deze gids bekijken we deze nieuwe prestatieverbeteringen van Unsloth en passen we de geoptimaliseerde fine-tuningworkflow van Unsloth toe op een praktische vision-language-taak. We fine-tunen Qwen3.5 Vision 4B voor medische OCR, waarbij het model leert om gestructureerde tekst uit afbeeldingen van medische documenten te extraheren met behulp van een kleine subset met een medische uitstraling uit een OCR-dataset.

We gebruiken:

  • Qwen3.5 4B (Vision) als basismodel
  • 4-bit QLoRA om VRAM-gebruik te verminderen
  • LoRA-adapters voor efficiënt fine-tunen
  • Unsloth gradient checkpointing om geheugen te besparen tijdens training
  • Een subset van 300 samples uit een medische OCR-dataset
  • Beeldvoorverwerking met vaste afmetingen voor soepelere vision-training
  • Evaluatie vóór en na om de output van het basis- en het fijn-afgestelde model te vergelijken

De door NVIDIA geoptimaliseerde fine-tuningworkflow van Unsloth gebruiken

Voordat we gaan fine-tunen, is het handig te begrijpen wat de samenwerking van Unsloth met NVIDIA verbetert en hoe dat aansluit op deze gids.

Unsloth meldt dat de samenwerking met NVIDIA LLM-training ongeveer 25% sneller maakt, zonder verlies aan nauwkeurigheid, bovenop de bestaande 2–5x fine-tuningversnellingen. Deze winst komt door verborgen overhead rond het hoofdtrainingsproces te verminderen in plaats van het leerdoel van het model te veranderen. Met andere woorden: het doel is fine-tunen sneller en efficiënter te maken, terwijl de nauwkeurigheid gelijk blijft.

Source: How to Make LLM Training Faster with Unsloth and NVIDIA

Bron: How to Make LLM Training Faster with Unsloth and NVIDIA 

Verbeterde trainingprestaties

De samenwerking rapporteert meerdere prestatieverbeteringen, waaronder:

  • 14,3% sneller per batch op een Qwen3-14B QLoRA SFT-benchmark via caching van packed-sequence-metadata
  • 8,4% sneller op 8B-modellen, 6,7% op 14B-modellen en 4,6% op 32B-modellen dankzij double-buffered asynchrone gradient checkpointing
  • Rond 10–15% versnelling voor GPT-OSS MoE-training, met 23% snellere forward en 13% snellere backward in het gerichte routingpad

Een deel van de grootste prestatiewinsten uit de samenwerking tussen Unsloth en NVIDIA geldt voor packed text-only training en Mixture-of-Experts-modellen. Die gebruiken we hier niet, omdat onze workflow focust op Qwen3.5 Vision OCR-fine-tuning. 

In deze gids gebruiken we een NVIDIA RTX 3090 GPU, dus de workflow is ingericht op NVIDIA GPU-versnelling en de geoptimaliseerde fine-tuningroute van Unsloth. We benchmarken Unsloth niet tegen een andere trainer, dus deze gids is geen onafhankelijk bewijs van de gerapporteerde versnellingen. In plaats daarvan passen we de geoptimaliseerde fine-tuningworkflow van Unsloth toe op een echte vision-language-taak.

Gradient checkpointing

Voor deze workflow is Unsloth’s gradient checkpointing de meest relevante optimalisatie. Het helpt het geheugenverbruik tijdens training te verlagen door te voorkomen dat elke activatie in GPU-geheugen moet worden opgeslagen. Dit is vooral nuttig bij vision-language fine-tunen, waar het model zowel afbeeldingsinput als tekstoutput moet verwerken.

1. Unsloth instellen voor sneller fine-tunen

Om deze gids te volgen, heb je toegang tot een NVIDIA GPU nodig. Je kunt er een huren via platforms zoals RunPod, Vast.ai of een andere cloud-GPU-provider. Ik probeerde eerst RunPod omdat het meestal snel en betrouwbaar is, maar op dat moment waren de beschikbare RTX 3090-opties beperkt. Daarom heb ik voor deze workflow een Vast.ai RTX 3090 GPU-machine gebruikt. 

Voor een vergelijking van de verschillende platforms, bekijk onze gids over de beste GPU-cloudproviders.

Vast.ai RTX 3090 instance

Bron:  Vast.ai | Console 

Na het starten van de instance heb ik Jupyter Notebook geopend en een nieuw notebook aangemaakt. Op Vast.ai koos ik de beschikbare kernel van de hoofdomgeving om de vereiste Python-pakketten in de notebookomgeving te installeren zonder systeema fhankelijkheden te beïnvloeden. 

Benodigde pakketten installeren

Installeer eerst de vereiste pakketten voor Unsloth, PyTorch, training van vision-modellen, datasetloading en integratie met Hugging Face: 

!pip install --upgrade \
    "torch>=2.8.0" "triton>=3.4.0" \
    numpy pillow torchvision bitsandbytes \
    unsloth "unsloth_zoo>=2026.4.6" \
    "datasets>=4.0.0" huggingface_hub hf_transfer pandas \
    transformers==5.2.0 torchcodec timm

Deze pakketten zijn gebaseerd op de officiële Unsloth-notebooksetup en bevatten de belangrijkste libraries om Qwen3.5 Vision te laden, beeld-tekstdata voor te bereiden en het model te fine-tunen met Unsloth. 

Het CUDA-apparaat configureren

Vervolgens configureren we het CUDA-apparaat en controleren we of de juiste NVIDIA GPU beschikbaar is. Omdat deze gids een RTX 3090 gebruikt, controleert de code of CUDA is ingeschakeld, bevestigt de geselecteerde GPU, print de CUDA- en PyTorch-versies en verifieert dat de machine genoeg VRAM heeft voor dit experiment. 

import os
import platform

CUDA_DEVICE_INDEX = 0
TARGET_GPU_NAME = "3090"

# Must be set before CUDA / Unsloth are initialized. Restart the kernel if you change these.
os.environ["CUDA_VISIBLE_DEVICES"] = str(CUDA_DEVICE_INDEX)

# RunPod + Qwen3.5 Vision OCR can hit Torch Dynamo fullgraph recompile limits.
# This disables Unsloth's torch.compile path while keeping Unsloth model loading,
# LoRA, gradient checkpointing, collator, and 8-bit optimizer benefits.
os.environ["UNSLOTH_COMPILE_DISABLE"] = "1"
os.environ["TORCH_COMPILE_DISABLE"] = "1"

import torch

DEVICE = torch.device("cuda:0")

print("Python:", platform.python_version())
print("PyTorch:", torch.__version__)
print("CUDA available:", torch.cuda.is_available())

if not torch.cuda.is_available():
    raise RuntimeError("CUDA is not available. Select a GPU instance before continuing.")

torch.cuda.set_device(0)
props = torch.cuda.get_device_properties(0)
gpu_name = torch.cuda.get_device_name(0)
total_gpu_memory_gb = props.total_memory / 1024**3

print("Selected device:", DEVICE)
print("GPU:", gpu_name)
print("CUDA version:", torch.version.cuda)
print("BF16 supported:", torch.cuda.is_bf16_supported())
print("Total GPU memory:", round(total_gpu_memory_gb, 2), "GB")

if TARGET_GPU_NAME not in gpu_name:
    raise RuntimeError(f"Expected an RTX {TARGET_GPU_NAME}, but CUDA device 0 is: {gpu_name}")

if total_gpu_memory_gb < 20:
    raise RuntimeError(f"Expected a 24 GB class 3090, but only found {total_gpu_memory_gb:.2f} GB VRAM.")

In mijn setup gaf de omgeving de volgende GPU-configuratie terug:

Python: 3.12.13
PyTorch: 2.12.0+cu130
CUDA available: True
Selected device: cuda:0
GPU: NVIDIA GeForce RTX 3090
CUDA version: 13.0
BF16 supported: True
Total GPU memory: 23.56 GB

Dit bevestigt dat het notebook draait op een NVIDIA GeForce RTX 3090 met voldoende VRAM voor het fine-tuningexperiment.

Trainingsinstellingen en prompts definiëren

Na het verifiëren van de GPU definiëren we het model, de dataset, trainingsinstellingen, outputmappen, beeldgrootte en OCR-prompts.

MODEL_NAME = "unsloth/Qwen3.5-4B"
DATASET_NAME = "naazimsnh02/medocr-vision-dataset"

SAMPLE_COUNT = 300
EVAL_INDEX = 0
MAX_LENGTH = 4096
MAX_STEPS = 30

PER_DEVICE_BATCH_SIZE = 4
GRADIENT_ACCUMULATION_STEPS = 2
LEARNING_RATE = 2e-4
SEED = 3407

OUTPUT_DIR = "outputs/qwen35_vision_medical_ocr"
ADAPTER_DIR = "qwen35-vision-medical-ocr-lora"

# Medical document images vary heavily in size. Fixed-size canvases avoid
# repeated Torch Dynamo recompiles during vision training.
# 768x1024 is a practical portrait-page compromise for a 24 GB 3090 smoke test.
FIXED_IMAGE_SIZE = (768, 1024)

# Official Unsloth Qwen3.5 Vision notebook uses False here for 16-bit LoRA.
# Set True only if you hit VRAM limits.
LOAD_IN_4BIT = True

SYSTEM_PROMPT = "You are a medical OCR transcription engine. Return only the exact text visible in the medical document image."
INSTRUCTION = "Extract all readable text from this medical document exactly. Preserve structure when possible. Return only the OCR text, with no explanation, no diagnosis, no medical advice, and no reasoning."

Hier gebruiken we Qwen3.5 Vision 4B van Unsloth en de medische OCR-visiondataset. Voor deze gids kiezen we 300 samples en trainen we 30 stappen, zodat de run licht blijft maar toch laat zien hoe het model zich aanpast aan het doel-OCR-formaat.

De vaste beeldgrootte van 768×1024 helpt de beeldinputs tijdens training consistent te houden. Medische documenten kunnen sterk variëren in resolutie en beeldverhouding, dus het schalen naar een vast canvas maakt de workflow soepeler en vermindert shape-gerelateerde issues tijdens vision-language fine-tunen.

2. Het model laden

Nu de omgeving klaar is, kunnen we het Qwen3.5 Vision 4B-model laden met Unsloth’s FastVisionModel

import unsloth
from unsloth import FastVisionModel
torch.cuda.set_device(0)

model, tokenizer = FastVisionModel.from_pretrained(
    MODEL_NAME,
    load_in_4bit=LOAD_IN_4BIT,
    use_gradient_checkpointing="unsloth",
)

print("Loaded:", MODEL_NAME)
print("4-bit:", LOAD_IN_4BIT)
print("Model device:", next(model.parameters()).device)

Na het laden bevestigt de output dat het juiste model is geladen, 4-bit-modus is ingeschakeld en het model op de GPU staat: 

Loaded: unsloth/Qwen3.5-4B
4-bit: True
Model device: cuda:0

Hier laadt FastVisionModel.from_pretrained() het vision-language-model en past Unsloth’s optimalisaties toe voor sneller en geheugen-efficiënter fine-tunen. We schakelen ook load_in_4bit in, wat VRAM-gebruik vermindert door het model in 4-bit precisie te laden. Dat is handig bij een 24 GB GPU zoals de RTX 3090.

We schakelen ook Unsloth gradient checkpointing in met use_gradient_checkpointing="unsloth"

Dit helpt het geheugenverbruik tijdens training te verlagen, wat vooral belangrijk is voor vision-language-modellen omdat ze zowel beeld- als tekstinput verwerken. 

3. LoRA-adapters toevoegen

Vervolgens voegen we LoRA-adapters toe aan het model. LoRA laat ons een kleiner aantal trainbare parameters fine-tunen in plaats van het volledige model te updaten. Dat maakt trainen sneller, geheugenefficiënter en eenvoudiger op één GPU te draaien. 

 
model = FastVisionModel.get_peft_model(
    model,
    finetune_vision_layers=True,
    finetune_language_layers=True,
    finetune_attention_modules=True,
    finetune_mlp_modules=True,
    r=16,
    lora_alpha=16,
    lora_dropout=0,
    bias="none",
    random_state=SEED,
    use_rslora=False,
    loftq_config=None,
)

In deze gids worden de adapters toegevoegd aan zowel het vision- als het taalgedeelte van het model. Dit helpt het model om medische documentafbeeldingen te lezen en de verwachte gestructureerde OCR-tekst te produceren. Na deze stap is het model klaar om te trainen op de medische OCR-dataset. 

4. De medische OCR-dataset laden

Nu laden we de medische OCR-dataset van Hugging Face en bereiden we een kleine subset voor op fine-tunen.

from datasets import load_dataset
from PIL import Image

raw_dataset = load_dataset(DATASET_NAME, split="train")

MEDICAL_KEYWORDS = [
    "doctor", "dr.", "clinic", "hospital", "patient", "medication",
    "medications", "prescription", "signature", "department", "report",
    "diagnosis", "lab", "laboratory", "blood", "hemoglobin", "mg", "dose",
    "<s_ocr>",
]

De dataset bevat documentafbeeldingen en hun bijbehorende OCR-tekst. Omdat we voor deze gids alleen OCR-voorbeelden in medische stijl willen, filteren we de dataset met een eenvoudige aanpak op basis van trefwoorden. De code zoekt naar termen die vaak voorkomen in medische documenten, zoals doctor, clinic, patient, medication, prescription, diagnosis en doseringsgerelateerde woorden. 

def looks_medical(sample):
    text = str(sample.get("text", "")).lower()
    return any(keyword in text for keyword in MEDICAL_KEYWORDS)

medical_indices = []
for idx, sample in enumerate(raw_dataset):
    if looks_medical(sample):
        medical_indices.append(idx)
        if len(medical_indices) >= SAMPLE_COUNT:
            break

if not medical_indices:
    raise RuntimeError("No medical-looking OCR samples found. Broaden MEDICAL_KEYWORDS or inspect the dataset text field.")

print(f"Selected {len(medical_indices)} medical-looking samples.")

Dit geeft ons een lichte manier om voorbeelden te selecteren die relevant lijken voor de medische OCR-taak. Voor deze run selecteren we 300 medische voorbeelden.

Vervolgens normaliseren we elke afbeelding naar een vast canvas van 768×1024. Afbeeldingen van medische documenten kunnen verschillende formaten en beeldverhoudingen hebben, dus deze stap helpt de trainingsdata consistenter te maken. De afbeelding wordt geschaald met behoud van de oorspronkelijke beeldverhouding en daarna op een witte achtergrond geplaatst. 

def normalize_ocr_image(image, size=FIXED_IMAGE_SIZE):
    image = image.convert("RGB")
    target_w, target_h = size
    scale = min(target_w / image.width, target_h / image.height)
    new_w = max(1, int(image.width * scale))
    new_h = max(1, int(image.height * scale))
    resized = image.resize((new_w, new_h), Image.Resampling.LANCZOS)

    canvas = Image.new("RGB", size, "white")
    left = (target_w - new_w) // 2
    top = (target_h - new_h) // 2
    canvas.paste(resized, (left, top))
    return canvas

In plaats van datasets.map te gebruiken, bouwen we handmatig een eenvoudige Python-lijst. Dit voorkomt mogelijke vastlopers in sommige cloudnotebookomgevingen bij het herschrijven van PIL-afbeeldingen. 

dataset = []
for idx in medical_indices:
    sample = raw_dataset[idx]
    dataset.append(
        {
            "image": normalize_ocr_image(sample["image"]),
            "text": sample["text"],
        }
    )

print("Examples:", len(dataset))
print("Columns:", list(dataset[0].keys()))
print("Fixed image size:", dataset[EVAL_INDEX]["image"].size)
print("Sample text:", dataset[EVAL_INDEX]["text"])

Na de preprocessing bevat elk voorbeeld twee velden: de genormaliseerde afbeelding en de doel-OCR-tekst. 

Examples: 300
Columns: ['image', 'text']
Fixed image size: (768, 1024)
Sample text: <s_ocr> doctor_name: Dr. A. Smith clinic_name: Meadowview Health clinic_address: 45 Oak Ave. patient_name: John Doe patient_age: 35 date: 2024-12-16 medications: - Hydrochlorothiazide 25 mg - Before meals signature: Dr. A. Smith </s>

We kunnen ook een van de geschaalde voorbeelden bekijken:

dataset[EVAL_INDEX]["image"].resize((384, 512))

De preview toont een documentafbeelding in medische stijl met kliniekgegevens, naam van de arts, patiëntinformatie, medicatie en handtekening. Dit bevestigt dat de dataset geschikt is voor de OCR-fine-tuningtaak. 

Doctors prescription.

5. Samples omzetten naar visionconversaties

Nu de dataset is geladen en de afbeeldingen zijn genormaliseerd, moeten we elk voorbeeld omzetten naar het conversatieformaat dat Qwen3.5 Vision verwacht.

Elke trainingssample moet drie onderdelen bevatten:

  • Een systeembericht dat de rol van het model definieert als een medische OCR-transcriptie-engine
  • Een gebruikersbericht dat de afbeelding en OCR-instructie bevat
  • Een assistentbericht met de verwachte OCR-output
def build_ocr_messages(image=None, target_text=None, instruction=INSTRUCTION):
    user_content = [
        {"type": "image"},
        {"type": "text", "text": instruction},
    ]

    if image is not None:
        user_content[0]["image"] = image

    messages = [
        {"role": "system", "content": [{"type": "text", "text": SYSTEM_PROMPT}]},
        {"role": "user", "content": user_content},
    ]

    if target_text is not None:
        messages.append(
            {
                "role": "assistant",
                "content": [{"type": "text", "text": target_text}],
            }
        )

    return messages

De hulpfunctie hierboven maakt de berichtstructuur voor zowel training als inferentie. Tijdens training voegen we de doel-OCR-tekst toe als antwoord van de assistent. Tijdens inferentie geven we alleen de afbeelding en instructie en vragen we het model om de OCR-tekst te genereren.

Vervolgens zetten we elke dataset-sample om naar dit conversatieformaat:

def convert_to_conversation(sample):
    return {
        "messages": build_ocr_messages(
            image=sample["image"],
            target_text=sample["text"],
        )
    }


converted_dataset = [convert_to_conversation(sample) for sample in dataset]
converted_dataset[0]

Na de conversie bevat elke sample een lijst met berichten. Het eerste voorbeeld bevat de systeemprompt, de documentafbeelding, de OCR-instructie en de verwachte gestructureerde OCR-transcriptie. Dit formaat laat het model leren hoe een afbeelding en een instructie naar de juiste tekstoutput worden gemapt. 

{'messages': [{'role': 'system',
   'content': [{'type': 'text',
     'text': 'You are a medical OCR transcription engine. Return only the exact text visible in the medical document image.'}]},
  {'role': 'user',
   'content': [{'type': 'image',
     'image': <PIL.Image.Image image mode=RGB size=768x1024>},
    {'type': 'text',
     'text': 'Extract all readable text from this medical document exactly. Preserve structure when possible. Return only the OCR text, with no explanation, no diagnosis, no medical advice, and no reasoning.'}]},
  {'role': 'assistant',
   'content': [{'type': 'text',
     'text': '<s_ocr> doctor_name: Dr. A. Smith clinic_name: Meadowview Health clinic_address: 45 Oak Ave. patient_name: John Doe patient_age: 35 date: 2024-12-16 medications: - Hydrochlorothiazide 25 mg - Before meals signature: Dr. A. Smith </s>'}]}]}

6. Het basismodel evalueren vóór fine-tunen

Voor we gaan trainen, testen we het basismodel op één OCR-voorbeeld. Dit geeft ons een referentiepunt om de output vóór en na fine-tunen te vergelijken.

Eerst definiëren we een hulpfunctie om de chattemplate van het model toe te passen. Sommige tokenizer-versies ondersteunen enable_thinking=False, andere niet, daarom bevat de functie een fallback om de code compatibel te houden.

def render_ocr_chat_template(tokenizer, messages):
    try:
        return tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=True,
            enable_thinking=False,
        )
    except TypeError:
        return tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=True,
        )

Vervolgens definiëren we de generatiefunctie. Die bouwt de OCR-prompt, geeft zowel de afbeelding als de tekstinstructies door aan de tokenizer, genereert de modeloutput en decodeert alleen de nieuw gegenereerde tokens. 

def generate_ocr_text(model, tokenizer, image, instruction=INSTRUCTION, max_new_tokens=512):
    messages = build_ocr_messages(instruction=instruction)
    input_text = render_ocr_chat_template(tokenizer, messages)
    inputs = tokenizer(
        images=image,
        text=input_text,
        add_special_tokens=False,
        return_tensors="pt",
    ).to(DEVICE)

    with torch.inference_mode():
        outputs = model.generate(
            **inputs,
            max_new_tokens=max_new_tokens,
            use_cache=True,
            do_sample=False,
            temperature=None,
            top_p=None,
        )

    prompt_length = inputs["input_ids"].shape[-1]
    generated_tokens = outputs[:, prompt_length:]
    return tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]

Nu schakelen we het model naar inferentiemodus en genereren we OCR-tekst voor de eerste evaluatieafbeelding:

FastVisionModel.for_inference(model)
eval_image = dataset[EVAL_INDEX]["image"]
base_output = generate_ocr_text(model, tokenizer, eval_image)

print("Target:")
print(dataset[EVAL_INDEX]["text"])
print("\nBase model output:")
print(base_output)

De output van het basismodel is leesbaar, maar volgt het doelstructuur niet exact:

Target:
<s_ocr> doctor_name: Dr. A. Smith clinic_name: Meadowview Health clinic_address: 45 Oak Ave. patient_name: John Doe patient_age: 35 date: 2024-12-16 medications: - Hydrochlorothiazide 25 mg - Before meals signature: Dr. A. Smith </s>

Base model output:
Meadowview Health
45 Oak Ave.
Prescribed by: Dr. A. Smith
Date: 2024-12-16
Patient: John Doe, Age: 35
Hydrochlorothiazide 25 mg - Before meals
Signature: Dr. A. Smith

Dit is een bruikbaar startpunt. Het basismodel kan al veel van het document lezen, maar het geeft de tekst weer in een natuurlijke OCR-stijl in plaats van het gestructureerde formaat uit de dataset. Fine-tunen zou moeten helpen de output op het doel­formaat af te stemmen en consistenter te maken.

7. Het model trainen

Nu de dataset in het juiste visionconversatieformaat staat, kunnen we het model trainen met TRL’s SFTTrainer en Unsloth’s vision-datacollator. 

from unsloth.trainer import UnslothVisionDataCollator
from trl import SFTTrainer, SFTConfig

FastVisionModel.for_training(model)

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    data_collator=UnslothVisionDataCollator(model, tokenizer),
    train_dataset=converted_dataset,
    args=SFTConfig(
        per_device_train_batch_size=PER_DEVICE_BATCH_SIZE,
        gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS,
        warmup_steps=5,
        max_steps=MAX_STEPS,
        learning_rate=LEARNING_RATE,
        logging_steps=1,
        optim="adamw_8bit",
        weight_decay=0.001,
        lr_scheduler_type="linear",
        seed=SEED,
        output_dir=OUTPUT_DIR,
        report_to="none",
        remove_unused_columns=False,
        dataset_text_field="",
        dataset_kwargs={"skip_prepare_dataset": True},
        max_length=MAX_LENGTH,
    ),
)
trainer_stats = trainer.train()

Eerst schakelen we het model naar trainingsmodus met FastVisionModel.for_training(model). Daarna maken we de trainer aan met de geconverteerde OCR-dataset.

Het belangrijkste hier is de UnslothVisionDataCollator. Omdat dit een vision-language-taak is, moet de trainer zowel de documentafbeeldingen als de doel-OCR-tekst correct verwerken. De collator bereidt deze multimodale voorbeelden voor om tijdens supervised fine-tuning aan het model te voeren.

Voor deze gids trainen we 30 stappen met een per-device batchgrootte van 4 en gradient accumulation van 2, wat een effectieve batchgrootte van 8 oplevert. Zo blijft de run licht, terwijl je toch ziet hoe het model zich begint aan te passen aan het gestructureerde OCR-formaat.

Fine-tuning the Qwen 3.5 4b Vision language model

Tijdens het trainen print Unsloth nuttige informatie over de setup, waaronder het aantal voorbeelden, stappen en batches, het aantal trainbare parameters en geheugenbesparende features. In deze run meldt Unsloth dat double buffering is ingeschakeld voor de backward pass, wat de wachttijd tijdens gradient checkpointing helpt verkorten.

8. Het fijn-afgestelde model evalueren

Na de training schakelen we het model terug naar inferentiemodus en genereren we OCR-tekst voor dezelfde evaluatieafbeelding als vóór het fine-tunen.

FastVisionModel.for_inference(model)
fine_tuned_output = generate_ocr_text(model, tokenizer, eval_image)

print("Target:")
print(dataset[EVAL_INDEX]["text"])
print("\nBase model output:")
print(base_output)
print("\nFine-tuned output:")
print(fine_tuned_output)

Na het fine-tunen ligt de modeloutput veel dichter bij de doelstructuur van de dataset:

Target:
<s_ocr> doctor_name: Dr. A. Smith clinic_name: Meadowview Health clinic_address: 45 Oak Ave. patient_name: John Doe patient_age: 35 date: 2024-12-16 medications: - Hydrochlorothiazide 25 mg - Before meals signature: Dr. A. Smith </s>

Base model output:
Meadowview Health
45 Oak Ave.
Prescribed by: Dr. A. Smith
Date: 2024-12-16
Patient: John Doe, Age: 35
Hydrochlorothiazide 25 mg - Before meals
Signature: Dr. A. Smith


Fine-tuned output:
<s_ocr> doctor_name: Dr. A. Smith clinic_name: Meadowview Health clinic_address: 45 Oak Ave. patient_name: John Doe patient_age: 35 date: 2024-12-16 medications: - Hydrochlorothiazide 25 mg - Before meals signature: Dr. A. Smith </s>

Dit laat zien dat het fijn-afgestelde model het verwachte OCR-responsformaat heeft geleerd. Het basismodel kon al het meeste van de zichtbare tekst extraheren, maar na fine-tunen volgde het de datasetstructuur consistenter.

We kunnen het model ook testen op een ander voorbeeld uit de dataset:

EVAL_INDEX_2 = 35
eval_image_2 = dataset[EVAL_INDEX_2]["image"]

fine_tuned_output = generate_ocr_text(model, tokenizer, eval_image_2)

print("Target:")
print(dataset[EVAL_INDEX]["text"])
print("\nFine-tuned output:")
print(fine_tuned_output)

Bij dit tweede voorbeeld volgt het model de verwachte structuur, maar het maakt een kleine OCR-fout door Amoxicillin te genereren in plaats van Amlodipine:

Target:
<s_ocr> doctor_name: Dr. C. Rossi clinic_name: Riverside Clinic clinic_address: 45 Oak Ave. patient_name: Wei Li patient_age: 70 date: 2024-12-16 medications: - Acetaminophen 20 mg - Take twice daily - Amlodipine 20 mg - After meals signature: Dr. C. Rossi </s>

Fine-tuned output:
<s_ocr> doctor_name: Dr. C. Rossi clinic_name: Riverside Clinic clinic_address: 45 Oak Ave. patient_name: Wei Li patient_age: 70 date: 2024-12-16 medications: - Acetaminophen 20 mg - Take twice daily - Amoxicillin 20 mg - After meals signature: Dr. C. Rossi </s>

Dit herinnert eraan dat het model beter wordt in het volgen van het formaat, maar dat OCR-nauwkeurigheid nog steeds afhangt van datakwaliteit, beeldscherpte, trainingsomvang en het aantal fine-tuningstappen. Voor een productie-OCR-systeem zou je trainen op een grotere, meer diverse dataset en de nauwkeurigheid evalueren over een brede reeks voorbeelden.

9. De fijn-afgestelde adapter opslaan

Zodra de training klaar is, slaan we de LoRA-adapter en tokenizer lokaal op.

model.save_pretrained(ADAPTER_DIR)
tokenizer.save_pretrained(ADAPTER_DIR)

print("Saved adapter to:", ADAPTER_DIR)

De output bevestigt dat de adapter is opgeslagen:

Saved adapter to: qwen35-vision-medical-ocr-lora

Hiermee worden alleen de fijn-afgestelde adaptergewichten opgeslagen, niet een volledige kopie van het basismodel. Later kun je het basis Qwen3.5-4B-model opnieuw laden en deze adapter toepassen om het fijn-afgestelde OCR-gedrag te hergebruiken. Daardoor blijft het opgeslagen model lichtgewicht en makkelijker te bewaren, delen of deployen.

Tot slot

Het trainingsproces was licht en praktisch op een enkele NVIDIA RTX 3090. Hoewel fine-tunen van vision-language-modellen normaal gesproken veel geheugen vraagt, gebruikte de run veel minder VRAM dan verwacht. Het maximale VRAM-gebruik lag rond 14 GB, terwijl het gemiddelde dichter bij 9 GB lag, wat indrukwekkend is voor het fine-tunen van een Qwen3.5 Vision-model.

Het model paste zich ook snel aan. Na slechts enkele trainingsstappen kwam de output veel dichter bij de doel-OCR-structuur. Het basismodel kon het document al lezen, maar na fine-tunen volgde het de datasetopmaak consistenter. 

Toch was de setup-ervaring niet perfect. Het installeren van Unsloth vergde veel trial-and-error. Het kan lastig zijn om alles correct te configureren, zeker wanneer je werkt met verschillende lokale omgevingen, virtuele omgevingen, CUDA-versies en cloud-GPU-providers. 

In sommige gevallen kunnen CUDA-compatibiliteitsproblemen de omgeving breken, en het debuggen daarvan kan meer tijd kosten dan verwacht. Zelfs beginnen met een Unsloth Docker-image op een cloud-GPU-platform kan tijdrovend zijn als de omgeving niet direct netjes werkt.

Een andere belangrijke les is dat de modeltemplate ertoe doet. Als de dataset niet wordt omgezet naar het juiste chat- of visionconversatieformaat, leert het model mogelijk niet goed. Voor Qwen3.5 Vision is het essentieel om de juiste beeld-tekstberichtstructuur te gebruiken. Zonder de juiste template kan de training wel draaien, maar past het model zich mogelijk niet echt aan de taak aan.

Al met al is Unsloth een sterke optie voor gebruikers met beperkte GPU-toegang die modellen efficiënt willen fine-tunen op lokale machines of gehuurde GPU’s. Het verlaagt het geheugenverbruik, maakt kleinere hardware nuttiger en kan experimenten versnellen. Voor gebruikers die regelmatig modellen fine-tunen en trainen kan de setupcomplexiteit echter frustrerend zijn. Standaard, op Transformers gebaseerde training is vaak stabieler, makkelijker te installeren en eenvoudiger te reproduceren over omgevingen heen.

Als de installatiedrempel is wat je tegenhoudt, lees dan onze gids over Unsloth Studio, waarin we laten zien hoe je Qwen3.5-9B kunt fine-tunen zonder handmatige omgevingssetup in de lokale web-UI van Unsloth.


Abid Ali Awan's photo
Author
Abid Ali Awan
LinkedIn
Twitter

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

Top AI-cursussen

Cursus

Introductie tot Deep Learning met PyTorch

4 Hr
88.5K
Leer hoe je je eerste neural network bouwt, hyperparameters aanpast en classificatie- en regressieproblemen aanpakt in PyTorch.
Bekijk detailsRight Arrow
Begin Met De Cursus
Meer zienRight Arrow
Gerelateerd

blog

AI vanaf nul leren in 2026: een complete gids van de experts

Ontdek alles wat je moet weten om in 2026 AI te leren, van tips om te beginnen tot handige resources en inzichten van industrie-experts.
Adel Nehme's photo

Adel Nehme

15 min

Meer ZienMeer Zien