跳至内容

梯度检查点:借助 Unsloth 与 NVIDIA 高效微调

了解 Unsloth 面向 NVIDIA 优化的梯度检查点与 QLoRA 如何在对 Qwen3.5 4B 进行医疗文档 OCR 微调时降低显存占用。
更新 2026年8月14日  · 12分钟

用 AI 探索

ChatGPTClaudePerplexity

Unsloth 与 NVIDIA 的合作专注于通过减少隐藏的训练开销来加速微调。与其仅依赖更大的 GPU 或更小的模型,这些改进针对训练过程中的瓶颈,例如重复的元数据构建、激活重载延迟以及低效的 token 路由。对用户而言,这意味着在受支持的 NVIDIA GPU 上训练更快、微调流程更顺畅。

在本指南中,我们将了解这些新的 Unsloth 性能提升,并将 Unsloth 的优化微调流程应用于一个实用的视觉-语言任务。我们将对 Qwen3.5 Vision 4B 进行医疗 OCR 微调,让模型使用 OCR 数据集中的一个小型、具有医疗风格的子集,学习从医疗文档图像中抽取结构化文本。

我们将使用:

  • Qwen3.5 4B(Vision) 作为基座模型
  • 4 位 QLoRA 以降低显存占用
  • LoRA 适配器 提升微调效率
  • Unsloth 梯度检查点 在训练时节省内存
  • 医疗 OCR 数据集的 300 条样本子集
  • 固定尺寸的图像预处理,提升视觉训练稳定性
  • 微调前后评估,对比基座与微调后模型的输出

使用 Unsloth 的 NVIDIA 优化微调流程

在开始微调前,先了解一下 Unsloth 与 NVIDIA 的合作带来了哪些改进,以及它与本指南的关联。

Unsloth 报告称,与 NVIDIA 的合作让大语言模型训练在其既有 2–5 倍微调加速的基础上再快约 25%,且不损失精度。这些收益来自于降低主训练过程之外的隐藏开销,而非改变模型的学习目标。换句话说,目标是在保持精度不变的前提下,让微调更快、更高效。

来源:How to Make LLM Training Faster with Unsloth and NVIDIA

来源:How to Make LLM Training Faster with Unsloth and NVIDIA 

训练性能改进

此次合作报告了多项性能改进,包括:

  • 在 Qwen3-14B QLoRA SFT 基准上,通过打包序列元数据缓存,每个 batch 加速 14.3%
  • 双缓冲异步梯度检查点带来 8B 模型 8.4%、14B 模型 6.7%、32B 模型 4.6% 的加速
  • 针对 GPT-OSS MoE 训练约 10–15% 的加速,其中目标路由路径前向快 23%、反向快 13%

Unsloth 与 NVIDIA 合作带来的最大性能收益之一适用于打包的纯文本训练和专家混合(MoE)模型。我们在本指南中没有使用这些,因为我们的流程专注于 Qwen3.5 Vision 的 OCR 微调。

本指南使用 NVIDIA RTX 3090 GPU,因此流程围绕 NVIDIA GPU 加速与 Unsloth 的优化微调路径构建。我们不对 Unsloth 与其他训练器进行基准对比,因此不应将本指南视为对所报告加速的独立证据。我们只是将 Unsloth 的优化微调流程应用于一个真实的视觉-语言任务。

梯度检查点

在本流程中,最相关的优化是 Unsloth 的梯度检查点。它通过避免在 GPU 内存中存储每个激活来减少训练过程中的内存占用。对于视觉-语言微调尤其有用,因为模型需要同时处理图像输入和文本输出。

1. 为更快的微调设置 Unsloth

要运行本指南,您需要使用 NVIDIA GPU。您可以从 RunPod、Vast.ai 或其他任何云 GPU 提供商租用。我起初尝试使用 RunPod,因为它通常快速且稳定,但当时可用的 RTX 3090 选项有限。因此,我在本流程中使用了一台 Vast.ai RTX 3090 GPU 机器

如需平台对比,请查看我们的 最佳 GPU 云服务商指南

Vast.ai RTX 3090 instance

来源: Vast.ai | Console 

启动实例后,我打开 Jupyter Notebook 并创建了一个新笔记本。在 Vast.ai 上,我选择了可用的主环境内核,在笔记本环境中安装所需的 Python 包,而不影响系统级依赖。

安装所需包

首先,安装用于 Unsloth、PyTorch、视觉模型训练、数据集加载以及 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

这些包基于官方的 Unsloth 笔记本设置,包含加载 Qwen3.5 Vision、准备图文数据、并使用 Unsloth 微调模型所需的主要库。

配置 CUDA 设备

接下来,我们配置 CUDA 设备并验证是否有正确的 NVIDIA GPU 可用。由于本指南使用 RTX 3090,代码将检查 CUDA 是否启用、确认所选 GPU、打印 CUDA 和 PyTorch 版本,并验证该机器是否有足够的显存以进行本实验。

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

