模型部署时的调试技巧,debug方法

调试法则

  1. 善用python工作流,联合python/cpp一起进行问题调试。因为python的工作流比较完善,比如有pycharm等。总之遇到一个问题时不要想着用c++调试,尽可能将工作流程转到python上,这样效率比较高,比如在终端打印由于内容太长显示不完全,这时候就可以利用pycharm调试。尤其是深度学习的东西,都是张量矩阵居多,在python上分析问题效率更高。比如onnx模型推理在python工作流上的复现就可以用onnxruntime实现推理。
  2. 去掉前后处理情况下,确保onnx与pytorch结果一致,排除所有因素。这一点通常是能够保证的。例如都输入全为5的张量,必须使得输出之间差距小于1e-4,确保中间没有例外情况发生
  3. 推理时的预处理和pytorch中的预处理一般很难保证完全一样(比如pytorch中用的PIL,推理时是opencv),考虑把python的预处理结果储存文件,c++加载文件后推理,得到的结果应该差异小于1e-4
  4. 考虑把python模型推理后的结果储存为文件,先用numpy写一遍后处理。然后用c++复现。总之是优先在python上做实验调通,理解清楚,再上到c++,这时其实就是个代码转换的过程。解决问题的效率就会变得很高。
  5. 如果出现bug,应该把tensor从c++中储存文件后,python加载文件后,放到python上调试查看。避免在c++中debug。

python中加载Tensor和储存Tensor的代码

# 以下代码是python中加载Tensor
 import numpy as np

 def load_tensor(file):
     
     with open(file, "rb") as f:
         binary_data = f.read()

     magic_number, ndims, dtype = np.frombuffer(binary_data, np.uint32, count=3, offset=0)
     assert magic_number == 0xFCCFE2E2, f"{
      
      file} not a tensor file."
     
     dims = np.frombuffer(binary_data, np.uint32, count=ndims, offset=3 * 4)

     if dtype == 0:
         np_dtype = np.float32
     elif dtype == 1:
         np_dtype = np.float16
     else:
         assert False, f"Unsupport dtype = {
      
      dtype}, can not convert to numpy dtype"
         
     return np.frombuffer(binary_data, np_dtype, offset=(ndims + 3) * 4).reshape(*dims)


 def save_tensor(tensor, file):

     with open(file, "wb") as f:
         typeid = 0
         if tensor.dtype == np.float32:
             typeid = 0
         elif tensor.dtype == np.float16:
             typeid = 1
         elif tensor.dtype == np.int32:
             typeid = 2
         elif tensor.dtype == np.uint8:
             typeid = 3

         head = np.array([0xFCCFE2E2, tensor.ndim, typeid], dtype=np.uint32).tobytes()
         f.write(head)
         f.write(np.array(tensor.shape, dtype=np.uint32).tobytes())
         f.write(tensor.tobytes())

部署时实现一个模型的流程

  1. 先把代码跑通predict(模型的predict),单张图作为输入。屏蔽一切与该目标不符的东西,可以修改删除任意多余的东西
  2. 自行写一个新的python程序,简化predict的流程,掌握predict所需要的最小依赖和最少代码。比如原始的predict流程可能依赖于dataset,dataloader,config等等,所以这时候应该考虑把它简化掉,因为推理时只考虑先用单张图片作为输入,所以简化的目的就是只用几行代码把predict写出来。这里的predict指的是模型的predict,并没有包含前后处理
  3. 如果第二步比较困难,则可以考虑,直接在pred = model(x)这个步骤上研究,例如直接在此处写torch.onnx.export(model, (pred,) … )。或者直接把pred的结果储存下来研究,等等
  4. 把前处理、后处理分析出来并实现一个python最简化版本。
  5. 利用简化版本进行debug、理解分析。然后考虑预处理后处理的合理安排,例如是否可以把部分后处理放到onnx中
  6. 导出onnx,在c++上先复现预处理部分(这时先不要复现后处理),使得其结果接近(因为大多时候不能得到一样的结果)
  7. 把python上的pred结果储存后,使用c++读取并复现所需要的后处理部分。确保结果正确。(因为这样做就可以在每次debug我们写的后处理模块时,避免去等待tensorrt的编译推理,那样的话效率太低了。)
  8. 把前后处理与onnx对接起来,形成完整的推理

猜你喜欢

转载自blog.csdn.net/Rolandxxx/article/details/127814207