TensorFlow 笔记04 - 使用类封装写好的 TensorRT 模型,包括 int8 优化要用的 calibrator


▶ 使用类封装写好的 TensorRT 模型,每个函数、类成员各司其职,而不是以前程序那样纯过程式,变量全部摊开

● 代码,程序入口 enter.py

  1 import os
  2 import sys
  3 import numpy as np
  4 import tensorrt as trt
  5 import pycuda.autoinit
  6 import pycuda.driver as cuda
  7 from datetime import datetime as dt
  8 
  9 import loadPara as ld
 10 import calibrator
 11 
 12 DEBUG           = True                     
 13 testDataPath    = "./"
 14 calibDataPath   = "./"
 15 tempPath        = "./"
 16 paraFile        = tempPath + "para.h5"
 17 cacheFile       = tempPath + "calib.cache"
 18 outputFile      = tempPath + "output.txt"
 19             
 20 iGpu            = 0
 21 calibCount      = 10                        # int8 校正次数
 22 inputSize       = (1,1,1)                   # 输入数据尺寸,CHW
 23 
 24 class TrtPredictor:
 25     def __init__(self, batchSize, dataType):
 26         self.logger     = trt.Logger(trt.Logger.ERROR)                      # 创建 logger
 27         self.batchSize  = batchSize
 28         self.dataType   = dataType
 29         self.h5f, ...   = fld.loadPara(paraFile)                            # 读取训练好的参数
 30         
 31         trtFilePath = tempPath + "engine-" + self.dataType + ".trt"         # 尝试读取创建好的引擎,没有则现场创建引擎
 32         if os.path.isfile(trtFilePath) and not DEBUG:
 33             f =  open(trtFilePath, 'rb')
 34             engineStr = f.read()                                            # enginStr 不作为成员变量
 35             self.runtime = trt.Runtime(self.logger)                         # 运行时读取文件中的引擎
 36             self.engine = self.runtime.deserialize_cuda_engine(engineStr)
 37             f.close()
 38             print("succeeded loading engine!")
 39         else:                             
 40             self.create_engine()                                            # 创建 engine,并写入文件,方便下次调用
 41             if self.engine == None:
 42                 print("failed building engine!")
 43                 return
 44             engineStr = self.engine.serialize()
 45             f = open(trtFilePath, 'wb')
 46             f.write(engineStr)
 47             f.close()
 48             print("succeeded building engine!")
 49                 
 50         self.context = self.engine.create_execution_context()               # 创建 CUDA 上下文和流
 51         self.stream = cuda.Stream()
 52                 
 53     def __del__(self):
 54         self.context = None
 55         self.engine  = None
 56         ld.close(self.h5f)
 57   
 58     def create_engine(self):                                                # 构造引擎
 59         self.builder = trt.Builder(self.logger)
 60         self.builder.max_batch_size     = 16
 61         self.builder.max_workspace_size = 1 << 30
 62         self.builder.fp16_mode          = self.dataType == 'float16'
 63         self.builder.int8_mode          = self.dataType == 'int8'
 64         self.network                    = self.builder.create_network()
 65         self.builder.strict_type_constraints = True        
 66                
 67         h0 = self.network.add_input("h0", trt.DataType.FLOAT, (1,) + inputSize) # 强制 N 为 1,多的数据堆在更高维度上
 68         
 69         #...                                                                # 中间层
 70         
 71         self.network.mark_output(h0.get_output(0))                          # 标记输出层
 72 
 73         if self.dataType == 'int8':                                         # int8 需要额外的校正,放到 builder 中
 74             self.builder.int8_calibrator = calibrator.MyCalibrator(calibCount, (self.batchSize,) + inputSize, calibDataPath, cacheFile)
 75 
 76         self.engine = self.builder.build_cuda_engine(self.network)          # 创建引擎(最容易失败的地方,返回构造函数后要检查是否成功)
 77     
 78     def infer(self, hInPart, dIn, dOut, hOut):                              # 推理
 79         cuda.memcpy_htod_async(dIn, hInPart, self.stream)
 80         self.context.execute_async(len(hInPart), [int(dIn), int(dOut)], self.stream.handle)
 81         cuda.memcpy_dtoh_async(hOut, dOut, self.stream)            
 82         self.stream.synchronize()
 83                
 84 def predict(hIn, batchSize, dataType):    
 85     predictor = TrtPredictor(batchSize, dataType)                           # 构造一个预测器
 86     
 87     dIn  = cuda.mem_alloc(hIn[0].nbytes * batchSize)                        # 准备主机和设备内存
 88     hOut = np.empty((batchSize,) + tuple(predictor.engine.get_binding_shape(1)), dtype = np.float32)
 89     dOut = cuda.mem_alloc(hOut.nbytes)                                      # dOut 和 hOut 的大小一定是相同的
 90     res=[]
 91     for i in range(0, len(hIn), batchSize):                                 # 分 batch 喂入数据
 92         predictor.infer(hIn[i:i+batchSize], dIn, dOut, hOut)                    
 93         res.append( hOut )
 94     
 95     return res
 96 
 97 if __name__ == "__main__":                                                  # main 函数负责管理 cuda.Device 和 cuda.Context
 98     _ = os.system("clear")
 99     batchSize = int(sys.argv[1])    if len(sys.argv) > 1 and sys.argv[1].isdigit()                         else 1
