エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

AI・機械学習チーム流MLOpsの歴史

エムスリー Advent Calendar 2023 五日目担当、AI・機械学習チームの横本(yokomotod)です。前日は同じくAIチーム大垣さん(id:Hi_king)からの「画像を理解するGPT-4 Visionで、既存の画像認識モデルを説明可能にする」でした。

たまたま並んでしまいましたが、昨日のAIチームのMLエンジニアリングな話に続けて、今日はMLOpsやインフラについてのお話です。

(さらに本日はmabl Advent Calendar 2023としてQAチームの城本さん(@yuki_shiro_823)から「mabl Experience'23で「複数チームでmablを活用する際の課題と対応」について話しました 」も公開されています!)

どうやらエムスリーAIチームも2017年の発足からもう6年が経過しているようです。 私がチームに参加したのは2019年ごろですが、見てきた中だけでも、効率的で安定したMLモデルの開発・デプロイ・運用のためいくつもの改善が行われて来ました。

今回は私自身の勉強も兼ねてこれまで積み重ねられてきた改善を振り返り、独断と偏見でピックアップした歴史の一部を紹介できればと思います。

2017年: 始まりはECS

Amazon Elastic Container Service (ECS)

最初期、AIチームではMLプロダクトの運用をAmazon ECSを中心に行っていました。エムスリー全体の多くのサービスでもECSはよく採用されており、自然にそれに倣ったのだろうと思われます。

Batch, DB, API構成

2023年現在でも、AIチームでは「バッチで学習・推論して結果をDBに保存しておき、APIはDBの参照のみ行う(リクエスト時に推論をしない)」という構成をよく採用しています。

AIチームあるある構成(現代)

現在では主にGoogleCloud, GKEを利用(後述)していますが、このアーキテクチャ自体は最初期から採用され続けてきた伝統の構成でした。必要なパターンの分の推論結果をすべて事前に計算して格納しておくことで、API呼び出し時に高速で安定したレスポンスを実現しています。

2018年: gokart爆誕

gokart

gokartはAIチーム発のMLパイプライン構築用ライブラリです。現在AIチームでMLバッチを書く際は当たり前のように利用しているgokartですが、どうやら誕生したのはチーム発足からしばらくした2018年の末のことだったようです。

かなり早い段階からgokartがチームの標準として定着したようで、チーム内のMLバッチのソースコードが共通のレールに沿ったものになりました。そのおかげで今日の私達は、他の人が書いたコードであってもすんなりと読めるようになっています。

gokartについては以下の記事などで詳しく解説されています。

www.m3tech.blog

gokart用社内ライブラリ

gokartだけでも再現性の担保やコーディングスタイルの統一などのメリットは大きいですが、さらに開発効率向上に威力を発揮しているのが、合わせて開発が始まった「各種データ取得用の社内ライブラリ」です。ちなみに名前は「マッシュルーム」。

このライブラリではBigQueryなどからデータを取得する部分をgokartのTaskとして集約しています。新規のMLプロダクトであっても学習の元になるデータは既存のプロダクトと共通なことはよくあり、例えばユーザの閲覧履歴データなどは非常に一般的です。

このライブラリのおかげで、新たなMLプロダクトを開発する際もデータ取得部分は既存の資産を再利用でき、すぐにモデル部分の開発に着手できるようになっています。

2019年: GKEへの転換

GKE (Google Kubernetes Engine)

AIチームのインフラ構成において今のところ過去最大の転換点は、おそらくこのECSからGKEへの移行でしょう。

私は当時の技術選定に関わったわけではないのですが、振り返ると以下のような恩恵があったかなと思います。

  • 個別クラスタから共有クラスタへ
    • GKEでは各環境ごとにクラスタを1つだけ構築してプロダクトで共有しました
    • これにより多数のクラスタをメンテナンスする必要がなくなりました
  • デプロイ用のManifest(YAMLファイル)をアプリケーション側のリポジトリ管理に分離
    • ECS運用時はクラスタのセットアップのterraformに混ざる形でTask Definitionのterraformが管理されていました
    • GKE移行後はデプロイしたい対象のYAMLに集中できるようになり、チームメンバー全員がk8sのyamlを書く現在の文化の助けになった気がします
  • OSSのKubernetes (k8s)がベース
    • 機能開発が活発
    • 活用できる3rd partyツールが増えた
    • ソースコードが読める
  • AWS → GoogleCloud
    • エムスリーではBigQueryをメインのデータウェアハウスとして活用しているので距離が近くなった

「GKEに移行してよかった」という現状認識ですが、ECSかGKEかというよりはインフラの設計方針の変化による恩恵という面も強いかもしれません。

また現在であればクラウドは変えずAmazon Elastic Kubernetes Service (EKS)にするというのも有力な選択肢でしょう。

GKEでのバッチ監視

当初GKEでのバッチの監視は、シンプルにk8sからのエラーイベントを通知していました。当時の方法の詳細は下記で解説しています。

www.m3tech.blog

監視周りはこの後いろいろと進化していくポイントの1つです。

2020年: GKE改善いろいろ

min: 0 オートスケールの積極利用

GKEのNodePoolにはオートスケーリング機能がありますが、この頃から最小サイズを 0 にしたノードプールを各種インスタンスサイズごとに多数用意しておく構成になりました。

こうしておくことで、JobなどでPodがデプロイされたときだけ要求リソースに応じて対応するインスタンスサイズのNodeが立ち上がり、終了すると自動的にスケールインさせることが出来ます。

Workload Identity

当初、サービスアカウントの認証にはCredential JSONを利用していましたが、Workload Identityという機能を利用することでより安全に、かつJSONを発行する手間もなく認証が可能になりました。

cloud.google.com

エラーログの通知

GKEの監視としてJobの BackoffLimitExceeded eventの発生などを通知して行っていたものに追加して、ログから「 Traceback 」などの文字列を検知して通知する仕組みが追加されました。

フィルタ条件が非常にナイーブなためノイズなどが少し心配でしたが案外平気で、あくまでも補助としてですがエラー発生時の初動で「なぜ落ちたのか」をすぐに確認することが出来るようになり、監視体験が向上しました。

2021年: SLA監視

「エラー発生」の監視から「正常に完了したかどうか」の監視へ

先述の通り、この頃までのMLバッチの監視はエラーイベントが発生すると通知されるという形でした。至ってストレートな方法ですが、これには以下のような悩みがありました。

  • ハングしていたり、何らかの理由で起動さえもしなくなっていても気づけない
  • 一度はエラーが発生しつつも、その後は正常に動いているなら緊急性は下がるので区別したい
    • 例えば10分ごとに起動するCronJobで、毎回データを洗い変えるバッチなど

これに対する解決策として、各バッチごとに「いつまでに完了しているべきか」を定義しておいて、その時刻までに「完了イベント」がきちんと発生しているかを最もクリティカルな監視項目にしました。

こちらは下記の記事で詳しく解説されています。

www.m3tech.blog

Spot VM

Spot VMは、可用性が保証されないかわりに通常のVMに比べて非常に安価に利用できるインスタンスです。

cloud.google.com

ここまでの

  • gokartがタスクごとにキャッシュを持っており、途中停止・再実行に頑健なこと(実行済みでキャッシュがあるタスクは処理がスキップ出来る)
  • SLA監視により途中停止の有無を問わず、期限内に完了できているか監視できること

という点から非常に相性が良く、「基本的にバッチはSpot VM」というレベルで採用し、大幅なコストカットを実現しました。

Berglas Secret Controller

GKE運用において、秘匿情報をどのようにコンテナに渡すかは悩ましいポイントでした。

gitにコミットするわけにもいかず、ここまでは素朴に kubectl create secret ... でSecretリソースを作成する運用などしていました。

この問題を解決したのがBerglasというOSSと、それをk8sと連携するBerglas Secret Controller(チームメンバー 北川さん @kitagry作のOSS)です。

これらを用いることでGCSやSecret Managerに秘匿情報を格納しGKEから連携出来るようになりました。

以下の記事で詳しく書かれています。

www.m3tech.blog

2022年: なんかもう色々

Argo Workflow

この頃まで、MLバッチの起動はk8sのCronJob(cron式の設定に基づいてJobを起動してくれる)で管理されていました。

複雑な依存関係はgokartのレイヤでパイプラインにしているので、k8sのレイヤではcron程度で十分だったのですが、複雑な依存関係を定義したいケースが出てくるようになりArgo Workflowが導入されました。

シンプルなものはCronJob、それで不十分ならArgo Workflow、と使い分けて利用しています。

argoproj.github.io

Gatekeeper

事故防止やベストプラクティスの徹底のためにGatekeeperも導入されました。

open-policy-agent.github.io

GKE導入時から、k8sのManifest YAMLはアプリケーション側リポジトリが持つことで気軽・柔軟にデプロイ時の設定をカスタマイズすることが出来ていましたが、一方で全体を統一してベストプラクティスを守ることは難しくなっていました。

Gatekeeperはクラスタへのデプロイ時にManifest YAMLを検証することが出来るため、YAMLのリポジトリが分散していても一括でチェック出来ます。例えば以下のような検証を行っています。

  • Containerにrequest limit は設定してあるか
  • Jobに activeDeadlineSeconds は設定してあるか
  • Ingressに正しい sslPolicy は設定してあるか

Workload Identity Federation

先述のWorkload Identityと名前が似ていますが、今度はGoogle CloudとCI/CD環境との認証の改善です。

CI/CD環境からGKE等にデプロイするための認証用途にもCredential JSONなどを持たなくて良くなり、さらに安全性、利便性が高まりました。

こちらについては下記のブログで詳しく紹介しています。

www.m3tech.blog

.gitlab-ci.yml のライブラリ化

Workload Identity Federationの設定などはだんだんと複雑になり必要な記述量が増えてくるので、個別のCI/CD設定(.gitlab-ci.yml)に書いて回るのはかなり苦しくなってきます。

他にも、ここまで紹介し切れていませんが「CIでよしなにslack通知を飛ばす」など、固定で共通の記述が増えてきていました。

そこで .gitlab-ci.yml の外部ファイル参照機能(別リポジトリのファイルも参照できる!)を活用して、共通の記述をまとめたinclude用リポジトリが作成されました。

docs.gitlab.com

用意された部品を各リポジトリからincludeすることで、手間が省け、変更時の修正も一箇所で済むまさしくDRY原則の恩恵を受けることが出来ています。

こちらも以下で記事化されています。

www.m3tech.blog

2023年: そして…?

さてここまで、はじまりの2017年から遡って、積み上げられてきた改善の一部を紹介してきました。

AIチームでは2020年から「最高のMR・PRベスト10決定戦」と称してその年の素晴らしかったMerge Requet、Pull Requestの投票を行っています。決定戦の対象はMLOpsやインフラに限りませんが、今回紹介した多くの改善もベスト10入りを果たしています。

さらに2022年からはその結果をテックブログでも公開し始めました。

www.m3tech.blog

2023年分についても今月投票が行われ結果を発表する予定なので、ここではネタバレせず取っておこうと思います。月末に公開されるかと思うので興味を持って頂けた方は是非楽しみにしておいてください。

おわり

今回の紹介した歴史の中で、参考にしてもらえたり、真似して導入できることなどがあれば幸いです。

また今回ブログを書こうとしてみて正直忘れていたことや「あれっていつだっけ」となること多数だったので、考古学してみて面白かったです。みなさんのチームの歴史も記録に残してみては如何でしょうか。

「最高のMR・PRベスト10決定戦」のようなイベントもオススメです。

We are hiring!

エムスリーではいっしょにガンガン改善していく仲間を募集中です。

社内外問わず、基盤を使ったシステムの開発や施策により世の中にインパクトを与える機会が多数ありますので、是非我こそは!という方はカジュアル面談、ご応募お待ちしています!

jobs.m3.com

インターンも通年募集しています。実際にインターンで解決してもらった課題もあるので、興味のある方は是非ご応募ください。

【新卒】MLOpsエンジニア(インターン) / エムスリー株式会社

【新卒】機械学習エンジニア(インターン) / エムスリー株式会社