Pular para o conteúdo principal

Gradient checkpointing: ajuste fino eficiente com Unsloth e NVIDIA

Aprenda como o gradient checkpointing otimizado pela NVIDIA do Unsloth e o QLoRA reduzem o uso de VRAM ao ajustar o Qwen3.5 4B para OCR de documentos médicos.
Atualizado 14 de ago. de 2026  · 12 min lido

Explorar com IA

ChatGPTClaudePerplexity

A colaboração da Unsloth com a NVIDIA tem como foco deixar o ajuste fino mais rápido reduzindo custos ocultos de treinamento. Em vez de depender apenas de GPUs maiores ou modelos menores, as melhorias atacam gargalos dentro do processo de treino, como reconstrução repetida de metadados, atrasos no recarregamento de ativações e roteamento ineficiente de tokens. Para você, isso significa treinos mais rápidos e um fluxo de ajuste fino mais fluido em GPUs NVIDIA compatíveis.

Neste guia, vamos conhecer essas novas melhorias de desempenho do Unsloth e aplicar o fluxo de trabalho de ajuste fino Unsloth otimizado em uma tarefa prática de visão e linguagem. Vamos ajustar o Qwen3.5 Vision 4B para OCR médico, em que o modelo aprende a extrair texto estruturado de imagens de documentos médicos usando um pequeno subconjunto com aparência médica de um dataset de OCR.

Vamos usar:

  • Qwen3.5 4B (Vision) como modelo base
  • QLoRA em 4 bits para reduzir o uso de VRAM
  • Adaptadores LoRA para ajuste fino eficiente
  • Gradient checkpointing do Unsloth para economizar memória durante o treino
  • Um subconjunto com 300 amostras de um dataset de OCR médico
  • Pré-processamento de imagem com tamanho fixo para um treino de visão mais estável
  • Avaliação antes e depois para comparar as saídas do modelo base e do modelo ajustado

Usando o fluxo de ajuste fino otimizado pela NVIDIA do Unsloth

Antes de começar o ajuste fino, vale entender como a colaboração da Unsloth com a NVIDIA melhora o processo e como isso se conecta a este guia.

A Unsloth reporta que sua colaboração com a NVIDIA deixa o treinamento de LLMs cerca de 25% mais rápido, sem perda de acurácia, além dos ganhos de 2 a 5 vezes já obtidos no ajuste fino. Esses ganhos vêm de reduzir custos ocultos ao redor do processo de treino, e não de mudar o objetivo de aprendizagem do modelo. Em outras palavras, a meta é tornar o ajuste fino mais rápido e eficiente mantendo a precisão intacta.

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

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

Você quer começar a usar a IA generativa?

Saiba como trabalhar com LLMs em Python diretamente em seu navegador

Comece Agora

Desempenho de treinamento aprimorado

A colaboração traz várias melhorias de desempenho, incluindo:

  • 14,3% mais rápido por batch em um benchmark QLoRA SFT do Qwen3-14B graças ao cache de metadados de sequências empacotadas
  • Ganho de 8,4% em modelos 8B, 6,7% em 14B e 4,6% em 32B com gradient checkpointing assíncrono com double buffering
  • Cerca de 10–15% de ganho no treino MoE do GPT-OSS, com 23% mais rápido no forward e 13% no backward na rota de encaminhamento otimizada

Os maiores ganhos dessa colaboração Unsloth + NVIDIA se aplicam a treinos de texto empacotado (apenas texto) e a modelos Mixture-of-Experts. Não usaremos esses cenários aqui, pois nosso fluxo foca no ajuste fino de OCR com Qwen3.5 Vision.

Neste guia, usamos uma GPU NVIDIA RTX 3090, então o fluxo é pensado para aceleração em GPU NVIDIA e para o caminho de ajuste fino otimizado do Unsloth. Não estamos comparando o Unsloth com outro trainer; portanto, este guia não deve ser interpretado como prova independente dos ganhos reportados. Em vez disso, aplicamos o fluxo otimizado do Unsloth a uma tarefa real de visão e linguagem.

