Google Cloudでの非同期ML推論

目次

  1. System Design
  2. Computing Service on Google Cloud
  3. Async ML Inference System Design

Google CloudでML推論のコストが下がるアーキテクチャ考えてみた

System Design

システム設計をする上で考慮することの例

  • 同期処理 or 非同期処理
  • WebAPI, Batch, Streaming ...etc

DBやネットワーク、セキュリティなど考えることは他にもあるが、

この発表ではコンピューティングに絞って考える

同期処理と非同期処理

同期処理

  • 処理結果が返ってくるまで次に進まない
  • 逐次処理されるのでワークフローはシンプルになる
  • 処理の待ち時間がボトルネックになることがある

非同期処理

  • 処理結果を待たずに次に進み、結果は後で取得する
  • 出力先が呼び出し元と別でも良い

同期処理と非同期処理

同期処理と非同期処理

PushとPull

Push

Server側で負荷調整がしにくい構成。負荷によってスケールアウトしても、間に合わず過負荷になり得る。Rate LimitやExponential Backoffを用いてリクエストを送りすぎないように工夫が求められる

Pull

Server側で負荷調整がしやすい構成。Serverが処理可能な状態の時にQueueにあるものから取り出して新しく処理を開始する

同期処理とPush型の問題点

  • 同期処理もPush型も準備できたサーバーがある前提。リクエストに備えて常時立ち上げることもあり、その分費用がかかる
  • 負荷によってスケールアウトし、新規にサーバーを起動することもあるが、プロビジョニングに時間がかかる。その間に既存のサーバーが過負荷になり不安定になる

特にGPUを使うようなコンテナイメージは数GB~あるのでスケールアウトに時間がかかる

同期処理とPush型の問題点

  • 同期処理もPush型も準備できたサーバーがある前提。リクエストに備えて常時立ち上げることもあり、その分費用がかかる
  • 負荷によってスケールアウトし、新規にサーバーを起動することもあるが、プロビジョニングに時間がかかる。その間に既存のサーバーが過負荷になり不安定になる

特にGPUを使うようなコンテナイメージは数GB~あるのでスケールアウトに時間がかかる

→非同期&PullのGPU推論アーキテクチャを考えてみた

Compute Service

Google Cloud で Computing 機能を有するサービス

  • Google Compute Engine(GCE)
  • Google Kubernets Engine(GKE)

  • Google App Engine(GAE)

  • Vertex AI series

  • Cloud Functions

  • Cloud Run

  • Cloud Run for Anthos

  • Cloud Run Jobs

  • Batch

  • Dataflow

Service Preemptible Auto Scale Batch Streaming WebAPI GPU
GCE
GKE
Cloud Run × ×
Cloud Run Jobs × × × ×
GAE × ×
Batch × ×
Dataflow ×
Vertex AI series ×
Cloud Functions × × ×

各サービスの星取表

(Anthos含む)

Preemptible Instance

Google Cloudの余っているリソースを割安で使うことのできるオプション。リソースが余ってない時は使えない。

プリエンプティブル VM インスタンスは、標準 VM の料金よりもはるかに低価格(60~91% 割引)で利用できます。ただし、他の VM に割り当てるためにコンピューティング容量を再利用する必要がある場合、Compute Engine はこのインスタンスを停止(プリエンプト)する可能性があります。プリエンプティブル インスタンスは、Compute Engine の余剰の容量を利用する機能であり、使用できるかどうかは利用状況に応じて変わります。

GPUとPreemptible Instance両方が使える次のサービスの活用を考えてみる

  • GCE
  • Batch
  • Dataflow

GKEはGPUのshared resouseで費用抑えたりもできるが、今回は対象外

Instance template: マシンのスペックやOS、起動時のスクリプトやコンテナイメージなどをあらかじめ指定しているVM

GCE Managed Instance Group(MIG)

Instance templateを条件によってスケールさせられる。最小は0台

  • CPU使用率、ロードバランサの状況、Cloud Monitoringの指標でスケール
  • Pub/Subの未確認メッセージの数でもスケールできる
  • schedulingもできる。平日昼間は多く立ち上げるみたいなことが可能
  • カスタム指標でスケールさせることができる
  • 複数の指標を組み合わせることもでき、その内の最大の数が採用される
  • GitHub Actionsのself-hostedでも使える

