在NVIDIA Drive PX上利用TensorRT 3 进行快速INT8推理

时间:2022-06-20
本文章向大家介绍在NVIDIA Drive PX上利用TensorRT 3 进行快速INT8推理,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

自动驾驶要求安全,并提供高性能的计算解决方案来处理极其精确的传感器数据。研究人员和开发人员必须优化他们的网络,以确保低延迟推理和能源效率。多亏了NVIDIA TensorRT中新的Python API,这个过程变得更加简单。

图1所示。TensorRT优化训练过的神经网络模型,以生成可部署的运行时推理引擎。

TensorRT是一种高性能的深度学习推理优化器和用于深度学习应用程序生产部署的运行时引擎。开发人员可以优化TensorFlow或Caffe培训过的模型,以生成内存效率高的运行时引擎,从而最大限度地提高推理吞吐量,从而使深度学习对于像自动驾驶这样的延迟关键产品和服务变得切实可行。

最新的TensorRT 3版本引入了一个功能齐全的Python API,使研究人员和开发人员能够使用熟悉的Python代码优化和序列化DNN。使用TensorRT 3,您可以在Python、云服务或c++中部署模型,用于实时应用程序,如运行在NVIDIA DRIVE PX AI汽车计算机上的自动驾驶软件。

在这篇文章中,我将向您展示如何在主机上使用TensorRT 3 Python API来缓存语义分割网络的校准结果,以便使用INT8精度进行部署。然后,校准缓存可用于在DRIVE PX平台上使用c++ API优化和部署网络。

Cityscapes数据集和全卷积网络

图2.来自Cityscapes数据集的示例图像

Cityscapes数据集[Cordts et al. 2016]用于城市自主驾驶场景的语义分割。图2显示了来自数据集的示例图像。该数据集共有30个不同的类,分为8个不同的类别。为了评估性能,我使用了19个类和7个类别,如图3所示。

图3.Cityscapes基准中使用的类和类别,以及IoU(交叉-联合)度量

对于评估,我使用IoU(交叉-联合)度量,它提供两个平均分数,一个是类的,另一个是类别的。

为了演示TensorRT的功能,我设计了一个基于VGG16的全卷积网络(FCN [Long et al. 2015])的变体。该网络由一个基于vgg16的编码器和两个使用反卷积层实现的上采样层组成。我在Cityscapes数据集上使用NVIDIA数字进行网络训练,使用Caffe [Jia et al. 2014]后端。

图5.示例FCN网络输出

这个网络旨在512×1024输入图像,产生逐像素分类结果,如图5所示。训练后的网络IoU类平均得分为48.4分,类平均得分为76.9分。如果我直接使用Caffe和cuDNN在其中一个 NVIDIA DRIVE PX AutoChauffeur GPU (Pascal)上运行推理,这个网络可以实现大约242毫秒的延迟和大约4张图像/秒的吞吐量。以每小时35英里的速度,242毫秒相当于大约12英尺的驾驶距离。这种水平的性能不足以为自动驾驶做出及时的决策。让我们看看如何做得更好。

作为第一步,使用TensorRT优化网络,使用FP32精度提供了一个良好的加速。仅仅通过使用TensorRT,我就实现了170毫秒的延迟和大约6张图像/秒的吞吐量。这比Caffe提高了50%,但TensorRT可以进一步优化网络。

下面几节将演示如何使用TensorRT提高该网络的推理性能,使用INT8降低了推理精度,同时保持原FP32网络的良好精度。

INT8推理与校准

DRIVE PX AutoChauffeur中的Pascal dGPU能够执行8位整数4元向量点积(DP4A,见图6)指令来加速深度神经网络推理。虽然这条新指令提供了更快的计算速度,但在以这种简化的INT8格式表示深度神经网络的权值和激活度方面存在重大挑战。如表1所示,与FP32或FP16相比,INT8的可表示值的动态范围和粒度受到了很大的限制。

图6. DP4A指令:4元素点积累加

TensorRT提供了一种快速、简单的方法来获取在FP32中训练的模型,并自动转换网络以进行部署,INT8降低了精度,减少了精度损失。为了实现这一目标,TensorRT使用了一个校准过程,该过程在用有限的8位整数表示逼近FP32网络时最小化了信息损失。

让我们看看如何使用新的TensorRT Python API来创建一个校准缓存。

使用Python API创建校准缓存

随着TensorRT Python API的引入,现在完全可以在Python中实现INT8校准器类。这个例子展示了如何处理图像数据和校正器。修改这个示例以支持Python中不同类型的数据和网络应该很简单。

import pycuda.driver as cuda
import pycuda.autoinit
import numpy as np
from PIL import Imageimport ctypes
import tensorrt as trt

