すべてのプロダクト
Search
ドキュメントセンター

Platform For AI:PAI-BladeとTorchScriptのカスタムC ++ 演算子を使用してRetinaNetモデルを最適化する

最終更新日:Jul 22, 2024

オブジェクト検出モデルの後処理効率を向上させるために、TorchScriptのカスタムC ++ 演算子を使用して、以前はPythonで実現されていた後処理ネットワークを構築できます。 次に、モデルをエクスポートし、Machine Learning Platform for AI (PAI)-Bladeを使用してモデルを最適化できます。 このトピックでは、TorchScriptのカスタムC ++ 演算子を使用してオブジェクト検出モデルの後処理ネットワークを構築し、PAI-Bladeを使用してモデルを最適化する方法について説明します。

背景情報

RetinaNetは、1ステージ領域ベースの畳み込みニューラルネットワーク (R-CNN) タイプの検出ネットワークです。 RetinaNetの基本構造は、バックボーン、複数のサブネットワーク、および非最大抑制 (NMS) で構成されています。 NMSは後処理アルゴリズムである。 RetinaNetは多くのトレーニングフレームワークで実装されています。 Detectron2は、RetinaNetを使用する典型的なトレーニングフレームワークです。 Detectron2のscripting_with_instancesメソッドを呼び出してRetinaNetモデルをエクスポートし、PAI-Bladeを使用してモデルを最適化できます。 詳細については、「PAI-Bladeを使用したDetectron2フレームワークのRetinaNetモデルの最適化」をご参照ください。

ほとんどの場合、オブジェクト検出モデルの後処理ネットワークのプログラムロジックは、ボックスnmsを計算し、フィルタリングするロジックを含みます。 Pythonを使用して後処理ネットワークを構築すると、ロジックを実装する効率は低くなります。 代わりに、TorchScriptカスタムC ++ 演算子を使用して、後処理ネットワークを構築できます。 次に、モデルをエクスポートし、PAI-Bladeを使用してモデルを最適化できます。

制限事項

このトピックの手順で使用する環境は、次のバージョン要件を満たす必要があります。

  • システム環境: Python 3.6以降、GNUコンパイラコレクション (GCC) 5.4以降、NVIDIA Tesla T4、CUDA 10.2、およびcuDNN 8.0.5.39

  • フレームワーク: PyTorch 1.8.1以降、およびDetectron2 0.4.1以降

  • 推論最適化ツール: PAI-Blade V3.16.0以降

手順

PAI-BladeおよびカスタムC ++ 演算子を使用してRetinaNetモデルを最適化するには、次の手順を実行します。

  1. 手順1: TorchScriptカスタムC ++ 演算子を含むPyTorchモデルを作成する

    TorchScriptカスタムC ++ 演算子を使用して、RetinaNetモデルの後処理ネットワークを構築します。

  2. ステップ2: TorchScriptモデルのエクスポート

    Detectron2のTracingAdapterまたはscripting_with_instancesメソッドを呼び出して、RetinaNetモデルをエクスポートします。

  3. ステップ3: PAI-Bladeを使用してモデルを最適化

    モデルを最適化し、最適化されたモデルを保存するには、blade.optimizeメソッドを呼び出します。

  4. ステップ4: 最適化モデルのロードと実行

    最適化されたモデルがパフォーマンステストに合格し、期待を満たす場合は、推論のために最適化されたモデルを読み込みます。

ステップ1: TorchScriptカスタムC ++ 演算子を含むPyTorchモデルを作成する

