ResNet50优化案例:使用Blade优化基于TensorFlow的ResNet50模型

ResNet50作为一个广泛应用的经典结构网络,其优化在多种推理部署场景中都具有很高的实用价值。本文介绍如何使用Blade优化基于TensorFlowResNet50模型。

背景信息

残差网络ResNet(Residual Network)作为计算机视觉任务主干经典神经网络的一部分,堪称图像领域深度学习模型实战的"Hello World"。ResNet使用卷积层提取图像的特征,并通过引入残差块结构,解决了深层神经网络训练时的梯度消失和梯度爆炸问题,大幅提升了深度神经网络的训练效果。ResNet典型的网络有ResNet26、ResNet50ResNet101等。

使用限制

本文使用的环境需要满足以下版本要求:

  • 系统环境:Linux系统中使用Python 3.6及其以上版本、CUDA 10.0。

  • 框架:TensorFlow 1.15。

  • 推理优化工具:Blade 3.17.0及其以上版本。

操作流程

使用Blade优化基于TensorFlowResNet50模型的流程如下:

  1. 步骤一:准备工作

    安装支持TensorRT优化的Blade Wheel包,并下载ResNet50模型及测试数据。

  2. 步骤二:调用Blade优化模型

    调用blade.optimize接口优化模型。

  3. 步骤三:验证性能

    对优化前后的推理速度进行测试,从而验证优化报告中信息的正确性。

  4. 步骤四:加载运行优化后的模型

    集成Blade SDK,加载优化后的模型进行推理。

步骤一:准备工作

本案例中对ResNet50模型主要生效的优化项为TensorRT,因此您需要使用支持这一优化功能的Blade版本,即Blade 3.17.0及其以上版本。

  1. 安装对应TensorFlow 1.15.0CUDA 10.0版本的Blade。

    pip3 install pai_blade_gpu==3.17.0 -f https://pai-blade.oss-cn-zhangjiakou.aliyuncs.com/release/repo.html
  2. 下载TensorFlowResNet50模型及对应的测试数据。

    wget http://pai-blade.cn-hangzhou.oss.aliyun-inc.com/tutorials/tf_resnet50_v1.5.tar.gz
  3. 下载的压缩包tf_resnet50_v1.5.tar.gz中包含了一份Resnet50模型的frozen.pb及对应的不同Batch Size的测试数据,您需要手动解压该压缩包。

    tar zxvf tf_resnet50_v1.5.tar.gz

