LLaVA加载报错?修复CLIPImageProcessor无patch_size问题
2025-05-04 22:01:04
搞定 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.safetensors
、config.json
、tokenizer.model
、preprocessor_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 模型呢?
为什么会出这个错?
这个问题根源在于 LlavaProcessor
和 CLIPImageProcessor
的结构以及 Hugging Face transformers
库加载配置的方式。
- 组件分离 :
LlavaProcessor
本身是一个方便的封装,它内部组合了两个主要部分:一个用于处理文本的tokenizer
(通常是 LlamaTokenizer 或其变种)和一个用于处理图像的image_processor
(在 LLaVA 中通常是CLIPImageProcessor
)。这两部分是相对独立的。 - 配置来源不同 :
- 模型的
vision_config
(包括patch_size
)是定义在整个 LLaVA 模型 (LlavaForConditionalGeneration
)的配置文件 (config.json
) 中的vision_config
字段里。这个配置决定了模型内部视觉部分(比如 Vision Transformer)如何处理图像块 (patches)。 CLIPImageProcessor
在初始化时,会根据一个 预处理器 的配置(preprocessor_config.json
)来设定其行为,比如图像缩放尺寸 (size
)、归一化参数 (image_mean
,image_std
) 等。
- 模型的
patch_size
的归属 :patch_size
本质上是视觉模型 的一个参数,而不是图像预处理器 (CLIPImageProcessor
) 的直接配置项。CLIPImageProcessor
主要负责准备输入给模型的图像张量(调整大小、归一化等),它本身不直接关心模型内部是如何分块的。虽然处理器的行为(比如最终图像分辨率)可能需要和模型的patch_size
兼容,但它自身不存储或暴露patch_size
这个属性。- 加载机制 :当你调用
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
。
操作步骤:
- 优先加载模型: 使用
LlavaForConditionalGeneration.from_pretrained
加载你的模型。 - 加载处理器: 使用
LlavaProcessor.from_pretrained
加载对应的处理器。 - 需要时从模型获取
patch_size
: 如果你的代码逻辑确实需要用到patch_size
这个值,直接从加载好的model
对象中读取。 - 进行推理: 正常使用
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
是模型的属性,就从模型那里拿。推理时,processor
和 model
会自动协同工作。
方案二:检查保存和加载的完整性
有时候问题可能出在保存或加载的环节不够“原子化”,导致配置未能完全对齐。虽然你的保存代码看起来是标准的,但确保加载顺序和方式也能提高稳定性。
原理:
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
,但理解它的内容有助于排查是否是这个文件本身出了问题(比如损坏或内容不兼容)。
操作步骤:
-
找到文件: 在你的
save_path
目录下找到preprocessor_config.json
文件。 -
查看内容: 用文本编辑器打开它。你会看到类似下面的 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 } // ... 可能还有其他字段 }
-
分析: 注意,这里面通常没有
patch_size
字段。CLIPImageProcessor
加载时就读取这些参数。 -
(极不推荐)尝试修改: 你 可以 尝试在这个 JSON 文件里添加
"patch_size": 14
字段,但这非常不推荐 。首先,CLIPImageProcessor
的from_pretrained
方法很可能根本不认这个字段。其次,这破坏了配置文件的原始意图,可能导致难以预料的行为。如果这个文件本身损坏或内容缺失,更应该考虑重新保存模型和配置,而不是手动修补。
安全建议/警告:
手动修改 preprocessor_config.json
文件风险很高。除非你非常清楚自己在做什么,并且其他方法都无效,否则不要 这样做。配置文件的结构和字段都是由 transformers
库内部定义的,随意更改可能导致加载失败或更隐蔽的运行时错误。依赖方案一或方案二从模型本身获取信息是更安全、更标准的做法。
关键点总结
AttributeError: 'CLIPImageProcessor' object has no attribute 'patch_size'
是因为CLIPImageProcessor
对象本身设计上就没有这个属性。patch_size
是 LLaVA 模型 视觉部分 (vision_config
) 的配置参数,了模型内部如何处理图像块。- 最可靠获取
patch_size
的方式是从加载好的LlavaForConditionalGeneration
模型实例的.config.vision_config.patch_size
属性读取。 - 推荐的加载顺序是先用
LlavaForConditionalGeneration.from_pretrained
加载模型,再用LlavaProcessor.from_pretrained
加载处理器。 - 对于标准的图像问答或生成任务,你通常不需要在代码中显式地使用
patch_size
这个值,processor
和model
会在内部配合好。 - 避免手动修改
preprocessor_config.json
来添加patch_size
,这很可能无效且有风险。
遵循方案一或方案二的加载和使用方式,应该就能顺利地加载并运行你的 fine-tuned LLaVA 模型了。