Hoppa till huvudinnehållet

Gradientcheckpointing: effektiv finjustering med Unsloth och NVIDIA

Lär dig hur Unsloths NVIDIA‑optimerade gradientcheckpointing och QLoRA minskar VRAM‑användningen när du finjusterar Qwen3.5 4B för OCR av medicinska dokument.
Uppdaterad 14 aug. 2026  · 12 min läsa

Utforska med AI

ChatGPTClaudePerplexity

Unsloths samarbete med NVIDIA fokuserar på att göra finjustering snabbare genom att minska dold träningsöverhead. I stället för att bara förlita sig på större GPU:er eller mindre modeller riktar förbättringarna in sig på flaskhalsar i själva träningsprocessen, såsom upprepad konstruktion av metadata, fördröjningar vid omladdning av aktiveringar och ineffektiv tokenrouting. För dig som användare innebär det snabbare träning och ett smidigare finjusteringsflöde på stödda NVIDIA‑GPU:er.

I den här guiden går vi igenom dessa nya prestandaförbättringar i Unsloth och tillämpar Unsloths optimerade finjusteringsflöde på en praktisk uppgift för bild‑ och språkförståelse. Vi finjusterar Qwen3.5 Vision 4B för medicinsk OCR, där modellen lär sig att extrahera strukturerad text från bilder av medicinska dokument med hjälp av en liten medicinskt präglad delmängd av en OCR-dataset.

Vi kommer att använda:

  • Qwen3.5 4B (Vision) som basmodell
  • 4-bit QLoRA för att minska VRAM‑användningen
  • LoRA‑adaptrar för effektiv finjustering
  • Unsloth gradient checkpointing för att spara minne under träning
  • Ett delurval på 300 prover ur en medicinsk OCR‑dataset
  • Bildförbehandling med fast storlek för jämnare visionträning
  • Utvärdering före och efter för att jämföra bas- och finjusterad modellutdata

Använda Unsloths NVIDIA‑optimerade finjusteringsflöde

Innan vi börjar finjustera är det bra att förstå vad Unsloths NVIDIA‑samarbete förbättrar och hur det kopplar till den här guiden.

Unsloth rapporterar att NVIDIA‑samarbetet gör LLM‑träning cirka 25 % snabbare, utan förlust i noggrannhet, utöver de befintliga 2–5x hastighetsvinsterna vid finjustering. Vinsterna kommer av att minska dold overhead runt huvudträningen snarare än att ändra modellens inlärningsmål. Med andra ord är målet att göra finjustering snabbare och mer effektiv utan att påverka noggrannheten.

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

Källa: How to Make LLM Training Faster with Unsloth and NVIDIA 

Förbättrad träningsprestanda

Samarbetet rapporterar flera prestandaförbättringar, bland annat:

  • 14,3 % snabbare per batch på ett Qwen3‑14B QLoRA SFT‑riktmärke genom cachelagring av metadata för packade sekvenser
  • 8,4 % hastighetsökning på 8B‑modeller, 6,7 % på 14B‑modeller och 4,6 % på 32B‑modeller med dubbelfrabad asynkron gradientcheckpointing
  • Ungefär 10–15 % hastighetsökning för GPT‑OSS MoE‑träning, med 23 % snabbare framåtpass och 13 % snabbare bakåtpass i den riktade routningsvägen

Några av de största prestandavinsterna från Unsloth och NVIDIA gäller packad text‑endast‑träning och Mixture‑of‑Experts‑modeller. Vi använder inte dem i den här guiden eftersom vårt arbetsflöde fokuserar på Qwen3.5 Vision OCR‑finjustering. 

I den här guiden använder vi en NVIDIA RTX 3090‑GPU, så arbetsflödet är uppbyggt kring NVIDIA‑acceleration och Unsloths optimerade finjusteringsväg. Vi benchmarkar inte Unsloth mot en annan tränare, så den här guiden ska inte läsas som oberoende bevis för de rapporterade hastighetsökningarna. I stället tillämpar vi Unsloths optimerade finjusteringsflöde på en verklig bild‑språkuppgift.

Gradientcheckpointing

För det här arbetsflödet är den mest relevanta optimeringen Unsloths gradientcheckpointing. Det hjälper till att minska minnesanvändningen under träning genom att undvika att varje aktivering måste lagras i GPU‑minnet. Detta är särskilt användbart för finjustering av vision‑språk, där modellen måste bearbeta både bildinmatningar och textutmatningar.

1. Konfigurera Unsloth för snabbare finjustering