Gradient checkpointing

Para este fluxo, a otimização mais relevante é o gradient checkpointing do Unsloth. Ele ajuda a reduzir o uso de memória durante o treino evitando armazenar todas as ativações na memória da GPU. Isso é especialmente útil em ajuste fino de visão e linguagem, onde o modelo processa entradas de imagem e saídas de texto.

1. preparando o Unsloth para um ajuste fino mais rápido

Para seguir este guia, você precisa de acesso a uma GPU NVIDIA. Você pode alugar em plataformas como RunPod, Vast.ai ou qualquer outro provedor de GPU em nuvem. Eu tentei primeiro o RunPod, que costuma ser rápido e confiável, mas as opções de RTX 3090 estavam limitadas no momento. Então usei uma máquina RTX 3090 da Vast.ai para este fluxo.

Para comparar plataformas, confira nosso guia com os melhores provedores de GPU em nuvem.

Vast.ai RTX 3090 instance

Fonte: Vast.ai | Console 

Depois de iniciar a instância, abri o Jupyter Notebook e criei um novo notebook. Na Vast.ai, selecionei o kernel principal disponível para instalar os pacotes Python necessários no ambiente do notebook sem afetar dependências de sistema.

Instalando os pacotes necessários

Primeiro, instale os pacotes necessários para o Unsloth, PyTorch, treino de modelos de visão, carregamento de dataset e integração com 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

Esses pacotes seguem a configuração oficial do notebook do Unsloth e incluem as principais bibliotecas para carregar o Qwen3.5 Vision, preparar dados imagem-texto e ajustar o modelo com o Unsloth.

Configurando o dispositivo CUDA

Em seguida, configuramos o dispositivo CUDA e verificamos se a GPU NVIDIA correta está disponível. Como este guia usa uma RTX 3090, o código checa se o CUDA está ativo, confirma a GPU selecionada, imprime as versões de CUDA e PyTorch e verifica se a máquina tem VRAM suficiente para o experimento.

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

No meu ambiente, a configuração de GPU retornou:

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

Isso confirma que o notebook está rodando em uma NVIDIA GeForce RTX 3090 com VRAM suficiente para o experimento de ajuste fino.

Definindo configurações de treino e prompts

Depois de verificar a GPU, definimos o modelo, o dataset, as configurações de treino, diretórios de saída, tamanho da imagem e prompts de 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."

Aqui usamos o Qwen3.5 Vision 4B do Unsloth e o dataset de visão para OCR médico. Para este guia, selecionamos 300 amostras e treinamos por 30 steps, mantendo a execução leve e mostrando como o modelo se adapta ao formato de OCR alvo.

O tamanho fixo de 768×1024 ajuda a manter as entradas de imagem consistentes durante o treino. Documentos médicos variam muito em resolução e proporção; redimensioná-los para uma tela fixa deixa o fluxo mais estável e reduz problemas de forma no ajuste fino de visão e linguagem.

2. carregando o modelo

Com o ambiente pronto, podemos carregar o modelo Qwen3.5 Vision 4B usando o FastVisionModel do 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)

Após o carregamento, a saída confirma que o modelo correto foi carregado, o modo em 4 bits está ativado e o modelo foi alocado na GPU:

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

Aqui, FastVisionModel.from_pretrained() carrega o modelo de visão e linguagem e aplica as otimizações do Unsloth para um ajuste fino mais rápido e eficiente em memória. Também ativamos load_in_4bit, que reduz o uso de VRAM carregando o modelo em precisão de 4 bits — útil em uma GPU de 24 GB como a RTX 3090.

Também habilitamos o gradient checkpointing do Unsloth com use_gradient_checkpointing="unsloth"

Isso ajuda a reduzir o uso de memória durante o treino — algo crucial em modelos de visão e linguagem, pois eles processam entradas de imagem e texto.

3. adicionando adaptadores LoRA

Agora, adicionamos adaptadores LoRA ao modelo. O LoRA permite ajustar um conjunto menor de parâmetros treináveis em vez de atualizar o modelo inteiro. Isso deixa o treino mais rápido, econômico em memória e viável em uma única 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,
)

