✌️Polygraphy-Cheatsheet

安装Polygraphy

python -m pip install colored polygraphy --extra-index-url https://pypi.ngc.nvidia.com

API

从ONNX模型导出engine,并执行推理,比较输出结果

以下两个模型均为identity模型

"""
This script builds and runs a TensorRT engine with FP16 precision enabled
starting from an ONNX identity model.
"""
import numpy as np
from polygraphy.backend.trt import CreateConfig, EngineFromNetwork, NetworkFromOnnxPath, SaveEngine, TrtRunner


def main():
    # We can compose multiple lazy loaders together to get the desired conversion.
    # In this case, we want ONNX -> TensorRT Network -> TensorRT engine (w/ fp16).
    #
    # NOTE: `build_engine` is a *callable* that returns an engine, not the engine itself.
    #   To get the engine directly, you can use the immediately evaluated functional API.
    #   See examples/api/06_immediate_eval_api for details.
    build_engine = EngineFromNetwork(
        NetworkFromOnnxPath("identity.onnx"), config=CreateConfig(fp16=True)
    )  # Note that config is an optional argument.

    # To reuse the engine elsewhere, we can serialize and save it to a file.
    # The `SaveEngine` lazy loader will return the TensorRT engine when called,
    # which allows us to chain it together with other loaders.
    build_engine = SaveEngine(build_engine, path="identity.engine")

    # Once our loader is ready, inference is simply a matter of constructing a runner,
    # activating it with a context manager (i.e. `with TrtRunner(...)`) and calling `infer()`.
    #
    # NOTE: You can use the activate() function instead of a context manager, but you will need to make sure to
    # deactivate() to avoid a memory leak. For that reason, a context manager is the safer option.
    with TrtRunner(build_engine) as runner:
        inp_data = np.ones(shape=(1, 1, 2, 2), dtype=np.float32)

        # NOTE: The runner owns the output buffers and is free to reuse them between `infer()` calls.
        # Thus, if you want to store results from multiple inferences, you should use `copy.deepcopy()`.
        outputs = runner.infer(feed_dict={"x": inp_data})

        assert np.array_equal(outputs["y"], inp_data)  # It's an identity model!

        print("Inference succeeded!")


if __name__ == "__main__":
    main()

直接导入engine,并执行推理,比较输出结果

快速查看模型结构

  • 输出结果示例

比较多个不同推理后端的输出

查看多个runner的输出结果

  • 输出结果示例

验证单个推理后端的输出结果

Polygraphy 提供的Comparator可用于比较多个runner的少量结果,但不太适合用使用真实数据集来验证单个runner,尤其是在数据集较大的情况下。

在数据集较大的情况下,建议直接使用runner。与使用Comparator不同,使用runner可以完全自由地决定如何加载输入数据以及如何验证输出结果。

可以使用 data_loader 参数为 Comparator.run() 提供自定义的输入数据。

TensorRT API和Polygraphy互操作

Polygraphy 的一个主要特点是与 TensorRT 以及其他后端完全互操作。由于 Polygraphy 没有隐藏底层后端 API,因此可以在 Polygraphy API 和 后端 API (TensorRT等)之间自由切换使用。

Polygraphy 提供了一个 extend 装饰器,可用于轻松扩展现有的 Polygraphy 加载器(loaders)。 可以用来解决以下情况:

  • 在构建引擎前修改 TensorRT 网络

  • 使用 Polygraphy 目前不支持的 TensorRT builder flag

查看代码将生成的TensorRT网络

TensorRT中的int8校准

TensorRT 中的 Int8 校准涉及向 TensorRT 提供一组有代表性的输入数据,作为engine构建过程的一部分。在 TensorRT calibration API中,要求用户将输入数据复制到 GPU 并管理 TensorRT 生成的校准缓存。

Polygraphy 提供了校准器,既可与 Polygraphy 一起使用,也可直接与 TensorRT 一起使用。在后一种情况下,Polygraphy 校准器的行为与普通 TensorRT int8 校准器完全相同。

