TensorRT 5 开发者手册 中文版 使用深度学习框架(三-6)

矫情吗;* 2022-04-18 06:22 585阅读 0赞

原创作品,转载时请务必以超链接形式标明文章原始出处: http://www.dapalm.com/?p=206,作者:大数据,怕了么?
  本手册为TensorRT 4.0.1.6 GA版英文手册翻译而来,主要作为备忘所用,分享出来以供更多开发者使用。TensorRT Developer Guide手册一共分为四个章节,主要内容在第二、三章,看懂这两章,写代码够用了。第一章为TensorRT综述,就是自吹有多牛逼。第四章为示例,介绍demo的代码结构及功能。开篇是目录,前三章每章为两到三篇,最后第四章示例,会拆分几个关键示例进行详细说明。
  注意,2018年10月份,NVIDIA又更新TensorRT 5.0.2.6 Linux版本和TensorRT 5.0.1.3 Windows版,正式支持Windows10,经测试向下兼容Windows7也可以用。
  本来这章应该是TensorRT4的第3章,在TensorRT5的手册上直接到了第8章,这里暂时先从第8章开始,继续翻译。有时间再补充前7章中与TensorRT4中增加的内容。

第8章

  • 第8章 使用深度学习框架
    • 8.1 框架支持的算子
    • 8.2 使用TensorFlow
      • 8.2.1 TensorFlow模型生成冻结图
      • 8.2.2 Keras模型生成冻结图
      • 8.2.3 冻结图转换为UFF
      • 8.2.4 使用TensorFlow RNN权重
        • 8.2.4.1 TensorRT支持的TensorFlow RNN单元
        • 8.2.4.2 保持TensorFlow和TensorRT之间的模型一致性
        • 8.2.4.3 工作流
        • 8.2.4.4 转存(dumped)TensorFlow权重
        • 8.2.4.5 加载转存权重
        • 8.2.4.6 将权重转换为TensorRT格式
          • 8.2.4.6.1 TensorFlow checkpoint存储格式
          • 8.2.4.6.2 TensorFlow kernel张量存储格式
          • 8.2.4.6.3 Kernel权重转换为TensorRT格式
          • 8.2.4.6.4 TensorFlow Bias权重存储格式
          • 8.2.4.6.5 Bias张量转换成TensorRT格式
        • 8.2.4.7 BasicLSTMCell示例
          • 8.2.4.7.1 BasicLSTMCell Kernel张量
          • 8.2.4.7.2 BasicLSTMCell Bais张量
        • 8.2.4.8 设置转换的权重和偏差
      • 8.2.5 使用Graph Surgeon API预处理TensorFlow图
    • 8.3 使用PyTorch和其他框架

第8章 使用深度学习框架

使用Python API,利用TensorFlow,Caffe或ONNX等兼容框架构建的模型使用TensorRT提供的解析器构建加速引擎。Python API还支持以NumPy兼容格式存储权重的框架,如PyTorch。

8.1 框架支持的算子

Caffe
Caffe框架支持的操作:

  • Convolution:3D,with or without bias
  • Pooling :Max, Average, Max_Average_blend
  • InnerProduct
  • Softmax
  • Activation : ReLu, Sigmoid,Tanh
  • LRN
  • Power
  • ElementWise: sum, product, maximum, subtraction,division, power
  • Concatenation: across channel
  • Deconvolution
  • BatchNormalization
  • Scale
  • Crop
  • Reduction : sum,prod,max,min,avg
  • Reshape
  • Permute
  • Dropout
  • Concat
  • ElementWise
  • RNN : Input,Output,Forget,Update,Reset,Cell,Hidden
  • Unary : exp,log,sqrt,recip,abs,neg
  • Padding
  • Shuffle
  • Topk max,min
  • Gather
  • Matrix_Multiply
  • Ragged_Softmax
  • Constant : Uff模型的权值就是保存为常量类型
  • RNN_v2
  • Plugin : FasterRCNN fused plugin (RPN + ROI pooling). Normalize plugin. Permute plugin. PriorBox plugin. SSD DetectionOutput plugin. Concat plugin. YOLO PReLU Plugin. YOLO Reorg Plugin. YOLO Region Plugin.