Neste guia, os adaptadores são adicionados tanto na parte de visão quanto na de linguagem do modelo. Isso ajuda o modelo a ler imagens de documentos médicos e produzir o texto estruturado de OCR esperado. Após esta etapa, o modelo está pronto para treinar no dataset de OCR médico.

4. carregando o dataset de OCR médico

Agora vamos carregar o dataset de OCR médico do Hugging Face e preparar um pequeno subconjunto para o ajuste fino.

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

O dataset contém imagens de documentos e seus textos de OCR correspondentes. Como queremos apenas exemplos com “cara” de OCR médico, filtramos o dataset com uma abordagem simples baseada em palavras-chave. O código busca termos comuns em documentos médicos, como doctor, clinic, patient, medication, prescription, diagnosis e palavras ligadas a dosagem.

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

Isso nos dá uma forma leve de selecionar exemplos relevantes para a tarefa de OCR médico. Nesta execução, escolhemos 300 amostras com aparência médica.

Depois, normalizamos cada imagem para uma tela fixa de 768×1024. Como imagens de documentos médicos têm tamanhos e proporções variados, esse passo ajuda a tornar os dados de treino mais consistentes. A imagem é redimensionada preservando a proporção original e colocada sobre um fundo branco.

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

Em vez de usar datasets.map, montamos manualmente uma lista Python simples. Isso evita travamentos em alguns ambientes de notebook na nuvem ao reescrever imagens 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"])

Após o pré-processamento, cada exemplo tem dois campos: a imagem normalizada e o texto de OCR alvo.

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>

Também podemos pré-visualizar um dos exemplos redimensionados:

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

A prévia mostra uma imagem de documento médico com dados da clínica, nome do médico, informações do paciente, medicação e assinatura. Isso confirma que o dataset é adequado para a tarefa de ajuste fino de OCR.

Doctors prescription.

5. convertendo amostras em conversas de visão

Com o dataset carregado e as imagens normalizadas, precisamos converter cada exemplo para o formato de conversa esperado pelo Qwen3.5 Vision.

Cada amostra de treino deve incluir três partes:

  • Uma mensagem de sistema definindo o papel do modelo como um mecanismo de transcrição de OCR médico
  • Uma mensagem do usuário contendo a imagem e a instrução de OCR
  • Uma mensagem do assistente com a saída de OCR esperada
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

A função auxiliar acima cria a estrutura de mensagens para treino e inferência. No treino, incluímos o texto de OCR alvo como resposta do assistente. Na inferência, fornecemos apenas a imagem e a instrução, e pedimos que o modelo gere o texto de OCR.

Em seguida, convertemos cada amostra do dataset para esse formato de conversa:

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]

Após a conversão, cada amostra contém uma lista de mensagens. O primeiro exemplo inclui o prompt de sistema, a imagem do documento médico, a instrução de OCR e a transcrição estruturada esperada. Esse formato permite ao modelo aprender a mapear imagem + instrução para a saída de texto correta.

{'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. avaliando o modelo base antes do ajuste fino

Antes do treino, devemos testar o modelo base em um exemplo de OCR. Isso nos dá um ponto de referência para comparar a saída antes e depois do ajuste fino.

Primeiro, definimos uma função auxiliar para aplicar o chat template do modelo. Algumas versões de tokenizer suportam enable_thinking=False, outras não; então a função inclui um fallback para manter a compatibilidade.

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

Depois, definimos a função de geração. Ela monta o prompt de OCR, passa a imagem e as instruções de texto para o tokenizer, gera a saída do modelo e decodifica apenas os tokens recém-gerados.

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]

Agora colocamos o modelo em modo de inferência e geramos o texto de OCR para a primeira imagem de avaliação:

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)

A saída do modelo base é legível, mas não segue exatamente a estrutura alvo:

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

É um bom ponto de partida. O modelo base já consegue ler grande parte do documento, mas entrega o texto em um estilo de OCR natural, e não no formato estruturado do dataset. O ajuste fino deve alinhar a saída ao formato alvo e deixá-la mais consistente.

