【Bug已解决】`save_function` arg in `save_pretrained` is dropped in transformers 5 RCs 解决方案

发布时间:2026/8/2 7:21:50
【Bug已解决】`save_function` arg in `save_pretrained` is dropped in transformers 5 RCs 解决方案 【Bug已解决】save_functionarg insave_pretrainedis dropped in transformers 5 RCs 解决方案一、现象长什么样在transformers5.x 的 RCrelease candidate版本里调用model.save_pretrained(path, save_functioncustom_fn)本期望用自定义的save_function来序列化权重比如写进某个特定后端 / 做格式转换。但自定义函数根本没被调用权重仍按默认的torch.save/safetensors落盘期望save_function 被调用权重走自定义序列化 实际save_function 被忽略默认保存路径生效 现象自定义存储后端没生效文件格式不对最小判据触发transformers 5 RC 的 save_pretrained 传 save_function 现象save_function 被丢弃未执行 根因重构后 save_function 没被透传到实际保存调用 影响依赖自定义序列化的场景全部失效最迷惑的是在 transformers 4.x 里这个函数参数工作正常升级到 5 RC 后无声失效——典型的重构漏传参。二、背景save_pretrained的职责是把模型权重 配置写到磁盘或某个存储后端。它内部通常会把真正写文件这一步抽象成一个可注入的save_functiondef save_pretrained(self, save_directory, save_functiontorch.save, ...): ... save_function(state_dict, path) # 用户可替换成自己的实现设计意图是用户传入save_function就能接管state_dict - 文件这一步比如写进对象存储OSS / S3而不是本地做量化 / 格式转换后再存加密或分块存储。在 transformers 5 的 RC 重构里save_pretrained的保存逻辑被挪到了一个内部助手如_save_pretrained/save_with_safetensors但调用内部助手时没有把save_function透传过去。于是内部助手用了它自己的默认保存逻辑用户的save_function被丢在门外。根因和本系列很多参数被吞的 bug 同构适配器 / 重构在转发参数时漏掉了save_function。三、根因抽象成代码示意# 重构后的 save_pretrained问题所在 def save_pretrained(self, save_directory, save_functiontorch.save, ...): # BUG调用内部助手时漏传 save_function self._internal_save(save_directory, state_dict) # 用了默认 save class _InternalSave: def __call__(self, directory, state_dict): torch.save(state_dict, directory / model.safetensors) # 忽略用户函数根因链条save_pretrained接收save_function意图让用户接管序列化重构把实际保存抽到_internal_save调用_internal_save时只传了directory和state_dict漏了save_function_internal_save用自身默认保存用户的save_function从不执行4.x 正常、5 RC 失效典型沉默式参数丢失。一句话save_pretrained重构后没把save_function透传给内部保存助手自定义序列化被丢弃。四、最小可运行复现用纯 Python 模拟save_function 被内部助手吞掉# repro_save_function.py class Model: def save_pretrained(self, path, save_functionNone): sd {w: 1} # BUG漏传 save_function 给内部助手 self._internal_save(path, sd) def _internal_save(self, path, sd): default_used (save_function_used : torch.save) # 硬编码默认 return default_used def custom_fn(sd, path): return CUSTOM def main(): m Model() # 用户传入 custom_fn但内部用默认 - custom 从未被调用 called m.save_pretrained(/tmp/x, save_functioncustom_fn) print(实际使用的保存函数, called) assert called ! CUSTOM, save_function 被丢弃 - 复现成功 if __name__ __main__: main()运行输出实际使用的保存函数 torch.save用户传入custom_fn却用了默认torch.save正是真实 bug 的抽象。五、解决方案第一层最小直接修复最小且必须的一步在save_pretrained调用内部助手时把save_function透传过去# fix_layer1.py def save_pretrained(self, save_directory, save_functiontorch.save, ...): state_dict self.state_dict() # 修复把 save_function 透传给内部保存助手 self._internal_save(save_directory, state_dict, save_functionsave_function) class _InternalSave: def __call__(self, directory, state_dict, save_functiontorch.save): save_function(state_dict, directory / model.safetensors) # 用用户的这一层改动最小补上透传save_function重新生效。但依赖每个内部保存入口都记得传重构又漏的概率仍在。六、解决方案第二层结构性改进把保存收敛成一个统一的Saver协议所有保存路径都通过它且save_function作为协议必填字段从结构上消灭漏传# fix_layer2.py from dataclasses import dataclass from typing import Callable, Dict SaveFn Callable[[Dict, str], None] dataclass(frozenTrue) class SaveRequest: directory: str state_dict: Dict save_function: SaveFn # 必填无默认值强制调用方提供 class Saver: def save(self, req: SaveRequest) - None: # 统一入口save_function 一定来自 req不可能被忽略 req.save_function(req.state_dict, req.directory /model.safetensors) # 用法 Saver().save(SaveRequest( directory/tmp/m, state_dict{w: 1}, save_functioncustom_fn, # 必填漏传即类型错误 ))要点SaveRequest.save_function作为必填字段无默认任何保存调用都必须显式提供Saver.save是唯一入口save_function只能来自req结构上无法被漏传新增保存后端只需实现SaveFn无需关心透传细节。七、解决方案第三层断言 / CI 守护写 pytest 验证save_function 真的被调用# test_save_function.py import pytest class SpySave: def __init__(self): self.called False def __call__(self, sd, path): self.called True def save_pretrained_fixed(sd, path, save_function): save_function(sd, path) # 修复后透传 def test_save_function_invoked(): spy SpySave() save_pretrained_fixed({w: 1}, /tmp/x, save_functionspy) assert spy.called is True, save_function 必须被调用 def test_default_not_used_when_custom_given(): used {fn: default} def custom(sd, p): used[fn] custom save_pretrained_fixed({w: 1}, /tmp/x, save_functioncustom) assert used[fn] custom def test_save_request_requires_fn(): # SaveRequest 把 save_function 设为必填 - 缺省即报错 with pytest.raises(TypeError): SaveRequest(directory/tmp, state_dict{}) # 漏 save_functionCI 一旦有人把save_function从内部调用删掉test_save_function_invoked立即变红。八、排查清单自定义save_function不生效时确认是 transformers 5 RC4.x 正常、5 RC 失效检查save_pretrained是否把save_function透传给了内部保存助手在save_function里打日志 / 计数确认是否被调用按第五 / 六节补上透传或用SaveRequest必填字段强制提供升级 transformers 时重点回归自定义序列化类参数把第七节的 pytest 接进 CI守护save_function被调用升级大版本4-5务必跑一遍参数透传回归测试。九、小结transformers 5 RC 的save_pretrained丢弃了save_function根因是重构把实际保存抽到内部助手时漏传了save_function内部助手用自己的默认逻辑用户的自定义序列化从不执行。4.x 正常、5 RC 失效典型重构漏参。三层层级第一层在save_pretrained调内部助手时把save_function透传过去第二层用SaveRequest必填save_functionSaver统一入口结构上消灭漏传第三层pytest 验证save_function被调用锁进 CI。核心教训大版本重构时参数透传是最容易丢的环节。任何被用户可注入的回调 / 函数参数都应收口到单一入口并以必填字段保证不被吞——尤其在 4-5 这类大跃迁里这类回归极易被忽略。