返回

LLaVA加载报错?修复CLIPImageProcessor无patch_size问题

Ai

搞定 LLaVA 加载 fine-tuned 模型时的 AttributeError: 'CLIPImageProcessor' 无 'patch_size' 属性

我们碰到的问题是:在 Google Colab 里 fine-tune 了一个 LLaVA 模型,保存到 Google Drive 后,尝试加载回来做推理时,报了个错:AttributeError: 'CLIPImageProcessor' object has no attribute 'patch_size'

具体点说,保存模型的代码大概是这样的:

# 省略挂载 Google Drive 和 import os 的代码
save_path = "/content/drive/MyDrive/fineTune model1/LLaVA-med-MAKAUT_v1"  
os.makedirs(save_path, exist_ok=True)  

# 保存模型主体、tokenizer 和 image_processor
trainer.model.save_pretrained(save_path)  
trainer.tokenizer.save_pretrained(save_path)  
processor.image_processor.save_pretrained(save_path) 

保存后,save_path 目录下生成了 adapter_model.safetensorsconfig.jsontokenizer.modelpreprocessor_config.json 等一堆配置文件和模型文件。看起来一切正常。

但是,加载的代码一跑就出问题:

import torch  
from PIL import Image  
from transformers import LlavaProcessor, LlavaForConditionalGeneration, CLIPImageProcessor  

model_path = "/content/drive/MyDrive/fineTune model/LLaVA-med-MAKAUT_v1" 
# 问题出在这里
processor1 = LlavaProcessor.from_pretrained(model_path)

# 尝试访问 patch_size 属性
# print(processor1.image_processor.patch_size) 
# >> AttributeError: 'CLIPImageProcessor' object has no attribute 'patch_size'

奇怪的是,如果我们先加载模型本身,查看它的 vision_config,里面明明是有 patch_size 的:

# 假设 new_model_v1 是通过 LlavaForConditionalGeneration.from_pretrained(model_path) 加载的模型
patch_size = new_model_v1.config.vision_config.patch_size  
print("Patch size:", patch_size) 
# 输出: Patch size: 14 

尝试手动给 processor1.image_processor 添加 patch_size 属性也不行,因为它确实没设计这个属性。

# 这样做是无效的
# processor1.image_processor.patch_size = 14 

那这到底是咋回事?为啥模型配置里有 patch_size,加载出来的 image_processor 对象却没有?又该如何正确加载和使用这个 fine-tuned 模型呢?

为什么会出这个错?

这个问题根源在于 LlavaProcessorCLIPImageProcessor 的结构以及 Hugging Face transformers 库加载配置的方式。

  1. 组件分离LlavaProcessor 本身是一个方便的封装,它内部组合了两个主要部分:一个用于处理文本的 tokenizer(通常是 LlamaTokenizer 或其变种)和一个用于处理图像的 image_processor(在 LLaVA 中通常是 CLIPImageProcessor)。这两部分是相对独立的。
  2. 配置来源不同
    • 模型的 vision_config(包括 patch_size)是定义在整个 LLaVA 模型LlavaForConditionalGeneration)的配置文件 (config.json) 中的 vision_config 字段里。这个配置决定了模型内部视觉部分(比如 Vision Transformer)如何处理图像块 (patches)。
    • CLIPImageProcessor 在初始化时,会根据一个 预处理器 的配置(preprocessor_config.json)来设定其行为,比如图像缩放尺寸 (size)、归一化参数 (image_mean, image_std) 等。
  3. patch_size 的归属patch_size 本质上是视觉模型 的一个参数,而不是图像预处理器 (CLIPImageProcessor) 的直接配置项。CLIPImageProcessor 主要负责准备输入给模型的图像张量(调整大小、归一化等),它本身不直接关心模型内部是如何分块的。虽然处理器的行为(比如最终图像分辨率)可能需要和模型的 patch_size 兼容,但它自身不存储或暴露 patch_size 这个属性。
  4. 加载机制 :当你调用 LlavaProcessor.from_pretrained(model_path) 时,它会:
    • model_path 加载 tokenizer 的配置和文件。
    • model_path 寻找 preprocessor_config.json 来初始化 image_processor(也就是 CLIPImageProcessor)。
    • 重点:它不一定会自动去读取模型主配置文件 (config.json) 里的 vision_config.patch_size 并塞给 CLIPImageProcessor 实例。 CLIPImageProcessor 的实例被创建出来时,就是按照 preprocessor_config.json 定义的属性来的,而这个文件通常不包含 patch_size

