course
Colaborarea Unsloth cu NVIDIA se concentrează pe accelerarea reglării fine prin reducerea costurilor ascunse de antrenare. În loc să te bazezi doar pe GPU-uri mai mari sau modele mai mici, îmbunătățirile vizează blocajele din procesul de antrenare, precum reconstrucția repetată a metadatelor, întârzieri la reîncărcarea activărilor și rutarea ineficientă a tokenilor. Pentru utilizatori, asta înseamnă antrenare mai rapidă și un flux de lucru mai cursiv pentru reglarea fină pe GPU-urile NVIDIA compatibile.
În acest ghid, vom afla despre noile îmbunătățiri de performanță ale Unsloth și vom aplica fluxul de lucru optimizat de reglare fină al Unsloth într-o sarcină practică vizual-lingvistică. Vom regla fin Qwen3.5 Vision 4B pentru OCR medical, unde modelul învață să extragă text structurat din imagini de documente medicale, folosind un subset mic, cu aspect medical, al unui set de date OCR.
Vom folosi:
- Qwen3.5 4B (Vision) ca model de bază
- QLoRA pe 4 biți pentru a reduce utilizarea VRAM
- Adaptoare LoRA pentru reglare fină eficientă
- Gradient checkpointing Unsloth pentru a economisi memorie în timpul antrenării
- Un subset de 300 de eșantioane dintr-un set de date OCR medical
- Preprocesare a imaginilor la dimensiune fixă pentru un antrenament vizual mai cursiv
- Evaluare înainte și după pentru a compara ieșirile modelului de bază și ale celui reglat fin
Folosirea fluxului de lucru de reglare fină optimizat pentru NVIDIA al Unsloth
Înainte să începem reglarea fină, e util să înțelegi ce aduce colaborarea Unsloth cu NVIDIA și cum se leagă de acest ghid.
Unsloth raportează că parteneriatul cu NVIDIA face antrenarea LLM cu aproximativ 25% mai rapidă, fără pierderi de acuratețe, peste accelerările sale existente de 2–5x la reglarea fină. Aceste câștiguri vin din reducerea costurilor ascunse din jurul procesului principal de antrenare, nu din schimbarea obiectivului de învățare al modelului. Cu alte cuvinte, scopul este să facă reglarea fină mai rapidă și mai eficientă, păstrând aceeași acuratețe.

Sursă: How to Make LLM Training Faster with Unsloth and NVIDIA
Performanță îmbunătățită la antrenare
Colaborarea raportează mai multe îmbunătățiri de performanță, inclusiv:
- 14,3% mai rapid per batch pe un benchmark Qwen3-14B QLoRA SFT datorită caching-ului de metadata pentru secvențe împachetate
- 8,4% accelerare pe modele 8B, 6,7% pe 14B și 4,6% pe 32B datorită gradient checkpointing asincron cu dublu buffering
- Aproximativ 10–15% accelerări pentru antrenarea GPT-OSS MoE, cu 23% mai rapid pe forward și 13% pe backward în calea de rutare țintită
Unele dintre cele mai mari câștiguri de performanță din colaborarea Unsloth și NVIDIA se aplică antrenării pe text împachetat și modelelor Mixture-of-Experts. Nu le folosim în acest ghid deoarece fluxul nostru se concentrează pe reglarea fină pentru OCR cu Qwen3.5 Vision.
În acest ghid folosim un GPU NVIDIA RTX 3090, așa că fluxul este construit în jurul accelerării pe GPU NVIDIA și al traseului optimizat de reglare fină al Unsloth. Nu facem benchmarking între Unsloth și un alt trainer, așa că ghidul nu trebuie citit ca dovadă independentă a accelerărilor raportate. În schimb, aplicăm fluxul optimizat de reglare fină al Unsloth pe o sarcină reală vizual-lingvistică.
Gradient checkpointing
Pentru acest flux de lucru, optimizarea cea mai relevantă este gradient checkpointing-ul Unsloth. Ajută la reducerea utilizării memoriei în timpul antrenării, evitând necesitatea stocării fiecărei activări în memoria GPU. Este deosebit de util pentru reglarea fină vizual-lingvistică, unde modelul trebuie să proceseze atât intrări imagine, cât și ieșiri text.
1. Configurarea Unsloth pentru reglare fină mai rapidă
Pentru a rula acest ghid, ai nevoie de acces la un GPU NVIDIA. Poți închiria unul de pe platforme precum RunPod, Vast.ai sau orice alt furnizor cloud de GPU. Inițial am încercat RunPod pentru că de obicei e rapid și fiabil, dar opțiunile cu RTX 3090 erau limitate atunci. Prin urmare, am folosit o mașină Vast.ai cu GPU RTX 3090 pentru acest flux de lucru.
Pentru o comparație între diferitele platforme, consultă ghidul nostru despre cei mai buni furnizori cloud de GPU.