在我的设置中,环境返回了以下 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

这确认了笔记本正在一块 NVIDIA GeForce RTX 3090 上运行,且显存足以支持本次微调实验。

定义训练设置与提示词

验证 GPU 后,我们定义模型、数据集、训练设置、输出目录、图像大小,以及 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."

此处我们使用 Unsloth 的 Qwen3.5 Vision 4B 和医疗 OCR 视觉数据集。为本指南选择 300 条样本并训练 30 步,能够保持运行轻量化,同时展示模型如何适应目标 OCR 格式。

固定的 768×1024 图像尺寸有助于在训练期间保持图像输入一致。医疗文档在分辨率和纵横比上差异很大,将其调整到固定画布可让流程更顺畅,并减少视觉-语言微调中的形状相关问题。

2. 加载模型

环境就绪后,我们使用 Unsloth 的 FastVisionModel 加载 Qwen3.5 Vision 4B 模型。

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)

加载模型后,输出确认已加载正确的模型、启用了 4 位模式,且模型已放置在 GPU 上:

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

这里,FastVisionModel.from_pretrained() 会加载视觉-语言模型,并应用 Unsloth 的优化以实现更快、更省内存的微调。我们还启用了 load_in_4bit,通过 4 位精度加载模型来降低显存占用。这在使用 24 GB 显存的 RTX 3090 等 GPU 时尤其实用。

我们还通过 use_gradient_checkpointing="unsloth" 启用 Unsloth 的梯度检查点。

这有助于在训练时降低内存占用,对需要同时处理图像与文本输入的视觉-语言模型尤为重要。

3. 添加 LoRA 适配器

接下来我们为模型添加 LoRA 适配器LoRA 允许我们仅微调较小的一组可训练参数,而非更新完整模型。这让训练更快、更省内存,并更易在单块 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,
)

在本指南中,适配器同时加在模型的视觉与语言部分。这有助于模型学会读取医疗文档图像,并生成预期的结构化 OCR 文本。完成此步骤后,模型即可在医疗 OCR 数据集上进行训练。

4. 加载医疗 OCR 数据集

现在从 Hugging Face 加载 医疗 OCR 数据集,并准备一个小型子集用于微调。

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

该数据集包含文档图像及其对应的 OCR 文本。由于本指南只需医疗风格的 OCR 示例,我们使用简单的关键词方法进行过滤。代码会搜索医疗文档中常见的术语,如 doctor、clinic、patient、medication、prescription、diagnosis,以及剂量相关词。

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

这样我们就能以一种轻量方式选择与医疗 OCR 任务相关的示例。本次运行将选择 300 条看起来像医疗文档的样本。

接着,我们将每张图像标准化到固定的 768×1024 画布。医疗文档图像大小与纵横比差异较大,此步骤可让训练数据更一致。图像在保持原始纵横比的前提下被缩放,然后放置在白色背景上。

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

我们不使用 datasets.map,而是手动构建一个简单的 Python 列表。这可避免在某些云端笔记本环境中重写 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"])

预处理后,每个样本包含两个字段:标准化图像与目标 OCR 文本。

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>

我们还可以预览一条已调整大小的样本:

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

预览显示了一张具有医疗风格的文档图像,包含诊所信息、医生姓名、患者信息、用药与签名。这确认了该数据集适用于 OCR 微调任务。

Doctors prescription.

5. 将样本转换为视觉对话

加载并标准化图像后,我们需要将每个样本转换为 Qwen3.5 Vision 期望的对话格式。

每个训练样本应包含三部分:

  • 系统消息,将模型的角色设定为医疗 OCR 转写引擎
  • 用户消息,包含图像与 OCR 指令
  • 助手消息,包含期望的 OCR 输出
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

上述辅助函数会为训练与推理构建消息结构。训练期间,我们将目标 OCR 文本作为助手回复;推理时,只提供图像与指令,然后请求模型生成 OCR 文本。

接着,将每个数据集样本转换为该对话格式:

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]

转换后,每个样本包含一组消息。第一个示例包括系统提示、医疗文档图像、OCR 指令以及期望的结构化 OCR 转写。该格式允许模型学习如何将图像与指令映射到正确的文本输出。

{'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. 微调前评估基座模型

训练前,我们应在一个 OCR 示例上测试基座模型。这可以提供参考点,以便对比微调前后的模型输出。

首先,我们定义一个辅助函数来应用模型的聊天模板。部分 tokenizer 版本支持 enable_thinking=False,部分不支持,因此该函数包含回退逻辑以保持代码兼容性。

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

接着定义生成函数。它构建 OCR 提示,向 tokenizer 传入图像与文本指令,生成模型输出,并仅解码新生成的 token。

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]

