4. PyTorch 模型导出:torch.jit.trace 与 torch.jit.script 的区别与实战

模型训练好了,怎么把它搬到生产环境去?这是每个部署工程师都要面对的问题。PyTorch 提供了两种导出方式:torch.jit.tracetorch.jit.script。说白了,一个靠「看」来理解模型,一个靠「读」来理解模型。

我个人习惯把 trace 叫做「傻瓜式导出」,script 叫做「聪明式导出」。为什么这么叫?往下看你就明白了。

4.1 torch.jit.trace:跟踪执行路径

Trace 的原理很简单——给模型一个输入,它跑一遍,把每一步操作都记录下来。就像你开车走一遍路,它就把导航轨迹记下来了。

核心特点:

  • 只记录实际执行到的代码路径
  • 不支持动态控制流(if/for 依赖数据的情况)
  • 导出速度快,生成的模型体积小

来看个例子:

import torch

class MyModel(torch.nn.Module):
    def forward(self, x):
        return x * 2 + 1

model = MyModel()
example_input = torch.randn(1, 3, 224, 224)

# 使用 trace 导出
traced_model = torch.jit.trace(model, example_input)
traced_model.save("model_traced.pt")

嗯,这里要注意。Trace 有个大坑——它只记录你给的那个输入下的执行路径。我在项目中遇到过一个问题:模型里有个 if x.sum() > 0 的分支,测试时输入都是正数,结果导出后部署到线上,遇到负数输入就直接崩了。

避坑指南:我曾经因为 trace 导出导致线上服务挂了两个小时。原因是模型里有个动态的循环,trace 只记录了第一次迭代的路径。所以,如果你的模型有数据依赖的分支或循环,千万别用 trace。

4.2 torch.jit.script:编译计算图

Script 就不一样了。它直接读取你的 Python 代码,把整个控制流都编译成 TorchScript 的中间表示。你想想看,这就像把菜谱完整地翻译成另一种语言,而不是只看你做一次菜。

核心特点:

  • 完整保留控制流逻辑
  • 支持动态图结构
  • 导出速度慢一些,但更安全
import torch

class MyModel(torch.nn.Module):
    def forward(self, x):
        if x.sum() > 0:
            return x * 2
        else:
            return x * 0.5

model = MyModel()

# 使用 script 导出
scripted_model = torch.jit.script(model)
scripted_model.save("model_scripted.pt")

Script 也不是万能的。它要求你的代码必须是「TorchScript 兼容」的。什么意思?就是你不能用那些 Python 特有的语法,比如 yieldwith 语句,或者某些第三方库。

4.3 核心区别对比

对比维度 torch.jit.trace torch.jit.script
工作原理 跟踪执行路径 编译源代码
控制流支持 不支持动态控制流 完整支持
导出速度 较慢
代码兼容性 几乎无限制 需 TorchScript 兼容
调试难度 中等
推荐场景 简单前馈网络 复杂控制流模型

4.4 实战:如何选择?

我一般遵循这个原则:

  1. 先试 trace——如果模型没有动态控制流,trace 是最快最稳的选择
  2. trace 报错再换 script——遇到 UnsupportedNodeError 之类的错误,说明模型里有动态逻辑
  3. 混合使用——有时候可以把模型拆开,静态部分用 trace,动态部分用 script

小技巧:我个人习惯在开发阶段先用 script 导出,确保所有逻辑都被正确编译。等到模型稳定了,再考虑用 trace 优化性能。这样既保证了正确性,又兼顾了效率。

4.5 常见问题与解决方案

Q: trace 导出的模型在 C++ 里跑不起来?

A: 检查一下输入输出的 dtype 和 shape 是否一致。我曾经遇到过一个 bug,训练时用的是 float32,但 trace 时给了 float64 的输入,结果导出的模型在 C++ 端死活加载不了。

Q: script 导出时提示 Cannot insert a Tensor that requires grad

A: 这说明你的模型里还有需要梯度的张量。导出前记得调用 model.eval()torch.no_grad()

Q: 模型里有自定义的 nn.Module 怎么办?

A: 只要你的自定义模块继承自 nn.Module,并且 forward 方法里只用了 PyTorch 支持的操作,script 和 trace 都能处理。如果用了第三方库,那就得考虑用 @torch.jit.ignore 装饰器跳过某些方法。

好了,这一章就到这里。记住一句话:trace 是快刀,script 是钝刀。快刀能切大部分菜,但遇到骨头还是得用钝刀。下一章我们聊聊 ONNX 导出,那又是另一个故事了。