💸

【警告】AOAIモデルをデプロイしただけで◯万円?PTUなモデルデプロイ有無を一括確認できるようにしてみた話

2024/09/14に公開

はじめに

先日、Azure OpenAI Service(以下、AOAI)でモデルをデプロイしただけで高額な課金が発生するという、ヒヤリとする体験をしました。同様の事例が他の方にも起こっていることを知り、これは対策が必要だと感じました。(せめて横並び確認の自動化くらいは・・・)

参考にした記事はこちらです:

https://zenn.dev/umi_mori/articles/aoai-warning-ptu

この記事では、同じ問題に直面した背景と、その解決策としてテナント内のすべてのAOAIリソースを一括でチェックし、Provisioned-managedなデプロイメントを特定するシェルスクリプトを作成した経緯と使い方を紹介します。

背景

高額課金の発生

上記の記事と同様に、AOAIでモデルをデプロイしただけで、まだAPIを使用していないのに数時間後に数万円の課金が発生しました。ただ、今回は予算アラートを設定していたため、すぐに気づいて対処できましたが、設定していなければもっと大きな金額になっていたかもしれません。

PTUの料金について

PTUの料金などのAOAIの料金表については以下の公式ドキュメントをご参照ください。

https://azure.microsoft.com/ja-jp/pricing/details/cognitive-services/openai-service/

例えば、以下の画像のように、カナダリージョンにて、50PTUでGPT-4oのPTU-managedをデプロイした場合、1日あたり約35万円(=50PTU×24h×289.031円/(PTU・h))となり、割と大きな額の課金となります。

原因の分析

原因は、モデルのデプロイ時に『Provisioned-Managed』という設定がデフォルトで選択されていたことです。この設定では、Provisioned Throughput Unit(PTU)が設定され、時間単位の課金が発生します。何も知らずにデプロイすると高額な課金が発生します。

加えて、一部のモデルでは、「グローバル標準」の設定ができず、デフォルトで「Provisioned-Managed」が指定されることがあります。これに気づかずデプロイすると、思わぬコストがかかる可能性があります。

以前まではPTU契約はMS営業を通して契約する必要がありましたが、8月頃に営業を通さず契約できる形態ができ、Standardデプロイと同様な感覚でPTU相当のデプロイができるようになっています。(検証などいろいろと便利な使い道はあるので、このアップデートは個人的には嬉しいアップデートでした。)
詳しくは以下にあります。

https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/unveiling-azure-openai-service-provisioned-reservations-and/ba-p/4214560

こういった想定外の重課金事象はクラウドあるあるな事象ですが、従量課金だけがクラウドの課金モデルではないことを心に留めておく必要があるなーと感じました。

対策の必要性

この問題は、AOAIを利用する多くの方々にとって見落としがちなポイントです。特に、大規模なAzure環境を管理している場合、どのリソースで「Provisioned-Managed」が設定されているかを一つ一つ確認するのは現実的ではありません。

そこで、自動化された方法でProvisioned-managedなデプロイメントを一括確認できるスクリプトを作成しました。

スクリプトの概要

このシェルスクリプトは、以下の機能を持ちます:

  • 全サブスクリプションの取得:Azureアカウントに紐づくすべてのサブスクリプションIDを取得します。
  • OpenAIリソースの検出:各サブスクリプション内で、kind"OpenAI" のCognitive Servicesアカウントを特定します。
  • デプロイメントのチェック:各リソース内のデプロイメントを取得し、sku.name"Provisioned-managed" のものを特定します。
  • 結果の出力:Provisioned-managedなデプロイメントと、それ以外のデプロイメントをそれぞれ別のファイルに出力します。ファイル名にはタイムスタンプが含まれます。

GitHubリポジトリ

このスクリプトはGitHubで公開しています。ご自由にダウンロード・ご利用ください。

https://github.com/naoki1213mj/azure-openai-provisioned-managed-checker

スクリプトの使い方

前提条件

  1. Azure CLIのインストール

    Azure CLIがインストールされている必要があります。

    # macOSの場合
    brew update
    brew install azure-cli
    
    # Ubuntuの場合
    curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
    
  2. Azureへのログイン

    az login
    
  3. 必要な拡張機能のインストール

    az extension add --name cognitiveservices
    
  4. jqのインストール

    # macOSの場合
    brew install jq
    
    # Ubuntuの場合
    sudo apt-get install jq
    
  5. 適切なAzureロールの割り当て

    スクリプトを正常に実行するためには、対象のサブスクリプションおよびリソースに対する読み取りアクセス権限が必要です。

    • サブスクリプションレベルでの「閲覧者(Reader)」ロールが割り当てられていることを確認してください。
    • これにより、スクリプトはサブスクリプション内のリソース情報を取得できます。