现在将模型切换到推理模式,并为首张评估图像生成 OCR 文本:

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)

基座模型的输出可读,但未严格遵循目标结构:

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

这是一个有用的起点。基座模型已经能读取大部分文档,但它以自然的 OCR 风格输出,而非数据集中使用的结构化格式。微调应能帮助模型对齐目标格式,使其响应更一致。

7. 训练模型

当数据集转换为正确的视觉对话格式后,我们即可使用 TRL 的 SFTTrainer,配合 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()

首先使用 FastVisionModel.for_training(model) 将模型切换到训练模式。然后,使用转换后的 OCR 数据集创建训练器。

这里的重点是 UnslothVisionDataCollator。由于这是一个视觉-语言任务,训练器需要正确处理医疗文档图像与目标 OCR 文本。该整理器会为监督微调准备这些多模态样本。

在本指南中,我们训练 30 步,设备端 batch 大小为 4,梯度累积为 2,有效 batch 大小为 8。这样既轻量,又能展示模型如何开始适应结构化 OCR 格式。

微调 Qwen 3.5 4b 视觉语言模型

训练期间,Unsloth 会打印有关设置的有用信息,包括样本数、步数与批次数、可训练参数数量,以及内存节省功能。在本次运行中,Unsloth 报告反向传播启用了双缓冲,这有助于在梯度检查点期间减少等待时间。

8. 评估微调后的模型

训练完成后,我们将模型切换回推理模式,并对微调前使用的同一评估图像生成 OCR 文本。

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)

微调后,模型输出与数据集目标结构更加接近:

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>

这表明微调后的模型已学会预期的 OCR 响应格式。基座模型已能提取大部分可见文本,而微调帮助其与训练数据使用的结构化格式保持一致。

我们也可以在数据集中的另一条样本上测试模型:

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)

在第二个样本中,模型遵循了预期结构,但出现了一个小的 OCR 错误:将 Amlodipine 识别为 Amoxicillin。

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>

这提醒我们,模型在格式对齐方面虽有提升,但 OCR 准确性仍取决于数据质量、图像清晰度、训练规模与微调步数。对于生产级 OCR 系统,应使用更大、更多样化的数据集进行训练,并在更广泛的示例上评估准确率。

9. 保存微调后的适配器

训练完成后,我们将 LoRA 适配器与 tokenizer 保存到本地。

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

print("Saved adapter to:", ADAPTER_DIR)

输出确认适配器已保存:

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

此操作仅保存微调后的适配器权重,而非基座模型的完整副本。之后您可以重新加载基座 Qwen3.5-4B,并应用该适配器以复用微调后的 OCR 行为。这样保存的模型更轻量,便于存储、共享或部署。

结语

整个训练过程在单块 NVIDIA RTX 3090 上轻量且实用。尽管视觉-语言微调通常内存占用较高,但本次运行的显存使用量远低于预期。显存峰值约为 14 GB,平均更接近 9 GB,对于微调 Qwen3.5 Vision 而言相当可观。

模型也适应得很快。仅经过少量训练步数,输出就更接近目标 OCR 结构。基座模型虽然能读出文档,但微调后其对数据集格式的遵循更为一致。

话虽如此,搭建体验并不完美。安装 Unsloth 花费了大量试错。跨本地环境、虚拟环境、CUDA 版本与云 GPU 提供商之间进行配置,可能较为困难。

在某些情况下,CUDA 兼容性问题会破坏环境,调试这些问题可能比预期更耗时。即便在云 GPU 平台使用 Unsloth 的 Docker 镜像,如果环境并非开箱即用,也会比较费时。

另一个重要经验是模型模板很关键。如果没有将数据集转换为正确的聊天或视觉对话格式,模型可能无法 正确学习。对于 Qwen3.5 Vision,使用正确的图文消息结构至关重要。没有合适的模板,即便训练在运行,模型也可能无法真正适应任务。

总的来说,对于 GPU 资源有限、希望在本地机器或租用 GPU 上高效微调模型的用户,Unsloth 是一个很强的选项。它能够降低内存占用、提升小型硬件的实用性,并加速试验迭代。不过,对于经常进行微调与训练的用户而言,其搭建复杂度可能令人沮丧。基于 Transformers 的标准训练通常更稳定、更易安装,也更易在不同环境中复现。

如果安装摩擦正是让您打退堂鼓的原因,建议阅读我们的 Unsloth Studio 指南,它演示了如何在 Unsloth 的本地 Web UI 中,无需手动环境搭建即可微调 Qwen3.5-9B。

主题

顶尖 AI 课程

Courses

使用 PyTorch 的深度学习入门

4小时
88.5K
学习如何在 PyTorch 中构建你的第一个神经网络、调整超参数,并解决分类和回归问题。
查看详情Right Arrow
开始课程
查看更多Right Arrow