CHANNEL = 3
HEIGHT = 512
WIDTH = 1024class PythonEntropyCalibrator(trt.infer.EntropyCalibrator):
  def __init__(self, input_layers, stream):
    trt.infer.EntropyCalibrator.__init__(self)       
    self.input_layers = input_layers
    self.stream = stream
  self.d_input = cuda.mem_alloc(self.stream.calibration_data.nbytes)
    stream.reset()

  def get_batch_size(self):
    return self.stream.batch_size

  def get_batch(self, bindings, names):
    batch = self.stream.next_batch()
    if not batch.size:   
      return None
      
    cuda.memcpy_htod(self.d_input, batch)
    for i in self.input_layers[0]:
      assert names[0] != i

    bindings[0] = int(self.d_input)
    return bindings

  def read_calibration_cache(self, length):
    return None

  def write_calibration_cache(self, ptr, size):
    cache = ctypes.c_char_p(int(ptr))
    with open('calibration_cache.bin', 'wb') as f:
      f.write(cache.value)
    return None

pythonentropycalibration类是INT8校准器的Python实现。该类负责分配CUDA内存并为所有输入层创建绑定。每当调用get_batch()时,它将校准输入数据上传到预先分配的CUDA内存中。校准批大小定义了在同一时间处理多少个校准图像,以收集计算正确的缩放因子所需的输入分布。校准批次大小可以不同于最大批次大小参数进行推断。使用较大的批处理大小通常会加快校准过程,我建议使用GPU内存中能够容纳的最大批处理大小。

