[1.3. How Does TensorRT Work?]

inference 과정을 최적화 시키기 위해서, TensorRT는 network definition을 가져와서 해당 환경(GPU)에서 최적화를 하며, inference engine을 생성한다. 이 과정은 상당한 시간이 소요되며, embedded 된 platform(하드웨어)에서는 더 오래걸린다. 이런 이유 때문에, 보통 engine을 만들면 그것을 serialize화 하여, 저장하고 후에 읽어와서 사용하는 방법을 선호한다.

Note: 위의 generated된 file은 다른 tensorRT 버전 혹은 다른 플렛폼에서 사용할 수 없다. 특정 gpu에서만 사용해야한다.

serialize

직렬화(直列化) 또는 시리얼라이제이션(serialization)은 컴퓨터 과학의 데이터 스토리지 문맥에서 데이터 구조오브젝트 상태를 동일하거나 다른 컴퓨터 환경에 저장(이를테면 파일이나 메모리 버퍼에서, 또는 네트워크 연결 링크 간 전송)하고 나중에 재구성할 수 있는 포맷으로 변환하는 과정이다.[1]

https://ko.wikipedia.org/wiki/%EC%A7%81%EB%A0%AC%ED%99%94

다음과 같은 과정을 통해서 inference engine이 만들어진다.

  • 사용되지 않는 output을 가지는 layer를 삭제
  • convolution, bias, Relu operations를 결합하여 연산
  • 비슷한 parameter 혹은 같은 source tensor를 사용하는 연산을 묶기
  • layer output을 correct eventual destination에 지정하여 concatenation layer 합치기

또한 precision of weights를 변경하여 최적화하기도 한다. (half-precision)

[1.4. What Capabilities Does TensorRT Provide?]

import, calibrate, generate, deploy optimized networks

Network는 Caffe 혹은 다른 framework를 통해서 import할 수 있다. (UFF or ONNX formax)

다음은 tensorRT의 대표적인 라이브러리이다.

  • Network Definition

    input, output tensor를 정의하거나, 특정 layer를 정의할 때 사용할 수 있다.

  • Builder

    engine을 만들 때 사용

  • Engine

    inference를 실행할 때 사용한다.

  • Caffe Parser

    caffe로 저장된 모델을 불러들일때 사용

  • UFF Parser

    UFF로 저장된 모델을 불러들일때 사용

  • ONNX Parser

    ONNX로 저장된 모델을 불러들일

지원하지 않는 노드/레이어

object detection 혹은 다른 모델을 tensorRT에 적용할 때, 지원하지 않는 노드가 있을 수도 있다. 이럴때는 GraphSurgeon을 이용하여 따로 정의해주어야 한다.

아래는 SSD에 tensorRT를 적용할 때의 예시이다.

# Model download and UFF convertion utils
import os
import sys
import tarfile

import requests
import tensorflow as tf
import tensorrt as trt
import graphsurgeon as gs
import uff

class ModelData(object):
    # Name of input node
    INPUT_NAME = "Input"
    # CHW format of model input
    INPUT_SHAPE = (3, 300, 300)
    # Name of output node
    OUTPUT_NAME = "NMS"

    @staticmethod
    def get_input_channels():
        return ModelData.INPUT_SHAPE[0]

    @staticmethod
    def get_input_height():
        return ModelData.INPUT_SHAPE[1]

    @staticmethod
    def get_input_width():
        return ModelData.INPUT_SHAPE[2]

def ssd_unsupported_nodes_to_plugin_nodes(ssd_graph):
    """Makes ssd_graph TensorRT comparible using graphsurgeon.
    This function takes ssd_graph, which contains graphsurgeon
    DynamicGraph data structure. This structure describes frozen Tensorflow
    graph, that can be modified using graphsurgeon (by deleting, adding,
    replacing certain nodes). The graph is modified by removing
    Tensorflow operations that are not supported by TensorRT's UffParser
    and replacing them with custom layer plugin nodes.
    Note: This specific implementation works only for
    ssd_inception_v2_coco_2017_11_17 network.
    Args:
        ssd_graph (gs.DynamicGraph): graph to convert
    Returns:
        gs.DynamicGraph: UffParser compatible SSD graph
    """
    # Create TRT plugin nodes to replace unsupported ops in Tensorflow graph
    channels = ModelData.get_input_channels()
    height = ModelData.get_input_height()
    width = ModelData.get_input_width()

    Input = gs.create_plugin_node(name="Input",
        op="Placeholder",
        dtype=tf.float32,
        shape=[1, channels, height, width])
    PriorBox = gs.create_plugin_node(name="GridAnchor", op="GridAnchor_TRT",
        minSize=0.2,
        maxSize=0.95,
        aspectRatios=[1.0, 2.0, 0.5, 3.0, 0.33],
        variance=[0.1,0.1,0.2,0.2],
        featureMapShapes=[19, 10, 5, 3, 2, 1],
        numLayers=6
    )
    NMS = gs.create_plugin_node(
        name="NMS",
        op="NMS_TRT",
        shareLocation=1,
        varianceEncodedInTarget=0,
        backgroundLabelId=0,
        confidenceThreshold=1e-8,
        nmsThreshold=0.6,
        topK=100,
        keepTopK=100,
        numClasses=91,
        inputOrder=[0, 2, 1],
        confSigmoid=1,
        isNormalized=1
    )
    concat_priorbox = gs.create_node(
        "concat_priorbox",
        op="ConcatV2",
        dtype=tf.float32,
        axis=2
    )
    concat_box_loc = gs.create_plugin_node(
        "concat_box_loc",
        op="FlattenConcat_TRT",
        dtype=tf.float32,
        axis=1,
        ignoreBatch=0
    )
    concat_box_conf = gs.create_plugin_node(
        "concat_box_conf",
        op="FlattenConcat_TRT",
        dtype=tf.float32,
        axis=1,
        ignoreBatch=0
    )

    # Create a mapping of namespace names -> plugin nodes.
    namespace_plugin_map = {
        "MultipleGridAnchorGenerator": PriorBox,
        "Postprocessor": NMS,
        "Preprocessor": Input,
        "ToFloat": Input,
        "image_tensor": Input,
        "MultipleGridAnchorGenerator/Concatenate": concat_priorbox,
        "MultipleGridAnchorGenerator/Identity": concat_priorbox,
        "concat": concat_box_loc,
        "concat_1": concat_box_conf
    }

    # Create a new graph by collapsing namespaces
    ssd_graph.collapse_namespaces(namespace_plugin_map)
    # Remove the outputs, so we just have a single output node (NMS).
    # If remove_exclusive_dependencies is True, the whole graph will be removed!
    ssd_graph.remove(ssd_graph.graph_outputs, remove_exclusive_dependencies=False)
    return ssd_graph
  • https://github.com/NVIDIA/object-detection-tensorrt-example/tree/master/SSD_Model/utils

Reference

https://docs.nvidia.com/deeplearning/sdk/tensorrt-developer-guide/index.html#fit__fit2