AutoModel 几乎是我每天都要打交道的类。日常做微调,加载预训练权重用AutoModel.from_pretrained,训练完把结果存回去用save_pretrained,再换一台机器继续跑推理。这套流程在内置模型上非常顺滑,但一旦牵扯到自定义模型结构,“自动”两个字就开始变得微妙——你会发现类名匹配、注册表、权重键名、甚至config.json里一行字段都会决定你能不能成功加载。这篇文章把我踩过的坑和验证过的方案整理出来,聊清楚如何把自定义模型通过 AutoModel 完整地保存下来,再稳稳地加载回去。适合自己改过模型结构、或者写了全新模型但希望沿用标准接口的开发者,也适合部署时经常被模型加载问题卡住的同学。
1. AutoModel 的自动映射机制与自定义模型的对接点
1.1 AutoModel 是怎么认出你要加载的模型的
很多人天天用AutoModel.from_pretrained,但没细想过它内部到底做了什么。简单来说,AutoModel 维护了一张“架构注册表”,加载本地模型目录时,它的核心步骤大致是这样的:
- 读取目标目录下的
config.json - 从
config.json里拿到architectures字段,比如["BertForSequenceClassification"] - 拿这个类名去内部的注册表里查
- 查到了就实例化对应类,查不到就直接报错
- 从
pytorch_model.bin或model.safetensors里把权重读进来,填充到模型中
这里最关键的是第 2 步。architectures字段存的不是一个模型名字符串,而是一个“类名”。AutoModel 本身并不会去猜你的模型结构,它只是按照这份登记表找人。
用一个生活化的类比:AutoModel 就像一个前台接待,来了一看你的登记表(config.json),上面写着要找“张三”,它就去内部通讯录里查张三这个人。如果通讯录里根本没有“张三”,那不管你在登记表上写了多少个名字,前台也只能两手一摊。对内置模型来说,Bert、GPT、T5 这类架构早就被注册进去了,所以一切顺滑。对自定义模型来说,问题往往就出在这里:你的类只存在于自己的代码文件里,不在 AutoModel 的通讯录里。
1.2 为什么自定义模型常常“保存成功”但“加载失败”
这是我最常被问到的问题:“我明明save_pretrained成功了,目录里权重文件也在,怎么换一个环境就加载不了?”
原因通常分三种。
第一种,模型类没有继承PreTrainedModel。很多人自定义模型直接继承torch.nn.Module,然后手动torch.save权重。这样当然能保存成功,但save_pretrained需要的config机制、from_pretrained需要的类名映射,它全都不具备。AutoModel 拿到config.json之后,回头找不到对应的类,自然就失败了。
第二种,模型继承了PreTrainedModel,但没有把类“告诉”AutoModel。这种情况下你手动实例化再load_state_dict是能加载的,但AutoModel.from_pretrained(model_dir)这种写法不行,因为注册表里没有这个类。
第三种,类名对得上,但构造参数对不上。config.json里写了你自定义类的名字,加载时也找到了类,结果__init__需要 5 个参数,AutoModel 只传进去了一个config,直接 TypeError。这也是自定义模型接入标准接口时常犯的错误。
所以,自定义模型要真正能“保存 → 加载 → 推理”闭环,核心不只是把权重存下来,而是要让 AutoModel 在拿到config.json之后,能顺着一套机制找到你的类、正确实例化、然后再填权重。
1.3 一个自定义模型目录最少要有哪些文件
这里先给出一份最小文件清单,后面所有方案都围绕这份清单展开。假设你的模型目录叫my_model_dir,里面通常会有这些文件:
| 文件 | 作用 | 缺失后果 |
|---|---|---|
config.json | 记录模型超参数、架构类名、模型类型 | from_pretrained直接报错,连模型类都确定不了 |
model.safetensors或pytorch_model.bin | 存放训练好的权重 | 模型结构能建出来,但权重全是随机的 |
tokenizer_config.json | 记录分词器参数 | 加载分词器时容易报参数缺失 |
vocab.json/merges.txt/tokenizer.model | 词表或 SentencePiece 模型 | 分词器无法重建,或vocab_size不匹配 |
special_tokens_map.json | 特殊 token 定义 | 特殊 token 可能被拆成普通子词或报 key 错误 |
很多人在自定义模型时只盯着权重文件,把tokenizer那一堆文件当作“是可选项”,结果到了推理阶段才发现 decode 出来的文本乱七八糟。其实对文本模型来说,模型权重和词表必须配套保存,一个都不能少。
2. 保存链路:从训练完成到目录打包,缺一不可的几个文件
2.1 save_pretrained 到底替你做了什么事
假设你的模型已经是PreTrainedModel的子类,调用:
model.save_pretrained("my_model_dir")这行代码会做两件事:第一,把model.state_dict()序列化后写到权重文件里;第二,把你的model.config序列化后写到config.json里。
这里面有个很容易忽略的细节:config.json里的architectures字段默认填的是你模型类的__name__,也就是字符串形式的类名。它不会自动帮你把模块路径也写进去,更不会自动把类注册到 AutoModel 里。也就是说,save_pretrained只是帮你“登记了名字”,但没有保证“AutoModel 认识这个名字”。
如果你没有提前注册,目录里这份config.json拿到新环境里,AutoModel 一看架构名是MyCustomModel,内部查无此类,照样报错。所以记住一句话:保存动作本身很简单,难点在于让“下一次加载”时类名可解析。
另外有一个非常实际的问题:如果你的模型类继承的是PreTrainedModel,那么save_pretrained能直接工作;如果只是nn.Module,我建议不要硬套save_pretrained,而是手动保存:
import torch torch.save(model.state_dict(), "my_model_dir/pytorch_model.bin") model.config.to_json_file("my_model_dir/config.json")然后加载时手动实例化类,再用load_state_dict填权重。这种方式虽然在“自动加载”这件事上不够优雅,但在很多从旧代码迁移过来的场景里最省事。
2.2 分词器必须跟着一起保存
文本模型很少只存模型权重。你用AutoTokenizer.from_pretrained("my_model_dir")加载目录时,系统会期望目录里有对应的词表文件。
保存分词器只需要一行:
tokenizer.save_pretrained("my_model_dir")它会自动把tokenizer_config.json、vocab.json、merges.txt、special_tokens_map.json等文件写进目录。实际使用中,我建议保存完之后立即检查一下目录,很多问题的根源就是vocab.json没保存成功,但加载时报错却很晚才出现。
一个小经验:如果你用的是 SentencePiece 类分词器,目录里应该出现tokenizer.model或类似命名的模型文件,而不只是vocab.json。有些环境里.model文件被忽略,会导致换机器加载时tokenizer的行为不一致。保存后第一时间把目录压缩备份,别只依赖 git 上传,因为分词器模型文件经常被.gitignore规则漏掉。
2.3 权重格式:pytorch_model.bin 还是 model.safetensors
现在新训练的模型,很多会选择safetensors格式。它和bin的核心区别在于安全和加载性能:safetensors不会在反序列化时执行任意 Python 代码,天然规避了恶意权重攻击;同时它对大文件支持 mmap,加载速度通常更快。
如果你是直接用model.save_pretrained()保存,且环境里装了safetensors库,那么默认会写出model.safetensors。如果只写出了一个pytorch_model.bin,有两种可能:环境里没安装safetensors,或者你在调用时显式传了safe_serialization=False。
我自己在自定义模型里遇到过这样一个坑:模型里注册了一些非张量类型的 buffer,比如把某个 int 型计数、一个 numpy 数组也放进了 state_dict。torch.save可以容忍这些东西,但safetensors序列化时只接受torch.Tensor,结果保存直接抛异常。解决办法是把非张量 buffer 转移走,或者在保存前过滤:
import safetensors.torch state_dict = model.state_dict() tensor_dict = {k: v.contiguous() for k, v in state_dict.items() if isinstance(v, torch.Tensor)} safetensors.torch.save_file(tensor_dict, "my_model_dir/model.safetensors")如果你希望强制使用旧格式bin,可以在加载时传use_safetensors=False,或者直接把目录里的model.safetensors临时移走。反过来,如果你只想要safetensors,但目录里同时存在两种格式,新版本库会优先加载safetensors,这个行为在不同版本间略有差异,部署前最好确认一下。
2.4 保存完必须做的一个自检动作
保存代码写完别急着关终端。我强烈建议做一次“删除变量再重新加载”的自检:
import gc del model gc.collect() loaded = AutoModel.from_pretrained("my_model_dir") tokenizer = AutoTokenizer.from_pretrained("my_model_dir")这个自检最好放在新开的一个 Python 进程里跑,而不是在同一个 notebook cell 里从上到下执行。因为同一个进程里可能还残留着类定义、注册表信息、全局变量,很多加载问题只有在全新进程里才会暴露。
我常见到的做法是:保存完成后,立刻打开终端跑一个小脚本,脚本里只做三件事:加载模型、加载分词器、打印模型参数量和分词器词表大小。三行都跑通,才代表这份目录真正自洽。
3. 加载链路:from_pretrained 的完整解析过程与常见报错
3.1 路径和目录名的区别,以及 local_files_only 的作用
很多人对from_pretrained的一个误解是:传入目录路径后,它还会时不时去访问网络。实际上,如果你传入的是一个本地目录路径,比如from_pretrained("./my_model_dir"),它不会去联网下载,只会读本地文件。
但如果传入的是一个不带路径分隔符的名字,比如from_pretrained("my_model_dir"),它会认为你给的是模型仓库名,先尝试联网查找,失败后再回退到本地目录。在完全离线的环境里,这个联网尝试会耗费很长时间的超时等待。
所以部署到离线机器时,我建议统一写成绝对路径,并加上local_files_only=True:
model = AutoModel.from_pretrained( "/opt/models/my_model_dir", local_files_only=True )local_files_only=True的意思是:只允许使用本地文件,任何缺失都不去联网。这个参数把它写进代码里,比在环境变量层面配置更稳。否则同一套代码,换到有网的开发机上可能正常,换到内网部署机就开始莫名超时。
另外还有两个参数值得留意。cache_dir控制了缓存下载文件的位置;subfolder可以在一个仓库或目录下再指定子目录,比如from_pretrained("/opt/model_root", subfolder="my_model_dir")。如果模型权重、配置、分词器文件被组织在一个大目录的不同子目录里,这个参数很有用。
3.2 常见报错的排查对照
我整理了自定义模型加载时出现频率最高的一批报错,以及对应的处理办法:
| 报错信息特征 | 根本原因 | 处理方式 |
|---|---|---|
does not have a configuration file | 目录里没有config.json,或路径写错 | 检查文件确实存在,检查路径大小写 |
is not in the list of supported models | config.json里架构类名没有被 AutoModel 注册表识别 | 注册自定义类,或手动实例化加载 |
Some weights ... not used | 权重文件里的键比模型结构多,通常是加载了不配套的权重 | 确认architectures与模型类匹配,必要时删掉多余键 |
Some weights ... not initialized from checkpoint | 模型里的键在权重文件中缺失,模型是随机初始化状态 | 检查是否用了不同配置的 checkpoint,或键名不匹配 |
size mismatch | 权重中的张量尺寸与模型初始化尺寸不一致 | 检查config.json的hidden_size、num_layers、vocab_size是否与训练时一致 |
Error(s) in loading state_dict | 键名不匹配,例如多了module.前缀 | 过滤或删除module.前缀 |
KeyError: 'xxx' | 分词器词表不匹配 | 重新用对应 tokenizer 保存词表,统一版本 |
这份表格是我排查问题的主索引。遇到加载报错,先不要急着改代码,把报错信息和这些典型原因对照一遍,基本能定位九成问题。
3.3 权重键名不匹配:最容易忽视的隐形坑
比“类找不到”更隐蔽的问题是键名不匹配。模型能实例化,state_dict的键数量也对得上,但加载时直接报size mismatch或者在load_state_dict里 throw error。最常见的情况有两种。
第一种是训练时用了DistributedDataParallel,或者保存前模型被torch.compile包过一层,导致权重键名带了module.前缀。加载前最好统一处理成无前缀形式:
state_dict = torch.load("pytorch_model.bin", map_location="cpu") if all(k.startswith("module.") for k in state_dict): state_dict = {k[len("module."):]: v for k, v in state_dict.items()} model.load_state_dict(state_dict)第二种情况是,你改了模型里某些层的名字,比如把encoder改成了backbone,旧权重里全是encoder.0.self_attention...这种键名,新模型里全是backbone.0.self_attention...。这种情况下没有捷径,只能写一个键名映射函数,把旧键名逐项映射到新键名。写映射函数的时候可以打印两个键名集合的差集,一眼就能看出差异集中在哪个前缀。
3.4 加载后的冒烟测试:别等推理时报错
加载成功不等于万事大吉。我每次加载完自定义模型,都会先跑一个冒烟测试:构造极小的 dummy 输入,跑一次 forward,确认输出形状符合预期,同时打印model.config里的关键参数、tokenizer.vocab_size以及模型 embedding 层维度,人工检查三者是否匹配。
inputs = tokenizer("这是一段测试文本", return_tensors="pt") outputs = model(**inputs) print("input_ids shape:", inputs["input_ids"].shape) print("output shape:", outputs.logits.shape if hasattr(outputs, "logits") else outputs.shape) print("vocab_size:", tokenizer.vocab_size) print("embedding size:", model.config.hidden_size)这个冒烟测试建议直接写成脚本,换环境加载时第一件事就是跑它。很多部署事故都是“加载没报错,推理时输出全是乱码”,原因往往就是vocab_size和 embedding 矩阵维度不一致。手动检查一遍,成本极小,收益很大。
4. 自定义模型真正可 auto 化的方案:注册与子类化
4.1 方案一:最保守,手动实例化 + load_state_dict
如果你的自定义模型结构不太复杂,也不想引入额外的注册机制,最直接的办法是放弃AutoModel.from_pretrained,改成手动构建模型再加载权重。
from my_modeling import MyModel, MyConfig config = MyConfig.from_pretrained("my_model_dir") model = MyModel(config) state_dict = torch.load("my_model_dir/pytorch_model.bin", map_location="cpu") model.load_state_dict(state_dict) model.eval()这个方案的好处是零注册成本,老代码改起来也快。缺点也很明显:加载代码和特定模型类绑定,不能做到“换模型目录不改代码”。如果你的项目只是固定一两个模型,这个方案完全够用;如果目标是搭一个类似于“模型仓库统一入口”的东西,就得上注册方案。
4.2 方案二:AutoConfig.register + AutoModel.register
这是把自定义模型“正规化”进入 AutoModel 体系的方法。注册的方式很直接,既支持注册配置类,也支持注册模型类。注册之前,要保证你的模型类是PreTrainedModel的子类,配置类是PretrainedConfig的子类。
from transformers import AutoConfig, AutoModel # 先注册配置 AutoConfig.register("my_model", MyConfig) # 再注册模型 AutoModel.register(MyConfig, MyModel)注册之后,config.json里要写两部分关键信息:model_type设置为"my_model",architectures设置为["MyModel"]。这样在同一个 Python 进程里,AutoModel.from_pretrained("my_model_dir")就能正确解析到你的类。
这里有几个细节要注意。
第一,AutoModel.register有个隐藏约束:MyModel的__init__必须接受config作为唯一参数。AutoModel 实例化时会自动读取config.json,然后传入你的类构造函数,如果你还额外传了hidden_size之类的参数,加载就会挂。
第二,注册只在当前 Python 进程内生效。下次重启进程,如果没重新执行注册脚本,AutoModel一样不认你的类。所以,如果你不是在一个长期运行的服务里一次注册终身生效,就需要考虑把注册逻辑放进项目入口脚本,或者使用方案三的auto_map。
4.3 方案三:config.json 的 auto_map 字段
auto_map是更适合“把模型交给别人用”的方案。思路是:在config.json里直接写明自定义配置类和模型类的模块路径,并且把对应的 Python 文件放进模型目录,加载时通过trust_remote_code=True允许 AutoModel 执行这些自定义代码。
config.json大致长这样:
{ "architectures": ["MyModel"], "model_type": "my_model", "auto_map": { "AutoConfig": "my_modeling.MyConfig", "AutoModel": "my_modeling.MyModel" } }同时目录里放一个my_modeling.py文件,里面定义了MyConfig和MyModel。加载时:
model = AutoModel.from_pretrained("my_model_dir", trust_remote_code=True)这种方案的自动化程度最高,跨进程、跨机器都成立,只要目录完整拷贝过去就行。但代价是trust_remote_code=True会执行目录里的 Python 代码。虽然对可信来源的模型没问题,但如果从一个不可控来源下载模型目录,直接信任代码是有安全风险的。我的建议是:团队内部模型可以开trust_remote_code=True,但不要盲目信任第三方分享来的未知模型目录。
另外要提醒一点:my_modeling.py里的依赖尽量少,模块顶层不要做重量级导入,也不要有文件读取之类的副作用。因为from_pretrained加载时可能会多次执行这段代码,写得越干净越不容易出问题。
4.4 三种方案怎么选
我用一个表来总结这三种方案的适用场景:
| 方案 | 自动化程度 | 跨进程/机器 | 安全性 | 适用场景 |
|---|---|---|---|---|
| 手动实例化 + load_state_dict | 低 | 不受影响 | 最安全,不执行额外代码 | 临时脚本、调试、老代码迁移 |
| AutoModel.register | 中 | 不天然跨进程,需要入口脚本重新注册 | 安全,代码在项目内 | 实验研究、快速迭代、服务常驻进程 |
| auto_map + trust_remote_code | 高 | 天然跨进程、跨机器 | 有执行外部代码风险 | 模型对外发布、团队共享、统一加载入口 |
我的个人经验是:日常研究阶段用方案二就够了,注册代码写在一个register.py里,所有训练和推理脚本开头都import register。到了要交付模型给别人的阶段,再改成方案三,把my_modeling.py和权重打成一个完整目录。
5. 跨环境迁移实测:一台机器训练的模型,另一台机器加载时注意什么
5.1 版本一致性:第一大坑
跨机器加载自定义模型,我踩过的最大一个坑不是代码逻辑,而是版本不一致。Transformers库不同版本之间,配置类默认值、序列化字段名、AutoModel注册表内容都可能变化。比如4.30版本在加载一个4.40版本保存的config.json时,某个新加的字段会被忽略,导致模型尺寸或初始化逻辑不同,最终表现为size mismatch或随机初始化。
处理方式没那么玄学:项目里锁依赖。至少要把transformers、torch、tokenizers这三个库的版本号固化到requirements.txt里:
transformers==4.40.0 torch==2.2.0 tokenizers==0.19.0运气差的场景是,开发机和部署机的 Python 版本都不一样,torch.save产生的 pickle 文件有时候在低版本 torch 环境下会有兼容问题。这种情况我会优先在部署机尝试weights_only=True加载,如果失败,回到开发机先把权重换成safetensors格式再转移,能绕开 pickle 兼容性。
5.2 设备与精度:从 GPU 到 CPU,从 fp32 到 fp16
迁移场景里第二个常见问题是设备不匹配。训练在 GPU 上,推理或继续训练可能在 CPU 上。加载时用device_map可以控制模型和设备的关系:
model = AutoModel.from_pretrained( "/opt/models/my_model_dir", device_map="auto", torch_dtype=torch.float16 )device_map="auto"会尽量把能放的数据放在 GPU 上,必要时才放到 CPU;torch_dtype=torch.float16则让模型以半精度加载,节省显存。这两个参数对部署大型模型很有用,但对自定义模型有一个隐患:如果模型的前向逻辑里硬编码了某些float32计算,半精度加载后可能出现精度下降甚至 NaN。判断方法很简单,先用 fp32 加载验证一遍输出,再换 fp16 对比一次,看结果是否合理。
如果模型的 forward 逻辑里写了类似x.float()的强制类型转换,那么不管外层怎么传torch_dtype,内部都会把部分张量拉回 fp32,这在推理时还好,在比较显存占用时会造成困惑。建议在模型代码里统一使用model.dtype来控制精度,而不是写死。
5.3 离线环境加载必须要做的两件事
离线环境加载AutoModel,听起来只是路径问题,实际有两件事容易被忽略。
第一,所有文件必须一次性拷贝到位。少一个词表文件,加载时可能不会立刻报错,而是到分词阶段就行为异常。我建议拷贝完在部署机上跑一遍“加载 + 分词 + forward”三重检查,不要只看文件数量。
第二,local_files_only=True一定要写。前面说过,带路径的本地加载理论上不会联网,但如果你一不小心把model_id写成了仓库格式的名字,离线机器会卡在连接超时。加了local_files_only=True之后,只要本地文件缺失就会直接报错,不会傻等网络,排查起来也更快。
5.4 一个实际迁移案例复盘
举一个我实际遇到的模拟场景。某项目组的 A 同学在 GPU 开发机上用AutoModel训练了一个自定义结构的小模型,训练完保存目录一切正常,开发机上也测试通过了。把目录拷到 CPU 推理机后,加载时报了一个size mismatch,模型 embedding 层维度和权重尺寸对不上。
排查过程是这样的:先看config.json里的vocab_size,训练机上的词表是 30522,部署机加载时打印tokenizer.vocab_size也是 30522,看似一致。再往下查,发现推理机的transformers版本是 4.30,开发机是 4.40。4.40 的config.json里多了一个自动写入的字段,4.30 不识别这个字段,导致某层的隐藏维度初始化逻辑不同。升级推理机版本到一致后,问题消失。
这个案例说明一个很现实的问题:模型目录“拷贝过去能用”不一定是真的能用,只有在新环境下完整跑通冒烟测试,才算迁移成功。版本不一致是自定义模型迁移失败率最高的原因,甚至比模型代码本身的问题还高。
6. 完整闭环:把 AutoModel 保存与加载串成一个可复现流程
6.1 定义一个最小可用的自定义模型
为了展示完整流程,下面用一个小例子串一遍。假设我们要实现一个简单的分类模型,配置类和模型类都继承自标准基类:
from transformers import PretrainedConfig, PreTrainedModel, AutoConfig, AutoModel import torch.nn as nn class MyConfig(PretrainedConfig): model_type = "my_model" def __init__( self, vocab_size=1000, hidden_size=128, num_labels=2, **kwargs ): super().__init__(**kwargs) self.vocab_size = vocab_size self.hidden_size = hidden_size self.num_labels = num_labels class MyModel(PreTrainedModel): config_class = MyConfig def __init__(self, config): super().__init__(config) self.embedding = nn.Embedding(config.vocab_size, config.hidden_size) self.classifier = nn.Linear(config.hidden_size, config.num_labels) def forward(self, input_ids): pooled = self.embedding(input_ids).mean(dim=1) logits = self.classifier(pooled) return logits类定义完后,在入口脚本里做一次注册:
AutoConfig.register("my_model", MyConfig) AutoModel.register(MyConfig, MyModel)6.2 训练完保存的完整代码
简单模拟一次“训练”之后保存:
config = MyConfig(vocab_size=1000, hidden_size=128, num_labels=2) model = MyModel(config) # 这里省略真实训练过程 # ... model.save_pretrained("my_model_dir") tokenizer.save_pretrained("my_model_dir") # 如果用了分词器保存之后,检查一下my_model_dir里的config.json。因为做了注册,AutoModel已经能认识architectures里的MyModel类名,但如果你希望这个目录在没有注册脚本的机器上也能被自动加载,更稳妥的是把my_modeling.py文件一起放进目录,并在config.json里补上auto_map字段:
{ "architectures": ["MyModel"], "model_type": "my_model", "auto_map": { "AutoConfig": "my_modeling.MyConfig", "AutoModel": "my_modeling.MyModel" } }6.3 新进程里加载并验证
新建一个 Python 文件,只在头部写一句注册脚本导入,或者干脆用auto_map加trust_remote_code=True:
from transformers import AutoModel, AutoTokenizer model = AutoModel.from_pretrained( "/path/to/my_model_dir", trust_remote_code=True ) model.eval() tokenizer = AutoTokenizer.from_pretrained("/path/to/my_model_dir") inputs = tokenizer("测试一下这个模型通不通", return_tensors="pt") logits = model(**inputs) print(logits.shape)跑通这一步,意味着从训练、保存、迁移到加载推理的完整链路已经闭合。
6.4 保底的调试技巧
如果加载还是不成功,我有两个保底的调试习惯。
第一,用torch.load直接看权重键,和模型自身的state_dict().keys()做差集:
saved = torch.load("pytorch_model.bin", map_location="cpu") model_sd = model.state_dict() print("only in saved:", saved.keys() - model_sd.keys()) print("missing in saved:", model_sd.keys() - saved.keys())差集结果瞬间能告诉你到底是权重文件不对,还是模型结构定义变了。只要键名前缀不一致,这一招比盯报错日志快得多。
第二,准备好一个“极简复现脚本”。把加载逻辑单独抽出来,脚本里不依赖训练代码、不加载数据,只做“读取目录 → 构建模型 → 打印参数量 → 跑一次 forward”。任何环境出问题,先在这个脚本里复现。一旦复现稳定,再逐步加回业务逻辑,定位速度会快很多。
最后再分享一个我个人的习惯:任何自定义模型的目录,保存时我都会额外生成一个README.md,记录训练用了什么版本库、什么配置、什么词表来源。这个文件不会影响代码加载,但在半年后回看项目、或者同事接手调试时,它比任何注释都有用。模型能不能顺利加载,很多时候考验的不是你会不会写from_pretrained,而是你为“下一次环境里的加载”做了多少准备。