BentoML:LinuxでAI/MLモデルをproduction-readyなREST APIとしてパッケージ化・デプロイする

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

MLモデルのトレーニングが終わると、次に必ず来る問いがある:どうやってproductionに出すか? 自分もモデルをラップするFlask APIを自前で書いた経験が何度もあって、その都度いろんな問題に直面した――バージョン管理の欠如、スケールの難しさ、ログの混乱。チームがBentoMLを試したとき、ML servingに対する考え方が根本から変わった。

全体像:MLモデルのデプロイ方法

実際には、MLモデルをAPIとして公開する方法はいくつかある。大きく4つのグループに分けられる:

  • 自前でAPI実装(Flask/FastAPI — 最も柔軟だが、すべてを一から自分で処理する必要がある
  • BentoML — ML serving専用のフレームワーク、多くのproduction機能が組み込まれている
  • TorchServe — PyTorch専用、PyTorchオンリーのチームには最適
  • Triton Inference Server(NVIDIA) — 超高性能だが複雑で、GPUクラスターが必要

各アプローチの詳細比較

Flask/FastAPIによる自前実装

Flask/FastAPIは誰でも知っているため、最も一般的な方法だ。モデルをロードしてエンドポイントを書けば完成。だが問題は別のところにある:

  • モデルのバージョン管理機能がない――モデルを更新するたびにサービス全体を再デプロイする必要がある
  • バッチ処理は自前で実装必須――高負荷時のスループットに大きく影響する
  • ヘルスチェックやモニタリングも自前で追加実装が必要
  • モデルと依存関係をまとめてパッケージ化する標準的な方法がない

自分もそういったFlaskサービスを6ヶ月間メンテナンスしたことがある。データサイエンティストがモデルを更新するたびに、チーム全員が手動でコーディネートしなければならなかった――ファイルのコピー、サービスの再起動、ログの確認。非常に時間がかかり、インシデントも起きやすかった。

TorchServe

チームがPyTorchのみを使用していて、Metaの公式ソリューションを求めるならTorchServeが選択肢になる。ただし、かなり制約が多い――PyTorchのみ対応、XMLベースの設定(やや古い)、実際のエッジケースに関するドキュメントが不足している。

Triton Inference Server

GPUのパフォーマンスを最大限に引き出す必要があるとき――ダイナミックバッチング、複数バックエンド(TensorRT、ONNX、TF、PyTorch)――Tritonはセットアップの手間に見合う。問題は学習曲線が急で、設定が複雑で、GPUクラスターがなければ明らかにover-engineeringになることだ。

BentoML

BentoMLは「自前のFlask」と「複雑なTriton」のちょうど中間に位置する。フレームワーク非依存(sklearn、PyTorch、TF、XGBoost、ONNXなどをサポート)、バージョン管理の組み込み、アダプティブバッチング、Dockerの自動エクスポートを備えている。複雑なセットアップなしに、80%のproductionユースケースに対応できる。

BentoMLをproductionに選ぶ理由

比較の結果、自分のDevOpsチームが3つの具体的な理由からBentoMLに決めた:

  1. モデルレジストリが内蔵済み — バージョン管理付きのモデル保存・読み込みが可能で、この用途だけのためにDVCやMLflowを別途用意する必要がない
  2. Bento = 完全にパッケージ化されたアーティファクト — モデル+コード+依存関係を一つのアーティファクトにまとめ、どこにでもデプロイできる
  3. Dockerイメージの自動生成 — 1コマンドでproduction-readyなDockerイメージが完成し、複雑なDockerfileを書く必要がない

最もあまり語られないメリットが、実はチームにとって最も重要だった:「新しいモデルはどこに置いた?Pythonのバージョンは?依存関係は何が必要?」というやりとりがなくなった。データサイエンティストがタグ付きでモデルを保存し、DevOpsがそのタグを使ってDockerをビルドする。この標準化されたプロセスのおかげで、新しいモデルをリリースするたびに約2時間の節約になった。

Linux上でのBentoMLデプロイ手順

1. インストール

pip install bentoml

# バージョン確認
bentoml --version

2. BentoMLモデルストアへのモデル保存

最初のステップは、トレーニング済みモデルをBentoMLレジストリに登録すること。scikit-learnの例:

import bentoml
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# モデルをトレーニング(実際にはすでにモデルがある想定)
iris = load_iris()
clf = RandomForestClassifier(n_estimators=100)
clf.fit(iris.data, iris.target)

# BentoMLモデルストアに保存
saved_model = bentoml.sklearn.save_model(
    "iris_classifier",
    clf,
    signatures={"predict": {"batchable": True}},
    metadata={"accuracy": 0.97, "dataset": "iris"}
)

print(f"Model saved: {saved_model.tag}")
# 出力例: iris_classifier:3mxqpfzbs6tpjuqj

モデルは~/bentoml/models/に保存され、タグハッシュによる自動バージョン管理が行われる――ロールバックしたいときはタグを変えるだけでいい。

3. BentoML Serviceの作成

核心部分 ― APIエンドポイントと処理ロジックを定義する:

# service.py
import bentoml
import numpy as np
from bentoml.io import NumpyNdarray, JSON

# レジストリからモデルを読み込む
iris_runner = bentoml.sklearn.get("iris_classifier:latest").to_runner()

svc = bentoml.Service("iris_classifier_service", runners=[iris_runner])

IRIS_CLASSES = ["setosa", "versicolor", "virginica"]

@svc.api(input=NumpyNdarray(shape=(-1, 4), dtype=np.float32), output=JSON())
async def predict(input_data: np.ndarray):
    batch_pred = await iris_runner.predict.async_run(input_data)
    result = [IRIS_CLASSES[i] for i in batch_pred]
    return {"predictions": result}

4. ローカルでのテスト

# 開発サーバーを起動
bentoml serve service:svc --reload

# curlでテスト(別のターミナルで)
curl -X POST http://localhost:3000/predict \
  -H "Content-Type: application/json" \
  -d '[[5.1, 3.5, 1.4, 0.2]]'

# レスポンス:
# {"predictions": ["setosa"]}

5. Bentoアーティファクトのビルド

bentofile.yamlを作成して依存関係をすべて記述する:

service: "service:svc"
labels:
  owner: devops-team
  project: iris-api
include:
  - "service.py"
python:
  packages:
    - scikit-learn
    - numpy
bentoml build

# 出力:
# Successfully built Bento(tag="iris_classifier_service:7a3bk2...")
# Bento size: 15.2 MB

6. DockerによるProductionへのデプロイ

# BentoをDockerイメージにコンテナ化
bentoml containerize iris_classifier_service:latest

# タグ付けしてレジストリにプッシュ
docker tag iris_classifier_service:latest your-registry.com/iris-api:v1
docker push your-registry.com/iris-api:v1

# コンテナを起動
docker run -p 3000:3000 iris_classifier_service:latest serve

7. systemdを使ったLinuxへの直接デプロイ

Dockerを使わない場合は、systemdサービスを作成してプロセスを管理する:

# /etc/systemd/system/iris-api.service
[Unit]
Description=BentoML Iris Classifier API
After=network.target

[Service]
User=www-data
WorkingDirectory=/opt/iris-api
ExecStart=/opt/iris-api/venv/bin/bentoml serve iris_classifier_service:latest \
  --host 0.0.0.0 \
  --port 3000 \
  --workers 4
Restart=always
RestartSec=5
Environment=BENTOML_HOME=/opt/iris-api/bentoml

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable iris-api
systemctl start iris-api
systemctl status iris-api

Production運用時の注意点

アダプティブバッチング

BentoMLにはアダプティブバッチング機能がある――複数の小さなリクエストを自動的にバッチにまとめてスループットを向上させる。bentofile.yamlで設定:

runners:
  - name: iris_runner
    max_batch_size: 100
    max_latency_ms: 15

Prometheusによるモニタリング

BentoMLは/metricsにPrometheusメトリクスをあらかじめ公開している。Prometheusの設定に追加するだけですぐに使える:

scrape_configs:
  - job_name: 'bentoml'
    static_configs:
      - targets: ['your-server:3000']

ヘルスチェックエンドポイント

/healthz/readyzに組み込み済み――何も追加実装せずにKubernetesのliveness/readiness probeとしてそのまま使える。

まとめ

自前で書いたFlaskサービス3つをBentoMLに移行した。新しいモデルをproductionに上げるまでの時間――データサイエンティストから引き渡されてAPIが動くまで――は約3時間から20〜30分に短縮された。主にDockerイメージのビルド時間だ。モデルレジストリのおかげで、新しいモデルに問題が生じたときのロールバックも容易になった。

BentoMLは万能ではない。TensorRTでGPUのパフォーマンスを最大化する必要があるならTritonの方が適している。しかしほとんどのML/DevOpsチームにとって、BentoMLはデプロイ速度とproductionの安定性の間のバランスポイントとして実用的だ。pip install bentoml scikit-learnで今すぐ試して、上記の手順を実行してみてほしい。

Share: