モデルへの扉はひとつに。第9回 evalゲートとモデルの引退
timeoutした承認はstageをskippedにし、失敗にはしない。第9回はevalを本物のゲートにし、モデルの引退をプロジェクトではなくクエリに変える。

Azure Pipelinesでは、timeout内に完了しなかったapprovalはstageを失敗ではなくskippedとしてマークする。skippedなstageはほとんどのダッシュボードとほとんどの通知テンプレートで緑に見えるため、誰も目を通していないモデルのプロモーションが、成功を報告するpipeline実行を完了させうる。変更は一切出ていないのに、あるいはもっと悪いことに、先行するstageがすでにその半分を済ませているのに、だ。第8回はゲートウェイの上にアプリケーションを載せた。本稿はゲートウェイ自身を繰り返し可能なやり方で本番へ届け、第1回のモデル引退メールをデータベースクエリに変える。
実際にデプロイされているものは何か
アーティファクトは4つ、変化する速度は3つ。だからこそ1本ではなく3本のpipelineになる。
- プラットフォームインフラ。第2回の
10-platformにあたる、API Management、AKS、ネットワーク、observability。年に数回しか変わらず、人のapprovalを経てデプロイされ、誰も急いでいない。 - モデルのdeploymentとゲートウェイの設定。
20-modelsレイヤーに、ポリシーXMLとaliasマップを加えたもの。週次で変わる。eval gateが必要なのはこのpipelineだ。 - テナント設定。
30-tenantsレイヤー。オンボーディングのたびに変わり、コンシューマーチームからのセルフサービスのpull requestが意味を持つ唯一の場所だ。 - AKS上のコントロールプレーンのコンテナ。ごく普通のアプリケーションCI/CDであり、普通であるがゆえに4つの中で最も面白みがない。
ポリシーXMLには別途注記が要る。すべてのリクエストで実行されるのだから本番コードであり、Terraformと同じレビューを経て、同じpipelineで本番に到達する。ポータルは読むための場所であって編集する場所ではない。それを徹底させる方法は、デプロイ済みのポリシーがリポジトリと食い違ったら失敗するdrift checkをpipelineに置くことだ。
service connectionと、誰も設定しないタイマー
pipelineはAzure Resource Manager service connectionでAzureに認証する。推奨される構成はworkload identity federationで、app registrationかmanaged identityのいずれかを使い、シークレットとシークレット管理を丸ごと不要にする。第5回の主張全体がクレデンシャルのライフサイクルについてだったプラットフォームで、ここに保存済みシークレットを使うのは擁護しがたい。
運用上の詳細が2つ、現場で発見するのではなくランブックに書いておく価値がある。Azure Pipelinesは100日間使われなかったservice connectionを自動的に無効化する。再有効化できるのはservice connectionの管理者かProject Administratorだ。四半期ごとのインフラ変更でしか動かないpipelineはまさにこれを踏み、失敗は最悪のタイミングで、つまり何か緊急のデプロイが必要になったときに届く。そして、シークレットからfederationへ変換したservice connectionを元に戻せるのは7日間だけだ。その後は新しいシークレットを手で作るしかなく、変換後に手動で変更を加えたconnectionはツールからはまったく戻せない。
eval gateはjobではなくenvironmentに属する
直感的にはdeploy jobの最後にevalステップを足したくなる。だがそれではevalはpipelineが行う作業の1つになり、pipelineの作者が順序を変えたり、条件でスキップしたり、ゲートすべきだったdeploymentの後ろへ動かしたりできてしまう。
Azure Pipelinesには正しいプリミティブがある。environment、またはstageが消費するservice connectionに対するcheckだ。stageが実行される前に、使用するすべてのリソース上のすべてのcheckが満たされなければならず、否定の判定が1つでもあればstageは拒否される。evalを、コントロールプレーンのevalサービスを呼ぶInvoke REST API checkとして構成すれば、ゲートはpipeline定義のものであることをやめ、environmentのものになる。
ここで最終性のルールが効いてくる。approvalとほとんどのcheckの判定は最終だ。Invoke Azure FunctionとInvoke REST APIのcheckは例外で、再実行が可能であり、time between evaluationsをゼロ以外に設定するとcheckの判定は非最終になり、再評価され続ける。eval gateにとって、これは細部ではなく設計上の選択だ。再実行され続ける非最終のcheckは「このstageが最終的に始まる時点でもevalが通り続けていること」に正しく、最終のcheckは「このビルドはevalを通過した、出荷せよ」に正しい。どちらかを選ぶこと。そして、推奨される方法で構成した非同期形式は最終である、と知っておくこと。
evalの上に、production environmentへの人間のapprovalを重ね、承認者が自分自身の実行を承認できないよう制限するオプションを有効にする。そしてtimeoutは意図をもって設定すること。本稿の冒頭の通り、期限切れのapprovalはstageをskippedとしてマークし、skippedは緑として読まれる。緩和策は、短いtimeout、skippedなstageに特化したアラート、そして緑のpipelineをデプロイが起きた証拠として決して扱わないことだ。
evalが実際に検査するもの
プラットフォームのコンシューマーが気にかけるケースのgolden setを、ポリシーXMLの隣でリポジトリにバージョン管理し、モデルのエンドポイントに対してではなく、ゲートウェイ経由で候補の構成に対して実行する。この最後の点こそが、これをモデルのベンチマークではなくプラットフォームのevalにする。aliasの解決、ポリシー、content safetyフィルター、retrieval経路を実際に通すのであり、そのどれもが、モデルが一切変わっていなくてもレスポンスを壊しうる。
thresholdは3つで、3つ目こそ人が省くものだ。golden setに対する集計の品質スコア。レイテンシ予算。わずかに良く、意味のあるほど遅いモデルは、対話型アシスタントにとってアップグレードではないからだ。そしてリグレッションリスト。平均がどうであれ悪化してはならない特定のケースの一覧だ。平均を改善しながらカスタマーサービスチームが依存する3つのプロンプトを壊す新しいモデルバージョンは、悪いプロモーションの典型的な形であり、それを捕まえられるのはリグレッションリストだけだ。
プロモーション、canary、rollbackはすべて1つのメカニズム
第3回がbackend poolの前にaliasを置いたおかげで、モデルバージョンの変更はどのアプリケーションにも触れない。3つの形をとる設定変更だ。
- Canary: 新しいdeploymentを、同じpriorityグループ内の低いweightでaliasのpoolに加える。トラフィックの小さな一部がそこへ流れ、第5回のメトリクスがモデルalias別のレイテンシとtokenの差を示す。何ひとつ再デプロイは要らなかった。
- プロモーション: weightをシフトし、その後poolから古いdeploymentを外す。
- Rollback: その逆向きのweight変更。再デプロイではなく数秒であり、それこそがaliasが存在する理由のすべてだ。
これがやってくれない唯一のことは、canaryの境界をまたいでレスポンスを同一にすることだ。同じaliasを提供する2つのモデルバージョンは、同じプロンプトに対して異なる出力を生む。したがって、レスポンスをキャッシュ、比較、リプレイするものはすべて、どのバージョンが答えたかを知る必要がある。それはログフィールドであり、canaryと同時に追加するもので、後からではない。
引退。今やクエリになった
第1回の引退メールは、リポジトリのgrepに2日を費やさせた。今ならクエリ1つで済むはずだ。以前の3つの決定が、答えを知りうるものにしたからだ。第2回はモデルバージョンをピン留めし、version_upgrade_optionを意図的に設定したので、どのdeploymentも勝手には動かない。第3回はすべてのアプリケーションをaliasの背後に置いたので、aliasからdeploymentへのマッピングはデータだ。第5回はすべてのリクエストをテナントと解決済みモデルとともにログに残したので、直近30日にそのdeploymentを実際に使ったテナントの集合は、推測ではなくログクエリだ。
引退のランブックは、チケットに収まる4ステップになる。どのテナントが最近そのdeploymentを使ったかをクエリする。代替のdeploymentを立ち上げ、それに対してeval gateを走らせる。aliasをcanaryし、リグレッションリストを見張る。切り替えて、外す。アプリケーションは、そのどれが起きたことも決して知らない。それこそ、このシリーズ全体が目指してきた結果だ。
注視すべき失敗モード
- 成功として読まれるskipped。期限切れのapprovalはstageをskipする。skipped状態そのものに明示的にアラートを出すこと。pipelineの通知でも、チームが実際に見ているダッシュボードでも。
- jobステップとしてのeval。順序を変えたり条件で消したりできるなら、それはドキュメントだ。environmentのcheckであればゲートだ。
- 可視性なきバイパス。checkのバイパスにはリソースに対する管理者権限が必要で、誰が行ったかとともに記録される。これは良いことであり、checksパネルに放置するのではなく週次レビューで表に出す価値がある。
- 100日で無効化されたservice connection。四半期ごとのインフラpipelineは、インシデントの最中にそれを発見する。
- ポータルでのポリシー編集。drift checkがなければ、ポータルで行われたhotfixは、次のデプロイが黙ってそれを巻き戻すまで生き残る。考えうる最悪のタイミングだ。
- 誰も更新しないevalセット。1年間変わっていないgolden setは、去年のプロダクトを測っている。
第10回が受け継ぐもの
pipelineを通って出荷されるゲートウェイ、ゲートとしてのeval、設定変更としてのモデル変更。シリーズのすべてのメカニズムがいまや揃い、そのすべてが何かを発している。最終回はそれをどうするかの話だ。トレース、pipelineの中ではなく本番でのeval、EU AI Actが問うガバナンスの問い、そして第1回が約束した数字。
次に読む
- モデルへの扉はひとつに。第10回 observability、ガバナンス、そして数字。締めくくりの回。何をログに残すか、規制は何を問うか、そして第1回の目標は達成されたのか。
- モデルへの扉はひとつに。第8回 ゲートウェイの上のorchestration。これらのevalが振る舞いを守ろうとしている当のアプリケーションたち。
- AIコーディングエージェントにもステージング環境が必要だ。ゲートと爆発半径についての同じ議論を、開発プロセスの1段下で。
これを大規模に運用する際のインフラおよびプラットフォーム側の話はercan.cloudのフィールドノートに、ハブはercanermis.comにある。
参考資料
Ercan の他のサイト
同じ著者、別の領域のサイトが2つ。