För att köra den här guiden behöver du tillgång till en NVIDIA‑GPU. Du kan hyra en från plattformar som RunPod, Vast.ai eller någon annan moln‑GPU‑leverantör. Jag försökte först använda RunPod eftersom det brukar vara snabbt och pålitligt, men de tillgängliga RTX 3090‑alternativen var begränsade just då. Jag använde därför en Vast.ai RTX 3090‑maskin för det här arbetsflödet. 

För en jämförelse av olika plattformar, läs vår guide till bästa molnleverantörerna för GPU.

Vast.ai RTX 3090 instance

Källa:  Vast.ai | Console 

Efter att ha startat instansen öppnade jag Jupyter Notebook och skapade en ny notebook. På Vast.ai valde jag den tillgängliga huvudkärnan för miljön för att installera nödvändiga Python‑paket i notebook‑miljön utan att påverka systemnivåberoenden. 

Installera nödvändiga paket

Börja med att installera nödvändiga paket för Unsloth, PyTorch, träning av visionsmodeller, inläsning av dataset och Hugging Face‑integration: 

!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

Dessa paket baseras på Unsloths officiella notebook‑setup och inkluderar de viktigaste biblioteken för att ladda Qwen3.5 Vision, förbereda bild‑text‑data och finjustera modellen med Unsloth. 

Konfigurera CUDA‑enheten

Därefter konfigurerar vi CUDA‑enheten och verifierar att rätt NVIDIA‑GPU är tillgänglig. Eftersom guiden använder en RTX 3090 kontrollerar koden om CUDA är aktiverat, bekräftar vald GPU, skriver ut CUDA‑ och PyTorch‑versioner och verifierar att maskinen har tillräckligt med VRAM för experimentet. 

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.")

I min miljö returnerade systemet följande GPU‑konfiguration:

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

Detta bekräftar att notebooken körs på en NVIDIA GeForce RTX 3090 med tillräckligt VRAM för finjusteringsexperimentet.

Definiera träningsinställningar och uppmaningar

När GPU:n är verifierad definierar vi modell, dataset, träningsinställningar, utdatakataloger, bildstorlek och OCR‑uppmaningar.

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

Här använder vi Qwen3.5 Vision 4B från Unsloth och den medicinska OCR‑visionsdatamängden. För den här guiden väljer vi 300 exempel och tränar i 30 steg, vilket håller körningen lättviktig men visar hur modellen anpassar sig till målformatet för OCR.

Den fasta bildstorleken 768×1024 hjälper till att hålla bildinmatningarna konsekventa under träning. Medicinska dokument kan variera mycket i upplösning och bildförhållande, så att storleksändra dem till en fast canvas gör arbetsflödet smidigare och minskar formrelaterade problem under vision‑språksfinjustering.

2. Ladda modellen

Nu när miljön är klar kan vi ladda Qwen3.5 Vision 4B med Unsloths 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)

Efter att modellen laddats bekräftar utdata att rätt modell har lästs in, 4‑bit‑läge är aktiverat och att modellen ligger på GPU:n: 

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

Här laddar FastVisionModel.from_pretrained() bild‑språk‑modellen och tillämpar Unsloths optimeringar för snabbare och mer minnes‑effektiv finjustering. Vi aktiverar också load_in_4bit, som minskar VRAM‑användningen genom att ladda modellen i 4‑bitars precision. Detta är användbart när du arbetar med en 24 GB‑GPU som RTX 3090.

Vi aktiverar även Unsloths gradientcheckpointing med use_gradient_checkpointing="unsloth"

Det hjälper till att minska minnesanvändningen under träning, vilket är extra viktigt för bild‑språk‑modeller eftersom de bearbetar både bild- och textinmatningar. 

3. Lägg till LoRA‑adaptrar

Därefter lägger vi till LoRA‑adaptrar i modellen. LoRA gör att vi kan finjustera en mindre uppsättning träningsbara parametrar i stället för att uppdatera hela modellen. Det gör träningen snabbare, mer minnes‑effektiv och enklare att köra på en enda GPU. 

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

I den här guiden läggs adaptrarna till i både visions‑ och språkdelarna av modellen. Det hjälper modellen att lära sig läsa bilder av medicinska dokument och producera förväntad strukturerad OCR‑text. Efter detta steg är modellen redo att tränas på den medicinska OCR‑datamängden. 

4. Ladda den medicinska OCR‑datamängden

Nu laddar vi den medicinska OCR‑datamängden från Hugging Face och förbereder en liten delmängd för finjustering.

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>",
]

Dataseten innehåller dokumentbilder och motsvarande OCR‑text. Eftersom vi bara vill ha medicinskt präglade OCR‑exempel i den här guiden filtrerar vi datasetet med en enkel nyckelordsbaserad metod. Koden söker efter termer som ofta förekommer i medicinska dokument, som doctor, clinic, patient, medication, prescription, diagnosis och dosrelaterade ord. 

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.")