Sursă: Vast.ai | Console
După pornirea instanței, am deschis Jupyter Notebook și am creat un notebook nou. Pe Vast.ai, am selectat kernelul principal de mediu disponibil pentru a instala pachetele Python necesare în mediul notebook-ului, fără a afecta dependențele la nivel de sistem.
Instalarea pachetelor necesare
Mai întâi, instalează pachetele necesare pentru Unsloth, PyTorch, antrenarea modelului vizual, încărcarea setului de date și integrarea cu 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
Aceste pachete se bazează pe configurarea oficială din notebook-ul Unsloth și includ bibliotecile principale necesare pentru a încărca Qwen3.5 Vision, a pregăti date imagine-text și a regla fin modelul cu Unsloth.
Configurarea dispozitivului CUDA
În continuare, configurăm dispozitivul CUDA și verificăm dacă GPU-ul NVIDIA corect este disponibil. Deoarece acest ghid folosește un RTX 3090, codul verifică dacă CUDA este activat, confirmă GPU-ul selectat, afișează versiunile CUDA și PyTorch și verifică dacă mașina are suficient VRAM pentru acest 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.")
În configurarea mea, mediul a returnat următoarea configurare GPU:
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
Acest lucru confirmă că notebook-ul rulează pe un NVIDIA GeForce RTX 3090, cu suficient VRAM pentru experimentul de reglare fină.
Definirea setărilor de antrenare și a prompturilor
După verificarea GPU-ului, definim modelul, setul de date, setările de antrenare, directoarele de ieșire, dimensiunea imaginii și prompturile OCR.
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."
Aici folosim Qwen3.5 Vision 4B de la Unsloth și setul de date vizual pentru OCR medical. Pentru acest ghid, selectăm 300 de eșantioane și antrenăm pentru 30 de pași, ceea ce menține rularea ușoară, dar arată cum se adaptează modelul la formatul OCR țintă.
Dimensiunea fixă a imaginii de 768×1024 ajută la menținerea unor intrări imagine consistente în timpul antrenării. Documentele medicale pot varia mult în rezoluție și raport de aspect, astfel că redimensionarea într-un canvas fix face fluxul mai cursiv și reduce problemele legate de forme în reglarea fină vizual-lingvistică.
2. Încărcarea modelului
Acum că mediul este gata, putem încărca modelul Qwen3.5 Vision 4B folosind FastVisionModel din Unsloth.
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)
După încărcarea modelului, ieșirea confirmă că a fost încărcat modelul corect, modul pe 4 biți este activat, iar modelul este plasat pe GPU:
Loaded: unsloth/Qwen3.5-4B
4-bit: True
Model device: cuda:0
Aici, FastVisionModel.from_pretrained() încarcă modelul vizual-lingvistic și aplică optimizările Unsloth pentru o reglare fină mai rapidă și mai eficientă în memorie. Activăm și load_in_4bit, care reduce utilizarea VRAM prin încărcarea modelului în precizie pe 4 biți. Este util când lucrezi cu un GPU de 24 GB, precum RTX 3090.
Activăm și gradient checkpointing-ul Unsloth cu use_gradient_checkpointing="unsloth"
Acesta ajută la reducerea utilizării memoriei în timpul antrenării, lucru deosebit de important pentru modelele vizual-lingvistice, deoarece procesează atât intrări imagine, cât și text.
3. Adăugarea adaptoarelor LoRA
În continuare, adăugăm adaptoare LoRA modelului. LoRA ne permite să reglăm fin un set mai mic de parametri antrenabili, în loc să actualizăm întregul model. Asta face antrenarea mai rapidă, mai eficientă în memorie și mai ușor de rulat pe un singur 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,
)
Pentru acest ghid, adaptoarele sunt adăugate atât în partea vizuală, cât și în cea lingvistică a modelului. Asta ajută modelul să învețe cum să citească imaginile de documente medicale și să producă textul OCR structurat așteptat. După acest pas, modelul este gata să fie antrenat pe setul de date OCR medical.
4. Încărcarea setului de date OCR medical
Acum încărcăm setul de date OCR medical de pe Hugging Face și pregătim un subset mic pentru reglarea fină.
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>",
]
Setul de date conține imagini cu documente și textul lor OCR corespunzător. Deoarece ne dorim doar exemple de tip OCR medical pentru acest ghid, filtrăm setul de date folosind o abordare simplă bazată pe cuvinte cheie. Codul caută termeni întâlniți frecvent în documentele medicale, precum doctor, clinică, pacient, medicație, rețetă, diagnostic și cuvinte legate de dozaj.
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.")
Acest lucru ne oferă o modalitate ușoară de a selecta exemple relevante pentru sarcina de OCR medical. Pentru această rulare, selectăm 300 de eșantioane cu aspect medical.
Apoi normalizăm fiecare imagine într-un canvas fix de 768×1024. Imaginile de documente medicale pot avea dimensiuni și rapoarte de aspect diferite, așa că acest pas ajută la uniformizarea datelor de antrenare. Imaginea este redimensionată păstrând raportul original, apoi plasată pe fundal alb.
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
În loc să folosim datasets.map, construim manual o listă simplă Python. Astfel evităm blocajele posibile în unele medii de notebook cloud la rescrierea imaginilor PIL.
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"])
După preprocesare, fiecare exemplu conține două câmpuri: imaginea normalizată și textul OCR țintă.
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>
Putem previzualiza și unul dintre exemplele redimensionate:
dataset[EVAL_INDEX]["image"].resize((384, 512))
Previzualizarea arată o imagine de document cu stil medical, cu detalii despre clinică, numele medicului, informații despre pacient, medicație și semnătură. Asta confirmă că setul de date este potrivit pentru sarcina de reglare fină OCR.