Batch

  • shell scriptやコンテナイメージを実行してバッチ処理を行う。マネージドなサービスでバッチ処理が終わったらインスタンスを終了してくれる。
  • 複数台で実行できる
  • GCSやFilestoreをマウントできる
  • networkやGPUの設定をする時はコンソールからはできず、gcloud CLIやWeb API, SDKから実行する必要がある
  • GPU環境の動作確認をしたい場合にCIに組み込むことができるかも。。

※WebUIから起動する時はdefaultという名前のVPCが必要

Dataflow

  • Apache BeamをGoogle Cloudの環境で動かしたのがDataflow
  • Beamはバッチ処理とストリーミング処理用のフレームワーク
  • CPU負荷やPub/Subのメッセージの状況でAuto scaleする
  • 実行グラフで時間がかかってる部分を特定できたり、実行・マシンごとのメトリクス集計、コスト計算機能がある
  • Streaming Engineのmin workerは1。0に設定できない
  • Preemptible Instanceはバッチ処理だけで利用できる

Dataflow MLでPyTorch, Tensorflow, ONNX, TensorRT, scikit-learnなどの推論をサポートしている。実行中にモデルの差し替えができる。高頻度でモデルが変わるシステムと相性がいい。

Dataflow MLを使った実験

CPU

  1. resnet101

    •  30分で完了、1.4$

  2. mobilenet v2

    • 22分で完了、0.5$

それぞれ50,000の画像を使って実験

GPU

  1. resnet101:

    • 60分で完了、0.5$

  2. mobilenet v2:

    • 60分で完了、0.05$

GPUを使った方が費用が安いが、時間がかかってる。GPUを使うコンテナイメージは大きいのでプロビジョニングに時間がかかる

Dataflow ML Example

def run(
    argv=None,
    model_class=None,
    model_params=None,
    save_main_session=True,
    device='CPU',
    test_pipeline=None) -> PipelineResult:

  known_args, pipeline_args = parse_known_args(argv)
  pipeline_options = PipelineOptions(pipeline_args)
  pipeline_options.view_as(SetupOptions).save_main_session = save_main_session

  if not model_class:
    # default model class will be mobilenet with pretrained weights.
    model_class = models.mobilenet_v2
    model_params = {'num_classes': 1000}

  def preprocess(image_name: str) -> Tuple[str, torch.Tensor]:
    image_name, image = read_image(
      image_file_name=image_name,
      path_to_dir=known_args.images_dir)
    return (image_name, preprocess_image(image))

  def postprocess(element: Tuple[str, PredictionResult]) -> str:
    filename, prediction_result = element
    prediction = torch.argmax(prediction_result.inference, dim=0)
    return filename + ',' + str(prediction.item())

  # In this example we pass keyed inputs to RunInference transform.
  # Therefore, we use KeyedModelHandler wrapper over PytorchModelHandler.
  model_handler = KeyedModelHandler(
      PytorchModelHandlerTensor(
          state_dict_path=known_args.model_state_dict_path,
          model_class=model_class,
          model_params=model_params,
          device=device,
          min_batch_size=10,
          max_batch_size=100)).with_preprocess_fn(
              preprocess).with_postprocess_fn(postprocess)

  pipeline = test_pipeline
  if not test_pipeline:
    pipeline = beam.Pipeline(options=pipeline_options)

  filename_value_pair = (
      pipeline
      | 'ReadImageNames' >> beam.io.ReadFromText(known_args.input)
      | 'FilterEmptyLines' >> beam.ParDo(filter_empty_lines))
  predictions = (
      filename_value_pair
      | 'PyTorchRunInference' >> RunInference(model_handler))

  predictions | "WriteOutputToGCS" >> beam.io.WriteToText( # pylint: disable=expression-not-assigned
    known_args.output,
    shard_name_template='',
    append_trailing_newlines=True)

  result = pipeline.run()
  result.wait_until_finish()
  return result

まとめ

  • リアルタイムの処理が求められてないならPreemptible Instance使って費用を1/4くらいにできそう
  • 費用を抑えて、Auto scaleする構成ならmin 0に設定できるGCE MIGを使うのが良さそう
  • Pub/Subの未確認メッセージがうまく処理できてないなら一時的にBatch起動してブーストすることもできそう
  • Pub/Subからデータ受け取る構成ならMIG, Batch, Dataflowの間で切り替える際の外部の変更を最小限でできそう
  • Preemptible Instance使うならリソースが枯渇した時のことも考える必要がありそう

 

DataflowのStreamingでPreemptible Instance使えると嬉しい

Made with Slides.com