スクリプトの実行

  1. スクリプトを保存

    以下の内容をファイル(例:check_provisioned_managed_deployments.sh)に保存します。

check_provisioned_managed_deployments.sh
#!/bin/bash

# 現在の日時を取得してフォーマットする(例:YYYYMMDD_HHMMSS)
timestamp=$(date '+%Y%m%d_%H%M%S')

# 出力ファイル名(タイムスタンプを含む)
output_file_provisioned_managed="provisioned_managed_deployments_${timestamp}.txt"
output_file_other_deployments="other_deployments_${timestamp}.txt"

# 出力ファイルを初期化
> "$output_file_provisioned_managed"
> "$output_file_other_deployments"

# 全サブスクリプションを取得
subscriptions=$(az account list --query "[].id" -o tsv)

for subscription in $subscriptions; do
    # サブスクリプションを設定
    az account set --subscription "$subscription"

    echo "Checking subscription: $subscription"

    # OpenAIサービスリソースのリストを取得
    resources=$(az cognitiveservices account list --subscription "$subscription" --query "[?kind=='OpenAI'].{Name:name, ResourceGroup:resourceGroup}" -o tsv)

    if [ -z "$resources" ]; then
        echo "No OpenAI resources found in subscription: $subscription"
        continue
    fi

    any_provisioned_managed=false
    any_other_deployments=false
    resources_with_provisioned_managed_deployments=""
    resources_with_other_deployments=""

    # 各リソースについて確認
    while read -r resourceName resourceGroup; do
        echo "Checking resource: $resourceName in resource group: $resourceGroup"

        # デプロイメントのリストを取得
        deployments=$(az cognitiveservices account deployment list --resource-group "$resourceGroup" --name "$resourceName" --subscription "$subscription" -o json)

        if [ -z "$deployments" ] || [ "$deployments" == "[]" ]; then
            echo "No deployments found in resource: $resourceName"
            continue
        fi

        # 各デプロイメントを確認
        for row in $(echo "${deployments}" | jq -r '.[] | @base64'); do
            _jq() {
                echo "${row}" | base64 --decode | jq -r "${1}"
            }

            deploymentName=$(_jq '.name')
            skuName=$(_jq '.sku.name')
            modelName=$(_jq '.properties.model.name')

            if [ "$skuName" == "ProvisionedManaged" ]; then
                any_provisioned_managed=true
                resources_with_provisioned_managed_deployments+="$resourceName (Resource Group: $resourceGroup, DeploymentName: $deploymentName, SKU Name: $skuName, ModelName: $modelName)\n"
            else
                any_other_deployments=true
                resources_with_other_deployments+="$resourceName (Resource Group: $resourceGroup, DeploymentName: $deploymentName, SKU Name: $skuName, ModelName: $modelName)\n"
            fi
        done

    done <<< "$resources"

    # 'Provisioned-managed' なデプロイメントの出力
    if [ "$any_provisioned_managed" = true ]; then
        echo "Some deployments in subscription $subscription have SKU Name set to 'Provisioned-managed'."
        echo "-------------------------------------" >> "$output_file_provisioned_managed"
        echo "Subscription: $subscription" >> "$output_file_provisioned_managed"
        echo "Resources with Provisioned-managed deployments and their models:" >> "$output_file_provisioned_managed"
        echo -e "$resources_with_provisioned_managed_deployments" >> "$output_file_provisioned_managed"
        echo "-------------------------------------" >> "$output_file_provisioned_managed"
    else
        echo "No deployments with SKU Name 'Provisioned-managed' found in subscription $subscription."
    fi

    # その他のデプロイメントの出力
    if [ "$any_other_deployments" = true ]; then
        echo "Some deployments in subscription $subscription are not 'Provisioned-managed'."
        echo "-------------------------------------" >> "$output_file_other_deployments"
        echo "Subscription: $subscription" >> "$output_file_other_deployments"
        echo "Resources with other deployments and their models:" >> "$output_file_other_deployments"
        echo -e "$resources_with_other_deployments" >> "$output_file_other_deployments"
        echo "-------------------------------------" >> "$output_file_other_deployments"
    else
        echo "All deployments in subscription $subscription are 'Provisioned-managed' or no other deployments found."
    fi
done

echo "Subscriptions with deployments have been saved to $output_file_provisioned_managed and $output_file_other_deployments"
  1. 実行権限を付与

    chmod +x check_provisioned_managed_deployments.sh
    
  2. スクリプトを実行

    ./check_provisioned_managed_deployments.sh
    

出力結果の例

スクリプトの実行後、タイムスタンプ付きの2つのファイルが生成されます。

1. provisioned_managed_deployments_YYYYMMDD_HHMMSS.txt