TensorFlow
TensorFlow框架支持的算子:

  • Placeholder
  • Const
  • Add, Sub, Mul, Div, Minimum and Maximum
  • BiasAdd
  • Negative, Abs, Sqrt, Rsqrt, Pow, Exp and Log
    注意:NvUffParser只支持Neg, Abs, Sqrt, Rsqrt, Exp和Log的常量节点
  • FusedBatchNorm
  • ReLU, TanH, and Sigmoid
  • SoftMax
  • Mean
  • ConcatV2
  • Reshape
  • Transpose
  • Conv2D
  • DepthwiseConv2dNative
  • ConvTranspose2D
  • MaxPool
  • AvgPool
  • Pad

ONNX
ONNX解析器是一个开源项目,可以在GitHub:ONNX TensorRT中找到有关支持算子的最新信息。

8.2 使用TensorFlow

有关TensorFlow模型直接使用TensorRT的信息,请参阅:

  • Python示例 - [9.2.2 end_to_end_tensorflow_mnist]
  • TensorFlow框架中直接创建TensorRT引擎

8.2.1 TensorFlow模型生成冻结图

为了使用UFF命令行工具,TensorFlowGraph必须保存为.pd的冻结图文件,请参阅:

  • TensorFlow模型生成冻结图的方法
  • TensorFlow模型导出方法
    注意:一般是在模型导出的过程中直接转换成冻结图

8.2.2 Keras模型生成冻结图

使用如下代码生成Keras模型的冻结图:

  1. from keras.models import load_model
  2. import keras.backend as K
  3. from tensorflow.python.framework import graph_io
  4. from tensorflow.python.tools import freeze_graph
  5. from tensorflow.core.protobuf import saver_pb2
  6. from tensorflow.python.training import saver as saver_lib
  7. def convert_keras_to_pb(keras_model, out_names, models_dir,model_filename):
  8. model = load_model(keras_model)
  9. K.set_learning_phase(0)
  10. sess = K.get_session()
  11. saver = saver_lib.Saver(write_version=saver_pb2.SaverDef.V2)
  12. checkpoint_path = saver.save(sess, 'saved_ckpt', global_step=0,
  13. latest_filename='checkpoint_state')
  14. graph_io.write_graph(sess.graph, '.', 'tmp.pb')
  15. freeze_graph.freeze_graph('./tmp.pb', '',
  16. False, checkpoint_path, out_names,
  17. "save/restore_all", "save/Const:0",
  18. models_dir+model_filename, False, "")

8.2.3 冻结图转换为UFF

使用如下示例代码将.pb冻结图转换成.uff格式文件:

  1. convert-to-uff input_file [-o output_file] [-O output_node]
  2. convert-to-uff input_file -l #打印TensorFlow层

8.2.4 使用TensorFlow RNN权重

本节提供有关TensorFlow权重及其存储格式的信息。此外,以下部分将指导您如何从TensorFlow处理和解密RNN权重。
这节主要内容是将各种训练框架下输出的模型权重转换成TensorRT格式(planner格式)

8.2.4.1 TensorRT支持的TensorFlow RNN单元

TensorRT中的循环神经网络层来自TensorFlow的MultiRNNCell算子。每一层由具有相同配置的多个子层组成,换句话说,隐藏和嵌入大小。完成该封装使得多个子层之间的内部连接可以从用户抽象出来(其实和DenseBlock、ResBlock一个意思,内部包含多个层)。当涉及更深层的网络时,这允许更简单的代码。

TensorRT支持四种不同的RNN层类型。这些层类型是RNN relu,RNN tanh,LSTM和GRU。与这些类型匹配的TensorFlow Cells是:

TensorRT RNN Relu/Tanh Layer
BaiscRNNCells

  • 允许的激活函数有:tf.tanhtf.nn.relu
  • 这是一个独立于平台的单元

TensorRT LSTM Layer

BasicLSTMCell

  • 在TensorFlow中创建这个算子的实例时,forget_bias必须设置为0。为了支持非0的遗忘偏置,需要通过添加一个参数化的遗忘偏置去转存TensorFlow的遗忘偏置。
  • 这是一个独立于平台的单元

CudnnCompatibleLSTMCell

  • forget bias的条件和BasicLSTMCell一样
  • 目前不支持peepholes(gate由当前输入xt、上一时刻隐藏状态ht-1、上一时刻单元状态ct-1),use_peepholes必须设置成False
  • cudnn兼容

TensorRT GRU Layer
CudnnCompatibleGRUCell

  • cudnn兼容
  • 由于与标准的、平台独立的GRUCell不同,所以CudnnCompatibleGRUCell可以在TensorRT中正确使用

8.2.4.2 保持TensorFlow和TensorRT之间的模型一致性

对于未在TensorFlow RNN Cells Supported In TensorRT中列出的任何TensorFlow Cell,请参阅TensorRT API和TensorFlow API以确保Cell在数学上等同于TensorRT支持的Cell,并且存储格式与您期望的格式一致。 这样做的一个好方法是设计单元测试,使用TensorFlow作为正确输出来验证TensorRT的输出。

8.2.4.3 工作流

使用如下工作流来萃取和使用TensorFlow权重


Dump Weights

LoadWeights

ConvertWeights

SetWeights


8.2.4.4 转存(dumped)TensorFlow权重