5. Conversia eșantioanelor în conversații vizuale
Acum că setul de date este încărcat și imaginile sunt normalizate, trebuie să convertim fiecare exemplu în formatul de conversație așteptat de Qwen3.5 Vision.
Fiecare eșantion de antrenare ar trebui să includă trei părți:
- Un mesaj de sistem care definește rolul modelului ca motor de transcriere OCR medical
- Un mesaj al utilizatorului care conține imaginea și instrucțiunea OCR
- Un mesaj al asistentului care conține ieșirea OCR așteptată
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
Funcția de asistență de mai sus creează structura de mesaje atât pentru antrenare, cât și pentru inferență. În timpul antrenării, includem textul OCR țintă ca răspuns al asistentului. La inferență, oferim doar imaginea și instrucțiunea, apoi cerem modelului să genereze textul OCR.
În continuare, convertim fiecare eșantion din setul de date în acest format de conversație:
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]
După conversie, fiecare eșantion conține o listă de mesaje. Primul exemplu include promptul de sistem, imaginea documentului medical, instrucțiunea OCR și transcrierea OCR structurată așteptată. Acest format permite modelului să învețe cum să mapeze o imagine și o instrucțiune la ieșirea text corectă.
{'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. Evaluarea modelului de bază înainte de reglare fină
Înainte de antrenare, ar trebui să testăm modelul de bază pe un exemplu OCR. Asta ne oferă un punct de referință pentru a compara ieșirea modelului înainte și după reglarea fină.
Mai întâi, definim o funcție de asistență pentru a aplica șablonul de chat al modelului. Unele versiuni de tokenizer acceptă enable_thinking=False, în timp ce altele nu, așa că funcția include un fallback pentru a menține compatibilitatea codului.
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,
)
Apoi definim funcția de generare. Construiește promptul OCR, transmite atât imaginea, cât și instrucțiunile text către tokenizer, generează ieșirea modelului și decodează doar tokenii nou generați.
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]
Acum comutăm modelul în modul de inferență și generăm text OCR pentru prima imagine de evaluare:
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)
Ieșirea modelului de bază este lizibilă, dar nu respectă exact structura țintă:
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
Este un punct de plecare util. Modelul de bază poate deja citi mare parte din document, dar scoate textul într-un stil OCR natural, nu în formatul structurat folosit în setul de date. Reglarea fină ar trebui să ajute la alinierea la formatul țintă și să facă răspunsurile mai consecvente.
7. Antrenarea modelului
Acum că setul de date are formatul corect de conversație vizuală, putem antrena modelul folosind SFTTrainer din TRL, cu colatorul de date vizuale al Unsloth.
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()
Mai întâi, comutăm modelul în modul de antrenare cu FastVisionModel.for_training(model). Apoi, creăm trainerul folosind setul de date OCR convertit.
Partea importantă aici este UnslothVisionDataCollator. Deoarece este o sarcină vizual-lingvistică, trainerul trebuie să gestioneze corect atât imaginile documentelor medicale, cât și textul OCR țintă. Colatorul pregătește aceste exemple multimodale pentru a fi transmise modelului în timpul reglării supervizate.
Pentru acest ghid, antrenăm 30 de pași, cu un batch size per dispozitiv de 4 și acumulare a gradientului de 2, ceea ce oferă un batch efectiv de 8. Rularea rămâne ușoară, dar arată cum începe modelul să se adapteze la formatul OCR structurat.

