• 文档 >
  • 编写 TorchScript 转换器
快捷方式

编写 TorchScript 转换器

背景

在 JIT IR 中,操作表示为图中的节点。一个节点有输入和输出,由 torch::jit::Values 表示,它们是流入和流出节点的数据的类型化抽象表示。TensorRT 通过使用 nvinfer1::ILayersnvinfer1::ITensors 来表示其图,它们分别是节点和值的对应物。转换器的目标是创建新的 ILayer 和子图,这些 ILayer 和子图执行由节点指定的操作,并将生成的 ITensor 和 Value 关联在一起。

转换器

转换器应该是使用输入列表(nvinfer1::ITensorstorch::jit::IValues)来构建与 LibTorch 操作等效的层的函数。

转换器可以使用 RegisterNodeConversionPatterns 辅助类进行注册,在该类中,您实例化一个 RegisterNodeConversionPatterns 对象,并在其上调用模式函数(如下所示),该函数接受一个描述操作函数签名的字符串,该字符串将导致转换器运行,以及一个执行实际转换的 lambda 或函数。

注意,模式函数可以链式调用。

auto acthardtanh TORCHTRT_UNUSED = RegisterNodeConversionPatterns()
    .pattern({
        "aten::hardtanh(Tensor self, Scalar min_val=-1, Scalar max_val=1) -> (Tensor)",
        [](ConversionCtx* ctx, const torch::jit::Node* n, args& args) -> bool {
            auto in = args[0].ITensor();
            auto min = args[1].unwrapToDouble();
            auto max = args[2].unwrapToDouble();

            auto new_layer = ctx->net->addActivation(*in, nvinfer1::ActivationType::kCLIP);
            TORCHTRT_CHECK(new_layer, "Unable to create layer for aten::hardtanh");

            new_layer->setAlpha(min);
            new_layer->setBeta(max);

            new_layer->setName(util::node_info(n).c_str());
            auto out_tensor = ctx->AssociateValueAndTensor(n->outputs()[0], new_layer->getOutput(0));

            LOG_DEBUG("Output shape: " << out_tensor->getDimensions());
            return true;
        }
    });

转换器合约

向转换器保证了什么

  1. 在参数中,每个节点输入值都有一个条目,可以是 ITensor 或 IValue

  2. 输入将根据函数签名按顺序提供

转换器的职责

  1. 必须保证参数的类型,以便无需检查即可解包 Arg 联合体,通常输入张量参数可以预期为 ITensor

  2. 任何权重或静态值必须保证在转换结束前有效

    1. 一个有用的工具是下面描述的 Weights 辅助类

  3. 转换器应为节点的每个输出生成一个 IValue 或 ITensor。编译器将对此进行检查,如果存在没有关联的 ITensor 或 IValue 的 Value,则会产生警告。

  4. 输出必须被注解

    1. 在转换上下文的 value_tensor_map 中,JIT 节点的输出值与新 TRT 层的输出张量之间必须存在关联

  5. 命名您的层

    1. 当我们能够跟踪哪些层和节点相互对应时,调试会容易得多。我们当前使用的系统是使用节点的“节点信息”作为层的名称。

  6. 命名您的张量

    1. 使用输出值的调试名称作为新 ITensor 的名称(同样是为了调试)

转换上下文

转换上下文维护转换的状态,它管理网络定义、两个映射(一个存储 Value 和 IValue 之间的关联(evaluated_value_map),另一个存储 Value 和 ITensor 之间的关联),以及任何需要存活到转换结束的内存。在转换器中,您将与之交互的主要 API 是直接访问网络定义以添加层 ctx->net 和数据关联函数 ctx->AssociateValueAndTensor()ctx->AssociateValueAndIValue(),您将使用它们来向 TRT 层添加层,并记录节点输出和静态值或 TensorRT 层输出的配对。

参数 (Args)

提供给转换器的参数是 nvinfer1::ITensorstorch::jit::IValues(即 TensorRT 图中的抽象数据流和静态值)的可检查联合体。我们保证您会为节点的每个输入值提供一些参数。它们按函数签名的顺序提供。可以预期输入(即在 PyTorch 中传递给模块的 forward 函数的参数)将是 ITensors,但如果您不确定,Arg 类也提供了在解包前安全检查参数的机制。如果您知道是安全的,Args 还有深度解包方法,让您可以直接获取 IValue 中的底层数据。如果 IValue 有可能是 None,您还可以传入一个备用值。IValue 已被扩展,仅在 TensorList 的情况下能够容纳 ITensor 的包装器。您可以通过类似这样的模式从 IValue 中获取 ITensor:ivalue.toCustomClass<TensorContainer>()->tensor()。您可以使用 ivalue.isTensor()ivalue.isCustomClass() 来判断一个 IValue 是否包含 Tensor 或 ITensor。

权重 (Weights)

权重在构建时使用,因此任何权重都必须保证在转换阶段结束前有效。TensorRT 还使用其自己的权重结构来保存权重。转换器可以使用一个围绕此类的包装器,它抽象了大部分这些工作。

权重包装器类可以接受 at::Tensors 或单个值(目前是这样)。在构造这些权重时,您还需要传递转换上下文,因为权重类内部会分配由转换上下文管理的内存来存储张量数据的副本。当转换上下文的析构函数被销毁时,这些数据会被释放,因此转换器基本上不需要考虑它。

根据输入数据的形状会生成元数据,这在与 TensorRT 交互时非常有用,例如输入映射数、输出映射数和核形状。

其他建议

在处理权重和其他静态值时,您可以使用完整的 aten 库。这意味着您可以在转换期间做相当多的工作以实现高效转换。一个很好的例子是 batch_norm 转换器,其中转换器在创建 TensorRT 层之前,会使用 PyTorch 进行操作融合。

文档

访问全面的 PyTorch 开发者文档

查看文档

教程

为初学者和高级开发者提供深入的教程

查看教程

资源

查找开发资源并让您的问题得到解答

查看资源