class ImageBatchStream():
  def __init__(self, batch_size, calibration_files, preprocessor):
    self.batch_size = batch_size
    self.max_batches = (len(calibration_files) // batch_size) + 
                       (1 if (len(calibration_files) % batch_size) 
                        else 0)
    self.files = calibration_files
    self.calibration_data = np.zeros((batch_size, CHANNEL, HEIGHT, WIDTH), 
                                     dtype=np.float32)
    self.batch = 0
    self.preprocessor = preprocessor

  @staticmethod
  def read_image_chw(path):
    img = Image.open(path).resize((WIDTH,HEIGHT), Image.NEAREST)
    im = np.array(img, dtype=np.float32, order='C')
    im = im[:,:,::-1]
    im = im.transpose((2,0,1))
    return im
         
  def reset(self):
    self.batch = 0
     
  def next_batch(self):
    if self.batch < self.max_batches:
      imgs = []
      files_for_batch = self.files[self.batch_size * self.batch : 
                        self.batch_size * (self.batch + 1)]
      for f in files_for_batch:
        print("[ImageBatchStream] Processing ", f)
        img = ImageBatchStream.read_image_chw(f)
        img = self.preprocessor(img)
        imgs.append(img)
      for i in range(len(imgs)):
        self.calibration_data[i] = imgs[i]
      self.batch += 1
      return np.ascontiguousarray(self.calibration_data, dtype=np.float32)
    else:
      return np.array([])

ImageBatchStream是一个助手类,它负责文件I/O、图像大小的缩放、为处理创建批处理数据、将数据布局重新排序为CHW格式以及应用预处理函数(如减去图像平均值)。

校准的结果可以保存到缓存文件中,因此可以在不重复目标上的校准过程的情况下创建优化的TensorRT运行时引擎。在本例中,生成的文件名是calibration ation_cache。bin,如write_calibration_cache函数中处理的那样。

一旦校准器类准备好了,剩下的过程就可以使用TensorRT的新TensorRT进行简化。lite Python模块,旨在抽象掉许多低级细节,使数据科学家更容易使用TensorRT。这个包允许您添加预处理和后处理函数,并允许利用现有的Python数据预处理例程。在下面的代码中,sub_mean_chw函数将均值减法处理为预处理步骤,color_map函数将输出类ID映射为颜色,以便可视化输出。

import glob
from random import shuffle
import numpy as np
from PIL import Imageimport tensorrt as trt

import labels  #from cityscapes evaluation scriptimport calibrator    #calibrator.py

MEAN = (71.60167789, 82.09696889, 72.30508881)
MODEL_DIR = '/data/fcn8s/'
CITYSCAPES_DIR = '/data/Cityscapes/'
TEST_IMAGE = CITYSCAPES_DIR + 'leftImg8bit/val/lindau/lindau_000042_000019_leftImg8bit.png'
CALIBRATION_DATASET_LOC = CITYSCAPES_DIR + 'leftImg8bit/train/*/*.png'

CLASSES = 19
CHANNEL = 3
HEIGHT = 512
WIDTH = 1024def sub_mean_chw(data):
  data = data.transpose((1,2,0)) # CHW -> HWC
  data -= np.array(MEAN) # Broadcast subtract
  data = data.transpose((2,0,1)) # HWC -> CHW
  return data
             
def color_map(output):
  output = output.reshape(CLASSES, HEIGHT, WIDTH)
  out_col = np.zeros(shape=(HEIGHT, WIDTH), dtype=(np.uint8, 3))
  for x in range (WIDTH):
    for y in range (HEIGHT):
    out_col[y,x] = labels.id2label[labels.trainId2label[np.argmax(output[:,y,x])].id].color
  return out_col

下面是将所有代码组合在一起的主要函数。tensorrt.lite模块提供了高级功能,可以使用一个名为tensorrt.lite.Engine的函数将Caffe和TensorFlow模型转换为优化的引擎。

def create_calibration_dataset():
  # Create list of calibration images (filename)
  # This sample code picks 100 images at random from training set
  calibration_files = glob.glob(CALIBRATION_DATASET_LOC)
  shuffle(calibration_files)
  return calibration_files[:100]def main():
  calibration_files = create_calibration_dataset()
  
  # Process 5 images at a time for calibration
  # This batch size can be different from MaxBatchSize (1 in this example)
  batchstream = calibrator.ImageBatchStream(5, calibration_files, sub_mean_chw)
  int8_calibrator = calibrator.PythonEntropyCalibrator(["data"], batchstream)
  
  # Easy to use TensorRT lite package
  engine = trt.lite.Engine(framework="c1",
                           deployfile=MODEL_DIR + "fcn8s.prototxt",
                           modelfile=MODEL_DIR + "fcn8s.caffemodel",
                           max_batch_size=1,
                           max_workspace_size=(256 << 20),
                           input_nodes={"data":(CHANNEL,HEIGHT,WIDTH)},
                           output_nodes=["score"],
                           preprocessors={"data":sub_mean_chw},
                           postprocessors={"score":color_map},
                           data_type=trt.infer.DataType.INT8,
                           calibrator=int8_calibrator,
                           logger_severity=trt.infer.LogSeverity.INFO)
                           
  test_data = calibrator.ImageBatchStream.read_image_chw(TEST_IMAGE)
  out = engine.infer(test_data)[0]
  test_img = Image.fromarray(out, 'RGB')
  test_img.show()

在Cityscapes数据集中,有单独的训练、验证和测试集,遵循深度学习的常见实践。然而,这意味着没有单独的校准数据集放在一边。因此,可以从训练数据集中随机选择100幅图像作为校准数据集,以说明校准过程的工作情况。正如您将看到的,校准算法可以实现良好的准确性,只有100个随机图像!

使用包含具有计算能力6.1的NVIDIA GPU的系统(例如Quadro P4000、Tesla P4或P40),您可以运行INT8优化引擎来验证其准确性。我建议运行整个验证数据集,以确保使用降低的精度带来的小精度损失是可以接受的。通过使用全部500张验证图像运行Cityscapes评估脚本,我发现校准后的INT8模型达到了48.1个平均类IoU和76.8个平均类IoU,相比之下,原FP32精度模型的准确率分别为48.4和76.9。

优化Drive PX上的INT8模型

TensorRT builder实现了一个基于分析的优化,称为内核自动调优。这个过程需要在目标设备上优化网络。在这个on-target优化阶段,我们可以使用从主机生成的校准缓存文件来生成INT8模型,而不需要校准数据集。您需要编写一个实现readcalibration ationcache函数的校准器类,告诉TensorRT使用缓存的结果,如下面的代码所示。

bool getBatch(void* bindings[], const char* names[], int nbBindings) override {
    return false;
  }
  
  const void* readCalibrationCache(size_t& length) override
  {
    mCalibrationCache.clear();
    std::ifstream input(mCacheFile, std::ios::binary);
    input >> std::noskipws;
    if (input.good()) {
      std::copy(std::istream_iterator(input),
      std::istream_iterator<char>(),
      std::back_inserter<char>(mCalibrationCache));
    }
    length = mCalibrationCache.size();
    return length ? &mCalibrationCache[0] : nullptr;
  }private:
  std::string mCacheFile;
  std::vector<char> mCalibrationCache;};

利用TensorRT的INT8推断,该模型现在可以在Drive PX AutoChauffeur的一个Pascal GPU上以50毫秒延迟或20幅图像/秒的速度运行。图7总结了使用FP32和INT8推断TensorRT获得的性能。

图7.与在Caffe中运行的原始网络相比,使用TensorRT进行INT8推理可以提高大约5倍的推理吞吐量和延迟

您可以将优化后的引擎序列化到一个文件中进行部署,然后就可以在Drive PX上部署INT8优化后的网络了!

关于作者

Joohoon Lee领导着英伟达汽车深度学习解决方案架构团队。他专注于将深度学习研究转化为用于生产部署的真实世界自动驾驶软件。他的团队使汽车客户能够使用NVIDIA DRIVE平台进行DNN培训、微调、优化和部署。在加入英伟达之前,他是一名致力于DNN算法加速的GPU软件架构师。Joohoon在卡内基梅隆大学获得电气和计算机工程学士和硕士学位。