使用TensorRT API搭建网络

查看代码将生成的TensorRT网络

及时评估函数API

大多数情况下,Polygraphy 附带的惰性加载器(loaders)有几个优点:

  • 把工作推迟到真正需要的时候再做,可以节省时间和空间。

  • 由于构建的加载器非常轻便,因此使用惰性评估加载器(lazily evaluated loaders)的运行程序可以很容易地被复制到其他进程或线程中,然后在那里启动。 如果运行程序引用的是整个模型或者推理会话,那么以这种方式复制它们就不是一件容易的事。

  • 它们允许我们通过将多个加载器组合在一起来提前定义一系列的算子。 例如,我们可以创建一个加载器,从 ONNX 导入模型并生成序列化的 TensorRT 引擎:

  • 如果向加载器提供了可调用对象,加载器就会获得返回值的所有权。

然而,这有时会导致代码的可读性降低。 例如,请看下面的例子:

因此,Polygraphy 为每个加载器提供了相对应的及时评估函数(immediately-evaluated functional equivalents)。

对应于:

下面的代码主要说明了如何利用函数式 API 将 ONNX 模型转换为 TensorRT 网络、修改TensorRT网络、构建启用了 FP16 精度的 TensorRT 引擎并运行推理。 最后还将把引擎保存到文件中,看看如何再次加载并运行推理。

动态shape

为了在 TensorRT 中使用动态输入维度,我们必须在构建引擎时指定一个可能的维度范围。TensorRT 优化配置文件提供了这样的方法,主要包括以下两个步骤:

  1. 在引擎构建过程中,指定一个或多个优化配置文件。 一个优化配置文件包括 3种维度:

    • min:最小的形状。

    • opt:TensorRT应该优化的形状, 一般来说对应于最常用的形状。

    • max: 最大的形状

  2. 在推理过程中,在执行上下文中设置输入形状,然后查询执行上下文以确定输出形状,调整设备缓冲区的大小,以容纳整个输出。

    对于单输入、单输出模型,大致流程如下:

Polygraphy可以简化这两个步骤:

  1. 它提供了一个 Profile 抽象类,OrderedDict类型,可以转换为 TensorRT 的IOptimizationProfile ,并包含一些实用功能:

    • fill_defaults:使用网络的默认shape填充配置文件。

    • to_trt:使用Profile中的shape创建 TensorRT IOptimizationProfile

    此外,Profile 还能自动处理shape-tensornon-shape-tensor这类的输入。

  2. TrtRunner 会自动处理模型中的动态shape。 与Profile一样,还能自动处理shape-tensornon-shape-tensor这类的输入。此外,运行程序只会在需要时更新上下文绑定的形状、因为改变形状的开销很小。只有当输出设备缓冲区的当前大小小于上下文输出时,才会调整其大小,避免不必要的重新分配。

Setting The Stage

为了举例说明,我们设想一个假设的场景:

我们正在使用图像分类模型运行推理工作。通常,我们在在线情况下使用这种模型,即我们希望尽可能缩短延迟时间,因此我们将一次处理一幅图像。在这种情况下,假设批量大小为 [1]

但是,如果用户数量过多,我们就需要采用动态批处理,这样才不会影响吞吐量。我们的批处理规模范围仍然很小,以保持可接受的延迟。我们最常用的批量大小是 4。

在这种情况下,假设 batch_size 的范围为 [1, 32]。在更罕见的情况下,我们需要离线处理大量数据。在这种情况下,我们使用非常大的批处理量来提高吞吐量。 在这种情况下,假设 batch_size[128]

Performance Considerations

在实施推理过程中,我们需要考虑一些取舍:

  • 一个batchsize较大的Profile在整个范围内的表现不如多个batchsize较小的Profile

  • Profile内切换shape的成本很小,但并不为零。

  • 在上下文中切换配置文件的成本比在配置文件中切换shape的成本更高。

    我们可以为每个配置文件创建单独的执行上下文,并在运行时选择适当的上下文,从而避免切换配置文件的成本。 不过,请记住,每个上下文都需要一些额外的内存。