100     dataType  = sys.argv[2]         if len(sys.argv) > 2 and sys.argv[2] in ['float32', 'float16', 'int8'] else 'float32'
101     DEBUG     = int(sys.argv[3])>0  if len(sys.argv) > 3 and sys.argv[3].isdigit()                         else False
102     if DEBUG:                                                               # 清除建好的 engine 和 校正缓存,重头开始建立                                
103         oldEngineEAndCache = glob(tempPath+"*.trt") + glob(tempPath+"*.cache")
104         [ os.remove(oldEngineEAndCache[i]) for i in range(len(oldEngineEAndCache))]
105     print( "%s, start! GPU =  %s, batchSize = %2d, dataType  = %s" %( dt.now(), cuda.Device(iGpu).name(), batchSize, dataType ) )    
106     
107     inputData = loadData(testDataPath)                                      # 读取数据
108     oF = open(outputFile, 'w')
109     cuda.Device(iGpu).make_context()
110 
111     res = predict(inputData, batchSize, dataType)
112     for i in range(len(res)):
113         print( "%d -> %s" % (i,res[i]) )
114         oF.write(res[i] + '\n')
115         
116     oF.close()
117     cuda.Context.pop()
118     print( "%s, finish!" %(dt.now()) )

● 代码,矫正器 calibrator.py。核心思想是,手写一个数据生成器供 TensorRT 调用,每次从校正数据集中抽取 batchSize 那么多的数据,计算工作全部由 TensorRT 完成

 1 import os
 2 import numpy as np
 3 import tensorrt as trt
 4 import pycuda.driver as cuda
 5 import pycuda.autoinit
 6 
 7 class MyCalibrator(trt.IInt8EntropyCalibrator2):
 8     def __init__(self, calibCount, inputShape, calibDataPath, cacheFile):
 9         trt.IInt8EntropyCalibrator2.__init__(self)                                              # 基类默认构造函数                                        
10         self.calibCount     = calibCount                
11         self.shape          = inputShape
12         self.calibDataSet   = self.laodData(calibDataPath)                                      # 需要自己实现一个读数据的函数
13         self.cacheFile      = cacheFile
14         self.calibData      = np.zeros(self.shape, dtype=np.float32)        
15         self.dIn            = cuda.mem_alloc(trt.volume(self.shape) * trt.float32.itemsize)     # 准备好校正用的设备内存      
16         self.oneBatch       = self.batchGenerator()
17             
18     def batchGenerator(self):                                                                   # calibrator 的核心,一个提供数据的生成器
19         for i in range(self.calibCount):
20             print("> calibration ", i)
21             self.calibData = np.random.choice(self.calibDataSet, self.shape[0], replace=False)  # 随机选取数据 
22             yield np.ascontiguousarray(self.calibData, dtype=np.float32)                        # 调整数据格式后抛出   
23     
24     def get_batch_size(self):                                                                   # TensorRT 会调用,不能改函数名
25         return self.shape[0]
26 
27     def get_batch(self, names):                                                                 # TensorRT 会调用,不能改函数名,老版本 TensorRT 的输入参数个数可能不一样
28         try:
29             data = next(self.oneBatch)                                                          # 生成下一组校正数据,拷贝到设备并返回设备地址,否则退出
30             cuda.memcpy_htod(self.dIn, data)
31             return [int(self.dIn)]
32         except StopIteration:
33             return None
34 
35     def read_calibration_cache(self):                                                           # TensorRT 会调用,不能改函数名
36         if os.path.exists(self.cacheFile):
37             print( "cahce file: %s" %(self.cacheFile) )
38             f = open(self.cacheFile, "rb")
39             cache = f.read()
40             f.close()
41             return cache              
42             
43     def write_calibration_cache(self, cache):                                                   # TensorRT 会调用,不能改函数名
44         print( "cahce file: %s" %(self.cacheFile) )
45         f = open(self.cacheFile, "wb")
46         f.write(cache)
47         f.close()

 

▶ 我的程序在 TensorRT 5 中 float32 和 float16 一切正常,int8 无法正确计算。具体表现为:正确加载 calibrator 调用,部分中间层计算结果与 float32 一模一样(二进制位层次上的相同,显然是采用了 float32 代替进行计算了),部分层所有计算结果与 float32 有分歧(10-2 ~ 10-3 量级上的),在之后多层计算中误差会逐渐放大,最终计算结果与 float32 大相径庭。更新 TensorRT 6 之后问题消失,int8 也能计算正确结果并获得加速。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM