ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

【Bug已解决】Can i convert open-clip trained models (.pt) using code src/transformers/models/clip/convert

2026/8/8 6:08:18 拓冰建站 浏览量
【Bug已解决】Can i convert open-clip trained models (.pt) using code src/transformers/models/clip/convert 【Bug已解决】Can i convert open-clip trained models (.pt) using code srctransformersmodelsclipconvert_clip_original_pytorch_to_hf.py 解决方案一、现象长什么样你想把用 open_clip 训练出来的.pt权重转成 HuggingFace CLIP 格式于是用 transformers 提供的转换脚本convert_clip_original_pytorch_to_hf.py。但跑起来报错# 现象 Akey 不匹配大量张量没被转换 KeyError: visual.conv1.weight # open_clip 的 key 是 visual.conv1.weight脚本期望的也是这个 # 但某些 open_clip 变体把视觉塔放在 visual.trunk... 下对不上 # 现象 B配置文件缺失/字段不对 ValueError: Missing vision_config.proj_dim in config; open_clip checkpoint does not provide it directly, need to infer from visual.proj shape. # 现象 C文本塔结构不同如自定义 tokenizer / 多语言 RuntimeError: text model embedding size 512 ! checkpoint vocab projection 768 # open_clip 用了不同的 text tower 宽度或多语言 tokenizer与标准 CLIP 不一致 # 典型命令 python convert_clip_original_pytorch_to_hf.py \ --checkpoint_path open_clip_model.pt \ --pytorch_dump_folder_path ./hf_clip最典型的指纹脚本是为 OpenAI 官方 CLIP.pt写的对 open_clip 训练的自定义结构不同视觉塔、不同文本塔、不同 config 来源支持不完整于是转换半途失败或产出无法from_pretrained的残缺权重。二、背景convert_clip_original_pytorch_to_hf.py的设计目标是把OpenAI 原版 CLIPclip-ViT-*的.pt转成 HFCLIPModel。它硬编码了 OpenAI CLIP 的 key 命名visual.conv1、visual.proj、text_projection等和 config 字段。open_clip 在 OpenAI 基础上做了大量扩展视觉塔可以是ViT、也可是ConvNext、BTAdapter等key 前缀不同visual.trunk.xxx。文本塔支持多语言、更大的宽度、不同的context_length。checkpoint 里常带有model_visual/model_transformers或state_dict嵌套而非顶层扁平 key。config 信息散落在model_cfg/args里而非现成的vision_config。于是能转 OpenAI CLIP不等于能转 open_clip脚本遇到非标准结构就 KeyError / ValueError。三、根因根因有三类key 命名约定不一致。 OpenAI CLIP 的视觉塔 key 是visual.conv1.weightopen_clip 的部分变体把视觉塔包在visual.trunk下key 变成visual.trunk.conv1.weight。转换脚本用固定前缀提取遇到trunk嵌套就找不到 →KeyError。config 字段需要推断而非直接读取。proj_dim视觉-文本对齐维度在 OpenAI CLIP 的.pt里能直接从结构推断但 open_clip 的 checkpoint 有时不显式存vision_config需要靠visual.proj的形状反推。脚本若直接读不存在的字段 →ValueError。文本塔宽度/词表与标准 CLIP 不一致。 open_clip 可能用更宽的 text tower 或多语言 tokenizer导致text_projection形状与脚本假设不符 → 形状断言失败。四、最小可运行复现下面用纯 Python 模拟open_clip 的 key 嵌套在visual.trunk下脚本按固定前缀提取导致 KeyErrorfrom typing import Dict # OpenAI 原版 CLIP 的 state_dict扁平 OPENAI_SD { visual.conv1.weight: None, visual.proj: None, text_projection: None, } # open_clip 变体的 state_dict视觉塔嵌套在 trunk 下 OPENCLIP_SD { visual.trunk.conv1.weight: None, visual.trunk.proj: None, text_projection: None, } def convert_using_fixed_prefix(sd: Dict, prefixvisual): 模拟脚本按 visual.conv1 这种固定 key 提取。 needed [visual.conv1.weight, visual.proj] for k in needed: if k not in sd: raise KeyError(k) return ok # 原版成功 print(OpenAI CLIP:, convert_using_fixed_prefix(OPENAI_SD)) # open_clip复现 KeyError try: convert_using_fixed_prefix(OPENCLIP_SD) print(复现失败) except KeyError as e: print(复现成功(根因1):, e) # visual.conv1.weight 不在 open_clip SD 里 # 修正兼容嵌套 trunk def convert_flexible(sd: Dict): def find(key_suffix: str): # 依次尝试 visual.xxx / visual.trunk.xxx for cand in (fvisual.{key_suffix}, fvisual.trunk.{key_suffix}): if cand in sd: return cand raise KeyError(key_suffix) return find(conv1.weight), find(proj) print(open_clip 兼容提取:, convert_flexible(OPENCLIP_SD))运行后固定前缀提取在 open_clip 的 SD 上抛KeyError(visual.conv1.weight)而兼容版本能同时匹配visual.xxx与visual.trunk.xxx复现并修复了根因 1。五、解决方案第一层最小直接修复最快的止血在调用官方脚本之前先把 open_clip 的.pt预处理成脚本期望的扁平 key 结构并补全 configimport torch def flatten_open_clip_state_dict(ckpt_path: str): 第一层修复把 open_clip .pt 重写成 OpenAI-CLIP 风格的扁平 key。 ckpt torch.load(ckpt_path, map_locationcpu) sd ckpt.get(state_dict, ckpt) # open_clip 常嵌套在 state_dict new_sd {} for k, v in sd.items(): nk k # 去掉 visual.trunk 前缀对齐 OpenAI CLIP 的 visual.xxx if nk.startswith(visual.trunk.): nk visual. nk[len(visual.trunk.):] # 文本塔常见命名差异 if nk text_transformers or nk.startswith(text_transformers.): nk nk.replace(text_transformers, text) new_sd[nk] v # 推断 proj_dim若缺失 if visual.proj in new_sd and vision_config.proj_dim not in ckpt: proj new_sd[visual.proj] proj_dim proj.shape[-1] if proj.dim() 1 else proj.shape[0] print(f推断 proj_dim {proj_dim}) return new_sd # 之后把 new_sd 存成临时 .pt再喂给官方转换脚本 sd flatten_open_clip_state_dict(open_clip_model.pt) torch.save({state_dict: sd}, open_clip_flat.pt) # python convert_clip_original_pytorch_to_hf.py --checkpoint_path open_clip_flat.pt ...第一层让用户立刻绕过 key 不匹配把 open_clip 权重转成 HF CLIP 可加载的格式。六、解决方案第二层结构性改进用OpenClipConverter把key 重写 config 推断 文本塔适配做成一体化转换不再依赖手动预处理from dataclasses import dataclass, field from typing import Dict, List dataclass class OpenClipConverter: 把 open_clip .pt 稳健地转成 HF CLIP 权重与 config。 key_rewrites: Dict[str, str] field(default_factorylambda: { visual.trunk.: visual., text_transformers.: text., model_visual.: visual., }) def rewrite_keys(self, sd: Dict) - Dict: out {} for k, v in sd.items(): nk k for old, new in self.key_rewrites.items(): if nk.startswith(old): nk new nk[len(old):] out[nk] v return out def infer_config(self, sd: Dict, base_cfg: Dict) - Dict: cfg dict(base_cfg) # 从 visual.proj 推断投影维度 proj sd.get(visual.proj) if proj is not None: cfg[vision_config] cfg.get(vision_config, {}) cfg[vision_config][proj_dim] \ proj.shape[-1] if proj.dim() 1 else proj.shape[0] # 从 text_projection 推断 text 隐藏维度 tp sd.get(text_projection) if tp is not None: cfg[text_config] cfg.get(text_config, {}) cfg[text_config][projection_dim] \ tp.shape[-1] if tp.dim() 1 else tp.shape[0] return cfg # 使用 converter OpenClipConverter() sd torch.load(open_clip_model.pt, map_locationcpu).get(state_dict, {}) flat converter.rewrite_keys(sd) cfg converter.infer_config(flat, base_cfg{model_type: clip}) # 再用 flat cfg 构造 CLIPModel 并 save_pretrainedOpenClipConverter的语义是无论 open_clip 的 key 怎么嵌套、config 怎么缺失都用重写规则 形状推断补齐成 HF CLIP 期望的格式比手动预处理更稳。七、解决方案第三层断言 / CI 守护用 pytest 固化转换后权重可被CLIPModel.from_pretrained加载且 key 完整import pytest import torch def test_openclip_keys_rewritten(): from clip_convert import OpenClipConverter sd {visual.trunk.conv1.weight: torch.randn(3, 3, 3, 3), visual.trunk.proj: torch.randn(512, 512)} flat OpenClipConverter().rewrite_keys(sd) assert visual.conv1.weight in flat, 视觉塔前缀应被重写为 visual. assert visual.proj in flat def test_proj_dim_inferred(): from clip_convert import OpenClipConverter sd {visual.proj: torch.randn(512, 768)} # proj_dim768 cfg OpenClipConverter().infer_config(sd, {model_type: clip}) assert cfg[vision_config][proj_dim] 768 def test_converted_loadable(tmp_path): # 端到端转换后的权重能构造 CLIPModel from transformers import CLIPModel, CLIPConfig sd {visual.conv1.weight: torch.randn(3, 3, 3, 3), visual.proj: torch.randn(512, 512), text_projection: torch.randn(512, 512)} cfg CLIPConfig.from_dict(OpenClipConverter().infer_config(sd, {}) if False else {vision_config: {proj_dim: 512}, text_config: {projection_dim: 512}}) model CLIPModel(cfg) # 至少能构造、能 save/load model.save_pretrained(tmp_path) CLIPModel.from_pretrained(tmp_path)CI 跑pytest tests/test_openclip_convert.py以后只要转换脚本又漏了某类 open_clip 结构测试立刻红灯。八、排查清单当用官方脚本转 open_clip.pt失败按顺序查KeyError: visual.conv1.weight→ open_clip 把视觉塔嵌套在visual.trunk下用rewrite_keys去前缀。Missing proj_dim→ 从visual.proj形状推断补进 config。文本塔宽度/词表不一致 → 检查text_projection形状必要时自定义text_config。checkpoint 有state_dict嵌套 → 先.get(state_dict, ckpt)取真正的权重。长期方案用OpenClipConverter一体化处理 key 重写 config 推断而非每次手动改脚本。九、小结Can i convert open-clip trained models (.pt) 的根因是官方convert_clip_original_pytorch_to_hf.py是为 OpenAI 原版 CLIP 硬编码的对 open_clip 的嵌套 keyvisual.trunk、缺失 config 字段、不同文本塔宽度支持不完整于是 KeyError / ValueError。第一层转换前用flatten_open_clip_state_dict把 key 重写扁平并推断proj_dim立即能喂给官方脚本。第二层用OpenClipConverter一体化处理 key 重写 config 推断 文本塔适配更稳。第三层pytest 断言key 被重写、proj_dim 被推断、转换后可from_pretrained防止回归。记住转换脚本的 key 约定必须与实际 checkpoint 对齐凡是脚本期望但 checkpoint 没有的字段都要靠形状推断补齐而不是硬读。