出力例
-------------------------------------
Subscription: XXXXXXXX-〇〇〇〇-〇〇〇〇-〇〇〇〇-XXXXXXXX〇〇〇〇
Resources with Provisioned-managed deployments and their models:
openai-resource-1 (Resource Group: openai-rg, DeploymentName: deployment-1, SKU Name: ProvisionedManaged, ModelName: gpt-4o)
-------------------------------------

2. other_deployments_YYYYMMDD_HHMMSS.txt

出力例
-------------------------------------
Subscription: XXXXXXXX-〇〇〇〇-〇〇〇〇-〇〇〇〇-XXXXXXXX〇〇〇〇
Resources with other deployments and their models:
openai-resource-2 (Resource Group: openai-rg, DeploymentName: deployment-2, SKU Name: Standard, ModelName: gpt-4o-mini)
-------------------------------------

アウトプットファイルの説明

  • provisioned_managed_deployments_YYYYMMDD_HHMMSS.txt

    • sku.nameProvisionedManaged のデプロイメントが含まれます。
    • 高額な課金が発生する可能性のあるデプロイメントを特定できます。
  • other_deployments_YYYYMMDD_HHMMSS.txt

    • sku.nameProvisionedManaged ではないデプロイメント(例:Standard)が含まれます。
    • 他のデプロイメントの状況を把握するのに役立ちます。

注意事項

適切な権限の確認

  • 必要なAzureロール
    • スクリプトを正常に実行するためには、対象のサブスクリプションおよびリソースに対する読み取りアクセス権限が必要です。
    • サブスクリプションレベルでの「閲覧者(Reader)」ロールが割り当てられていることを確認してください。
  • 権限が不足している場合
    • スクリプト実行時にエラーが発生する可能性があります。
    • 組織のAzure管理者やサブスクリプションの所有者に問い合わせて、必要な権限を付与してもらってください。

その他の考慮事項

  • Azure CLIと拡張機能のバージョン
    • 最新版のAzure CLIとCognitive Services拡張機能を使用してください。
  • jqのインストール
    • JSONデータを処理するために必要です。
  • セキュリティポリシー
    • 最小権限の原則に従い、必要最低限の権限のみを付与することが推奨されます。
  • 定期的な権限の見直し
    • アカウントに過剰な権限が与えられていないか、定期的に確認してください。

対策と効果

このスクリプトを使用することで、以下のような効果が期待できます:

  • 高額課金の防止:意図せず「Provisioned-Managed」が設定されているデプロイメントを早期に発見し、不要なリソースを削除または設定変更できます。
  • リソース管理の効率化:複数のサブスクリプションやリソースグループにまたがる環境でも、一括で状況を把握できます。
  • 定期的な監視:タイムスタンプ付きのファイルにより、過去の状況との比較やトラッキングが容易になります。

まとめ

AOAIを利用する際には、モデルのデプロイ設定に十分注意が必要です。特に、「Provisioned-Managed」の設定は高額な課金を招く可能性があるため、定期的な確認が推奨されます。

今後、MicrosoftがUIや設定プロセスの改善を進め、ユーザーが誤って高額な課金を発生させないような仕組みや誤って設定してしまった場合の通知の仕組みなどが導入されることも一案かもしれません。(筆者自身は、こういった誤認識による課金はクラウドあるあるなので、一定程度はユーザ側が注意すべき派ではありますが・・・)
そういった改善が進むことで、より安心してAOAIを利用できるようになるでしょう。

同じ問題に直面した経験から、このスクリプトを作成しました。ぜひ活用していただき、コストの最適化とリスクの低減に役立ててください。

また、社内注意喚起やコストアラートの設定、IaCによるデプロイのテンプレート化などでの対策も並行して実施するのも推奨します。

合わせて読みたい

AOAIにおいてオプトアウトができてるかをテナント単位で一括確認するスクリプトも作りました。
https://zenn.dev/chips0711/articles/dd970558754075

以下では、3大クラウドの日本リージョンでのGPT/Claude/Geminiモデルの提供事情もまとめています。
https://zenn.dev/chips0711/articles/b4ab395089cb28

参考

公式ドキュメント:PTUについて
https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/provisioned-throughput

公式ドキュメント:スクリプト作成に参考にしたAzureのREST APIの仕様
https://learn.microsoft.com/en-us/rest/api/aiservices/accountmanagement/deployments/list?view=rest-aiservices-accountmanagement-2023-05-01&tabs=HTTP

(skuにデプロイタイプの値が入るなんて上記からだとわからなかったのが苦労ポイントでした)


免責事項

本記事およびスクリプトは、2024年9月14日時点の情報に基づいています。今後のAzure OpenAI Serviceの仕様変更などにより、スクリプトが正常に動作しなくなる可能性があります。本スクリプトの使用は自己責任で行ってください。また、本スクリプトに関するメンテナンスやサポートは提供されておりません。


Discussion