第二章 TensorRT Workflows
下列表格列出了TensorRT特点一支持的API以及解析器。
表2 特点与支持的API’s
下列表格列出了TensorRT特点以及支持的平台
表3 特点与支持的平台
注:序列化引擎不能再不同TensorRT版本间与不同平台间交叉使用。
2.1 Key Concepts
请确保你知悉以下关键概念:
UFF
UFF(Universal Framework Format)是一种描述DNN执行图的数据格式。绑定执行图的是输入与输出。UFF有严格规定的语法,并且支持核心运算的扩展,与完全用户自定义的运算。
包括以下内容:
Ø 具体序列化的格式,采用protobuf格式。
Ø 各种运算的有效定义,以python描述符来表述。
Ø 每个核心运算符执行文档。
PLANfile
PLAN文件是运行引擎用于执行网络的序列化数据。包含权重,网络中执行步骤以及用来决定如何绑定输入与输出缓存的网络信息。
NEF(NetworkExchange Format)
NEF是用来交换神经网络信息的数据格式。Caffe,UFF与ONNX是各种格式网络交换格式的样例。
Parser
TensorRT中设计用来解析NEF格式数据,建立运行引擎的前端。
Execution Context
运行上下文是前向引擎运行时需要的执行环境。
Engine
Engine是ICudaEngine的实例,Engine是前向运算示例。
NetworkDefinition
网络定义是由运算或者tensor组成的计算图。再TensorRT中是INetworkDefinition类型的示例,
CaffeFormat
Caffe格式的网络参数交换格式。
ONNX
ONNX格式的网络参数交换格式。
2.2 Workflow Diagrams
图1展示了典型的部署工作流程,用户在数据集上训练模型,训练好的网络可以用来进行前向运算。
图1 典型部署工作流
图1表示将训练网络导入到TensorRT中。用户将训练好的网络导入到TensorRT中,优化网络产生PLAN。PLAN用于前向运算,例如,验证优化是否正确的进行。
PLAN可以序列化到硬盘上以便之后TensorRT后续无需再次进行优化步骤就可以进行调用(见图2)。
图2 典型生产流程
2.3 Exporting From Frameworks
使用TensorRT的第一步是建立一个用户网络的TensorRT表达。一旦这个网络建立好了,TensorRT可以由此建立一个网络优化的运行引擎。由两种建立TensorRT网络的方式。
1、 使用Builder API从头开始建立。
2、 从现有的NVCaffe,ONNX或者TensorFlow网络模型使用ParserAPI加载(选择性的使用插件API加载部分TensorRT不支持的网络结构)。
两种方式都在下文中有介绍,以C++或者Python例程的形式展示。
有些网络模块TensorRT不支持。因此,你可以通过使用Plugin API或者修改原始网络结构,使用类似的模块替代。
2.3.1 NVCaffe Workflow
2.3.1.1 NVCaffe C++ Workflow
TensorRT可以直接加载NVCaffe的模型,通过NvCaffeParser接口。
使用NvCaffeParser的使用例程可以在SampleMNIST中找到。TensorRT网络定义结构直接从NVCaffe模型中通过NvCaffeParser库解析出来:
INetworkDefinition* network =builder->createNetwork();
CaffeParser* parser = createCaffeParser();
std::unordered_map<std::string, infer1::Tensor>blobNameToTensor;
const IBlobNameToTensor* blobNameToTensor =
parser->parse(locateFile(deployFile).c_str(),
locateFile(modelFile).c_str(),
*network,
DataType::kFLOAT);
NvCaffeParser通过指令生成32-bit float权重的网络,我们可以通过DataType::kHALF来生成16位的模型。
与主流的网络定义方式一样,解析器返回从NVCaffe blob名字映射到TensorRT tensors的maps。
注: TensorRT网络定义中没有原位操作的概念,例如ReLU的输入输出tensors是不同的。当NVCaffe网络中进行原位操作时,TensorRT的tensor返回blob中最后一次写入的内容对应的字典。例如,如果卷积层生成了一个blob,紧接着一个原位操作的ReLU,blob的名字将会映射到ReLU对应的TensorRT tensor。
由于NVCaffe模型并没有指定那个tensors时网络的输出,我们需要在解析后明确指定:
for (auto& s : outputs)
network->markOutput(*blobNameToTensor->find(s.c_str()));
对于输出tensors的个数没有限制,但是将tensor标记为输出可能会影响对tensor的一些优化操作。
注:
不要立即释放解析器实例,因为网络定义的参数是由NVCaffe模型引用过来的。只有在build过程时权重从NVCaffe模型中加载。
2.3.1.2 NVCaffe Python Workflow
TensorRT提供了Python接口,用于加载与优化NVCaffe模型,NVCaffe模型可以执行与存储与PLAN文件中。下列例子展示了实现的工作流程。例子中,你将学习如何在python中使用TensorRT来优化NVCaffe模型。
Python中TensorRT库由tensorrt表示。
你可以像加载其它任何包一样加载tensorrt。例如:、
import tensorrt as trt
有些工具会经常与python接口TensorRT同时使用,例如PyCUDA,NumPy。PyCUDA处理CUDA操作,比如在GPU上申请内显存,将数据传输到GPU上,结果回传到CPU。NumPy是存储与转移数据的常用工具。
import pycuda.driver as cuda
import pycuda.autoinit
import numpy as np
本例中,我们导入一个图像处理库(以pillow库为例)与randint。
from random import randint
from PIL import Image
由于我们需要转换NVCaffe模型,我们还需要使用位于tensorrt.parsers的caffeparser。
from tensorrt.parsers import caffeparser
通常,第一件事是建立一个日志模块,在模型转换与前向运算时许多地方都会使用。在tensorrt.infer.ConsolueLogger里有个简单的日志模块。
G_LOGGER = trt.infer.ConsoleLogger(trt.infer.LogSeverity.ERROR)
第二步,定义一些你模型相关的常量。本例中,我们利用DIGITS训练MNIST数据集的模型。
INPUT_LAYERS = ['data']
OUTPUT_LAYERS = ['prob']
INPUT_H = 28
INPUT_W = 28
OUTPUT_SIZE = 10
此外,定义一些路径。将下列路径映射到例子中你放数据的位置。
MODEL_PROTOTXT = '/data/mnist/mnist.prototxt'
CAFFE_MODEL = '/data/mnist/mnist.caffemodel'
DATA = '/data/mnist/'
IMAGE_MEAN = '/data/mnist/mnist_mean.binaryproto'
这一步开始建立引擎。TensorRT的python接口提供了一些实用工具是的这一步变得十分简单。我们使用tensorrt.utils中NVCaffe模型转换工具。我们提供一个日志模块,模型prototxt路径,模型路径,最大batchsize,最大workspace大小,输出层与权重的数据类型。
engine = tensorrt.utils.caffe_to_trt_engine(G_LOGGER,
MODEL_PROTOTXT,
CAFFE_MODEL,
1,
1 << 20,
OUTPUT_LAYERS,
trt.infer.DataType.FLOAT)
下一步,对输入图片进行均值处理。我们使用caffeparser读取.binaryproto文件。
parser = caffeparser.create_caffe_parser()
mean_blob = parser.parse_binary_proto(IMAGE_MEAN)
parser.destroy()
mean = mean_blob.get_data(INPUT_W ** 2) #NOTE: This is different than theC++
API, you must provide the size of the data
data = np.empty([INPUT_W ** 2])
for i in range(INPUT_W ** 2):
data[i] = float(img[i]) - mean[i]
mean_blob.destroy()
建立一个前向运算的运行环境,创建一个引擎的运行上下文。
runtime = trt.infer.create_infer_runtime(G_LOGGER)
context = engine.create_execution_context()
运行前向运算。第一步确认数据类型是否正确(本例中是FP32)。第二步,创建一个CPU下的array空间,存储前向运算的结果。
assert(engine.get_nb_bindings() == 2)
#convert input data to Float32
img = img.astype(np.float32)
#create output array to receive data
output = np.empty(OUTPUT_SIZE, dtype = np.float32)
第三步,使用PyCUDA申请GPU显存并在引擎中注册。申请的大小是整个batchsize大小的输入以及期望的输出指针大小。
d_input = cuda.mem_alloc(1 * img.size * img.dtype.itemsize)
d_output = cuda.mem_alloc(1 * output.size * output.dtype.itemsize)
引擎需要绑定GPU显存的指针。PyCUDA通过分配成ints实现内存申请。
bindings = [int(d_input), int(d_output)]
我们还需要建立一个CUDAstream前向运算。
stream = cuda.Stream()
下一步,将数据传输到GPU上,运行前向运算,复制结果回掉。
#transfer input data to device
cuda.memcpy_htod_async(d_input, img, stream)
#execute model
context.enqueue(1, bindings, stream.handle, None)
#transfer predictions back
cuda.memcpy_dtoh_async(output, d_output, stream)
#synchronize threads
stream.synchronize()
现在我们以及得到结果了,我们可以运行ArgMax得到最终预测的结果。
print("Test Case: " + str(rand_file))
print ("Prediction: " + str(np.argmax(output)))
我们也可以将我们的引擎存在文件中以备之后调用。
trt.utils.write_engine_to_file("/data/mnist/new_mnist.engine",
engine.serialize())
我们可以之后将通过以下命令将引擎加载运行。
new_engine =
trt.utils.load_engine(G_LOGGER,
"/data/mnist/new_mnist.engine")
最后一步,清理context,engine与runtime:
context.destroy()
engine.destroy()
new_engine.destroy()
runtime.destroy()
2.3.1.3 NvCaffeParser
尽管TensorRT独立于任何其它框架,TensorRT还是包含一个NVCaffe模型的解析器,叫做NvCaffeParser。
NvCaffeParser提供了一种简单的加载网络定义的机制。NvCaffeParser使用TensorRT层实现NVCaffe中的层。例如:
Ø Convolution
Ø Rectified Linear Unit(ReLU)
Ø Sigmoid
Ø Hyperbolic Tangent(tanh)
Ø Pooling
Ø Power
Ø BatchNorm
Ø ElementEise(Eltwise)
Ø Local Response Normalization(LRN)
Ø InnerProduct(在NVCaffe中称为FullyConnected层)
Ø SoftMax
Ø Scale
Ø Deconvolution
Ø Reduction
TensorRT中不支持的NVCaffe功能包括:
Ø Parammetric Rectified Linear Unit(PReLU)
Ø Leaky Rectified Linear Unit(Leaky ReLU)
Ø Scale(除了per-channel scaling)
Ø 多于两个输入的ElementWise(Eltwise)
注:
NvCaffeParser不支持原版NVCaffe的prototxt格式。特别注意的是,prototxt中层的类型需要由双引号括起来。
2.3.2 ONNX Workflow
TensorRT可以直接通过NvParser接口导入ONNX格式模型。解析器接口可以使用C++与Python代码。
2.3.2.1 ONNX C++ Workflow
在sampleONNXMNIST中,TensorRT网络定义结构利用ONNX解析器直接从ONNX模型中读取,在libnvparsers库中定义。ONNX解析器使用辅助参数管理器SampleConfig实例来将输入参数传到执行程序中来解析,例如:
//Create ONNX ParserConfiguration Object
nvonnxparser::IOnnxConfig*config = nvonnxparser::createONNXConfig();
//Copy relevant infofrom the Sample Config to ONNX Parser Config
sampleONNX::copyConfig(&apex,config);
//Create Parser
nvonnxparser::IONNXParser*parser = nvonnxparser::createONNXParser(*config);
//ONNX ParserIncludes a Logger object, it can be used for the sample
nvinfer1::ILogger*gLogger = parser->getLogger();
const char*onnx_filename = apex.getModelFileName();
constnvinfer1::DataType dataType = apex.getModelDtype();
//Parse the ONNXModel. The parser creates the equivalent TRT Network
if(!parser->parse(onnx_filename, dataType)) {
stringmsg("failed to parse onnx file");
gLogger->log(nvinfer1::ILogger::Severity::kERROR,msg.c_str());
exit(EXIT_FAILURE);
}
if(!parser->convertToTRTNetwork()) {
stringmsg("ERROR, failed to convert onnx network into TRT network");
gLogger->log(nvinfer1::ILogger::Severity::kERROR,msg.c_str());
exit(EXIT_FAILURE);
}
nvinfer1::INetworkDefinition*trtNetwork = parser->getTRTNetwork();
这里,如果trtNetwork是非空的,那么你成功的创建了trtNetwork。
下一步,我们建立一个engine builder:
nvinfer1::IBuildertrt_builder =
samples_common::infer_object(nvinfer1::createInferBuilder((*gLogger)));
给builder指定合适的参数:
trt_builder->setMaxBatchSize(apex.getMaxBatchSize());
trt_builder->setMaxWorkspaceSize(apex.getMaxWorkSpaceSize());
ONNX parser生成网络的默认权重精度是32比特的浮点型。此外你还可以通过指令生成一个16位权重的模型。
if(apex.getModelDtype() == nvinfer1::DataType::kHALF) {
trt_builder->setHalf2Mode(true);
}
当前INT8精度还不支持,也可以尝试使用以下方法:
if(apex.getModelDtype() == nvinfer1::DataType::kINT8)
{
stringmsg("Int8 mode not yet supported");
gLogger->log(nvinfer1::ILogger::Severity::kERROR,msg.c_str());
exit(EXIT_FAILURE);
}
nvinfer1::ICudaEngine*trt_engine = trt_builder->buildCudaEngine(*trtNetwork);
其它的使用方法与NVCaffe或者UFF一致。
2.3.2.2 ONNX Python Workflow
用户可以直接使用OnnxParser与Python接口来加载ONNX模型。你可以像加载其它任何包一样加载tensorrt包。例如:
Import tensorrt astrt
加载ONNXParser直接将ONNX模型转换成TensorRT网络。与C++接口类似,sample_onnx的Python例子中使用config实例将用户参数传入解析器实例。
fromtensorrt.parsers import onnxparser
apex =onnxparser.create_onnxconfig()
本例中,我们将解析一个训练好的图像分类模型,生成用于前向运算TensorRT engine。
这里我们通过解析用户输入参数来生成config实例:
apex.set_model_filename(“model_file_path”)
apex.set_model_dtype(trt.infer.DataType.FLOAT)
apex.set_print_layer_info(True)// Optional debug option
apex.report_parsing_info()// Optional debug option
为了控制debug的输出,有许多种控制冗余信息的方式,例如:
apex.add_verbosity()
apex.reduce_verbosity()
或者,你可以指定调试信息等级,例如:
apex.set_verbosity_level(3)
在config实例创建并且配置完成后,用户可以创建一个parser。此外,确保你从创建好的实例中可以检索出参数来解析输入的模型文件:
trt_parser =onnxparser.create_onnxparser(apex)
data_type =apex.get_model_dtype()
onnx_filename =apex.get_model_file_name()
解析模型后生成TensorRT网络结构,例如:
trt_parser.parse(onnx_filename,data_type)
// retrieve thenetwork from the parser
trt_parser.convert_to_trtnetwork()
trt_network =trt_parsr.get_trt_network()
// create builderand engine
trt_builder =trt.infer.create_infer_builder(G_LOGGER)
下一步,依据数据类型,设置合适的模式。当前,INT8模式不支持。
If(apex.get_model_dtype() == trt.infer.DataType_kHALF):
trt_builder.set_half_2mode(True)
elif
(apex.get_model_dtype()== trt.infer.DataType_kINT8):
msg = "Int8Model not supported"
G_LOGGER.log(trt.infer.Logger.Severity_kERROR,msg)
trt_engine =trt_builder.build_cuda_engine(trt_network)
其他的前向步骤与NVCaffe与UFFpython例子一致。
最后一步,清理parser,runtime,engine与builder,如下:
trt_parser.destroy()
trt_network.destroy()
trt_context.destroy()
trt_engine.destroy()
trt_builder.destroy()
2.3.3 TensorFlow Workflow
2.3.3.1 TensorFlow Python Workflow
TensorRT3.0引入了UFF(Universal Framework Format)解析器,可以载入UFF模型,并生成TensorRTengines。TensorRT中集成了UFF工具支持将TensorFlow模型转为UFF格式,因此,使得TensorFlow用户可以使用TensorRT来改善效率。本例中,你将学习如何使用使用TensorRT的Python接口将TensorFlow模型转成TensorRT格式。
使用TensorRT的Python接口,用户可以同一个python程序中完成TensorFlow训练与TensorRT部署。本例中,我们将在利用手写数据训练分类模型并生成TensorRT前向运算引擎。
在Python中,TensorRT库被称为tensorrt。
加载TensorFlow与其它相关库:
import tensorflow astf
fromtensorflow.examples.tutorials.mnist import input_data
同样有一些公用库与TensorRT的python接口一起使用,典型的有,例如,PyCUDA与NumPy。PyCUDA 处理CUDA相关操作,例如在GPU上申请显存,将输出传到GPU上,将结构传回CPU。NumPy广泛用于存储于传递数据。
import pycuda.driveras cuda
importpycuda.autoinit
import numpy as np
from random importrandint # generate a random test case
from PIL importImage
import time #importsystem tools
import os
最后,加载UFF工具,将TensorFlow中一系列frozen的图转为UFF格式。
import uff
用户可以通过以下命令导入TensorRT以及相关解析器:
import tensorrt astrt
fromtensorrt.parsers import uffparser
十分重要的一点是,确认UFF的版本符合TensorRT中要求的版本。TensorRT包中提供了接口来确认环境:
trt.utils.get_uff_version()
parser =uffparser.create_uff_parser()
defget_uff_required_version(parser):
returnstr(parser.get_uff_required_version_major()) + '.'
+str(parser.get_uff_required_version_minor()) + '.' +
str(parser.get_uff_required_version_patch())
iftrt.utils.get_uff_version() != get_uff_required_version(parser):
raiseImportError("""ERROR: UFF TRT Required versionmismatch""")
2.3.3.1.1 Training A Model In TensorFlow
本例中第一步定义一些超参数,之后定义一些帮助函数,使得代码简介。
STARTER_LEARNING_RATE= 1e-4
BATCH_SIZE = 10
NUM_CLASSES = 10
MAX_STEPS = 5000
IMAGE_SIZE = 28
IMAGE_PIXELS =IMAGE_SIZE ** 2
OUTPUT_NAMES =["fc2/Relu"]
注意我们padding我们的Conv2d层,TensorRT期望对称的padding层。
def WeightsVariable(shape):
returntf.Variable(tf.truncated_normal(shape, stddev=0.1, name='weights'))
defBiasVariable(shape):
returntf.Variable(tf.constant(0.1, shape=shape, name='biases'))
def Conv2d(x, W, b,strides=1):
# Conv2D wrapper,with bias and relu activation
filter_size =W.get_shape().as_list()
pad_size =filter_size[0]//2
pad_mat =np.array([[0,0],
[pad_size,pad_size],
[pad_size,pad_size],
[0,0]])
x = tf.pad(x,pad_mat)
x = tf.nn.conv2d(x,
W,
strides=[1, strides,strides, 1],
padding='VALID')
x =tf.nn.bias_add(x, b)
return tf.nn.relu(x)
def MaxPool2x2(x,k=2):
# MaxPool2D wrapper
pad_size = k//2
pad_mat =np.array([[0,0],
[pad_size,pad_size],
[pad_size,pad_size],
[0,0]])
returntf.nn.max_pool(x,
ksize=[1, k, k, 1],
strides=[1, k, k,1],
padding='VALID')
这一步我们将定义网络结构,并且定义我们的loss方法,训练与测试的steps,输入节点,与数据加载器:
def network(images):
# Convolution 1
withtf.name_scope('conv1'):
weights =WeightsVariable([5,5,1,32])
biases =BiasVariable([32])
conv1 =tf.nn.relu(Conv2d(images, weights, biases))
pool1 =MaxPool2x2(conv1)
# Convolution 2
withtf.name_scope('conv2'):
weights =WeightsVariable([5,5,32,64])
biases =BiasVariable([64])
conv2 =tf.nn.relu(Conv2d(pool1, weights, biases))
pool2 =MaxPool2x2(conv2)
pool2_flat =tf.reshape(pool2, [-1, 7 * 7 * 64])
Fully Connected 1
withtf.name_scope('fc1'):
weights =WeightsVariable([7 * 7 * 64, 1024])
biases =BiasVariable([1024])
fc1 =tf.nn.relu(tf.matmul(pool2_flat, weights) + biases)
# Fully Connected 2
withtf.name_scope('fc2'):
weights =WeightsVariable([1024, 10])
biases =BiasVariable([10])
fc2 =tf.reshape(tf.matmul(fc1,weights) + biases, shape= [-1,10] ,
name='Relu')
return fc2
defloss_metrics(logits, labels):
cross_entropy =tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=labels,
logits=logits,
name='softmax')
returntf.reduce_mean(cross_entropy, name='softmax_mean')
def training(loss):
tf.summary.scalar('loss',loss)
global_step =tf.Variable(0, name='global_step', trainable=False)
learning_rate =tf.train.exponential_decay(STARTER_LEARNING_RATE,
global_step,
100000,
0.75,
staircase=True)
tf.summary.scalar('learning_rate',learning_rate)
optimizer =tf.train.MomentumOptimizer(learning_rate, 0.9)
train_op =optimizer.minimize(loss, global_step=global_step)
return train_op
defevaluation(logits, labels):
correct =tf.nn.in_top_k(logits, labels, 1)
returntf.reduce_sum(tf.cast(correct, tf.int32))
def do_eval(sess,
eval_correct,
images_placeholder,
labels_placeholder,
data_set,
summary):
true_count = 0
steps_per_epoch =data_set.num_examples // BATCH_SIZE
num_examples =steps_per_epoch * BATCH_SIZE
for step inrange(steps_per_epoch):
feed_dict =fill_feed_dict(data_set,
images_placeholder,
labels_placeholder)
log, correctness =sess.run([summary, eval_correct],
feed_dict=feed_dict)
true_count +=correctness
precision =float(true_count) / num_examples
tf.summary.scalar('precision',tf.constant(precision))
print('Num examples%d, Num Correct: %d Precision @ 1: %0.04f' %
(num_examples,true_count, precision))
return log
defplaceholder_inputs(batch_size):
images_placeholder =tf.placeholder(tf.float32,
shape=(None, 28, 28,1))
labels_placeholder =tf.placeholder(tf.int32, shape=(None))
returnimages_placeholder, labels_placeholder
deffill_feed_dict(data_set, images_pl, labels_pl):
images_feed,labels_feed = data_set.next_batch(BATCH_SIZE)
feed_dict = {
images_pl:np.reshape(images_feed, (-1,28,28,1)),
labels_pl:labels_feed,
}
return feed_dict
这一步我们将在函数中定义我们的训练流水线,使得训练完毕后输出frozen的模型,并删除训练nodes:
defrun_training(data_sets):
withtf.Graph().as_default():
images_placeholder,labels_placeholder = placeholder_inputs(BATCH_SIZE)
logits =network(images_placeholder)
loss =loss_metrics(logits, labels_placeholder)
train_op =training(loss)
eval_correct = evaluation(logits,labels_placeholder)
summary =tf.summary.merge_all()
init =tf.global_variables_initializer()
saver =tf.train.Saver()
gpu_options =tf.GPUOptions(
per_process_gpu_memory_fraction=0.5)
sess = tf.Session(
config=tf.ConfigProto(gpu_options=gpu_options))
summary_writer =tf.summary.FileWriter(
"/tmp/tensorflow/mnist/log",
graph=tf.get_default_graph())
test_writer =tf.summary.FileWriter(
"/tmp/tensorflow/mnist/log/validation",
graph=tf.get_default_graph())
sess.run(init)
for step inrange(MAX_STEPS):
start_time =time.time()
feed_dict =fill_feed_dict(data_sets.train,
images_placeholder,
labels_placeholder)
_, loss_value =sess.run([train_op, loss],
feed_dict=feed_dict)
duration =time.time() - start_time
if step % 100 == 0:
print('Step %d: loss= %.2f (%.3f sec)' %
(step, loss_value,duration))
summary_str =sess.run(summary, feed_dict=feed_dict)
summary_writer.add_summary(summary_str,step)
summary_writer.flush()
if (step + 1) % 1000== 0 or (step + 1) == MAX_STEPS:
Checkpoint_file =os.path.join(
"/tmp/tensorflow/mnist/log",
"model.ckpt")
saver.save(sess,checkpoint_file, global_step=step)
print('ValidationData Eval:')
log = do_eval(sess,
eval_correct,
images_placeholder,
labels_placeholder,
data_sets.validation,
summary)
test_writer.add_summary(log,step)
graphdef =tf.get_default_graph().as_graph_def()
frozen_graph =tf.graph_util.convert_variables_to_constants(sess,
graphdef,OUTPUT_NAMES)
returntf.graph_util.remove_training_nodes(frozen_graph)
这一步骤中我们加载TensorFlow的MNIST数据加载器,并进行训练。生成的模型放在了一起,使得用户恶意通过TensorBoard监视训练。
MNIST_DATASETS =input_data.read_data_sets(
'/tmp/tensorflow/mnist/input_data')
tf_model =run_training(MNIST_DATASETS)
2.3.3.1.2 Converting A TensorFlow Model To UFF
本实例中,我们用uff.from_tensorflow中的UFF工具与帮助函数将TensorFlow模型转为序列化的UFF模型。为了进行模型转换,我们至少需要提供model stream与需要输出的节点名称。UFF工具还包含uff.from_tensorFlow_frozen_model函数来加载TensorFlowprotobuf文件路径。
两种工具,例如uff.from_tensorflow(serialized graph)与uff.from_tensorflow_frozen_model (protobuf file)都包含以下选项:
quiet
抑制转换日志
Input_node
在图中定义一系列输入节点。默认是placeholder节点。
text
使得用户可以在除二进制UFF文件外生成一个可读版本的UFF模型。
List_nodes
图中节点的列表。
Output_filename
如果设置了此参数,模型将输出到指定路径,而不是一系列模型。
为了将模型转为UFF格式,使用以下命令:
uff_model =uff.from_tensorflow(tf_model, ["fc2/Relu"])
2.3.3.1.3 Import A UFF Model Into TensorRT
我们现在有了一个UFF模型,我们可以通过生成一个TensorRT的logger来生成TensorRTengine。
G_LOGGER =trt.infer.ConsoleLogger(trt.infer.LogSeverity.ERROR)
创建一个UFF解析器,定义期望的输入与输出节点。
parser =uffparser.create_uff_parser()
parser.register_input("Placeholder",(1,28,28),0)
parser.register_output("fc2/Relu")
通过logger,parser,UFF模型stream,以及其他一些设置(最大batchsize与最大workspace大小),最终创建engine。
engine =trt.utils.uff_to_trt_engine(G_LOGGER,
uff_model,
parser,
1,
1 << 20)
**engine时在CPU上申请一些内存:
host_mem =parser.hidden_plugin_memory()
销毁解析器:
parser.destroy()
利用TensorFlow数据加载器(将数据转为FP32)建立一个测试实例:
img, label =MNIST_DATASETS.test.next_batch(1)
img = img[0]
#convert input datato Float32
img =input_img.astype(np.float32)
label = label[0]
创建一个engine的运行环境与执行上下文:
runtime =trt.infer.create_infer_runtime(G_LOGGER)
context =engine.create_execution_context()
下一步,GPU上申请显存,申请CPU内存来存放前向运算后的结果。存储空间的大小是batchsize的输入与期望输出指针大小。
output =np.empty(10, dtype = np.float32)
#allocate devicememory
d_input =cuda.mem_alloc(1 * img.size * img.dtype.itemsize)
d_output =cuda.mem_alloc(1 * output.size * output.dtype.itemsize)
engine需要绑定GPU显存的指针,PyCUDA通过将那些内存转为ints来申请内存空间。
bindings =[int(d_input), int(d_output)]
创建一个CUDAstream来运行推理运算。
stream =cuda.Stream()
将数据传输到GPU,运行推理运算,并取回结果。
#transfer input datato device
cuda.memcpy_htod_async(d_input,img, stream)
#execute model
context.enqueue(1,bindings, stream.handle, None)
#transferpredictions back
cuda.memcpy_dtoh_async(output,d_output, stream)
#synchronize threads
stream.synchronize()
现在我们有运行结果了,运行ArgMax取得预测结果:
print("TestCase: " + str(label))
print("Prediction: " + str(np.argmax(output)))
用户还可以将engine存到文件中以备后用。
trt.utils.write_engine_to_file("/data/mnist/tf_mnist.engine",
engine.serialize())
用户可以随后通过tensorrt.utils.load_engine来使用engine。
new_engine =trt.utils.load_engine(G_LOGGER, "/data/mnist/new_mnist.engine")
最后一步后,清理context,engine与runtime。
context.destroy()
engine.destroy()
new_engine.destroy()
runtime.destroy()
2.3.3.2 Exporting TensorFlow To A UFF File
TensorRT有UFF解析器来加载.uff文件,创建推理引擎。因此,为了转换TensorFlow模型到TensorRT可以运行的文件,必须用convert-to-uff工具将TensorFlow代码freeze为.pb文件,并转换成.uff格式。Convert-to-uff工具包含在uff.whl文件中,作为TensorRT安装包的一部分。
尽管网络可以使用NHWC与NCHW格式,还是鼓励TensorFlow用户使用NCHW数据格式用于达到最佳的性能。
1. 对于TensorFlow或者Keras(TensorFlow作为后端),将图freeze生成.pb(protobuf)文件。请参考SampleCode 1: Freezing the Graph from TensorFlow to .pb 与Sample Code2: Freezing Keras Code to .pb得到更多使用信息。
2. 使用convert-to-uff.py工具,将.pb格式的图转换成.uff格式文件。请参考Sample Code 3: Running convert_to_uff.py得到跟多代码与使用信息。
2.3.3.2.1 Freezing The Graph From TensorFlow
TensorFlow提供freeze_graph函数,使用方式可在freeze_graph_test.py文件中查看。为了使用这种方法,图必须使用graph.io.write_graph保存为.pb文件。其次,freeze_graph可以调用来转换变量为const ops来阐述一个新的frozen.pb文件。更多的freeze_graph信息可以在以下链接找到。
2.3.3.2.2 Sample Code 1:Freezing Keras Code To .pb
转换Keras模型,使用以下代码:
from keras.modelsimport load_model
import keras.backendas K
fromtensorflow.python.framework import graph_io
fromtensorflow.python.tools import freeze_graph
fromtensorflow.core.protobuf import saver_pb2
fromtensorflow.python.training import saver as saver_lib
defconvert_keras_to_pb(keras_model, out_names, models_dir,
model_filename):
model =load_model(keras_model)
K.set_learning_phase(0)
sess =K.get_session()
saver =saver_lib.Saver(write_version=saver_pb2.SaverDef.V2)
checkpoint_path =saver.save(sess, 'saved_ckpt', global_step=0,
latest_filename='checkpoint_state')
graph_io.write_graph(sess.graph,'.', 'tmp.pb')
freeze_graph.freeze_graph('./tmp.pb','',
False,checkpoint_path, out_names,
"save/restore_all","save/Const:0",
models_dir+model_filename,False, "")
2.3.3.2.3 Sample Code 2:Running convert-to-uff
为了将.pb的frozen graph转成.uff格式文件,见下列例程代码:
convert-to-ufftensorflow -o name_of_output_uff_file --inputfile
name_of_input_pb_file-O name_of_output_tensor
为了确定name_of_output_tensor,用户可以列出TensorFlow层:
convert-to-ufftensorflow --input-file name_of_input_pb_file -l
2.3.3.2.4 Supported TensorFlow Operations
当前输出支持TensorFlow以下原生层:
Ø placeholder转换成UFF输入层
Ø Const转成UFF的Const层
Ø Add,Sub,Mul,Div,Minimun与Maximum转成UFF二进制层
Ø BiasAdd转成UFF二进制层
Ø Negative,Abs,Sqrt,Rsqrt,Pow,Exp与Log转成UFF的Unary层
Ø FusedBatchNorm转换成UFF BatchNorm层
Ø Tanh,Relu,Sigmoid转换成UFF Activation层
Ø SoftMax转换成SoftMax层
Ø Mean转换成UFF Reduce层
Ø ConcatV2转换成UFFConcat层
Ø Reshape转换成UFFReshape层
Ø Transpose层转换成UFF Transpose层
Ø Conv2D与DepthwiseConv2dNative转换成UFF Conv层
Ø ConvTranspose2D转换成UFFConvTranspose层
Ø MaxPool与AvePool转换成UFF Pooling层
Ø 如果Pad是接在以下TensorFlow层后面则可以使用,Conv2D,DepthwiseConv2dNative,MaxPool与AvgPool
2.3.3.3 NvUffParser
关于如何将UFF文件导入到TensorRT中,见例子SampleUffMNISTUFF
Usage。
2.3.4 Converting A Model From An Unsupported Framework ToTensorRT With The TensorRT Python API
随着UFF版本的发布,将兼容框架的模型转换为TensorRT引擎变得十分容易。然而,也有些框架当前或者后续不会支持UFF加载器。TensorRT的Python接口提供了一种前向算法的方式,使得基于Python的框架可以使用NumPy兼容层参数。
以PyTorch为例,我们将展示如何训练一个模型,并且手动将模型转换为TensorRT的engine。
在Python中TensorRT库表示为tenssort。
同样有些工具配合TensorRT的Python接口,典型的工具,例如PyCUDA与NumPy。PyCUDA负责处理CUDA操作,用于GPU显存申请,将数据传送到GPU上,结构回传CPU上。NumPy是一种通用的数据转存工具。
import pycuda.driver as cuda
import pycuda.autoinit
import numpy as np
载入PyTorch以及相关包:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.autograd import Variable
用户可以像使用其他包一样载入tensorrt。例如:
import tensorrt as trt
2.3.4.1 Training A Model In PyTorch
本例中,通过设置一些超参数,创建一个数据加载器,定义你的网络结构,设置优化方式,定义训练与测试的steps。
BATCH_SIZE = 64
TEST_BATCH_SIZE = 1000
EPOCHS = 3
LEARNING_RATE = 0.001
SGD_MOMENTUM = 0.5
SEED = 1
LOG_INTERVAL = 10 #Enable Cuda
torch.cuda.manual_seed(SEED) #Dataloader
kwargs = {'num_workers': 1, 'pin_memory': True}
train_loader = torch.utils.data.DataLoader(
datasets.MNIST('/tmp/mnist/data', train=True,download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=BATCH_SIZE,
shuffle=True,
**kwargs)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('/tmp/mnist/data', train=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=TEST_BATCH_SIZE,
shuffle=True,
**kwargs) #Network
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 20, kernel_size=5)
self.conv2 = nn.Conv2d(20, 50, kernel_size=5)
self.conv2_drop = nn.Dropout2d()
self.fc1 = nn.Linear(800, 500)
self.fc2 = nn.Linear(500, 10)
def forward(self, x):
x = F.max_pool2d(self.conv1(x), kernel_size=2,stride=2)
x = F.max_pool2d(self.conv2(x), kernel_size=2,stride=2)
x = x.view(-1, 800)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.log_softmax(x)
model = Net()
model.cuda() optimizer = optim.SGD(model.parameters(),
lr=LEARNING_RATE,
momentum=SGD_MOMENTUM)
def train(epoch):
model.train()
for batch, (data, target) in enumerate(train_loader):
data, target = data.cuda(), target.cuda()
data, target = Variable(data), Variable(target)
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
if batch % LOG_INTERVAL == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss:{:.6f}'
.format(epoch,
batch * len(data),
len(train_loader.dataset),
100. * batch / len(train_loader),
loss.data[0]))
def test(epoch):
model.eval()
test_loss = 0
correct = 0
for data, target in test_loader:
data, target = data.cuda(), target.cuda()
data, target = Variable(data, volatile=True),Variable(target)
output = model(data)
test_loss += F.nll_loss(output, target).data[0]
pred = output.data.max(1)[1]
correct += pred.eq(target.data).cpu().sum()
test_loss /= len(test_loader)
print('Test: Average loss: {:.4f}, Accuracy: {}/{}({:.0f}%)\n'
.format(test_loss,
correct,
len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))
下一步,训练模型
for e in range(EPOCHS):
train(e + 1)
test(e + 1)
2.3.4.2 Converting The Model Into A TensorRT Engine
我们现在有训练好的模型了,我们需要利用state_dict提取层参数作为模型转换的第一步。
weights = model.state_dict()
为了将模型转成TensorRT使用,首先,建立一个builder与一个logger。
G_LOGGER =trt.infer.ConsoleLogger(trt.infer.LogSeverity.ERROR)
builder = trt.infer.create_infer_builder(G_LOGGER)
下一步,通过复制上述网络结构,并且从PyTorch模型中提取NumPy格式存储的权重。PyTorch中的NumPy矩阵展示了层的维度,然而我们对参数进行flatten操作。
注:TensorRT权重的格式是NCHW,因此,如果你的框架使用的是其他格式,你肯定需要在flatten前进行一些预处理。
network = builder.create_network()
#Name for the input layer, data type, tuple fordimension
data = network.add_input("data",
trt.infer.DataType.FLOAT,
(1, 28, 28))
assert(data)
#-------------
conv1_w =weights['conv1.weight'].cpu().numpy().reshape(-1)
conv1_b =weights['conv1.bias'].cpu().numpy().reshape(-1)
conv1 = network.add_convolution(data, 20, (5,5),conv1_w, conv1_b)
assert(conv1)
conv1.set_stride((1,1))
#-------------
pool1 = network.add_pooling(conv1.get_output(0),
trt.infer.PoolingType.MAX,
(2,2))
assert(pool1)
pool1.set_stride((2,2))
#-------------
conv2_w =weights['conv2.weight'].cpu().numpy().reshape(-1)
conv2_b =weights['conv2.bias'].cpu().numpy().reshape(-1)
conv2 = network.add_convolution(pool1.get_output(0),50, (5,5), conv2_w,
conv2_b)
assert(conv2)
conv2.set_stride((1,1))
#-------------
pool2 = network.add_pooling(conv2.get_output(0),trt.infer.PoolingType.MAX,
(2,2))
assert(pool2)
pool2.set_stride((2,2))
#-------------
fc1_w = weights['fc1.weight'].cpu().numpy().reshape(-1)
fc1_b = weights['fc1.bias'].cpu().numpy().reshape(-1)
fc1 = network.add_fully_connected(pool2.get_output(0),500, fc1_w, fc1_b)
assert(fc1)
#-------------
relu1 = network.add_activation(fc1.get_output(0),trt.infer.ActivationType.RELU)
assert(relu1)
#-------------
fc2_w =weights['fc2.weight'].cpu().numpy().reshape(-1)
fc2_b = weights['fc2.bias'].cpu().numpy().reshape(-1)
fc2 = network.add_fully_connected(relu1.get_output(0),10, fc2_w, fc2_b)
assert(fc2)
标记处输出层。
fc2.get_output(0).set_name("prob")
network.mark_output(fc2.get_output(0))
创建一个算法引擎并且利用torch的数据加载器创建一个测试实例。
runtime = trt.infer.create_infer_runtime(G_LOGGER)
img, target = next(iter(test_loader))
img = img.numpy()[0]
target = target.numpy()[0]
print("Test Case: " + str(target))
img = img.ravel()
为算法引擎创建一个运行上下文。
context = engine.create_execution_context()
在GPU上申请显存,在CPU上申请用来存储推理运算结果的内存。申请的大小是batchsize大小的输入与输出指针的大小。
output = np.empty(10, dtype = np.float32)
#allocate device memory
d_input = cuda.mem_alloc(1 * img.size *img.dtype.itemsize)
d_output = cuda.mem_alloc(1 * output.size *output.dtype.itemsize)
engine需要绑定GPU的显存。PyCUDA使得用户可以通过ints的方式进行内存申请。
bindings = [int(d_input), int(d_output)]
将数据传送到GPU上,运行推理,将结果传回。
#transfer input data to device
cuda.memcpy_htod_async(d_input, img, stream)
#execute model
context.enqueue(1, bindings, stream.handle, None)
#transfer predictions back
cuda.memcpy_dtoh_async(output, d_output, stream)
#synchronize threads
stream.synchronize()
现在我们得到了处理的结果,进行ArgMax就可以得到预测结果。
print("Test Case: " + str(target))
print ("Prediction: " +str(np.argmax(output)))
也可以将engine保存为文件以备后用。
trt.utils.write_engine_to_file("/data/mnist/pyt_mnist.engine",
engine.serialize())
后续可以使用tensorrt.util.load_engine来加载engine。
new_engine = trt.utils.load_engine(G_LOGGER,
"/data/mnist/new_mnist.engine")
最后,清理context,engine与runtime。
context.destroy()
engine.destroy()
new_engine.destroy()
runtime.destroy()
2.4 Build Phase
在build阶段,TensorRT载入网络定义,进行性能优化并生成推理引擎。
build阶段会消耗较多的时间,特别是在嵌入式平台上。因此,一般应用的时候会build意思engine,之后以serialize的方式使用。
build阶段在层图上进行了一下优化:
Ø 消除层输出没有被使用的层
Ø 融合卷积、偏置与非线性映射操作
Ø 将使用类似参数运算与相同源tensor(例如googleNetv5的inception模块中的1乘1卷积)进行聚合。
Ø 通过将层输出直接输出到正确的最终位置进行串联层合并
此外,build阶段还在虚拟数据上运行层以选择最快的核类型,进行权重的预格式化与适当的内存优化。
2.5 Execution Phase
在execution阶段,进行以下任务:
Ø 运行时库执行优化过的engine
Ø engine使用GPU输入与输出buffer运行推理任务
2.6 Command Line Wrapper
在samples目录下包含TensorRT的command linewrapper,称为giexec。可以用来作为网络在随机数据集上的benchmark,用于产生模型对应的系列engines。
命令行的参数如下:
Mandatory params:
--deploy=<file> Caffe deploy file
--output=<name> Output blob name (can bespecified
multiple times)
Optional params:
--model=<file> Caffe model file (default = nomodel,
random weights
used)
--batch=N Set batch size (default = 1)
--device=N Set cuda device to N (default = 0)
--iterations=N Run N iterations (default = 10)
--avgRuns=N Set avgRuns to N - perf is measured as an
average of
avgRuns (default=10)
--workspace=N Set workspace size in megabytes (default=
16)
--half2 Run in paired fp16 mode (default = false)
--int8 Run in int8 mode (default = false)
--verbose Use verbose logging (default = false)
--hostTime Measure host time rather than GPU time
(default =
false)
--engine=<file> Generate a serialized GIE engine
--calib=<file> Read INT8 calibration cache file
例如:
giexec --deploy=mnist.prototxt--model=mnist.caffemodel --
output=prob
如果没有提供模型,会使用随机权值。