步骤二:调用Blade优化模型

  1. 从上一步中下载的TAR包中获取TensorFlow模型和测试数据。

    import os
    os.environ["CUDA_VISIBLE_DEVICES"] = "1"
    import numpy as np
    import time
    # import tf to import graphdef model.
    import tensorflow.compat.v1 as tf
    import blade
    from blade.model.tf_model import TfModel
    
    def _load_model_and_data():
        local_dir = "./tf_resnet50_v1.5/"
        model_path = os.path.abspath(os.path.join(local_dir, "frozen.pb"))
        data_path = os.path.abspath(os.path.join(local_dir, "test_bc1.npy"))
        graph_def = tf.GraphDef()
        with open(model_path, 'rb') as f:
            graph_def.ParseFromString(f.read())
        test_data = np.load(data_path, allow_pickle=True, encoding='bytes').item()
        return graph_def, test_data
    
    # Let's go!
    
    # Load resnet model and test data.
    graph_def, test_data = _load_model_and_data()
    print(test_data)
  2. 使用Blade调用TensorRT进行优化。

    TensorRT优化按照输入的不同分为以下两种类型:

    • 静态Shape优化

      适用于模型请求的Shape保持不变的情况。例如为了延时考虑,限制了只允许输入某个特定尺寸。示例如下所示。

      config = blade.Config()
      config.gpu_config.aicompiler.enable = False
      config.gpu_config.disable_fp16_accuracy_check = True 
      config.gpu_config.tensorrt.enable = True # TensorRT optimization is enabled by default, you can also use this param to disable if necessary.
      
      # Function `optimize` is the entrance to Blade's one-stop optimization.
      optimized_model_static, opt_spec_static, report = blade.optimize(
          graph_def,  # The original model, here is a TF GraphDef.
          'o1',  # Optimization level o1 or o2.
          device_type='gpu',  # Target device to run the optimized model.
          config=config,  # The blade.Config with more detailed optimizations configs
          outputs=['softmax_tensor'],  # Name of outputs nodes. You can provide them or blade will guess.
          test_data=[test_data]
      )
      print(report)

      系统输出类似如下的优化报告。

      {
        "software_context": [
          {
            "software": "tensorflow",
            "version": "1.15.0"
          },
          {
            "software": "cuda",
            "version": "10.0.0"
          }
        ],
        "hardware_context": {
          "device_type": "gpu",
          "microarchitecture": "T4"
        },
        "user_config": "",
        "diagnosis": {
          "model": "tmp_graph.pbtxt",
          "test_data_source": "user provided",
          "shape_variation": "dynamic",
          "message": "",
          "test_data_info": "input_tensor:0 shape: (1, 224, 224, 3) data type: float32"
        },
        "optimizations": [
          {
            "name": "Tf2TrtPlus",
            "status": "effective",
            "speedup": "3.37",
            "pre_run": "6.81 ms",
            "post_run": "2.02 ms"
          },
          {
            "name": "TfStripUnusedNodes",
            "status": "effective",
            "speedup": "na",
            "pre_run": "na",
            "post_run": "na"
          },
          {
            "name": "TfFoldConstants",
            "status": "effective",
            "speedup": "na",
            "pre_run": "na",
            "post_run": "na"
          }
        ],
        "overall": {
          "baseline": "6.98 ms",
          "optimized": "2.11 ms",
          "speedup": "3.31"
        },
        "model_info": {
          "input_format": "frozen_pb"
        },
        "compatibility_list": [
          {
            "device_type": "gpu",
            "microarchitecture": "T4"
          }
        ],
        "model_sdk": {}
      }

      在优化过程中,由于没有提供更多额外的优化选项,因此启动静态Shape优化,此Shape选用test_data的尺寸,从上述的优化报告可以看出Tf2TrtPlus优化项生效。 在静态Shape优化生效情况下,如果模型Inference时输入的Shape和优化时提供的不一致,则会FallbackTensorFlow原始图执行,执行的效率会大幅度降低。

      上述优化结果仅为本示例的测试结果,您的优化效果以实际为准。关于优化报告的字段详情请参见优化报告

    • 动态Shape优化

      如果您部署的服务支持动态Batching功能,则通常会限制服务端将某个时间段内收到的请求组合成一个Batch。由于在一个较短时间段内收到的请求数是不确定的,因此最后组成的Batch大小可能是变化的。为了支持这种变化Shape的优化,Blade集成了TensorRT的动态Shape优化能力。您只需要在TensorRTConfig中提供一个额外配置,即可获取动态输入优化,示例如下。关于TensorRTConfig的参数配置请参见下文的附录:TensorRTConfig

      config_dynamic = blade.Config()
      config_dynamic.gpu_config.aicompiler.enable = False
      config_dynamic.gpu_config.disable_fp16_accuracy_check = True 
      config_dynamic.gpu_config.tensorrt.enable = True 
      config_dynamic.gpu_config.tensorrt.dynamic_tuning_shapes = {
          "min": [1, 224, 224, 3],
          "opts": [
              [1, 224, 224, 3],
              [2, 224, 224, 3],
              [4, 224, 224, 3],
              [8, 224, 224, 3],
          ],
          "max": [8, 224, 224, 3],
      }
      
      # Call Blade's one-stop optimization, with a dynamic shapes setting for TensorRT optimization.
      optimized_model_dynamic, opt_spec_dynamic, report = blade.optimize(
          graph_def,  
          'o1',  
          device_type='gpu',  
          config=config_dynamic,
          outputs=['softmax_tensor'],  
          test_data=[test_data]
      )
      print(report)
      
      with tf.gfile.FastGFile('optimized_model_dynamic.pb', mode='wb') as f:
          f.write(optimized_model_dynamic.SerializeToString())

      系统输出类似如下的优化报告。

      {
        "software_context": [
          {
            "software": "tensorflow",
            "version": "1.15.0"
          },
          {
            "software": "cuda",
            "version": "10.0.0"
          }
        ],
        "hardware_context": {
          "device_type": "gpu",
          "microarchitecture": "T4"
        },
        "user_config": "",
        "diagnosis": {
          "model": "tmp_graph.pbtxt",
          "test_data_source": "user provided",
          "shape_variation": "dynamic",
          "message": "",
          "test_data_info": "input_tensor:0 shape: (1, 224, 224, 3) data type: float32"
        },
        "optimizations": [
          {
            "name": "Tf2TrtPlus",
            "status": "effective",
            "speedup": "3.96",
            "pre_run": "7.98 ms",
            "post_run": "2.02 ms"
          },
          {
            "name": "TfStripUnusedNodes",
            "status": "effective",
            "speedup": "na",
            "pre_run": "na",
            "post_run": "na"
          },
          {
            "name": "TfFoldConstants",
            "status": "effective",
            "speedup": "na",
            "pre_run": "na",
            "post_run": "na"
          }
        ],
        "overall": {
          "baseline": "7.87 ms",
          "optimized": "2.52 ms",
          "speedup": "3.12"
        },
        "model_info": {
          "input_format": "frozen_pb"
        },
        "compatibility_list": [
          {
            "device_type": "gpu",
            "microarchitecture": "T4"
          }
        ],
        "model_sdk": {}
      }

      上述优化报告与静态Shape优化的报告类似。您需要注意的是只要模型Inference时输入的Shape落在优化时提供的minmax区间内,都会使用TensorRT优化,如果模型Inference时输入的Shape落在该区间外,则会FallbackTensorFlow的原始子图执行。

      上述优化结果仅为本示例的测试结果,您的优化效果请以实际为准。关于优化报告的字段详情请参见优化报告

