当前位置: 首页 > news >正文

【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 src/transformers/models/clip/convert_clip_original_pytorch_to_hf.py ? 解决方案

一、现象长什么样

你想把用 open_clip 训练出来的.pt权重转成 HuggingFace CLIP 格式,于是用 transformers 提供的转换脚本convert_clip_original_pytorch_to_hf.py。但跑起来报错:

# 现象 A:key 不匹配,大量张量没被转换 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.conv1visual.projtext_projection等)和 config 字段。

open_clip 在 OpenAI 基础上做了大量扩展:

  • 视觉塔可以是ViT、也可是ConvNextBTAdapter等,key 前缀不同(visual.trunk.xxx)。
  • 文本塔支持多语言、更大的宽度、不同的context_length
  • checkpoint 里常带有model_visual/model_transformersstate_dict嵌套,而非顶层扁平 key。
  • config 信息散落在model_cfg/args里,而非现成的vision_config

于是"能转 OpenAI CLIP"不等于"能转 open_clip",脚本遇到非标准结构就 KeyError / ValueError。

三、根因

根因有三类:

  1. key 命名约定不一致。 OpenAI CLIP 的视觉塔 key 是visual.conv1.weight;open_clip 的部分变体把视觉塔包在visual.trunk下,key 变成visual.trunk.conv1.weight。转换脚本用固定前缀提取,遇到trunk嵌套就找不到 →KeyError

  2. config 字段需要推断而非直接读取proj_dim(视觉-文本对齐维度)在 OpenAI CLIP 的.pt里能直接从结构推断,但 open_clip 的 checkpoint 有时不显式存vision_config,需要靠visual.proj的形状反推。脚本若直接读不存在的字段 →ValueError

  3. 文本塔宽度/词表与标准 CLIP 不一致。 open_clip 可能用更宽的 text tower 或多语言 tokenizer,导致text_projection形状与脚本假设不符 → 形状断言失败。

四、最小可运行复现

下面用纯 Python 模拟"open_clip 的 key 嵌套在visual.trunk下,脚本按固定前缀提取导致 KeyError":

from 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, prefix="visual"): """模拟脚本:按 '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 (f"visual.{key_suffix}", f"visual.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.xxxvisual.trunk.xxx,复现并修复了根因 1。

五、解决方案(第一层:最小直接修复)

最快的止血:在调用官方脚本之前,先把 open_clip 的.pt预处理成脚本期望的扁平 key 结构,并补全 config:

import torch def flatten_open_clip_state_dict(ckpt_path: str): """第一层修复:把 open_clip .pt 重写成 OpenAI-CLIP 风格的扁平 key。""" ckpt = torch.load(ckpt_path, map_location="cpu") 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_factory=lambda: { "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_location="cpu").get("state_dict", {}) flat = converter.rewrite_keys(sd) cfg = converter.infer_config(flat, base_cfg={"model_type": "clip"}) # 再用 flat + cfg 构造 CLIPModel 并 save_pretrained

OpenClipConverter的语义是:无论 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_dim=768 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失败,按顺序查:

  1. KeyError: visual.conv1.weight→ open_clip 把视觉塔嵌套在visual.trunk下,用rewrite_keys去前缀。
  2. Missing proj_dim→ 从visual.proj形状推断,补进 config。
  3. 文本塔宽度/词表不一致 → 检查text_projection形状,必要时自定义text_config
  4. checkpoint 有state_dict嵌套 → 先.get("state_dict", ckpt)取真正的权重。
  5. 长期方案:用OpenClipConverter一体化处理 key 重写 + config 推断,而非每次手动改脚本。

九、小结

"Can i convert open-clip trained models (.pt)" 的根因是:官方convert_clip_original_pytorch_to_hf.py是为 OpenAI 原版 CLIP 硬编码的,对 open_clip 的嵌套 key(visual.trunk)、缺失 config 字段、不同文本塔宽度支持不完整,于是 KeyError / ValueError。

  • 第一层:转换前用flatten_open_clip_state_dict把 key 重写扁平并推断proj_dim,立即能喂给官方脚本。
  • 第二层:用OpenClipConverter一体化处理 key 重写 + config 推断 + 文本塔适配,更稳。
  • 第三层:pytest 断言"key 被重写、proj_dim 被推断、转换后可from_pretrained",防止回归。

记住:转换脚本的 key 约定必须与实际 checkpoint 对齐;凡是脚本期望但 checkpoint 没有的字段,都要靠形状推断补齐,而不是硬读。

http://www.cnnetsun.cn/news/3903915.html

相关文章:

  • OpenClaw本地部署指南:打造私有AI智能体平台
  • 构建自我进化AI智能体:从静态执行到动态演进的工程实践
  • 深度解析郑州网站建设hndream在数字化转型中的核心价值与实战经验
  • 零基础构建AI自动化图文生产线:Coze与Image2工作流实战
  • 量化交易策略:多重条件过滤系统实现趋势启动点精准捕捉
  • 揭秘洋县建设银行网站背后的故事:本地金融服务的真实体验与选择指南
  • 同人漫画PV制作全流程解析:从AE特效到B站投稿的技术实践
  • 论文AI味太重不敢交?一套完整的AIGC降重+查重实操指南拿走就用
  • 揭秘乐从网站建设公司背后的真相与选择指南
  • AI智能体:从概念到实战,构建下一代自动化助手
  • 基于象限扫描的二维点云边界提取算法:原理、C++实现与调优
  • HFSS仿真速查手册:高频电磁场仿真核心概念与实战技巧
  • 从内存视角深度解析C语言数据类型:原理、陷阱与工程实践
  • Unity热力图与风向图实现:从数据解析到GPU渲染的免费方案
  • 揭秘平泉建设局网站背后的民生温度:从信息公开到服务升级的深度观察
  • SqlSugar ORM排序全解析:从基础用法到动态排序与性能优化实战
  • JuiceFS缓存优化:双节点支撑1.45TB/s短视频流量
  • 设备树学习5--读写实操(TODO)
  • 揭秘真相:一元购网站建设多少钱?找对团队才是省钱王道
  • PCB设计中盘中孔技术的核心原理、实战应用与避坑指南
  • SAP增强中直接更新表的危害与正确实践
  • PCB设计实战:ESD防护与EMC电磁兼容性核心要点解析
  • C++文件读写核心指南:fstream深度解析与性能优化实战
  • STK与MATLAB互联失败:stkInit命令无法执行的系统性解决方案
  • 山东建设厅网站是什么,如何查找官方入口?
  • VS2010 C++项目开发全流程指南:从环境配置到部署发布
  • C++ Type Traits:编译期类型查询与模板元编程核心技术解析
  • Power Query数据整形四板斧:逆透视、透视、转置与行列转换实战详解
  • CTC算法详解:从原理到实践,解决序列标注不对齐难题
  • Keepalived高可用架构:原理、实践与故障排查