7. treinando o modelo

Com o dataset no formato correto de conversa multimodal, podemos treinar o modelo usando o SFTTrainer do TRL com o collator de visão do 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()

Primeiro, colocamos o modelo em modo de treino com FastVisionModel.for_training(model). Depois, criamos o trainer usando o dataset de OCR convertido.

A parte importante aqui é o UnslothVisionDataCollator. Como esta é uma tarefa de visão e linguagem, o trainer precisa tratar corretamente as imagens dos documentos médicos e o texto de OCR alvo. O collator prepara esses exemplos multimodais para o modelo durante o ajuste fino supervisionado.

Neste guia, treinamos por 30 steps com batch size por dispositivo de 4 e acumulação de gradiente em 2, resultando em batch efetivo de 8. Isso mantém a execução leve e já mostra como o modelo começa a se adaptar ao formato de OCR estruturado.

Fine-tuning the Qwen 3.5 4b Vision language model

Durante o treino, o Unsloth imprime informações úteis sobre a configuração, como número de exemplos, steps e batches, quantidade de parâmetros treináveis e recursos de economia de memória. Nesta execução, o Unsloth indica que o double buffering está ativado no backward pass, o que ajuda a reduzir a espera durante o gradient checkpointing.

8. avaliando o modelo ajustado

Após o treino, voltamos o modelo para modo de inferência e geramos o texto de OCR para a mesma imagem de avaliação usada antes do ajuste fino.

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)

Depois do ajuste fino, a saída fica muito mais próxima da estrutura alvo do 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>

Isso mostra que o modelo ajustado aprendeu o formato de resposta de OCR esperado. O modelo base já extraía a maior parte do texto visível, mas o ajuste fino ajudou a alinhar a saída ao formato estruturado usado nos dados de treino.

Também podemos testar o modelo em outro exemplo do 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)

Neste segundo exemplo, o modelo segue a estrutura esperada, mas comete um pequeno erro de OCR gerando Amoxicillin em vez 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>

É um lembrete útil de que o modelo está melhorando no alinhamento de formato, mas a precisão do OCR ainda depende da qualidade dos dados, clareza das imagens, tamanho do treino e número de steps. Para produção, treine com um dataset maior e mais diverso e avalie a acurácia em uma ampla variedade de exemplos.

9. salvando o adaptador ajustado

Quando o treino termina, salvamos localmente o adaptador LoRA e o tokenizer.

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

print("Saved adapter to:", ADAPTER_DIR)

A saída confirma que o adaptador foi salvo:

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

Isso salva apenas os pesos do adaptador ajustado, não uma cópia completa do modelo base. Depois, você pode recarregar o modelo base Qwen3.5-4B e aplicar este adaptador para reutilizar o comportamento de OCR ajustado. O modelo salvo fica leve e mais fácil de armazenar, compartilhar ou implementar.

considerações finais

O processo de treino foi leve e prático em uma única NVIDIA RTX 3090. Apesar de ajuste fino de visão e linguagem geralmente exigir muita memória, o uso de VRAM ficou bem abaixo do esperado. O pico ficou por volta de 14 GB, e a média perto de 9 GB — impressionante para ajustar um modelo Qwen3.5 Vision.

O modelo também se adaptou rápido. Após poucos steps, a saída ficou muito mais próxima da estrutura de OCR alvo. O modelo base já lia o documento, mas, depois do ajuste fino, passou a seguir o formato do dataset de forma mais consistente.

Dito isso, a experiência de setup não foi perfeita. Instalar o Unsloth exigiu tentativa e erro. A configuração pode ser difícil, especialmente alternando entre ambientes locais, ambientes virtuais, versões de CUDA e provedores de GPU em nuvem.

Em alguns casos, problemas de compatibilidade com CUDA podem quebrar o ambiente, e depurar isso leva mais tempo do que o previsto. Mesmo começar com uma imagem Docker do Unsloth em uma plataforma de GPU na nuvem pode consumir tempo se o ambiente não funcionar direito logo de cara.