A Possible Solution

假设图像大小为 (3、28、28),我们将创建三个独立的配置文件,并为每个配置文件创建一个独立的上下文:

  1. 对于低延迟情况: min=(1, 3, 28, 28), opt=(1, 3, 28, 28), max=(1, 3, 28, 28)

  2. 对于动态分批情况: min=(1, 3, 28, 28), opt=(4, 3, 28, 28), max=(32, 3, 28, 28)

    请注意,我们在 opt 中使用的批量大小为 "4",因为这是最常见的情况。

  3. 对于离线情况: min=(128, 3, 28, 28), opt=(128, 3, 28, 28), max=(128, 3, 28, 28)

我们将为每个上下文创建一个相应的 TrtRunner。如果拥有引擎和上下文(不是通过惰性加载器获得的),那么激活runner的成本就会很低,它只需要分配输入和输出缓冲区。

  • 输出结果示例:

保存输入数据&处理运行结果

Comparator.run 推理的输入和输出可以序列化并保存为 JSON 文件,以便重复使用。输入存储为 List[Dict[str,ndarray]],而输出则存储在 RunResults 对象中,该对象可以存储多个运行器的输出。Polygraphy 包含方便的应用程序接口,可以轻松加载和操作这些对象。

先生成inputs.jsonsoutputs.json文件

CLI

TensorRT中的int8校准

  • 自定义数据集校准

还可以使用API中的示例,不过需要指定函数名称:

  • 使用缓存校准

在TensorRT中构建确定性引擎(engine)

在引擎构建过程中,TensorRT 会运行多个核函数并进行计时,以选出最优的核函数。由于每次计时可能会略有不同,因此这一过程本质上是非确定性的。

在许多情况下,我们可能需要确定性的引擎构建。实现这一点的一种方法是使用 IAlgorithmSelector API 来确保每次都选择相同的核函数。

为了简化这一过程,Polygraphy 提供了两个内置算法选择器:TacticRecorderTacticReplayer,与之对应的是 --save-tactics 和 --load-tactics 选项。

Running The Example

  1. 创建引擎并保存replay文件:

    生成的 replay.json 文件是可读的。我们可以选择使用inspect tactics查看它:

  2. replay文件用于另一个引擎构建:

  3. 验证两个engine是否完全相同:

以下是一些 diff 命令的常见示例:

  1. 比较两个文件并显示差异:

    这将比较 file1.txtfile2.txt,并显示它们之间的差异。

  2. 显示所有差异的详细信息:

    使用 -u 选项将以 Unified diff 格式显示所有差异的详细信息。

  3. 递归比较两个目录并显示差异:

    这将递归地比较 directory1directory2 中的文件,并显示它们之间的差异。

  4. 将差异输出到文件中:

    这将比较 file1.txtfile2.txt,并将差异输出到 diff_output.txt 文件中。

  5. 忽略空白字符的差异:

    使用 -b 选项可以忽略空白字符的差异。

  6. 显示所有差异并标出行号:

    使用 -N 选项将显示所有差异并在每行前面标出行号。

  7. 只显示不同之处的行:

    使用 -q 选项将只显示不同之处的行,而不会显示详细的差异信息。

动态shape

  1. 使用三个独立的profile创建engine

对于有多个输入的模型,只需为每个 --trt-*-shapes 参数提供多个输入即可。

--trt-min-shapes input0:[10,10] input1:[10,10] input2:[10,10] ...

如果min == opt == max,可以直接使用--input-shapes而不用单独设置min/opt/max

  • 输出结果示例:

将onnx转换为FP16格式&分析精度损失情况

  1. 将模型转换为FP16格式

  1. 查看转换后的模型文件

  • 输出结果示例:

  1. ONNX-Runtime 下运行 FP32 FP16 模型,然后比较结果:

  • 输出结果示例:

--atol 0.001--rtol 0.001: 这两个参数分别设置了绝对误差容限和相对误差容限。在模型输出和参考输出之间的比较中,这些参数用于确定何时认为两个值相等。在这种情况下,设置为 0.001 意味着只有当两个值之间的差异小于或等于 0.001 时,才会认为它们是相等的。

  1. 检查 FP16 模型的中间输出结果是否存在 NaN 或无穷大

调试TensorRT引擎生成策略(tactics)

由于 TensorRT 生成器依赖于定时策略(timing tactics),因此引擎生成是非确定性的。

解决这一问题的方法之一是多次运行生成器,保存每次运行的tatic文件,可以对它们进行比较,以确定哪种tactic可能是错误的根源。debug build命令自动执行上述过程。

有关调试工具如何工作的详细信息,请参阅帮助: polygraphy debug -hpolygraphy debug build -h

  1. ONNX-Runtime 生成输出:

使用debug build重复构建 TensorRT 引擎,并将结果与输出进行比较,每次都保存一个tactics文件:

  • 提供 --until 选项,以便工具知道何时停止,这可以是迭代次数,也可以是goodbad。在后一种情况下,工具将分别在找到第一个通过或失败的迭代后停止。

  • 我们指定 --save-tactics 来保存每次迭代的重放文件,然后使用 --artifacts 来告诉debug build来管理它们,这包括将它们分类到用 --artifacts-dir 指定的目录下的goodbad子目录中。

  1. 使用inspect diff-tactics来确定哪些tactics可能不好:

减少运行失败的ONNX模型

当一个模型由于某种原因出现故障时(例如,TensorRT 中的精度问题),将其定位到引发故障的最小可能子图很有用,这样更容易找出故障原因,debug reduce这个工具可以帮助我们快速定位到这样的子图。

在本例中,我们的模型(./model.onnx)在 TensorRT 中存在精度问题,模拟故障通过Mul节点触发。

  1. 对于使用动态输入形状或包含形状算子的模型,应冻结输入形状并折叠形状算子:

  1. 假设 ONNX-Runtime 能给出正确的输出结果。我们将首先为网络中的每个张量生成输出值。我们还将保存所使用的输入值:

查看 TensorRT 网络

inspect model工具可以自动将其他格式的网络转换为 TensorRT 网络,然后显示出来。

还可以使用 --show layers attrs weights 来显示更多的详细信息,包括图层属性信息。

查看 TensorRT 引擎

  1. 生成具有动态形状和 2 个profile的引擎:

  1. 查看TensorRT引擎

还可以使用 --show layers attrs weights 来显示更多的详细信息。

查看ONNX模型

还可以使用 --show layers attrs weights 来显示更多的详细信息。

查看模型的输出数据

  1. 使用 ONNX-Runtime 生成一些推理的输出结果:

  1. 查看输出结果

查看模型的输入数据

  1. 通过运行推理生成一些输入数据:

  1. 查看输入数据

查看tactic文件

  1. 生成tactic文件

  1. 查看tactic文件

检查TensorRT对ONNX算子的支持情况

  1. 可为给定的 ONNX 计算图提供有关 TensorRT 对ONNX 算子是否支持的详细信息。 它还会从原始模型中分割并保存支持和不支持的子图。

输出结果如下:

  • 原图

  • 支持的子图

  • 不支持的子图

在本例中,model.onnx 包含一个 TensorRT 不支持的 Fake 节点。 汇总表显示了不支持的运算符、不支持的原因、在图中出现的次数,以及这些节点在图中的索引范围(例如:一行中有多个不支持的节点)。 该索引遵循"包头不包尾"原则,和python中列表的索引规则是一样的。更多信息和选项,请参阅 polygraphy inspect capability --help

不同推理框架之间的比较

Last updated

Was this helpful?