所以,报错 AttributeError: 'CLIPImageProcessor' object has no attribute 'patch_size' 是符合预期的。你试图访问一个该对象本身就没有的属性。即使模型配置里有,CLIPImageProcessor 实例并不知道。

怎么搞定?

别慌,有好几种方法可以解决或者绕过这个问题,确保你能顺利使用 fine-tuned 模型。

方案一:直接从模型配置获取(推荐)

既然 patch_size 是模型视觉部分的配置,最可靠的来源就是加载好的模型本身。

原理:
模型加载时会完整读取 config.json,包括 vision_config。因此,只要模型加载成功,就可以从模型对象的 config.vision_config 中安全地获取 patch_size。对于推理来说,你通常不需要在 processor 对象上显式地知道 patch_size

操作步骤:

  1. 优先加载模型: 使用 LlavaForConditionalGeneration.from_pretrained 加载你的模型。
  2. 加载处理器: 使用 LlavaProcessor.from_pretrained 加载对应的处理器。
  3. 需要时从模型获取 patch_size 如果你的代码逻辑确实需要用到 patch_size 这个值,直接从加载好的 model 对象中读取。
  4. 进行推理: 正常使用 processor 处理输入,model.generate 进行推理。processor 在内部处理图像时,会根据其自身的配置(如 size)进行操作,这些配置在 fine-tuning 时已经和模型兼容了。

代码示例:

import torch
from PIL import Image
from transformers import LlavaProcessor, LlavaForConditionalGeneration

model_path = "/content/drive/MyDrive/fineTune model/LLaVA-med-MAKAUT_v1" 
device = "cuda" if torch.cuda.is_available() else "cpu"

# 1. 先加载模型
# 如果遇到内存不足的问题,可以考虑加载为半精度(float16) 或 8bit/4bit 量化
# model = LlavaForConditionalGeneration.from_pretrained(
#     model_path, 
#     torch_dtype=torch.float16, 
#     low_cpu_mem_usage=True, 
# ).to(device)
# 如果显存足够,可以直接加载
model = LlavaForConditionalGeneration.from_pretrained(model_path).to(device)
print("Model loaded successfully.")

# 2. 加载处理器
processor = LlavaProcessor.from_pretrained(model_path)
print("Processor loaded successfully.")

# 3. 如果需要 patch_size,从模型配置读取
vision_config = model.config.vision_config
patch_size = vision_config.patch_size
print(f"Patch size from model config: {patch_size}") 
# 这里只是演示如何获取,推理流程通常不需要显式用它

# 4. 准备输入并进行推理
# 假设你有一张图片 image.png 和文本提示
raw_image = Image.open("image.png").convert("RGB")
prompt = "USER: <image>\nWhat are the things I should be cautious about when I visit this place?\nASSISTANT:"

# 使用 processor 处理图像和文本
inputs = processor(prompt, raw_image, return_tensors='pt').to(device, dtype=torch.float16 if device=="cuda" else torch.float32) # 如果模型加载为 float16

# 生成回复
output = model.generate(**inputs, max_new_tokens=100, do_sample=False)

# 解码输出
response = processor.decode(output[0][2:], skip_special_tokens=True) # output[0][2:] 根据模型和任务可能需要调整
print(response)

# 清理显存(如果需要)
# del model
# del inputs
# torch.cuda.empty_cache() 

要点: 这个方法是最直接、最符合逻辑的。patch_size 是模型的属性,就从模型那里拿。推理时,processormodel 会自动协同工作。

方案二:检查保存和加载的完整性

有时候问题可能出在保存或加载的环节不够“原子化”,导致配置未能完全对齐。虽然你的保存代码看起来是标准的,但确保加载顺序和方式也能提高稳定性。

原理:
LlavaForConditionalGeneration.from_pretrained 在加载模型时,会更全面地处理相关的配置,包括视觉部分的初始化。先加载模型再加载处理器,有时能让库更好地处理内部依赖和配置对齐。