Python脚本dumpTFWts.py可用于从给定的TensorFlow检查点转储所有变量和权重。 该脚本位于tensorrt / samples / common / dumpTFWts.py目录中。通过dumpTFWts.py -h获取有关此脚本用法的更多信息。

  1. #!/usr/bin/python
  2. # Script to dump TensorFlow weights in TRT v1 and v2 dump format.
  3. # The V1 format is for TensorRT 4.0. The V2 format is for TensorRT 4.0 and later.
  4. import sys
  5. import struct
  6. import argparse
  7. try:
  8. import tensorflow as tf
  9. from tensorflow.python import pywrap_tensorflow
  10. except ImportError as err:
  11. sys.stderr.write("""Error: Failed to import module ({})""".format(err))
  12. sys.exit()
  13. parser = argparse.ArgumentParser(description='TensorFlow Weight Dumper')
  14. parser.add_argument('-m', '--model', required=True, help='The checkpoint file basename, example basename(model.ckpt-766908.data-00000-of-00001) -> model.ckpt-766908')
  15. parser.add_argument('-o', '--output', required=True, help='The weight file to dump all the weights to.')
  16. parser.add_argument('-1', '--wtsv1', required=False, default=False, type=bool, help='Dump the weights in the wts v1.')
  17. opt = parser.parse_args()
  18. if opt.wtsv1:
  19. print "Outputting the trained weights in TensorRT's wts v1 format. This format is documented as:"
  20. print "Line 0: <number of buffers in the file>"
  21. print "Line 1-Num: [buffer name] [buffer type] [buffer size] <hex values>"
  22. else:
  23. print "Outputting the trained weights in TensorRT's wts v2 format. This format is documented as:"
  24. print "Line 0: <number of buffers in the file>"
  25. print "Line 1-Num: [buffer name] [buffer type] [(buffer shape{e.g. (1, 2, 3)}] <buffer shaped size bytes of data>"
  26. inputbase = opt.model
  27. outputbase = opt.output
  28. def float_to_hex(f):
  29. return hex(struct.unpack('<I', struct.pack('<f', f))[0])
  30. def getTRTType(tensor):
  31. if tf.as_dtype(tensor.dtype) == tf.float32:
  32. return 0
  33. if tf.as_dtype(tensor.dtype) == tf.float16:
  34. return 1
  35. if tf.as_dtype(tensor.dtype) == tf.int8:
  36. return 2
  37. if tf.as_dtype(tensor.dtype) == tf.int32:
  38. return 3
  39. print("Tensor data type of %s is not supported in TensorRT"%(tensor.dtype))
  40. sys.exit();
  41. try:
  42. # Open output file
  43. if opt.wtsv1:
  44. outputFileName = outputbase + ".wts"
  45. else:
  46. outputFileName = outputbase + ".wts2"
  47. outputFile = open(outputFileName, 'w')
  48. # read vars from checkpoint
  49. reader = pywrap_tensorflow.NewCheckpointReader(inputbase)
  50. var_to_shape_map = reader.get_variable_to_shape_map()
  51. # Record count of weights
  52. count = 0
  53. for key in sorted(var_to_shape_map):
  54. count += 1
  55. outputFile.write("%s\n"%(count))
  56. # Dump the weights in either v1 or v2 format
  57. for key in sorted(var_to_shape_map):
  58. tensor = reader.get_tensor(key)
  59. file_key = key.replace('/','_')
  60. typeOfElem = getTRTType(tensor)
  61. val = tensor.shape
  62. if opt.wtsv1:
  63. val = tensor.size
  64. print("%s %s %s "%(file_key, typeOfElem, val))
  65. flat_tensor = tensor.flatten()
  66. outputFile.write("%s 0 %s "%(file_key, val))
  67. if opt.wtsv1:
  68. for weight in flat_tensor:
  69. hexval = float_to_hex(float(weight))
  70. outputFile.write("%s "%(hexval[2:]))
  71. else:
  72. outputFile.write(flat_tensor.tobytes())
  73. outputFile.write("\n");
  74. outputFile.close()
  75. except Exception as e: # pylint: disable=broad-except
  76. print(str(e))
  77. if "corrupted compressed block contents" in str(e):
  78. print("It's likely that your checkpoint file has been compressed "
  79. "with SNAPPY.")
  80. if ("Data loss" in str(e) and
  81. (any([e in inputbase for e in [".index", ".meta", ".data"]]))):
  82. proposed_file = ".".join(inputbase.split(".")[0:-1])
  83. v2_file_error_template = """ It's likely that this is a V2 checkpoint and you need to provide the filename *prefix*. Try removing the '.' and extension. Try: inspect checkpoint --file_name = {}"""
  84. print(v2_file_error_template.format(proposed_file))

8.2.4.5 加载转存权重

  1. //!
  2. //! \brief 加载权重到map容器.
  3. //!
  4. //! \param 权重文件路径, 权重文件必须由dumpTFWts.py脚本转换才能使用.
  5. //! \param 要提取的权重名称
  6. //!
  7. //! \return A map containing the extracted weights.
  8. //!
  9. //! \note Weight V2 files are in a very simple space delimited format.
  10. //! <number of buffers>
  11. //! for each buffer: [name] [type] [shape] <data as binary blob>\n
  12. //! Note: type is the integer value of the DataType enum in NvInfer.h.
  13. //!
  14. std::map<std::string, Weights> loadWeights(const std::string file, std::unordered_set<std::string> names)
  15. {
  16. std::map<std::string, Weights> weightMap;
  17. std::ifstream input(file, std::ios_base::binary);
  18. assert(input.is_open() && "Unable to load weight file.");
  19. int32_t count;
  20. input >> count;
  21. assert(count > 0 && "Invalid weight map file.");
  22. while (count--)
  23. {
  24. if (names.empty())
  25. break;
  26. Weights wt{ DataType::kFLOAT, nullptr, 0};
  27. // parse name and DataType
  28. std::string name;
  29. uint32_t type;
  30. input >> name >> std::dec >> type;
  31. wt.type = static_cast<DataType>(type);
  32. // extract shape
  33. std::string temp, shape;
  34. std::getline(std::getline(input, temp, '('), shape, ')');
  35. // calculate count based on shape
  36. wt.count = 1;
  37. std::istringstream shapeStream(shape);
  38. while (std::getline(shapeStream, temp, ','))
  39. wt.count *= std::stoul(temp);
  40. size_t numOfBytes = samplesCommon::getElementSize(wt.type) * wt.count;
  41. // skip reading of weights if name is not in the set of names requested for extraction
  42. if (names.find(name) == names.end())
  43. {
  44. input.seekg(input.tellg() + static_cast<std::streamoff>(2 + numOfBytes));
  45. continue;
  46. }
  47. else
  48. {
  49. names.erase(name);
  50. }
  51. // Read weight values
  52. input.seekg(input.tellg() + static_cast<std::streamoff>(1)); // skip space char
  53. char* wtVals = static_cast<char*>(malloc(numOfBytes));
  54. input.read(wtVals, numOfBytes);
  55. input.seekg(input.tellg() + static_cast<std::streamoff>(1)); // skip new-line char
  56. wt.values = wtVals;
  57. weightMap[name] = wt;
  58. }
  59. input.close();
  60. return weightMap;
  61. }

