Gemma 4 E2B 微调指南

当前对大规模视觉语言模型的追逐往往忽略了一个实际现实:大多数生产环境没有无限的计算资源。虽然前沿模型占据了头条新闻,但我想探索光谱的另一端——当为高度专业的视觉任务微调一个微型多模态模型时,我们能走多远?

在这个项目中,我使用了Gemma 4 (E2B)——Google Gemma系列中的轻量级模型之一——并使用Unsloth对其进行微调,看看一个紧凑的模型能否在不超出内存限制的情况下,在目标视觉领域提供高准确性。

为了在实际视觉任务上测试该模型,我使用了ChartQA基准测试(HuggingFaceM4/ChartQA):

dataset = load_dataset("HuggingFaceM4/ChartQA")

ChartQA为视觉语言模型提供了强大的挑战,因为它要求视觉感知(读取轴标签、颜色键和绘图点)和逻辑推理来正确回答查询。它将问题分为人类编写的短语和机器生成的模板,使我们能够评估微调模型如何处理严格模式输出与自然人类歧义。

我还使用了通过Kaggle Code提供的免费(有每周配额)T4 x 2 GPU。它有16 GB显存,按今天的标准来说相当小。

以下是训练设置、数据管道和评估的完整细分,以及用于重现结果的完整Kaggle笔记本:微调Gemma4:E2B用于视觉任务

在这篇文章中,我想讲述实验背后的故事。

1、从小型多模态模型开始

选择Gemma 4 E2B是关于权衡的明确选择。使用轻量级视觉语言模型完全改变了微调的计算经济学。对于狭窄的领域特定任务,大型通用模型往往过于强大;基础VLM已经理解低级视觉概念和自然语言结构。目标不是从头开始教模型如何看,而是将其视觉表示转向特定的输出格式。

通过将任务定位为目标微调而非基础训练,硬件需求显著降低。

我们首先设置环境并导入核心Unsloth工具:

%%capture
!pip install -U unsloth
!pip install --no-deps transformers==5.5.0 "tokenizers>=0.22.0,<=0.23.0"
!pip install --no-deps --upgrade timm
!pip install -U datasets huggingface_hub accelerate bitsandbytes trl peft

然后我们导入用于加载和训练视觉模型的组件。

from unsloth import FastVisionModel
from unsloth.trainer import UnslothVisionDataCollator

这里的中心入口点是FastVisionModel,它抽象了单独管理视觉编码器和语言解码器的复杂性,允许我们将整个多模态架构作为统一系统加载和微调。

在Kaggle笔记本中加载Gemma 4 E2B如下所示:

model, tokenizer = FastVisionModel.from_pretrained(
    "unsloth/gemma-4-e2b-it",
    load_in_4bit=True,
    use_gradient_checkpointing="unsloth",
)

在标准Kaggle T4 x 2 GPU上保持此执行实用性的关键是内存管理。通过QLoRA以4位精度加载基础模型,结合Unsloth优化的梯度检查点,大大减少了显存开销。这为前向和后向传播期间的序列长度缩放和批处理内存留出了足够的空间。

2、我们究竟在教模型什么?

微调视觉语言模型需要与传统视觉任务根本不同的心态。在经典的图像分类管道中,映射很简单:图像类别标签

使用VLM,目标从静态分类转向条件文本生成:图像 + 指令目标响应

这种转变改变了我们构建训练数据的方式。模型不仅仅是学习识别视觉特征;它学习将视觉表示与特定的指令上下文和输出格式对齐。

3、将数据格式化为对话

为了教模型这种新行为,我们需要使用OpenAI风格的模式将原始图像-文本样本映射到标准化的多轮对话格式:

def convert_to_conversation(sample):
    return {
        "messages": [
            {
                "role": "user",
                "content": [
                    {"type": "image", "image": sample["image"]},
                    {"type": "text", "text": PROMPT},
                ],
            },
            {
                "role": "assistant",
                "content": [
                    {"type": "text", "text": sample["answer"]},
                ],
            },
        ]
    }

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

数据格式化通常被视为不起眼的数据管道工作,但在多模态微调中,它是定义任务的地方。用户轮次将图像与指令配对(“看这张图片,告诉我X”),而助手轮次提供目标序列。

