# Polygraphy Python API ## Table of Contents - [Introduction](#introduction) - [Backends](#backends) - [Loaders](#loaders) - [Runners](#runners) - [Writing A Custom Runner](#writing-a-custom-runner) - [Comparator](#comparator) - [Data Loaders](#data-loaders) - [Logger](#logger) - [Putting It All Together](#putting-it-all-together) - [Enabling PyTorch](#enabling-pytorch) - [Examples](#examples) - [Python API Reference Documentation](#python-api-reference-documentation) - [Building Python API Documentation Locally](#building-python-api-documentation-locally) - [Deprecation Policy](#deprecation-policy) ## Introduction The Polygraphy API consists broadly of two major components: [`Backend`s](#backends) and the [`Comparator`](#comparator). **NOTE:** To help you get started with the API, you can use the [`run`](./tools/run/) tool with the `--gen-script` option to auto-generate template scripts that use the Polygraphy API. > :warning: Any APIs not documented in the [API reference documentation](#python-api-reference-documentation) should be considered internal only and do not adhere to the [deprecation policy](#deprecation-policy) for public APIs. Thus, they may be modified or removed at any time without warning. Avoid using undocumented APIs! ## Backends A Polygraphy backend provides an interface for a deep learning framework. Backends are comprised of two components: Loaders and Runners. ### Loaders A `Loader` is a functor or callable that loads or operates on models in some way. Existing `Loader`s can be composed for more advanced behaviors. For example, we can implement a conversion like `ONNX -> TensorRT Network -> TensorRT Engine`: ```python from polygraphy.backend.trt import EngineFromNetwork, NetworkFromOnnxPath build_engine = EngineFromNetwork(NetworkFromOnnxPath("/path/to/model.onnx")) ``` `build_engine` is a callable that will build a TensorRT engine. Polygraphy also provides immediately evaluated functional variants of each Loader. These use the same names, except `snake_case` instead of `PascalCase`, and expose the same APIs. Using the functional loaders, the conversion above would be: ```python from polygraphy.backend.trt import engine_from_network, network_from_onnx_path engine = engine_from_network(network_from_onnx_path("/path/to/model.onnx")) ``` `engine` is a TensorRT engine as opposed to a callable. ### Runners A `Runner` uses a loader to load a model and can then run inference (see [`BaseRunner`](./backend/base/runner.py)). **IMPORTANT:** Runners may reuse their output buffers. Thus, if you need to save outputs from multiple inferences, you should make a copy of the outputs with `copy.deepcopy(outputs)`. To use a runner, you just need to activate it, and then call `infer()`. Note that activating a runner can be very expensive, so you should minimize the number of times you activate a runner - ideally do not do this more than once. It is recommended to use a context manager to activate and deactivate the runner rather than calling the functions manually: ```python from polygraphy.backend.trt import TrtRunner with TrtRunner(build_engine) as runner: outputs = runner.infer(feed_dict={"input0": input_data}) ``` #### Writing A Custom Runner Generally, you do not need to write custom runners unless you want to support a new backend. In case you do, in the simplest case, you only need to implement two functions: - `infer_impl`: Accepts a dictionary of numpy buffers, runs inference, and finally returns a dictionary containing the outputs. - `get_input_metadata_impl`: Returns a [`TensorMetadata`](./common/struct.py) mapping input names to their shapes and data types. You may use `None`, negative numbers, or strings to indicate dynamic dimensions. For more advanced runners, where some setup is required, you may also need to implement the `activate_impl()` and `deactivate_impl()` functions. For example, in the `TrtRunner`, engines are created in `activate_impl()` and destroyed in `deactivate_impl()`. Importantly, the GPU is *not used at all* until these functions have been called (notice, for example, that in the `TrtRunner`, the CUDA runtime library is only loaded in the `activate_impl()` function). This allows the `Comparator` to optionally provide each runner with exclusive access to the GPU, to prevent any interference between runners. ## Comparator The `Comparator` is used to run inference for runners, and then compare accuracy (see [Comparator.py](./comparator/comparator.py)). This process is divided into two phases: 1. Running inference: ```python run_results = Comparator.run(runners) ``` This function accepts a list of runners and returns a `RunResults` object (see [Comparator.py](./comparator/comparator.py)) containing the inference outputs of each run. It also accepts an optional `data_loader` argument to control the input data. If not provided, it will use the default data loader. `Comparator.run()` continues until inputs from the data loader are exhausted. 2. Comparing results: ```python Comparator.compare_accuracy(run_results) ``` This function accepts the results returned by `Comparator.run` and compares them between runners. **IMPORTANT:** The Comparator is designed for scenarios where you need to compare a small number of inputs across multiple runners. It is **not** a good idea to use it to validate a model with an entire dataset! Instead, runners should be used directly for such cases (see the [example](../examples/api/02_validating_on_a_dataset)). ### Data Loaders A data loader is used by the `Comparator` to load input data to feed to each runner (for example, see Polygraphy's [default data loader](./comparator/data_loader.py)). A data loader can be any generator or iterable that yields a dictionary of input buffers. In the simplest case, this can just be a `list` of `dict`s. In case you don't know details about the inputs ahead of time, you can access the `input_metadata` property in your data loader, which will be set to an `TensorMetadata` instance by the Comparator. **NOTE:** Polygraphy provides a default `DataLoader` class that uses numpy to generate random input buffers. The input data can be bounded via parameters to the constructor. ## Logger Polygraphy also includes a global logger which can control the verbosity not only of messages emitted by Polygraphy, but also of those emitted by underlying frameworks, like TensorRT. For example, the `EXTRA_VERBOSE` verbosity corresponds to TensorRT's `kVERBOSE` logging mode. To set the verbosity of the logger, use: ```py G_LOGGER.module_severity = severity ``` For example: ```py G_LOGGER.module_severity = G_LOGGER.EXTRA_VERBOSE ``` By default logs are emitted to the stdout/stderr stream. If you would like to have them stored in a file, set the `log_file` property to your log file: ```py G_LOGGER.log_file = "your_log_file.log" ``` If you would like to have logs being emitted to python `logging` module, set the following flag: ```py G_LOGGER.use_python_logging_system = True ``` After that, you can define your logging configuration using the `logging` module. ## Putting It All Together Now that you know the basic components of Polygraphy, let's take a look at how they fit together. In this example, we will write a script that: 1. Builds a TensorRT engine from an ONNX model 2. Bounds input values in the range `[0, 2]` 3. Runs inference using ONNX-Runtime and TensorRT 4. Compares the results and checks that they match ```python from polygraphy.backend.onnxrt import OnnxrtRunner, SessionFromOnnx from polygraphy.backend.trt import TrtRunner, EngineFromNetwork, NetworkFromOnnxPath from polygraphy.comparator import Comparator, DataLoader model_path = "/path/to/model.onnx" build_onnxrt_session = SessionFromOnnx(model_path) build_engine = EngineFromNetwork(NetworkFromOnnxPath(model_path)) runners = [ OnnxrtRunner(build_onnxrt_session), TrtRunner(build_engine), ] data_loader = DataLoader(val_range=(0, 2)) run_results = Comparator.run(runners, data_loader=data_loader) assert bool(Comparator.compare_accuracy(run_results)) ``` ## Enabling PyTorch In order to enable PyTorch, you need to provide three things to the `PytRunner`: 1. A model loader: In the simplest case, this can be a callable that returns a `torch.nn.Module`. 2. `input_metadata`: A `TensorMetadata` describing the inputs of the model. This maps input names to their shapes and data types. As with other runners, `None` may be used to indicate dynamic dimensions. **NOTE:** Other runners are able to automatically determine input metadata by inspecting the model definition, but because of the way PyTorch is implemented, it is difficult to write a generic function to determine model inputs from a `torch.nn.Module`. 3. `output_names`: A list of output names. This is used by the `Comparator` to match `PytRunner` outputs to those of other runners. ## Examples You can find complete code examples that use the Polygraphy Python API [here](../examples/api). ## Python API Reference Documentation For more details, see the [Polygraphy Python API reference documentation](https://docs.nvidia.com/deeplearning/tensorrt/latest/_static/polygraphy/index.html). ### Building Python API Documentation Locally To build the API documentation, first install required packages: ```bash python -m pip install -r docs/requirements.txt ``` and then use the `make` target to build docs: ```bash make docs ``` The HTML documentation will be generated under `build/docs` To view the docs, open `build/docs/index.html` in a browser or HTML viewer. ## Deprecation Policy When removing or modifying public APIs in breaking ways, Polygraphy follows the following procedure: 1. The API is marked to be removed in some future version of Polygraphy (let's call it version `N`). 2. The API continues to work as normal until *at least* version `N`, but will issue deprecation warnings when used. These warnings typically suggest which new APIs should be used instead. 3. The API may be removed in any Polygraphy version >= `N`.