这个函数通过设置需要提取的权重的名称,返回对应的权重map。

8.2.4.6 将权重转换为TensorRT格式

此时,我们已经准备好转换的权重,接着需要去执行以下步骤:

  1. 理解并使用TensorFlow checkpoint来获取张量
  2. 理解并使用张量来提取和重新格式化相关权重,并将它们设置为TensorRT相应层的权重
8.2.4.6.1 TensorFlow checkpoint存储格式

TensorFlow checkpoint可能有两种存储格式:

  1. 独立于平台的格式 - 由层分隔

    • Cell_i_kernel
    • Cell_i_bias
  2. cuDNN兼容格式 - 由输入和循环分隔

    • Cell_i_Candidate_Input_kernel
    • Cell_i_Candidate_Hidden_kernel

换句话说,1.1 Cell_i_kernel 是 2.1 Cell_i_Candidate_Input_kernel 和 2.2 Cell_i_Candidate_Hidden_kernel 的串联形式. 因此, 存储格式2相对于存储格式1是一种更加精细的存储方式。

8.2.4.6.2 TensorFlow kernel张量存储格式

在存储checkpoint权重之前,TensorFlow先转置再将转置矩阵交错行的位置,交错方式在下届众描述。BaiscLSTMCell示例中提供一个图来进一步说明此格式。

基于层操作类型的门顺序转置的权重矩阵按以下顺序交错:

  1. RNN relu/tanh
    a.输入门(i)
  2. LSTM
    a.输入门(i),状态门(c),遗忘门(g),输出门(o)
  3. GRU
    a.重置门(r),更新门(u)
8.2.4.6.3 Kernel权重转换为TensorRT格式

从TensorFlow格式转换权重可以分为两个步骤:

  1. 转换权重维度来调整交错格式,去成为更低的维度
  2. 转置权重来完全解除交错格式,让权重连续存储在存储器中
    为了帮助正确执行这些转换,转换代码提供了reorderSubBuffers(),transposeSubBuffers(), and reshapeWeights()这些函数。
    有关更多信息,请参阅include/NvUtil.h。
8.2.4.6.4 TensorFlow Bias权重存储格式

Bias张量简单地存储为以TensorFlow Kernel张量存储格式中指定的顺序连接的连续向量。如果checkpoint存储与平台无关,则TensorFlow通过将它们添加在一起,将循环和输入偏差合并为单个张量。否则,循环和输入偏差并存储在单独的张量中。

8.2.4.6.5 Bias张量转换成TensorRT格式

因为bias存储方式为连续向量,所以不需要应用任何转换,它与TensorRT格式是相同的。

8.2.4.7 BasicLSTMCell示例

8.2.4.7.1 BasicLSTMCell Kernel张量