通过在这些对话结构上进行训练,适配器更新专注于引导模型的预训练视觉表示朝向我们期望的领域风格——使提示成为模型视觉推理的活跃组件,而不仅仅是包装器元数据。

4、建立零样本基线

直接跳入训练很诱人,但首先运行零样本基线至关重要。如果没有在代表性样本上评估开箱即用的模型,就无法衡量真正的适应。你可能在训练后得到正确的响应,并假设微调成功了,而实际上基础模型已经具备该能力——或者只需要轻微的格式对齐。

在附加适配器之前,我们将模型设置为推理模式,并通过其本机聊天模板传递图像-提示对:

FastVisionModel.for_inference(model)

messages = [
    {
        "role": "user",
        "content": [
            {"type": "image", "image": image},
            {"type": "text", "text": PROMPT},
        ],
    }
]

inputs = tokenizer.apply_chat_template(
    messages,
    add_generation_prompt=True,
    return_tensors="pt"
).to("cuda")

outputs = model.generate(
    **inputs,
    max_new_tokens=128,
    use_cache=True,
)

这个简单的检查有两个目的:它确认我们的数据准备管道端到端工作,并为我们提供一个精确的基准来评估稍后的微调权重。

5、配置参数高效微调(PEFT)

建立了基线后,我们使用LoRA(低秩适应)准备模型进行参数高效微调。我们不是更新整个网络的数十亿权重,而是向关键的注意力和MLP层注入低秩、可训练的矩阵。

使用Unsloth的FastVisionModel,我们可以同时显式地针对视觉编码器和语言解码器骨干:

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

这里的关键优势是灵活性。通过同时微调视觉编码器层和语言处理层,模型学习提取任务特定的视觉特征并将其直接映射到我们期望的文本输出。

这种参数高效的方法保留了基础模型广泛的预训练世界知识,同时允许我们在适度的硬件上专门化其行为。

6、训练模型

在构建训练器之前,我们将模型从评估模式切换到训练模式:

FastVisionModel.for_training(model)

为了执行训练循环,我们将转换后的数据集传递给Hugging Face的SFTTrainer。因为我们的数据集包含交错的图像和文本,而不是纯语言令牌,所以使用UnslothVisionDataCollator对于动态批处理和正确填充视觉特征和文本序列至关重要。

训练器配置如下:

from trl import SFTTrainer, SFTConfig

trainer = SFTTrainer(
    model=model,
    train_dataset=converted_train_dataset,
    processing_class=processor.tokenizer,
    data_collator=UnslothVisionDataCollator(model, processor),
    args=SFTConfig(
        per_device_train_batch_size=1,
        gradient_accumulation_steps=4,
        learning_rate=2e-4,
        max_grad_norm=0.3,
        num_train_epochs=1,
        warmup_steps=50,
        optim="adamw_8bit",
        weight_decay=0.001,
        lr_scheduler_type="cosine",
        logging_steps=10,
        save_strategy="steps",
        save_steps=250,
        save_total_limit=2,
        seed=SEED,
        output_dir=OUTPUT_DIR,
        report_to="none",
        remove_unused_columns=False,
        dataset_text_field="",
        dataset_kwargs={"skip_prepare_dataset": True},
        max_length=1024,
    ),
)

与其将此变为冗长的超参数调优练习,超参数设置保持精简和实用:8位AdamW优化器以节省显存,有效批大小为8(通过梯度累积),以及短暂的1个epoch运行。

这里的目标不是过拟合以达到完美,而是验证模型的适配器层学习将视觉输入映射到我们期望的目标模式的速度和效果。

7、评估模型:超越视觉检查

虽然比较单个文本输出可以立即了解模型行为,但适当的实验需要在测试数据集上进行结构化评估。

我构建了一个结构化评估循环来测量数据集上的两个关键指标,而不是仅依赖定性检查:

  • 精确匹配分数:预测文本和真实标签之间严格的令牌对令牌相等。
  • 宽松匹配准确性:灵活评分以考虑轻微的空白或字符串格式变化。

7.1 评估管道

