Google CloudでML推論のコストが下がるアーキテクチャ考えてみた
システム設計をする上で考慮することの例
DBやネットワーク、セキュリティなど考えることは他にもあるが、
この発表ではコンピューティングに絞って考える
Server側で負荷調整がしにくい構成。負荷によってスケールアウトしても、間に合わず過負荷になり得る。Rate LimitやExponential Backoffを用いてリクエストを送りすぎないように工夫が求められる
Server側で負荷調整がしやすい構成。Serverが処理可能な状態の時にQueueにあるものから取り出して新しく処理を開始する
同期処理もPush型も準備できたサーバーがある前提。リクエストに備えて常時立ち上げることもあり、その分費用がかかる
負荷によってスケールアウトし、新規にサーバーを起動することもあるが、プロビジョニングに時間がかかる。その間に既存のサーバーが過負荷になり不安定になる
特にGPUを使うようなコンテナイメージは数GB~あるのでスケールアウトに時間がかかる
同期処理もPush型も準備できたサーバーがある前提。リクエストに備えて常時立ち上げることもあり、その分費用がかかる
負荷によってスケールアウトし、新規にサーバーを起動することもあるが、プロビジョニングに時間がかかる。その間に既存のサーバーが過負荷になり不安定になる
特にGPUを使うようなコンテナイメージは数GB~あるのでスケールアウトに時間がかかる
→非同期&PullのGPU推論アーキテクチャを考えてみた
Google Cloud で Computing 機能を有するサービス
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含む)
Google Cloudの余っているリソースを割安で使うことのできるオプション。リソースが余ってない時は使えない。
プリエンプティブル VM インスタンスは、標準 VM の料金よりもはるかに低価格(60~91% 割引)で利用できます。ただし、他の VM に割り当てるためにコンピューティング容量を再利用する必要がある場合、Compute Engine はこのインスタンスを停止(プリエンプト)する可能性があります。プリエンプティブル インスタンスは、Compute Engine の余剰の容量を利用する機能であり、使用できるかどうかは利用状況に応じて変わります。
GPUとPreemptible Instance両方が使える次のサービスの活用を考えてみる
GKEはGPUのshared resouseで費用抑えたりもできるが、今回は対象外
Instance template: マシンのスペックやOS、起動時のスクリプトやコンテナイメージなどをあらかじめ指定しているVM
Instance templateを条件によってスケールさせられる。最小は0台
※WebUIから起動する時はdefaultという名前のVPCが必要
Dataflow MLでPyTorch, Tensorflow, ONNX, TensorRT, scikit-learnなどの推論をサポートしている。実行中にモデルの差し替えができる。高頻度でモデルが変わるシステムと相性がいい。
resnet101
30分で完了、1.4$
mobilenet v2
22分で完了、0.5$
それぞれ50,000の画像を使って実験
resnet101:
60分で完了、0.5$
mobilenet v2:
60分で完了、0.05$
GPUを使った方が費用が安いが、時間がかかってる。GPUを使うコンテナイメージは大きいのでプロビジョニングに時間がかかる
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
DataflowのStreamingでPreemptible Instance使えると嬉しい