【Bug已解决】PyTorch - RuntimeError: [enforce fail at inline_container.cc:209] . file not found: archive/data.pkl 解决方案
问题描述
在 PyTorch 中使用torch.jit.save()和torch.jit.load()保存和加载 TorchScript 模型时,开发者可能会遇到以下错误:
RuntimeError: [enforce fail at inline_container.cc:209] . file not found: archive/data.pkl这个错误发生在尝试加载一个 TorchScript 模型文件(.pt或.pth)时,表示模型文件格式不正确或文件已损坏。
具体表现为:
- 使用
torch.jit.load()加载模型时报错 - 模型文件在保存过程中被中断或损坏
- 使用错误的 API 保存模型(如用
torch.jit.save保存非 TorchScript 模型) - 文件传输过程中损坏(如通过 FTP、Git LFS 等)
- PyTorch 版本不兼容导致文件格式变化
- 磁盘空间不足导致保存不完整
错误复现
场景一:用torch.save保存的文件用torch.jit.load加载
import torch import torch.nn as nn class SimpleModel(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(10, 5) def forward(self, x): return self.fc(x) # 使用 torch.save 保存普通模型 model = SimpleModel() torch.save(model.state_dict(), 'model_state_dict.pt') # 错误:用 torch.jit.load 加载 loaded = torch.jit.load('model_state_dict.pt') # 报错报错:
RuntimeError: [enforce fail at inline_container.cc:209] . file not found: archive/data.pkl场景二:TorchScript 模型保存不完整
import torch import torch.nn as nn class SimpleModel(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(10, 5) def forward(self, x): return self.fc(x) # 创建 TorchScript 模型 model = SimpleModel() scripted = torch.jit.script(model) # 保存(假设磁盘空间不足或被中断) torch.jit.save(scripted, 'model_scripted.pt') # 文件可能不完整 loaded = torch.jit.load('model_scripted.pt') # 可能报错场景三:文件传输损坏
# 在一台机器上保存 torch.jit.save(scripted, 'model.pt') # 通过网络传输后(如 scp、FTP、Git LFS) # 文件可能损坏 # 在另一台机器上加载 loaded = torch.jit.load('model.pt') # 报错场景四:PyTorch 版本不兼容
# 在 PyTorch 2.0 上保存 torch.jit.save(scripted, 'model_v2.pt') # 在 PyTorch 1.10 上加载 loaded = torch.jit.load('model_v2.pt') # 可能报错根因分析
1.torch.savevstorch.jit.save的区别
这是最常见的混淆点:
torch.save(obj, path):保存任意 Python 对象(包括 state_dict、完整模型等),使用 Python 的 pickle 协议。文件内部结构是archive/data.pkl。torch.jit.save(model, path):保存 TorchScript 模型(ScriptModule 或 ScriptFunction),使用专门的序列化格式。文件内部结构包含archive/data.pkl和archive/code/等。
当用torch.jit.load()加载一个由torch.save()保存的文件时,文件格式不匹配,导致找不到archive/data.pkl。
2. TorchScript 模型的文件结构
TorchScript 模型保存为 ZIP 格式的归档文件,内部结构如下:
archive/ ├── data.pkl # 序列化的模型数据 ├── data/ # 张量数据 │ ├── 0 │ ├── 1 │ └── ... ├── code/ # 模型代码 │ └── __torch__.py └── constants.json # 常量如果data.pkl文件缺失,说明归档不完整或格式错误。
3. 文件损坏的原因
- 磁盘空间不足:保存过程中磁盘空间耗尽,文件被截断
- 网络传输错误:通过 FTP、HTTP、scp 等传输时发生错误
- Git LFS 问题:Git LFS 没有正确拉取大文件
- 并发写入冲突:多个进程同时写入同一文件
- 文件系统错误:存储介质故障
4. PyTorch 版本兼容性
不同版本的 PyTorch 可能使用不同的 TorchScript 序列化格式。新版本保存的模型可能包含旧版本不支持的特性。
5. 混淆.pt和.pth扩展名
虽然扩展名不影响文件内容,但约定俗成:
.pt或.pth:torch.save保存的文件(state_dict 或模型).pt:torch.jit.save保存的 TorchScript 模型
混用扩展名可能导致混淆。
解决方案
方案一:使用正确的保存/加载 API
import torch import torch.nn as nn class SimpleModel(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(10, 5) def forward(self, x): return self.fc(x) model = SimpleModel() # ========== 方式1: 保存/加载 state_dict(推荐用于训练) ========== torch.save(model.state_dict(), 'model_state_dict.pth') # 加载 model_loaded = SimpleModel() # 需要先创建模型实例 model_loaded.load_state_dict(torch.load('model_state_dict.pth')) model_loaded.eval() # ========== 方式2: 保存/加载 TorchScript 模型(推荐用于部署) ========== scripted = torch.jit.script(model) torch.jit.save(scripted, 'model_scripted.pt') # 加载 loaded = torch.jit.load('model_scripted.pt') loaded.eval() # ========== 方式3: 保存/加载完整模型(不推荐) ========== torch.save(model, 'model_complete.pth') # 加载 loaded_model = torch.load('model_complete.pth')方案二:验证文件完整性
import torch import zipfile import os def validate_torchscript_model(filepath): """验证 TorchScript 模型文件的完整性""" if not os.path.exists(filepath): print(f"[ERROR] 文件不存在: {filepath}") return False file_size = os.path.getsize(filepath) print(f"文件大小: {file_size} bytes") if file_size < 100: print(f"[ERROR] 文件太小 ({file_size} bytes),可能已损坏") return False # 检查是否为有效的 ZIP 文件(TorchScript 模型是 ZIP 格式) if not zipfile.is_zipfile(filepath): print(f"[ERROR] 不是有效的 ZIP 文件") return False # 检查内部结构 try: with zipfile.ZipFile(filepath, 'r') as zf: names = zf.namelist() print(f"归档内容: {names}") # 检查必需的文件 required_files = ['archive/data.pkl'] for req in required_files: if req not in names: print(f"[ERROR] 缺少必需文件: {req}") return False print("[OK] 文件结构验证通过") return True except zipfile.BadZipFile: print(f"[ERROR] ZIP 文件损坏") return False # 使用 validate_torchscript_model('model_scripted.pt')方案三:安全保存(防止文件损坏)
import torch import os import tempfile def safe_save_torchscript(model, filepath): """安全保存 TorchScript 模型(原子写入)""" # 先写入临时文件 temp_fd, temp_path = tempfile.mkstemp(suffix='.pt') try: with os.fdopen(temp_fd, 'wb') as f: torch.jit.save(model, temp_path) # 验证临时文件 try: test_load = torch.jit.load(temp_path) del test_load except RuntimeError as e: raise RuntimeError(f"保存的文件验证失败: {e}") # 原子性地重命名(在 Unix 上是原子操作) os.replace(temp_path, filepath) print(f"模型已安全保存到 {filepath}") except Exception: # 清理临时文件 if os.path.exists(temp_path): os.remove(temp_path) raise def safe_save_state_dict(state_dict, filepath): """安全保存 state_dict(原子写入)""" temp_fd, temp_path = tempfile.mkstemp(suffix='.pth') try: with os.fdopen(temp_fd, 'wb') as f: torch.save(state_dict, temp_path) os.replace(temp_path, filepath) print(f"State dict 已安全保存到 {filepath}") except Exception: if os.path.exists(temp_path): os.remove(temp_path) raise完整修复代码
以下是一个完整的模型保存/加载工具,处理各种场景:
""" PyTorch 模型保存/加载完整工具 处理 torch.save / torch.jit.save / torch.jit.load 的各种问题 """ import torch import torch.nn as nn import os import zipfile import tempfile import shutil from typing import Optional, Dict, Any class ModelManager: """模型保存/加载管理器""" @staticmethod def save_state_dict( model: nn.Module, filepath: str, optimizer: Optional[torch.optim.Optimizer] = None, epoch: int = 0, metadata: Optional[Dict] = None, safe: bool = True ): """ 保存模型 state_dict(推荐用于训练检查点) Args: model: PyTorch 模型 filepath: 保存路径 optimizer: 优化器(可选) epoch: 当前 epoch metadata: 额外的元数据 safe: 是否使用安全写入(原子操作) """ checkpoint = { 'epoch': epoch, 'state_dict': model.state_dict(), 'model_class': model.__class__.__name__, } if optimizer is not None: checkpoint['optimizer'] = optimizer.state_dict() if metadata: checkpoint['metadata'] = metadata if safe: ModelManager._safe_save(checkpoint, filepath) else: torch.save(checkpoint, filepath) print(f"State dict saved to {filepath}") @staticmethod def load_state_dict( model: nn.Module, filepath: str, optimizer: Optional[torch.optim.Optimizer] = None, device: str = 'cpu', strict: bool = True ) -> Dict[str, Any]: """ 加载模型 state_dict Returns: 包含 epoch 和 metadata 的字典 """ if not os.path.exists(filepath): raise FileNotFoundError(f"文件不存在: {filepath}") checkpoint = torch.load(filepath, map_location=device) if 'state_dict' in checkpoint: model.load_state_dict(checkpoint['state_dict'], strict=strict) if optimizer is not None and 'optimizer' in checkpoint: optimizer.load_state_dict(checkpoint['optimizer']) return { 'epoch': checkpoint.get('epoch', 0), 'metadata': checkpoint.get('metadata', {}) } else: # 可能是直接的 state_dict model.load_state_dict(checkpoint, strict=strict) return {} @staticmethod def save_torchscript( model: nn.Module, filepath: str, example_input: Optional[torch.Tensor] = None, safe: bool = True ): """ 保存 TorchScript 模型(推荐用于部署) Args: model: PyTorch 模型 filepath: 保存路径 example_input: 用于 tracing 的示例输入(如果使用 trace 方式) safe: 是否使用安全写入 """ # 转换为 TorchScript try: scripted = torch.jit.script(model) print("使用 torch.jit.script 转换成功") except Exception as e: print(f"torch.jit.script 失败: {e}") if example_input is not None: print("尝试使用 torch.jit.trace...") scripted = torch.jit.trace(model, example_input) print("torch.jit.trace 转换成功") else: raise RuntimeError( "torch.jit.script 失败且未提供 example_input 用于 trace" ) if safe: ModelManager._safe_save_torchscript(scripted, filepath) else: torch.jit.save(scripted, filepath) print(f"TorchScript model saved to {filepath}") @staticmethod def load_torchscript(filepath: str, device: str = 'cpu') -> torch.jit.ScriptModule: """ 加载 TorchScript 模型 Raises: FileNotFoundError: 文件不存在 RuntimeError: 文件格式错误或损坏 """ if not os.path.exists(filepath): raise FileNotFoundError(f"文件不存在: {filepath}") # 验证文件 if not ModelManager.validate_torchscript_file(filepath): raise RuntimeError(f"文件验证失败: {filepath}") try: model = torch.jit.load(filepath, map_location=device) model.eval() print(f"TorchScript model loaded from {filepath}") return model except RuntimeError as e: if 'file not found: archive/data.pkl' in str(e): raise RuntimeError( f"文件格式错误。请确认:\n" f" 1. 文件是使用 torch.jit.save() 保存的\n" f" 2. 文件没有损坏\n" f" 3. PyTorch 版本兼容\n" f"原始错误: {e}" ) raise @staticmethod def validate_torchscript_file(filepath: str) -> bool: """验证 TorchScript 文件的完整性""" if not os.path.exists(filepath): return False file_size = os.path.getsize(filepath) if file_size < 50: print(f"[WARNING] 文件太小 ({file_size} bytes)") return False if not zipfile.is_zipfile(filepath): print(f"[ERROR] 不是有效的 ZIP 格式") return False try: with zipfile.ZipFile(filepath, 'r') as zf: names = zf.namelist() if 'archive/data.pkl' not in names: print(f"[ERROR] 缺少 archive/data.pkl") print(f" 文件内容: {names[:10]}") return False return True except zipfile.BadZipFile: return False @staticmethod def _safe_save(obj, filepath): """安全保存(原子写入)""" temp_path = filepath + '.tmp' try: torch.save(obj, temp_path) os.replace(temp_path, filepath) except Exception: if os.path.exists(temp_path): os.remove(temp_path) raise @staticmethod def _safe_save_torchscript(model, filepath): """安全保存 TorchScript(原子写入)""" temp_path = filepath + '.tmp' try: torch.jit.save(model, temp_path) # 验证 test = torch.jit.load(temp_path) del test os.replace(temp_path, filepath) except Exception: if os.path.exists(temp_path): os.remove(temp_path) raise @staticmethod def diagnose_file(filepath: str): """诊断模型文件""" print("=" * 60) print(f"诊断文件: {filepath}") print("=" * 60) if not os.path.exists(filepath): print("[ERROR] 文件不存在") return file_size = os.path.getsize(filepath) print(f"文件大小: {file_size:,} bytes ({file_size/1024/1024:.2f} MB)") # 检查是否为 ZIP 文件 is_zip = zipfile.is_zipfile(filepath) print(f"ZIP 格式: {is_zip}") if is_zip: with zipfile.ZipFile(filepath, 'r') as zf: names = zf.namelist() print(f"归档文件数: {len(names)}") print(f"内容列表:") for name in names[:20]: info = zf.getinfo(name) print(f" {name} ({info.file_size:,} bytes)") if len(names) > 20: print(f" ... 和其他 {len(names) - 20} 个文件") # 尝试用不同方式加载 print(f"\n尝试加载:") # 尝试 torch.load try: obj = torch.load(filepath, map_location='cpu') print(f" torch.load 成功: {type(obj)}") if isinstance(obj, dict): print(f" 字典键: {list(obj.keys())[:10]}") except Exception as e: print(f" torch.load 失败: {str(e)[:100]}") # 尝试 torch.jit.load try: model = torch.jit.load(filepath, map_location='cpu') print(f" torch.jit.load 成功: {type(model)}") except Exception as e: print(f" torch.jit.load 失败: {str(e)[:100]}") # 诊断结论 print(f"\n诊断结论:") if is_zip and not any('data.pkl' in n for n in names if 'archive' in n): print(" 文件可能是 torch.save 保存的,不是 TorchScript 模型") print(" 使用 torch.load() 加载,而不是 torch.jit.load()") elif is_zip: print(" 文件是 TorchScript 模型") print(" 使用 torch.jit.load() 加载") # ============ 使用示例 ============ class SimpleModel(nn.Module): def __init__(self, input_dim=784, hidden_dim=256, output_dim=10): super().__init__() self.fc1 = nn.Linear(input_dim, hidden_dim) self.fc2 = nn.Linear(hidden_dim, output_dim) def forward(self, x): x = torch.relu(self.fc1(x)) return self.fc2(x) def demo_correct_usage(): """演示正确的保存/加载方式""" print("=" * 60) print("正确的保存/加载方式") print("=" * 60) model = SimpleModel() # 1. 保存 state_dict ModelManager.save_state_dict(model, 'demo_state_dict.pth', epoch=10) # 加载 state_dict new_model = SimpleModel() info = ModelManager.load_state_dict(new_model, 'demo_state_dict.pth') print(f"恢复的 epoch: {info.get('epoch')}") # 2. 保存 TorchScript ModelManager.save_torchscript(model, 'demo_torchscript.pt') # 加载 TorchScript loaded = ModelManager.load_torchscript('demo_torchscript.pt') # 测试 x = torch.randn(1, 784) output1 = model(x) output2 = loaded(x) print(f"输出差异: {(output1 - output2).abs().max().item():.2e}") # 清理 import os os.remove('demo_state_dict.pth') os.remove('demo_torchscript.pt') def demo_error_scenario(): """演示错误场景和修复""" print("\n" + "=" * 60) print("错误场景演示") print("=" * 60) model = SimpleModel() # 错误:用 torch.save 保存,用 torch.jit.load 加载 torch.save(model.state_dict(), 'wrong_format.pt') print("用 torch.save 保存了 state_dict") # 诊断 ModelManager.diagnose_file('wrong_format.pt') # 正确加载 print("\n正确加载方式:") checkpoint = torch.load('wrong_format.pt') print(f" 加载成功: {type(checkpoint)}") import os os.remove('wrong_format.pt') def main(): print("\n" + "=" * 60) print(" PyTorch 模型保存/加载工具") print("=" * 60) demo_correct_usage() demo_error_scenario() print("\n" + "=" * 60) print(" 演示完成!") print("=" * 60) if __name__ == '__main__': main()常见陷阱与注意事项
1.torch.save和torch.jit.save不能混用
最常见的错误是混淆这两个 API:
torch.save()保存的是 Python pickle 对象(state_dict 或模型)torch.jit.save()保存的是 TorchScript 模型
用错误的 API 加载会导致file not found: archive/data.pkl错误。
2. TorchScript 模型的版本兼容性
TorchScript 模型在不同 PyTorch 版本之间可能不兼容。新版本保存的模型可能包含旧版本不支持的算子或特性。建议在相同或兼容的 PyTorch 版本之间共享 TorchScript 模型。
3. Git LFS 和大文件
如果模型文件通过 Git 管理,确保使用 Git LFS 存储大文件。没有 LFS,Git 可能会截断或损坏大文件:
# 安装 Git LFS git lfs install # 跟踪模型文件 git lfs track "*.pt" git lfs track "*.pth" # 提交 git add .gitattributes git add model.pt git commit -m "Add model"4. 磁盘空间检查
保存模型前检查磁盘空间:
import shutil total, used, free = shutil.disk_usage('/') print(f"可用空间: {free / 1024**3:.2f} GB")5. 文件权限
确保对目标目录有写入权限。在 Docker 容器中,可能需要以正确的用户身份运行。
6. 并发写入
不要让多个进程同时写入同一个文件。使用文件锁或原子写入(如本文的safe_save方法)来避免并发写入问题。
7.torch.jit.tracevstorch.jit.script
torch.jit.trace:通过运行模型记录操作,不支持控制流(if/for/while)torch.jit.script:分析 Python 代码,支持控制流
如果模型有动态控制流,必须使用torch.jit.script。使用trace可能导致不正确的 TorchScript 模型。
8. 加载时的map_location
加载模型时指定map_location以避免设备不匹配问题:
# 在 CPU 上加载 GPU 模型 model = torch.jit.load('model.pt', map_location='cpu') # 在 GPU 上加载 model = torch.jit.load('model.pt', map_location='cuda:0')9. 模型文件的安全验证
在生产环境中加载模型文件前,始终验证文件的完整性和来源。恶意构造的模型文件可能包含安全风险。
10.torch.load的weights_only参数
从 PyTorch 2.6 开始,torch.load默认使用weights_only=True,只加载张量数据而不执行任意代码。如果加载包含复杂 Python 对象的 checkpoint,可能需要设置weights_only=False:
# PyTorch 2.6+ checkpoint = torch.load('model.pth', weights_only=False)总结
RuntimeError: [enforce fail at inline_container.cc:209] . file not found: archive/data.pkl错误的根本原因是文件格式与加载 API 不匹配或文件已损坏。解决步骤如下:
- 确认使用正确的 API:
torch.save/torch.load用于普通模型,torch.jit.save/torch.jit.load用于 TorchScript 模型 - 验证文件完整性:使用
zipfile检查文件是否为有效的 ZIP 归档 - 使用安全保存:通过临时文件 + 原子重命名避免文件损坏
- 检查 PyTorch 版本兼容性:确保保存和加载使用兼容的版本
- 使用诊断工具:通过
ModelManager.diagnose_file快速定位问题
通过本文提供的ModelManager工具类,可以系统化地处理模型保存/加载的各种问题,确保模型文件的安全性和可加载性。记住,在部署环境中,始终验证模型文件的完整性,并使用正确的 API 进行加载。