În timpul antrenării, Unsloth afișează informații utile despre configurare, inclusiv numărul de exemple, pași și batch-uri, numărul de parametri antrenabili și funcționalitățile de economisire a memoriei. În această rulare, Unsloth raportează că dublul buffering este activat pentru backward pass, ceea ce ajută la reducerea timpilor de așteptare în timpul gradient checkpointing-ului.
8. Evaluarea modelului reglat fin
După antrenare, comutăm modelul înapoi în modul de inferență și generăm text OCR pentru aceeași imagine de evaluare folosită înainte de reglarea fină.
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)
După reglarea fină, ieșirea modelului este mult mai aproape de structura țintă a setului de date:
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>
Asta arată că modelul reglat fin a învățat formatul de răspuns OCR așteptat. Modelul de bază putea deja extrage cea mai mare parte a textului vizibil, dar reglarea fină a ajutat la alinierea ieșirii cu formatul structurat folosit în datele de antrenare.
Putem testa modelul și pe un alt exemplu din setul de date:
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)
Pentru al doilea exemplu, modelul urmează structura așteptată, dar face o mică greșeală de OCR, generând Amoxicillin în loc de 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>
Este un memento util că modelul se îmbunătățește în alinierea formatului, dar acuratețea OCR depinde în continuare de calitatea datelor, claritatea imaginilor, mărimea antrenării și numărul de pași de reglare fină. Pentru un sistem OCR de producție, ai antrena pe un set de date mai mare și mai divers și ai evalua acuratețea pe o gamă largă de exemple.
9. Salvarea adaptorului reglat fin
După ce antrenarea este încheiată, salvăm local adaptorul LoRA și tokenizerul.
model.save_pretrained(ADAPTER_DIR)
tokenizer.save_pretrained(ADAPTER_DIR)
print("Saved adapter to:", ADAPTER_DIR)
Ieșirea confirmă că adaptorul a fost salvat:
Saved adapter to: qwen35-vision-medical-ocr-lora
Se salvează doar greutățile adaptorului reglat fin, nu o copie completă a modelului de bază. Mai târziu, poți reîncărca modelul de bază Qwen3.5-4B și aplica acest adaptor pentru a reutiliza comportamentul OCR reglat fin. Astfel, modelul salvat rămâne ușor și mai simplu de stocat, partajat sau distribuit.
Gânduri finale
Procesul de antrenare a fost ușor și practic pe un singur NVIDIA RTX 3090. Deși reglarea fină vizual-lingvistică este de obicei intensivă în memorie, rularea a folosit mult mai puțin VRAM decât ne-am așteptat. Utilizarea maximă a VRAM a fost în jur de 14 GB, iar media mai aproape de 9 GB, ceea ce este impresionant pentru reglarea fină a unui model Qwen3.5 Vision.
Modelul s-a adaptat și rapid. După doar câțiva pași de antrenare, ieșirea a devenit mult mai apropiată de structura OCR țintă. Modelul de bază putea deja citi documentul, dar după reglarea fină a urmat formatul setului de date mai consecvent.
Totuși, experiența de configurare nu a fost perfectă. Instalarea Unsloth a necesitat mult trial and error. Poate fi dificil de configurat corect, mai ales când lucrezi pe medii locale diferite, medii virtuale, versiuni de CUDA și furnizori cloud de GPU.
În unele cazuri, problemele de compatibilitate CUDA pot strica mediul, iar depanarea poate dura mai mult decât te aștepți. Chiar și pornirea cu o imagine Docker Unsloth pe o platformă cloud GPU poate consuma timp dacă mediul nu funcționează corect din start.
O altă lecție importantă este că șablonul modelului contează. Dacă setul de date nu este convertit în formatul corect de chat sau conversație vizuală, modelul s-ar putea să nu învețe corect. Pentru Qwen3.5 Vision, folosirea structurii corecte a mesajelor imagine-text este esențială. Fără șablonul potrivit, antrenarea poate rula, dar modelul s-ar putea să nu se adapteze de fapt la sarcină.
Per total, Unsloth este o opțiune solidă pentru utilizatorii cu acces limitat la GPU, care vor să regleze fin modele eficient pe mașini locale sau GPU-uri închiriate. Reduce utilizarea memoriei, face hardware-ul mai modest mai util și poate accelera experimentarea. Totuși, pentru utilizatorii care reglează fin și antrenează modele în mod regulat, complexitatea configurării poate fi frustrantă. Antrenarea standard bazată pe Transformers este adesea mai stabilă, mai ușor de instalat și mai simplu de reprodus între medii.
Dacă partea de instalare este cea care te descurajează, îți recomand să citești ghidul nostru despre Unsloth Studio, care îți arată cum să reglezi fin Qwen3.5-9B fără configurare manuală a mediului, în interfața web locală a Unsloth.