3. 模型导出与序列化:PyTorch 模型保存与加载、ONNX 导出、TorchScript 转换

GraphSAGE 模型从训练环境迁移到生产推理系统,核心挑战在于 序列化格式的兼容性 与 推理性能的优化。本章聚焦三种主流方案:PyTorch 原生序列化、ONNX 跨平台导出、TorchScript 静态图编译。每种方案在部署场景、算子支持度、推理速度上存在显著差异,需根据上线环境做出选择。

3.1 PyTorch 模型保存与加载

PyTorch 提供两种序列化范式:状态字典(state_dict) 与 完整模型(torch.save)。对于 GraphSAGE 这类包含自定义 SAGEConv 层的模型,推荐使用 state_dict 方式,以保持代码与权重的解耦。

保存方式 文件内容 加载依赖 适用场景
torch.save(model.state_dict(), "gsage.pt") 仅权重参数(OrderedDict) 需先实例化模型类 生产环境、模型版本管理
torch.save(model, "gsage_full.pt") 完整模型结构 + 权重 需保持相同 Python 环境 快速实验、本地调试
torch.jit.save(traced_model, "gsage_ts.pt") TorchScript 序列化图 无需原始 Python 类 C++ 部署、移动端
最佳实践: 保存 state_dict 时,同时序列化 model.args(如隐藏层维度、聚合方式)为 JSON 文件,确保加载时能重建完全一致的模型结构。
# 保存
torch.save({
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'epoch': epoch,
    'args': {'in_dim': 1433, 'hidden_dim': 128, 'out_dim': 7, 'aggregator': 'mean'}
}, "gsage_checkpoint.pt")

# 加载
checkpoint = torch.load("gsage_checkpoint.pt", map_location='cpu')
model = GraphSAGE(checkpoint['args']['in_dim'],
                  checkpoint['args']['hidden_dim'],
                  checkpoint['args']['out_dim'],
                  aggregator=checkpoint['args']['aggregator'])
model.load_state_dict(checkpoint['model_state_dict'])

3.2 ONNX 导出:跨平台与硬件加速

ONNX(Open Neural Network Exchange)为 GraphSAGE 提供 框架无关的中间表示,可部署到 ONNX Runtime、TensorRT、OpenVINO 等推理引擎。导出时需处理图神经网络特有的 可变输入形状 与 稀疏邻接矩阵。

常见陷阱: GraphSAGE 的邻接矩阵通常为稀疏 COO 格式,ONNX 原生不支持动态稀疏输入。解决方案:在导出前将邻接矩阵转换为 固定大小的稠密张量,或使用 torch.onnx.export 的 dynamic_axes 参数声明 batch 维度可变。
import torch.onnx

# 准备固定形状的示例输入
dummy_x = torch.randn(1, 1433)          # 单节点特征
dummy_adj = torch.randn(1, 1433, 1433)  # 稠密邻接矩阵(仅用于导出)

torch.onnx.export(
    model,
    (dummy_x, dummy_adj),
    "graphsage.onnx",
    input_names=['node_feat', 'adj_matrix'],
    output_names=['embedding'],
    dynamic_axes={
        'node_feat': {0: 'batch_size'},
        'adj_matrix': {0: 'batch_size'},
        'embedding': {0: 'batch_size'}
    },
    opset_version=17
)
ONNX 导出与推理链路 PyTorch 训练 GraphSAGE 模型 ONNX 导出 torch.onnx.export ONNX Runtime 推理引擎 CPU / GPU TensorRT OpenVINO 移动端 关键约束: • 邻接矩阵需转为稠密张量或使用 dynamic_axes 声明可变维度 • 自定义 SAGEConv 中的 scatter 操作需替换为 ONNX 兼容算子

3.3 TorchScript 转换:静态图与 C++ 部署

TorchScript 将动态 PyTorch 模型编译为 静态计算图,消除 Python 解释器开销,支持 C++ 生产环境直接加载。GraphSAGE 的转换有两种路径:追踪(Tracing) 与 脚本化(Scripting)。

  • Tracing(torch.jit.trace):通过一次前向传播记录算子序列,适合不含数据依赖控制流的模型。GraphSAGE 的固定深度采样场景适用。
  • Scripting(torch.jit.script):直接编译 Python 代码,支持 if/for 等控制流,但要求模型代码为 TorchScript 子集(无动态字典、无外部库调用)。
推荐策略: 对于 GraphSAGE,优先使用 torch.jit.trace 配合 torch.jit.script 混合模式——将 SAGEConv 层用 @torch.jit.script 装饰,整体模型用 trace 导出,兼顾灵活性与性能。
# 方法一:Tracing(推荐用于推理图固定的场景)
traced_model = torch.jit.trace(model, (dummy_x, dummy_adj))
traced_model.save("graphsage_traced.pt")

# 方法二:Scripting(需修改模型代码适配 TorchScript)
@torch.jit.script
def sage_forward(x, adj, weight):
    # 自定义聚合逻辑
    return torch.mm(adj, torch.mm(x, weight))

scripted_model = torch.jit.script(model)
scripted_model.save("graphsage_scripted.pt")

3.4 三种方案对比与选型

维度 PyTorch state_dict ONNX TorchScript
跨语言支持 仅 Python C++/Java/C#/Python C++/Python
推理速度(vs PyTorch) 基准 1.0x 1.2x – 3.0x(依赖引擎) 1.5x – 2.5x
动态图支持 完全支持 有限(需 static_axes) 有限(script 模式支持)
自定义算子 无限制 需注册 ONNX 算子 需 TorchScript 兼容
部署复杂度 低 中(需处理图输入) 中(需代码适配)
序列化方案选型决策流程 需要跨平台部署? ONNX 导出 处理稀疏→稠密转换 TorchScript trace + script 混合 若仅需 Python 内部版本管理,直接使用 state_dict + JSON 参数文件即可

3.5 序列化验证清单

模型导出后必须通过 数值一致性验证,确保推理结果与原始 PyTorch 模型在相同输入下的误差在可接受范围内(通常 < 1e-5)。

def validate_export(original_model, exported_path, dummy_input):
    # 原始模型推理
    original_model.eval()
    with torch.no_grad():
        ref_output = original_model(*dummy_input)

    # 加载导出模型
    if exported_path.endswith('.onnx'):
        import onnxruntime as ort
        session = ort.InferenceSession(exported_path)
        ort_inputs = {session.get_inputs()[i].name: dummy_input[i].numpy()
                      for i in range(len(dummy_input))}
        exported_output = torch.tensor(session.run(None, ort_inputs)[0])
    else:  # TorchScript
        exported_model = torch.jit.load(exported_path)
        exported_output = exported_model(*dummy_input)

    # 计算最大相对误差
    error = torch.max(torch.abs(ref_output - exported_output) /
                      (torch.abs(ref_output) + 1e-8))
    assert error < 1e-5, f"导出验证失败,最大相对误差:{error:.2e}"
    print(f"✅ 导出验证通过,最大相对误差:{error:.2e}")
生产级建议: 将验证脚本集成到 CI/CD 流水线中,每次模型更新后自动执行。对于 ONNX 导出,额外检查算子版本兼容性(使用 onnx.checker.check_model);对于 TorchScript,使用 torch.jit.optimize_for_inference 进一步折叠 BatchNorm 与 Dropout 层。