步骤三:验证性能

优化完成后,通过Python脚本对优化报告的信息进行验证。

import time
with tf.Session(config=TfModel.new_session_config()) as sess, opt_spec_dynamic:
    sess.graph.as_default()
    tf.import_graph_def(optimized_model_dynamic, name="")

    # Warmup!
    for i in range(0, 100):
        sess.run(['softmax_tensor:0'], test_data)

    # Benchmark!
    num_runs = 1000
    start = time.time()
    for i in range(0, num_runs):
        sess.run(['softmax_tensor:0'], test_data)
    elapsed = time.time() - start
    rt_ms = elapsed / num_runs * 1000.0

    # Show the result!
    print("Latency of optimized model: {:.2f}".format(rt_ms))

系统输出类似如下的结果。

Latency of optimized model: 2.26

从上述结果可以看到优化之后的模型性能2.26 ms与优化报告中"overall"下的 "optimized": "2.52 ms"基本一致,测试使用的数据是在动态尺寸优化的范围内的,因此优化生效。上述优化结果仅为本示例的测试结果,您的优化效果请以实际为准。

步骤四:加载运行优化后的模型

完成验证后,您需要对模型进行部署,Blade提供了PythonC++两种运行时SDK供您集成。关于C++的SDK使用方法请参见使用SDK部署TensorFlow模型推理,下文主要介绍如何使用Python SDK部署模型。

  1. 可选:在试用阶段,您可以设置如下的环境变量,防止因为鉴权失败而程序退出。
    export BLADE_AUTH_USE_COUNTING=1
  2. 获取鉴权。
    export BLADE_REGION=<region>
    export BLADE_TOKEN=<token>
    您需要根据实际情况替换以下参数:
    • <region>:Blade支持的地域,需要加入Blade用户群获取该信息,用户群的二维码详情请参见获取Token
    • <token>:鉴权Token,需要加入Blade用户群获取该信息,用户群的二维码详情请参见获取Token
  3. 加载运行优化后的模型。

    除了增加一行import blade.runtime.tensorflow,您无需为Blade的接入编写额外代码,即原有的推理代码无需任何改动。下面以刚才动态尺寸TensorRT优化得到的模型为例进行演示。

    import tensorflow.compat.v1 as tf
    import blade.runtime.tensorflow
    
    infer_data = np.load('./tf_resnet50_v1.5/test_bc1.npy', allow_pickle=True, encoding='bytes').item()
    # optimized model produced by blade.optimize
    model_path = './optimized_model_dynamic.pb'
    
    graph_def = tf.GraphDef()
    with open(model_path, 'rb') as f:
        graph_def.ParseFromString(f.read())
        
    with tf.Session() as sess:
        sess.graph.as_default()
        tf.import_graph_def(graph_def, name="")
    
        print(sess.run(['softmax_tensor:0'], infer_data))

附录:TensorRTConfig

由于TensorRT优化的特殊性,Bladeconfig专门为其预留了一个特殊的配置选项,帮助您更好的配置使用TensorRT优化以应对不同的部署需求。

class TensorRTConfig():
    def __init__(self) -> None:
        self.enable = True
        self.dynamic_tuning_shapes: Dict[str, List[List[Any]]] = dict()
        ......

此处重点介绍TensorRTConfig中的关键参数:

  • enable:Boolean参数,作为开关参数控制TensorRT优化是否开启。默认情况下,在GPU设备上优化时,会尝试TensorRT优化。

  • dynamic_tuning_shapes:服务端发送的请求可能包含多种不同尺寸的输入,为了对不同尺寸的输入获得较好的优化效果,Blade提供了动态尺寸优化功能,您需要设置dynamic_tuning_shapes字典作为优化的辅助参数。

    dynamic_tuning_shapes字典中包含了minmaxopts三个Key值。其中minmax对应的Value类型为List[List[int]],对应为模型输入的一组最小尺寸和一组最大尺寸。opts对应的Value类型为List[List[List[int]]],对应多组模型输入的尺寸。

    说明

    opts对应的多组模型输入的尺寸必须在最小尺寸和最大尺寸之间才能保证优化的成功,否则TensorRT优化会报错Dim value in \'opts\' is not between min_dim and max_dim

    以下是一个动态尺寸优化的设置示例,您可以看到opts对应的多组尺寸大小都是在最小尺寸和最大尺寸的区间之内。

    {
     "min": [[1, 3, 224, 224], [1, 50]],    # lower bound of the dynamic range of each inputs.
     "opts": [
         [[1, 3, 512, 512], [1, 60]],
         [[1, 3, 320, 320], [1, 55]],
      ], # shapes that should be optimized like static shapes
      "max": [[1, 3, 1024, 1024], [1, 70]]   # upper bound of the dynamic range.
    }