Detta ger oss ett lättviktigt sätt att välja exempel som verkar relevanta för den medicinska OCR‑uppgiften. För den här körningen väljer vi 300 medicinskt utseende exempel.

Därefter normaliserar vi varje bild till en fast canvas på 768×1024. Medicinska dokumentbilder kan ha olika storlekar och bildförhållanden, så detta steg gör träningsdatan mer konsekvent. Bilden skalas med bibehållet bildförhållande och placeras sedan på en vit bakgrund. 

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

I stället för att använda datasets.map bygger vi manuellt en enkel Python‑lista. Det undviker potentiella hängningar i vissa moln‑notebookmiljöer när PIL‑bilder skrivs om. 

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

Efter förbehandling innehåller varje exempel två fält: den normaliserade bilden och mål‑OCR‑texten. 

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>

Vi kan också förhandsgranska ett av de storleksändrade exemplen:

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

Förhandsgranskningen visar en dokumentbild i medicinsk stil med klinikuppgifter, läkarens namn, patientinformation, läkemedel och signatur. Detta bekräftar att datasetet är lämpligt för OCR‑finjusteringsuppgiften. 

Doctors prescription.

5. Konvertera exempel till vision‑konversationer

Nu när datasetet är inläst och bilderna normaliserade behöver vi konvertera varje exempel till det konversationsformat som Qwen3.5 Vision förväntar sig.

Varje träningsprov ska innehålla tre delar:

  • Ett systemmeddelande som definierar modellens roll som motor för medicinsk OCR‑transkribering
  • Ett användarmeddelande som innehåller bilden och OCR‑instruktionen
  • Ett assistentmeddelande som innehåller förväntad OCR‑utdata
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

Hjälpfunktionen ovan skapar meddelandestrukturen för både träning och inferens. Under träning inkluderar vi mål‑OCR‑texten som assistentens svar. Under inferens tillhandahåller vi bara bilden och instruktionen och ber sedan modellen generera OCR‑texten.

Därefter konverterar vi varje datasample till det här konversationsformatet:

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]

Efter konvertering innehåller varje exempel en lista med meddelanden. Det första exemplet innehåller systemprompten, bilden av det medicinska dokumentet, OCR‑instruktionen och den förväntade strukturerade OCR‑transkriberingen. Det här formatet gör att modellen kan lära sig hur man mappar en bild och en instruktion till korrekt textutdata. 

{'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. Utvärdera basmodellen före finjustering

Innan träningen bör vi testa basmodellen på ett OCR‑exempel. Det ger oss en referenspunkt så att vi kan jämföra modellens utdata före och efter finjusteringen.

Först definierar vi en hjälpfunktion för att tillämpa modellens chattmall. Vissa tokenizer‑versioner stöder enable_thinking=False medan andra inte gör det, så funktionen innehåller en fallback för att hålla koden kompatibel.

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

Sedan definierar vi genereringsfunktionen. Den bygger OCR‑prompten, skickar både bilden och textinstruktionerna till tokenizer:n, genererar modellutdata och avkodar endast de nygenererade tokenserna. 

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 växlar vi modellen till inferensläge och genererar OCR‑text för den första utvärderingsbilden:

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)

Basmodellens utdata är läsbar, men den följer inte målstrukturen exakt:

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

Detta är en bra utgångspunkt. Basmodellen kan redan läsa mycket av dokumentet, men den skriver ut texten i en naturlig OCR‑stil i stället för det strukturerade format som används i datasetet. Finjustering bör hjälpa till att anpassa modellen till målformatet och göra svaren mer konsekventa.

7. Träna modellen

Nu när datasetet är i rätt vision‑konversationsformat kan vi träna modellen med TRL:s SFTTrainer tillsammans med Unsloths visions‑datakollator. 

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

Först växlar vi modellen till träningsläge med FastVisionModel.for_training(model). Sedan skapar vi tränaren med det konverterade OCR‑datasetet.

Det viktiga här är UnslothVisionDataCollator. Eftersom detta är en uppgift för bild och språk måste tränaren hantera både dokumentbilderna och mål‑OCR‑texten korrekt. Kollatorn förbereder dessa multimodala exempel för att skickas till modellen under övervakad finjustering.

I den här guiden tränar vi i 30 steg med en per‑enhets batchstorlek på 4 och gradientackumulering på 2, vilket ger en effektiv batchstorlek på 8. Det håller körningen lätt men visar ändå hur modellen börjar anpassa sig till det strukturerade OCR‑formatet.

Fine-tuning the Qwen 3.5 4b Vision language model

Under träningen skriver Unsloth ut användbar information om setupen, inklusive antalet exempel, steg och batchar, antalet träningsbara parametrar och minnessparande funktioner. I den här körningen rapporterar Unsloth att dubbelbuffring är aktiverad för bakåtpasset, vilket hjälper till att minska väntetid under gradientcheckpointing.

8. Utvärdera den finjusterade modellen

Efter träningen växlar vi tillbaka modellen till inferensläge och genererar OCR‑text för samma utvärderingsbild som användes före finjusteringen.

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)

Efter finjustering ligger modellens utdata mycket närmare datasetets målstruktur:

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>

Detta visar att den finjusterade modellen har lärt sig det förväntade OCR‑svarsformatet. Basmodellen kunde redan extrahera det mesta av den synliga texten, men finjusteringen hjälpte till att anpassa utdata till det strukturerade format som används i träningsdatan.

Vi kan också testa modellen på ett annat exempel ur datasetet:

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)

För det andra exemplet följer modellen den förväntade strukturen, men gör ett litet OCR‑fel genom att generera Amoxicillin i stället för 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>

Detta påminner om att modellen förbättras i formatjustering, men OCR‑noggrannheten beror fortfarande på datakvalitet, bildskärpa, träningsstorlek och antal finjusteringssteg. För ett produktions‑OCR‑system bör du träna på en större, mer varierad datamängd och utvärdera noggrannheten över många olika exempel.

9. Spara den finjusterade adaptern

När träningen är klar sparar vi LoRA‑adaptern och tokenizer:n lokalt.

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

print("Saved adapter to:", ADAPTER_DIR)

Utdata bekräftar att adaptern har sparats:

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

Detta sparar endast de finjusterade adaptervikterna, inte en fullständig kopia av basmodellen. Senare kan du läsa in basmodellen Qwen3.5‑4B igen och tillämpa denna adapter för att återanvända det finjusterade OCR‑beteendet. Det gör den sparade modellen lättviktig och enklare att lagra, dela eller driftsätta.

Avslutande tankar

Träningsprocessen var lättviktig och praktisk på en enda NVIDIA RTX 3090. Även om finjustering av bild‑språk vanligtvis är minnesintensiv använde körningen betydligt mindre VRAM än väntat. Den maximala VRAM‑användningen låg runt 14 GB, medan snittet låg närmare 9 GB, vilket är imponerande för finjustering av en Qwen3.5 Vision‑modell.

Modellen anpassade sig också snabbt. Efter bara några få träningssteg blev utdatat mycket närmare målstrukturen för OCR. Basmodellen kunde redan läsa dokumentet, men efter finjusteringen följde den datasetets format mer konsekvent. 

Med det sagt var installationsupplevelsen inte perfekt. Att installera Unsloth krävde mycket trial and error. Det kan vara svårt att konfigurera korrekt, särskilt när man arbetar över olika lokala miljöer, virtuella miljöer, CUDA‑versioner och moln‑GPU‑leverantörer. 

I vissa fall kan CUDA‑kompatibilitetsproblem förstöra miljön, och felsökning av dessa problem kan ta längre tid än väntat. Även att starta med en Unsloth‑Docker‑image på en moln‑GPU‑plattform kan vara tidskrävande om miljön inte fungerar felfritt direkt.

En annan viktig lärdom är att mallar för modellen spelar roll. Om datasetet inte konverteras till rätt chatt- eller vision‑konversationsformat kanske modellen inte lär sig korrekt. För Qwen3.5 Vision är det avgörande att använda rätt bild‑text‑meddelandestruktur. Utan rätt mall kan träningen köra, men modellen kanske inte faktiskt anpassar sig till uppgiften.

Sammantaget är Unsloth ett starkt alternativ för dig med begränsad tillgång till GPU:er som vill finjustera modeller effektivt på lokala maskiner eller hyrda GPU:er. Det minskar minnesanvändningen, gör mindre hårdvara mer användbar och kan snabba upp experimenterandet. För dig som ofta finjusterar och tränar modeller kan dock installationskomplexiteten vara frustrerande. Standardbaserad träning med Transformers är ofta stabilare, enklare att installera och lättare att reproducera mellan miljöer.

Om det är installationsstrulet som avskräcker rekommenderar jag att du läser vår guide till Unsloth Studio, som visar hur du finjusterar Qwen3.5‑9B utan manuell miljösetup i Unsloths lokala webbgränssnitt.

Ämnen

Toppkurser inom AI

course

Introduktion till djupinlärning med PyTorch

4 timmar
88.5K
Lär dig bygga ditt första neurala nätverk, justera hyperparametrar och lösa klassificerings- och regressionsproblem i PyTorch.
Se detaljerRight Arrow
Starta Kursen
Se merRight Arrow