PAI-Bladeは、TorchScriptカスタムC ++ 演算子とシームレスに統合されています。 この手順では、演算子を使用してRetinaNetモデルの後処理ネットワークを構築する方法について説明します。 TorchScriptカスタムC ++ 演算子の詳細については、「拡張TORCHSCRIPT WITH custom C ++ operators」をご参照ください。 このトピックでは、RetinaNetモデルの後処理ネットワークのプログラムロジックは、NVIDIAのオープンソースコミュニティから提供されています。 詳細については、「retinanet-examples」をご参照ください。 この例では、コアコードを使用して、カスタム演算子を開発および実装する方法を示します。

  1. サンプルコードをダウンロードし、ダウンロードしたパッケージを解凍します。

    wget -nv https://pai-blade.oss-cn-zhangjiakou.aliyuncs.com/tutorials/retinanet_example/retinanet-examples.tar.gz -O retinanet-examples.tar.gz
    tar xvfz retinanet-examples.tar.gz 1>/dev/null 
  2. コンパイルTorchScriptカスタムC ++ 演算子。

    PyTorchは、カスタム演算子をコンパイルするための3つのメソッドを提供します。CMakeによる構築、JITコンパイルによる構築、Setuptoolsによる構築です。 詳細については、「カスタムC ++ オペレーターによるTORCHSCRIPTの拡張」をご参照ください。 これらの3つのコンパイル方法は、さまざまなシナリオに適しています。 必要に応じてメソッドを選択できます。 この例では、操作を簡略化するために、JITコンパイル方法を使用しています。 次のサンプルコードに例を示します。

    import torch.utils.cpp_extension
    import os
    codebase="retinanet-examples"
    sources=['csrc/extensions.cpp',
             'csrc/cuda/decode.cu',
             'csrc/cuda/nms.cu',]
    sources = [os.path.join(codebase,src) for src in sources]
    torch.utils.cpp_extension.load(
        name="custom",
        sources=sources,
        build_directory=codebase,
        extra_include_paths=['/usr/local/TensorRT/include/', '/usr/local/cuda/include/', '/usr/local/cuda/include/thrust/system/cuda/detail'],
        extra_cflags=['-std=c++14', '-O2', '-Wall'],
        extra_cuda_cflags=[
            '-std=c++14', '--expt-extended-lambda',
            '--use_fast_math', '-Xcompiler', '-Wall,-fno-gnu-unique',
            '-gencode=arch=compute_75,code=sm_75',],
        is_python_module=False,
        with_cuda=True,
        verbose=False,
    )

    上記のコードを実行すると、コンパイル後に生成されたcustom.soファイルがretinanet-examplesディレクトリに保存されます。

  3. カスタムC ++ 演算子を使用して、RetinaNetモデルの後処理ネットワークを構築します。

    簡単にするために、RetinaNet.forwardadapter_forwardに置き換えます。 adapter_forwardは、decode_cudaおよびnms_cudaカスタムC ++ 演算子を使用して、後処理ネットワークを構築します。 次のサンプルコードに例を示します。

    import os
    import torch
    from typing import Tuple, Dict, List, Optional
    codebase="retinanet-examples"
    torch.ops.load_library(os.path.join(codebase, 'custom.so'))
    
    decode_cuda = torch.ops.retinanet.decode
    nms_cuda = torch.ops.retinanet.nms
    
    # The main part of the code written for this method is the same as the code of RetinaNet.forward. However, the program logic of the post-processing network is realized by using the decode_cuda and nms_cuda custom operators. 
    def adapter_forward(self, batched_inputs: Tuple[Dict[str, torch.Tensor]]):
        images = self.preprocess_image(batched_inputs)
        features = self.backbone(images.tensor)
        features = [features[f] for f in self.head_in_features]
        cls_heads, box_heads = self.head(features)
        cls_heads = [cls.sigmoid() for cls in cls_heads]
        box_heads = [b.contiguous() for b in box_heads]
    
        # Build the post-processing network. 
        strides = [images.tensor.shape[-1] // cls_head.shape[-1] for cls_head in cls_heads]
        decoded = [
            decode_cuda(
                cls_head,
                box_head,
                anchor.view(-1),
                stride,
                self.test_score_thresh,
                self.test_topk_candidates,
            )
            for stride, cls_head, box_head, anchor in zip(
                strides, cls_heads, box_heads, self.cell_anchors
            )
        ]
    
        # Implement non-maximum suppression. 
        decoded = [torch.cat(tensors, 1) for tensors in zip(decoded[0], decoded[1], decoded[2])]
        return nms_cuda(decoded[0], decoded[1], decoded[2], self.test_nms_thresh, self.max_detections_per_image)
    
    from detectron2.modeling.meta_arch import retinanet
    
    # Replace RetinaNet.forward with adapter_forward. 
    retinanet.RetinaNet.forward = adapter_forward

ステップ2: TorchScriptモデルのエクスポート

Detectron2は、Facebook AI Research (FAIR) によって構築されたオープンソースのトレーニングフレームワークです。 Detectron2は、オブジェクト検出およびセグメンテーションアルゴリズムを実装し、柔軟性、拡張性、および構成可能です。 Detectron2の柔軟性により、TorchScriptモデルを通常の方法でエクスポートすると、エクスポートが失敗したり、間違ったエクスポート結果が返されたりする可能性があります。 TorchScriptモデルを確実にデプロイできるように、Detectron2ではTracingAdapterまたはscripting_with_instancesメソッドを呼び出してTorchScriptモデルをエクスポートできます。 詳細については、「Usage」をご参照ください。

PAI-Bladeを使用すると、すべてのタイプのTorchScriptモデルをインポートできます。 この例では、scripting_with_instancesメソッドを使用して、TorchScriptモデルをエクスポートする方法を説明します。 次のサンプルコードに例を示します。

import torch
import numpy as np

from torch import Tensor
from torch.testing import assert_allclose

from detectron2 import model_zoo
from detectron2.export import scripting_with_instances
from detectron2.structures import Boxes
from detectron2.data.detection_utils import read_image

# Call the scripting_with_instances method to export the RetinaNet model. 
def load_retinanet(config_path):
    model = model_zoo.get(config_path, trained=True).eval()
    # Set a new cell_anchors attributes to PyTorch model.
    model.cell_anchors = [c.contiguous() for c in model.anchor_generator.cell_anchors]
    fields = {
        "pred_boxes": Boxes,
        "scores": Tensor,
        "pred_classes": Tensor,
    }
    script_model = scripting_with_instances(model, fields)
    return model, script_model

# Download a sample image. 
# wget http://images.cocodataset.org/val2017/000000439715.jpg -q -O input.jpg
img = read_image('./input.jpg')
img = torch.from_numpy(np.ascontiguousarray(img.transpose(2, 0, 1)))

# Run the model and compare the latency before and after you export the model. 
pytorch_model, script_model = load_retinanet("COCO-Detection/retinanet_R_50_FPN_3x.yaml")
with torch.no_grad():
    batched_inputs = [{"image": img.float()}]
    pred1 = pytorch_model(batched_inputs)
    pred2 = script_model(batched_inputs)

assert_allclose(pred1[0], pred2[0])

ステップ3: PAI-Bladeを使用してモデルを最適化する

  1. PAI-bladeのBlade. optimizeメソッドを呼び出します。

    モデルを最適化するには、blade.optimizeメソッドを呼び出します。 次のサンプルコードに例を示します。 blade.optimizeメソッドの詳細については、「PyTorchモデルの最適化」をご参照ください。

    import os
    import blade
    import torch
    
    # Load the dynamic-link library of custom C++ operators. 
    codebase="retinanet-examples"
    torch.ops.load_library(os.path.join(codebase, 'custom.so'))
    
    blade_config = blade.Config()
    blade_config.gpu_config.disable_fp16_accuracy_check = True
    
    test_data = [(batched_inputs,)] # The test data used for a PyTorch model is a list of tuples of tensors. 
    
    with blade_config:
        optimized_model, opt_spec, report = blade.optimize(
        script_model,  # The TorchScript model exported in the previous step. 
        'o1',  # The optimization level of PAI-Blade. In this example, the optimization level is o1. 
        device_type='gpu',  # The type of the device on which the model is run. In this example, the device is GPU. 
        test_data=test_data,  # The given set of test data, which facilitates optimization and testing. 
        )
  2. 最適化レポートを表示し、最適化モデルを保存します。

    PAI-Bladeを使用して最適化されたモデルは、依然としてTorchScriptモデルです。 最適化が完了したら、次のコードを実行して最適化レポートを表示し、最適化モデルを保存できます。

    # Display the optimization report. 
    print("Report: {}".format(report))
    # Save the optimized model. 
    torch.jit.save(script_model, 'script_model.pt')
    torch.jit.save(optimized_model, 'optimized.pt')

    次のサンプルコードは、最適化レポートの例を示しています。 レポートのパラメーターの詳細については、「最適化レポート」をご参照ください。

    Report: {
      "software_context": [
        {
          "software": "pytorch",
          "version": "1.8.1+cu102"
        },
        {
          "software": "cuda",
          "version": "10.2.0"
        }
      ],
      "hardware_context": {
        "device_type": "gpu",
        "microarchitecture": "T4"
      },
      "user_config": "",
      "diagnosis": {
        "model": "unnamed.pt",
        "test_data_source": "user provided",
        "shape_variation": "undefined",
        "message": "Unable to deduce model inputs information (data type, shape, value range, etc.)",
        "test_data_info": "0 shape: (3, 480, 640) data type: float32"
      },
      "optimizations": [
        {
          "name": "PtTrtPassFp16",
          "status": "effective",
          "speedup": "3.92",
          "pre_run": "40.72 ms",
          "post_run": "10.39 ms"
        }
      ],
      "overall": {
        "baseline": "40.64 ms",
        "optimized": "10.41 ms",
        "speedup": "3.90"
      },
      "model_info": {
        "input_format": "torch_script"
      },
      "compatibility_list": [
        {
          "device_type": "gpu",
          "microarchitecture": "T4"
        }
      ],
      "model_sdk": {}
    }
  3. 元のモデルと最適化されたモデルのパフォーマンスをテストします。

    次のサンプルコードは、モデルのパフォーマンスをテストする方法の例を示しています。

    import time
    
    @torch.no_grad()
    def benchmark(model, inp):
        for i in range(100):
            model(inp)
        torch.cuda.synchronize()
        start = time.time()
        for i in range(200):
            model(inp)
        torch.cuda.synchronize()
        elapsed_ms = (time.time() - start) * 1000
        print("Latency: {:.2f}".format(elapsed_ms / 200))
    
    # Test the latency of the original model. 
    benchmark(script_model, batched_inputs)
    # Test the latency of the optimized model. 
    benchmark(optimized_model, batched_inputs)

    このパフォーマンステストの次の結果は参照のためです:

    Latency: 40.65
    Latency: 10.46

    前述の結果は、両方のモデルが200回実行された後、元のモデルの平均待ち時間は40.65 ms、最適化されたモデルの平均待ち時間は10.46 msであることを示しています。

ステップ4: 最適化されたモデルをロードして実行する

  1. オプション: 試用期間中に、次の環境変数設定を追加して、認証の失敗によるプログラムの予期しない停止を防止します。

    export BLADE_AUTH_USE_COUNTING=1
  2. PAI-Bladeを使用するように認証されます。

    export BLADE_REGION=<region>
    export BLADE_TOKEN=<token> 

    ビジネス要件に基づいて、次のパラメーターを設定します。

    • <region>: PAI-Bladeを使用するリージョンです。 PAI-BladeユーザーのDingTalkグループに参加して、PAI-Bladeを使用できるリージョンを取得できます。

    • <token>: PAI-Bladeを使用するために必要な認証トークン。 PAI-BladeユーザーのDingTalkグループに参加して、認証トークンを取得できます。

  3. 最適化されたモデルをロードして実行します。

    PAI-Bladeを使用して最適化されたモデルは、依然としてTorchScriptモデルです。 したがって、環境を変更せずに最適化モデルをロードできます。

    import blade.runtime.torch
    import detectron2
    import torch
    import numpy as np
    import os
    from detectron2.data.detection_utils import read_image
    from torch.testing import assert_allclose
    
    # Load the dynamic-link library of custom C++ operators. 
    codebase="retinanet-examples"
    torch.ops.load_library(os.path.join(codebase, 'custom.so'))
    
    script_model = torch.jit.load('script_model.pt')
    optimized_model = torch.jit.load('optimized.pt')
    
    img = read_image('./input.jpg')
    img = torch.from_numpy(np.ascontiguousarray(img.transpose(2, 0, 1)))
    
    # Run the model and compare the latency before and after you export the model. 
    with torch.no_grad():
        batched_inputs = [{"image": img.float()}]
        pred1 = script_model(batched_inputs)
        pred2 = optimized_model(batched_inputs)
    
    assert_allclose(pred1[0], pred2[0], rtol=1e-3, atol=1e-2)