为了理解这些张量存储的格式,让我们说明一个BasicLSTMCell的例子。图6说明了TensorFlow checkpoint中张量的样子。
张量在checkpoint中的存储格式
图6 TensorFlow checkpoint张量的存储格式
对于第一层的DS/Data Size与隐藏层Size是不同的,对于以下所有子层,Data Size等于隐藏层Size
在图6中,W代表输入权重,R代表隐藏权重,DS代表数据大小,HS代表隐藏层大小。由于这是一个独立于平台的单元,因此输入门权重和隐藏门权重已经连接在一起。如果我们使用CudnnCompatibleLSTMCell,那么这些权重将被分成两个独立的张量。
使用前面讨论的转换程序,将得到转换的张量如图7所示。
转换的权重

8.2.4.7.2 BasicLSTMCell Bais张量

Bias张量的存储格式如下图
Bias存储格式
因为这是一个独立于平台的格式,所以上图W代表ElementWise将输入和循环偏置加在一起的结果。在存储张量之前,TensorFlow在内部执行此加法以节省内存。
这已经是我们需要的格式,因此,我们不需要进行任何转换。

8.2.4.8 设置转换的权重和偏差

准备好转换好的Weight和Bias就可以分別调用IRNNv2Layer::setWeightsForGate()和IRNNv2Layer::setBiasForGate()来设置TensorRT的权重和偏置。
实际的例子通常是训练、转储、转换、设置处理。更多信息参考sampleCharRNN例子。

8.2.5 使用Graph Surgeon API预处理TensorFlow图

Graph Surgeon APISurgeon翻译为“外科医生”很贴切,对TensorFlow Graph进行搜索、操作,类似外科手术,功能如下:

  • 搜索
    搜索功能允许您查找节点
  • 操作
    操作功能允许您修改,添加或删除节点

使用Graph Surgeon API,您可以将某些节点(或节点集)标记为图中的Plugin节点。这些插件可以是TensorRT自带的插件,也可以是您编写的插件。有关更多信息,请参阅使用自定义层扩展TensorRT
如果您正在编写Plugin,请参阅有关如何实现IPluginExt和IPluignCreator类以及注册Plugin的详细信息,请参阅使用自定义层扩展TensorRT
以下代码片段示例如何使用Graph Surgeon API将TensorFlow Leaky ReLU算子映射到TensorRT Leaky ReLU插件节点。

  1. import graphsurgeon as gs
  2. # 创建leaky relu节点
  3. lrelu_node = gs.create_plugin_node(name=”trt_lrelu”, op=”LReLU_TRT”, negSlope=0.2)
  4. namespace_plugin_map = { tf_lrelu : lrelu_node }
  5. # 使用Graph surgeon API转换TensorFlow图,并保存到uff格式文件中
  6. dynamic_graph = gs.DynamicGraph(tf_lrelu.graph)
  7. dynamic_graph.collapse_namespaces(namespace_plugin_map)
  8. # 运行UFF转换器转换新的Graph
  9. uff_model = uff.from_tensorflow(dynamic_graph.as_graph_def(), ["trt_lrelu"], output_filename="test_lrelu.uff", text=True)

在上面的代码中,create_plugin_node方法中的op字段应该与注册的插件名称匹配。这使得UFF解析器能够使用该字段查找插件注册表,来将插件节点插入网络中。
有关Graph Surgeon API示例,请参阅C++ sampleUffSSD。
有关Graph Surgeon API的更多详细信息,请参阅Graph Surgeon API。

8.3 使用PyTorch和其他框架

将TensorRT与PyTorch和其他框架一起使用,涉及使用TensorRT API复制网络架构,然后从PyTorch(或具有NumPy兼容权重的任何其他框架)复制权重。有关将TensorRT与PyTorch模型一起使用的更多信息,请参阅:

  • 9.2.3节network_api_pytorch_mnist Python示例
  • 从TensorFlow和其他UFF兼容框架生成TensorRT引擎

发表评论

表情:
评论列表 (有 0 条评论,585人围观)

还没有评论,来说两句吧...

相关阅读