基於TensorRT 3的自動駕駛快速INT8推理
Fast INT8 Inference for Autonomous Vehicles with TensorRT 3
自主駕駛需要安全性,需要一種高性能的計算解決方案來處理極其精確的傳感器數據。研究人員和開發人員創建用於自動駕駛的深度神經網絡(DNNs)必須優化其網絡,以確保低延遲推理和能源效率。由於NVIDIA TensorRT中有了一個新的Python API,這個過程變得更加簡單。
Figure 1. TensorRT optimizes trained neural network models to produce deployment-ready runtime inference engines.
TensorRT是一個高性能的深度學習推理優化器和運行時引擎,用於深度學習應用程序的生產部署。開發人員可以優化在TensorFlow或Caffe中訓練的模型,以生成內存高效的運行時引擎,從而最大限度地提高推理吞吐量,使深度學習對於延遲關鍵的產品和服務(如自主駕駛)切實可行。。
最新的TensorRT 3版本引入了一個功能齊全的Python API,使研究人員和開發人員能夠使用熟悉的Python代碼優化和序列化DNN。使用TySoRRT 3,可以在Python中部署雲計算服務,或者在C++中實時應用,例如在英偉達驅動PX AI汽車計算機上運行的自主驅動軟件。
在本文中,將向展示如何在主機上使用TensorRT 3 Python API來緩存語義分段網絡的校准結果,以便使用INT8 precision進行部署。然后,可以使用校准緩存來優化和部署在驅動PX平台上使用C++ API的網絡。
城市景觀數據集與全卷積網絡The Cityscapes Dataset and Fully Convolutional Network
Figure 2. Sample images from the Cityscapes dataset.
城市景觀數據集[Cordts等人。2016]用於城市自主駕駛場景的語義分割。圖2顯示了數據集中的示例圖像。該數據集共有30個不同的類,分為8個不同的類別。為了評估性能,使用了19個類和7個類別,如圖3所示。
Figure 3. Classes and categories used in the Cityscapes benchmark, and the IoU (Intersection-over-Union) metric.
對於評估,使用IoU(Intersection over Union)度量,提供兩個平均分數,一個用於類,另一個用於類別。
Figure 4. A sample VGG16-based fully convolutional network trained for semantic segmentation.
為了證明TensorRT的能力,設計了一種全卷積網絡FCN[Long等人]。基於VGG16,如圖4所示。該網絡由一個基於VGG16的編碼器和兩個使用反卷積層實現的上采樣層組成。用NVIDIA數字和Caffe在城市景觀數據集上訓練了[2014年]網絡后端。
Figure 5. Sample FCN network output.
該網絡設計為獲取512×1024的輸入圖像並生成每像素分類結果,如圖5所示。訓練后的網絡IoU類平均得分為48.4分,IoU類平均得分為76.9分。如果直接使用Caffe和cuDNN在其中一個驅動器PX autochauffer gpu(Pascal)上運行推斷,該網絡將達到大約242ms的延遲和大約4個圖像/秒的吞吐量。在35英里/小時的速度下,242毫秒相當於大約12英尺的行駛距離。這種水平的表現不足以為自動駕駛做出及時的決定。讓來看看如何才能做得更好。
圖6. DP4A指令:四元點積累加。
作為第一步,使用TensorRT優化網絡,使用FP32精度提供了良好的加速。通過使用TensorRT,實現了170毫秒的延遲和大約6個圖像/秒的吞吐量。這比Caffe提高了50%,但是TensorRT可以進一步優化網絡。
以下各節將演示如何使用TensorRT,在保持原FP32網絡良好精度的同時,使用INT8降低的精度來提高該網絡的推理性能。
INT8推理和校准
驅動器PX autochauffer中的Pascal dGPU能夠執行8位整數4元向量點積(DP4A,見圖6)指令,以加速深層神經網絡推理。雖然這條新指令提供了更快的計算速度,但在用這種簡化的INT8格式表示深度神經網絡的權值和激活方面存在着重大挑戰。如表1所示,與FP32或FP16相比,INT8可表示值的動態范圍和粒度明顯受限。
TensorRT提供了一種快速簡便的方法來獲取在FP32中訓練過的模型,並自動轉換網絡以供部署,INT8的精度降低,精度損失最小。為了實現這一目標,TensorRT使用了一個校准過程,當用有限的8位整數表示逼近FP32網絡時,該校准過程將信息損失最小化。有關此算法如何工作的更多信息,請參閱TensorRT GPU技術會議演示的8位推理。
在准備校准數據集時,應在典型的推斷場景中捕獲預期的數據分布。需要確保校准數據集涵蓋所有預期場景,例如晴天、雨天、夜景等。如果正在創建自己的數據集,建議創建單獨的校准數據集。校准數據集不應與訓練、驗證或測試數據集重疊,以避免校准模型僅在這些數據集上正常工作的情況。
讓看看如何使用新的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 Image
import ctypes
import tensorrt as trt
CHANNEL = 3
HEIGHT = 512
WIDTH = 1024
class 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
PythonEntropyCalibrator類是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是一個helper類,負責處理文件I/O、圖像大小的縮放、創建要處理的批處理數據、將數據布局重新排序為CHW格式,以及應用預處理器函數(例如減去圖像平均值)。
校准結果可以保存到緩存文件中,因此可以創建優化的TensorRT運行時引擎,而無需在目標上重復校准過程。在本例中,生成的文件名是calibration_cache.bin,在write_calibration_cache函數中處理。
一旦准備好校准器類,剩下的過程就可以用TensorRT的新的拉氏體Python模塊,旨在抽象出許多低級細節,使數據科學家更容易使用TensorRT。這個包允許添加預處理和后處理函數,並使能夠利用現有的Python數據預處理例程。在下面的代碼中,函數sub_mean_chw作為預處理步驟處理均值減影,函數color_map處理將輸出類ID映射到顏色以可視化輸出。
import glob
from random import shuffle
import numpy as np
from PIL import Image
import tensorrt as trt
import labels #from cityscapes evaluation script
import 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 = 1024
def 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.
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()
在城市景觀數據集中,有獨立的訓練、驗證和測試集,遵循深度學習的常見做法。然而,這意味着沒有單獨的校准數據集。因此,可以從訓練數據集中隨機選擇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。
Optimizing the INT8 Model on DRIVE PX
TensorRT builder實現了一種基於分析的優化,稱為內核自動調整。此過程要求在目標設備上優化網絡。在這個目標優化階段,可以使用從主機生成的校准緩存文件生成INT8模型,而不需要校准數據集。需要編寫一個實現readCalibrationCache函數的校准器類,以告訴TensorRT使用緩存的結果,如下代碼所示。
class Int8CacheCalibrator : public IInt8EntropyCalibrator {
public:
Int8CacheCalibrator(std::string cacheFile)
: mCacheFile(cacheFile) {}
virtual ~Int8CacheCalibrator() {}
int getBatchSize() const override {return 1;}
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推斷,該模型現在可以在一個驅動器PX autochauffer的Pascal GPU上以50毫秒的延遲或20個圖像/秒的速度運行。圖7總結了TensorRT使用FP32和INT8推斷獲得的性能。
Figure 7. INT8 inference with TensorRT improves inference throughput and latency by about 5x compared to the original network running in Caffe.
可以將優化的引擎序列化到一個文件中進行部署,然后就可以在驅動器PX上部署INT8優化的網絡了!