使用pandas和tqdm,我们遍历评估样本并从基础模型和微调适配器收集结构化预测:

def evaluate_model(model, processor, eval_dataset, description="Evaluating"):
    rows = []
    model.eval()
    for i, sample in enumerate(tqdm(eval_dataset, desc=description)):
        question = sample["query"]
        reference = str(sample["label"][0])
        prediction = generate_answer(
            image=sample["image"],
            question=question,
            model=model,
            processor=processor,
            max_new_tokens=32,
        )
        exact = exact_match_score(prediction, reference)
        relaxed = relaxed_match_score(prediction, reference)
        rows.append({
            "index": i,
            "question": question,
            "reference": reference,
            "prediction": prediction,
            "exact_match": exact,
            "relaxed_accuracy": relaxed,
            "human_or_machine": sample["human_or_machine"],
        })
    results = pd.DataFrame(rows)
    return results

finetuned_results = evaluate_model(
    model=model,
    processor=processor,
    eval_dataset=test_raw,
    description="Evaluating fine-tuned Gemma 4 E2B",
)

7.2 评估结果:基线 vs. 微调

基础模型和微调模型之间的比较显示出明显的改进,即使只进行了一次微调步骤并且只使用了4000个样本进行训练。评估在200个测试样本上运行基础模型和微调模型。下图显示了两个模型在两个指标(精确匹配和宽松准确性)上的性能比较。

实验设置和获得结果的摘要也以这种简洁的半结构化形式显示:

{'model': 'unsloth/gemma-4-E2B-it',
 'train_samples': 4000,
 'test_samples': 200,
 'base_exact_match': 0.475,
 'base_relaxed_accuracy': 0.675,
 'finetuned_exact_match': 0.65,
 'finetuned_relaxed_accuracy': 0.75,
 'exact_match_gain': 0.175,
 'relaxed_accuracy_gain': 0.075,
 'seed': 3407}

我们还可以使用与数据关联的人类/机器标志对结果进行分组。ChartQA数据的标签部分是机器生成的,部分是人类生成的。在接下来的两张图中,我们显示了按人类/机器标志划分的准确性指标(包括精确匹配和宽松准确性):

8、接下来我会尝试什么

这个笔记本被设计为轻量级、实用的概念验证,而不是详尽的基准测试研究。如果我要为生产环境扩展这个实验,有三个明确的方向我会接下来探索:

8.1 受控模块消融

为了优化微调速度和内存使用,值得隔离哪些参数带来最大的性能增益。具体来说,比较:

  • 编码器冻结:finetune_vision_layers=False(仅适应语言层)
  • 完整多模态微调:finetune_vision_layers=True(适应视觉+语言层)

测试更新视觉编码器是否确实在此特定任务上提供可衡量的准确性提升——或者仅语言适配器调优是否足够——将阐明显存消耗和训练速度之间的权衡。

8.2 数据缩放曲线

不是在固定数据集大小上评估单个检查点,而是跨缩放阈值映射性能(例如100、500、1,000和5,000个样本)。

对于实际部署,了解Gemma 4 E2B的数据效率曲线比单个准确性指标更有用——它准确告诉我们模型达到可接受的任务精度之前需要多少人工标注工作。

8.3 分布外(OOD)测试

除了标准验证拆分之外,针对分布外视觉输入(例如不同的照明、分辨率变化或未见过的文档布局)测试适配器将暴露模型的真实边界限制和失败模式。

9、最终备注

这个实验强化了应用AI的一个引人注目的现实:我们并不总是需要大规模、高参数模型来解决实际的视觉问题。

通过将像Gemma 4 (E2B)这样的轻量级多模态骨干与通过Unsloth进行的参数高效微调相结合,我们得到了一种快速、低成本的专业计算机视觉配方:

紧凑基础VLM + 领域指令 + LoRA生产边缘模型

这里的主要目标不是将Gemma 4变成更好的通用模型——而是引导其现有的视觉语言表示朝向特定任务。对于大多数实际工程约束,这种专业化正是重要的。


原文链接:Teaching Gemma 4:E2B a New Visual Skill

汇智网翻译整理,请转载文章时标明出处。