Outra lição importante é que o template do modelo importa. Se o dataset não for convertido para o formato correto de chat ou conversa de visão, o modelo pode não aprender adequadamente. Para o Qwen3.5 Vision, usar a estrutura de mensagens imagem-texto correta é essencial. Sem o template certo, o treino pode até rodar, mas o modelo não vai, de fato, se adaptar à tarefa.

No geral, o Unsloth é uma ótima opção para quem tem acesso limitado a GPU e quer ajustar modelos com eficiência em máquinas locais ou GPUs alugadas. Ele reduz o uso de memória, torna hardwares menores mais úteis e pode acelerar a experimentação. Porém, para quem ajusta e treina modelos com frequência, a complexidade de setup pode ser frustrante. O treino padrão com Transformers costuma ser mais estável, fácil de instalar e simples de reproduzir entre ambientes.

Se a parte chata é a instalação, recomendo ler nosso guia do Unsloth Studio, que mostra como ajustar o Qwen3.5-9B sem configuração manual de ambiente, usando a interface web local do Unsloth.


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

Sou um cientista de dados certificado que gosta de criar aplicativos de aprendizado de máquina e escrever blogs sobre ciência de dados. No momento, estou me concentrando na criação e edição de conteúdo e no trabalho com modelos de linguagem de grande porte.

Tópicos

Principais cursos de IA

Curso

Introdução a Deep Learning com PyTorch

4 h
88.5K
Crie sua primeira rede neural no PyTorch, ajuste hiperparâmetros e trabalhe com tarefas de classificação e regressão.
Ver detalhesRight Arrow
Iniciar Curso
Ver maisRight Arrow
Relacionado
An avian AI exits its cage

blog

12 Alternativas de código aberto ao GPT-4

GPT-4 alternativas de código aberto que podem oferecer desempenho semelhante e exigem menos recursos computacionais para serem executadas. Esses projetos vêm com instruções, fontes de código, pesos de modelos, conjuntos de dados e interface de usuário do chatbot.
Abid Ali Awan's photo

Abid Ali Awan

9 min

Tutorial

Como fazer o ajuste fino do GPT 3.5: Liberando todo o potencial da IA

Explore o GPT-3.5 Turbo e descubra o potencial transformador do ajuste fino. Saiba como personalizar esse modelo de linguagem avançado para aplicativos de nicho, aprimorar seu desempenho e entender os custos associados, a segurança e as considerações de privacidade.
Moez Ali's photo

Moez Ali

Tutorial

Guia de Introdução ao Ajuste Fino de LLMs

O ajuste fino dos grandes modelos de linguagem (LLMs, Large Language Models) revolucionou o processamento de linguagem natural (PLN), oferecendo recursos sem precedentes em tarefas como tradução de idiomas, análise de sentimentos e geração de textos. Essa abordagem transformadora aproveita modelos pré-treinados como o GPT-2, aprimorando seu desempenho em domínios específicos pelo processo de ajuste fino.
Josep Ferrer's photo

Josep Ferrer

Tutorial

Tutorial do DeepChecks: Automatizando os testes de machine learning

Saiba como realizar a validação de dados e modelos para garantir um desempenho robusto de machine learning usando nosso guia passo a passo para automatizar testes com o DeepChecks.
Abid Ali Awan's photo

Abid Ali Awan

Tutorial

Ajuste fino do SAM 2 em um conjunto de dados personalizado: Tutorial

Saiba como fazer o ajuste fino do SAM 2 do Meta AI usando o conjunto de dados Chest CT Segmentation para melhorar o desempenho da segmentação de imagens do modelo na análise de imagens médicas.
Aashi Dutt's photo

Aashi Dutt

Tutorial

Ajuste fino do Llama 3.1 para classificação de textos

Comece a usar os novos modelos Llama e personalize o Llama-3.1-8B-It para prever vários distúrbios de saúde mental a partir do texto.
Abid Ali Awan's photo

Abid Ali Awan

Ver MaisVer Mais