操作步骤:
基本同方案一,强调先加载 LlavaForConditionalGeneration 再加载 LlavaProcessor

代码示例: (同方案一的代码)

# ... (imports and model_path definition)

# 1. 先加载模型
model = LlavaForConditionalGeneration.from_pretrained(
    model_path, 
    # Optional: Add precision control if needed
    # torch_dtype=torch.float16, 
    # low_cpu_mem_usage=True,
).to(device)

# 2. 后加载处理器
processor = LlavaProcessor.from_pretrained(model_path)

# 3. 从模型获取 patch_size (如果需要)
patch_size = model.config.vision_config.patch_size
print(f"Patch size from model config: {patch_size}")

# 4. 进行推理
# ... (inference code as in Solution 1) 

要点: 这个方案本质上是方案一的良好实践。加载模型主体通常是更复杂的操作,先完成它,确保视觉模型(包括其配置)已正确初始化,再加载辅助的处理器,流程更稳妥。

方案三:检查 preprocessor_config.json (了解即可,一般不需手动改)

这个方案是为了理解发生了什么,通常不推荐作为首选解决方案,因为它涉及手动检查甚至修改配置文件,容易出错。

原理:
理论上,CLIPImageProcessor 的行为是由 preprocessor_config.json 控制的。我们可以检查这个文件,看看里面到底有什么。虽然它通常不包含 patch_size,但理解它的内容有助于排查是否是这个文件本身出了问题(比如损坏或内容不兼容)。

操作步骤:

  1. 找到文件: 在你的 save_path 目录下找到 preprocessor_config.json 文件。

  2. 查看内容: 用文本编辑器打开它。你会看到类似下面的 JSON 结构:

    {
      "crop_size": {
        "height": 224,
        "width": 224 
      },
      "do_center_crop": true,
      "do_convert_rgb": true,
      "do_normalize": true,
      "do_rescale": true,
      "do_resize": true,
      "image_mean": [
        0.48145466,
        0.4578275,
        0.40821073
      ],
      "image_processor_type": "CLIPImageProcessor",
      "image_std": [
        0.26862954,
        0.26130258,
        0.27577711
      ],
      "resample": 3,
      "rescale_factor": 0.00392156862745098,
      "size": {
        "shortest_edge": 224
      }
      // ... 可能还有其他字段
    }
    
  3. 分析: 注意,这里面通常没有 patch_size 字段。CLIPImageProcessor 加载时就读取这些参数。

  4. (极不推荐)尝试修改:可以 尝试在这个 JSON 文件里添加 "patch_size": 14 字段,但这非常不推荐 。首先,CLIPImageProcessorfrom_pretrained 方法很可能根本不认这个字段。其次,这破坏了配置文件的原始意图,可能导致难以预料的行为。如果这个文件本身损坏或内容缺失,更应该考虑重新保存模型和配置,而不是手动修补。

安全建议/警告:
手动修改 preprocessor_config.json 文件风险很高。除非你非常清楚自己在做什么,并且其他方法都无效,否则不要 这样做。配置文件的结构和字段都是由 transformers 库内部定义的,随意更改可能导致加载失败或更隐蔽的运行时错误。依赖方案一或方案二从模型本身获取信息是更安全、更标准的做法。

关键点总结

  1. AttributeError: 'CLIPImageProcessor' object has no attribute 'patch_size' 是因为 CLIPImageProcessor 对象本身设计上就没有这个属性。
  2. patch_size 是 LLaVA 模型 视觉部分 (vision_config) 的配置参数,了模型内部如何处理图像块。
  3. 最可靠获取 patch_size 的方式是从加载好的 LlavaForConditionalGeneration 模型实例的 .config.vision_config.patch_size 属性读取。
  4. 推荐的加载顺序是先用 LlavaForConditionalGeneration.from_pretrained 加载模型,再用 LlavaProcessor.from_pretrained 加载处理器。
  5. 对于标准的图像问答或生成任务,你通常不需要在代码中显式地使用 patch_size 这个值,processormodel 会在内部配合好。
  6. 避免手动修改 preprocessor_config.json 来添加 patch_size,这很可能无效且有风险。

遵循方案一或方案二的加载和使用方式,应该就能顺利地加载并运行你的 fine-tuned LLaVA 模型了。