中野智文のブログ

データ・マエショリストのメモ

【Gemini / Firebase AI Logic】Structured OutputのHTTP 400原因はSchemaの「maxItems」だった話

Firebase AI LogicのStructured OutputがHTTP 400になるまで:maxItemsと非公開の複雑度制限を切り分けた記録

はじめに

ブラウザアプリからFirebase AI Logic経由でGeminiを呼び出し、音声分析結果をJSONとして受け取る機能を実装しました。

認証、App Check、課金、クォータ、音声送信までは正常に動作したものの、本番用のresponse schemaを指定したリクエストだけが、次のエラーで失敗しました。

HTTP 400 INVALID_ARGUMENT
Request contains an invalid argument.

最終的に、原因はJSON Schemaの構文誤りではなく、配列に指定していた大きなmaxItems値でした。Firebase AI Logicへ送るschemaのmaxItemsを最大4に制限すると、同じ完全なproduction schemaが成功しました。

この記事では、なぜ通常のテストでは見つからなかったのか、どのように原因を切り分けたのか、そしてローカルのデータ契約を弱めずに修正した方法を紹介します。

システム構成

対象は、ブラウザ上でゲーム用サウンドを編集するTypeScript/Viteアプリです。AI機能では、編集した音声を短いWAVに変換し、Geminiに分析させます。

実APIへのアクセスにはAPIキーを直接使用せず、Firebase AI Logicを利用しています。リクエストの成立には、次の条件が必要です。

  • Firebase Authentication
  • Email/Password認証
  • アプリ側の許可UID判定
  • reCAPTCHA Enterprise App Check
  • Firebase production mode
  • Firebase AI Logic
  • 対象Geminiモデル

この構成には、APIキーをブラウザへ直接置かず、不正利用を抑制できる利点があります。一方で、Firebase AI LogicのEmulatorが存在しないため、Auth、App Check、Firebaseのproxy、Geminiのschema検証をまとめて再現するには、本番相当の環境が必要になります。

最初はHTTP 429だった

当初のリクエストはHTTP 429を返していました。通常のRPMやTPM上限には達していませんでしたが、Google AI Studio側の前払いクレジット不足が原因でした。

クレジットを補充すると429は解消し、次はHTTP 400へ変化しました。この時点で、少なくとも次のことが分かりました。

  • 認証経路はGeminiまで到達している
  • 課金・残高によるブロックは解消している
  • 新しい問題はリクエスト内容にある

同じ「AIが利用できない」というUI表示でも、429と400では原因がまったく異なります。エラーを安全な分類と数値HTTP statusに分けて記録することが重要でした。

MockやEmulatorでは再現しなかった

以下のテストはすべて成功していました。

  • TypeScript strict modeのtypecheck
  • ESLint
  • Vitest
  • Firebase Authentication/Firestore/Storage Emulator
  • Mock AIを使ったPlaywright
  • production bundle検査

これらはアプリ側の型、制御フロー、UI、Firestore Rules、監査ログ、privacy boundaryを検証できます。しかし、実際のGeminiモデルがresponse schemaを受理するかどうかは検証できません。

つまり、問題は「本番Hostingでしか発見できない」のではありません。正確には、「実Firebase AI Logicと実Geminiを使う統合環境がなかったため、既存のテストでは発見できなかった」という状態でした。

一度にschemaを変更しない

Googleから返るメッセージは、Request contains an invalid argument.だけでした。どのfieldが原因かは示されません。

そこで、production schemaを段階的に組み立てる診断を用意しました。

最初の診断は次のような構成です。

Stage 検証内容 結果
A 基本テキスト 成功
B System Instruction 成功
C 0.1秒の無音WAV 成功
D 最小response schema 成功
E scalar fields 成功
F nested object 成功
G string enum 成功
H scalar array 成功
I object array 成功
J 完全production schema HTTP 400

これにより、Auth、App Check、音声、System Instruction、structured outputそのものは原因から除外できました。

診断は次の原則で実装しました。

  • 成功したStageはlocalStorageへcheckpointを保存する
  • resetされるまで成功Stageを再送しない
  • 失敗・未実行Stageは何度でも実行できる
  • 一括実行は最初の失敗で停止する
  • 2 requests/minuteなど既存のclient rate limitを守る
  • prompt、WAV、Base64、raw response、token、keyはログへ保存しない
  • 監査にはStage、schema分類、成功可否、安全なerror code、HTTP statusだけを残す

schemaをさらに分割した結果

production schemaを、知覚評価、観察項目、recommendationsへ分けました。

検証内容 結果
8項目のperceptual assessment 成功
observationのfield構造 成功
大きなcategory enum 成功
recommendation基本構造 成功
targetSectionsを含むrecommendations 成功
完全observations HTTP 400
完全production schema HTTP 400

個々のobject、enum、nested arrayは受理されています。しかし、productionのobservationsを組み立てると失敗します。

次に、構造を変えず、配列上限だけをproduction値へ変更しました。

成功する診断では、次の値を使っていました。

{
  "strengths.maxItems": 4,
  "issues.maxItems": 4,
  "evidence.maxItems": 4
}

一方、production schemaは次の値でした。

{
  "strengths.maxItems": 24,
  "issues.maxItems": 24,
  "evidence.maxItems": 12,
  "recommendations.maxItems": 20,
  "warnings.maxItems": 20
}

構造を維持したままproduction上限へ切り替えたStageは、単独でもHTTP 400になりました。逆に、完全なproduction schemaのすべてのmaxItemsを最大4にすると成功しました。

これにより、少なくともこのモデルとFirebase AI Logic経路では、大きなmaxItems値がschema complexityの判定に影響していたと確認できました。

公式ドキュメントから分かったこと、分からなかったこと

Firebase AI Logicの公式資料では、maxItemsはresponse schemaのサポート対象として掲載されています。

Geminiの公式資料にも、structured outputはJSON Schemaのsubsetをサポートし、maxItemsを利用できると記載されています。

また、schemaが大きい、深くネストされている、制約が多い場合にはAPIが拒否する可能性があり、schemaを単純化するよう案内されています。

しかし、次の情報は公開されていません。

  • maxItemsの実質的な上限
  • 複数の配列やnested objectを含む場合の複雑度計算
  • maxItems: 24が拒否され、4なら成功するという閾値
  • モデルごと、Firebase AI Logic経路ごとの差
  • HTTP 400になった具体的なfield

したがって、これは公式ドキュメントの単純な見落としではありません。「schema complexity」という抽象的な制限を、実際の環境で段階的に測定する必要がある問題でした。

修正方法

ローカルschemaの上限をすべて4へ変更すると、アプリ内部のデータ契約まで変わってしまいます。そこで、Firebaseへ送信するschemaだけを縮小しました。

概念的には次のような変換です。

function firebaseResponseSchema(
  schema: Record<string, unknown>,
  options: { maxItemsCap?: number } = {},
): Record<string, unknown> {
  return visitSchema(schema, (key, value) => {
    if (
      key === "maxItems" &&
      typeof value === "number" &&
      options.maxItemsCap !== undefined
    ) {
      return Math.min(value, options.maxItemsCap);
    }

    return value;
  });
}

audio analysis/refinement経路だけ、次のように指定します。

firebaseGenerationConfig(config, responseSchema, {
  maxItemsCap: 4,
});

この結果、境界は次のようになりました。

ローカルschema/parser
  strengths/issues: 最大24
  recommendations/warnings: 最大20
  evidence: 最大12

Firebase AI Logicへ送るschema
  すべてのmaxItems: 最大4

Geminiの出力件数は4に抑えられますが、ローカルのvalidatorは従来の上限を維持します。import済みデータや将来の別経路まで4件へ制限する必要はありません。

また、この変換はaudio AIに限定しました。正常に動作している通常のテキストAI schemaへ、診断由来の制約を不用意に広げないためです。

修正後の結果

修正後、次の条件を満たす完全production schemaが成功しました。

  • Firebase Authentication
  • 許可UID
  • App Check
  • Firebase AI Logic
  • 音声入力
  • System Instruction
  • nested object
  • string enum
  • object array
  • recommendations
  • maxItemsを最大4へ縮小した完全response schema

上限を縮小していない診断は引き続きHTTP 400になり、同じ構造を上限4で送る最終診断だけが成功しました。この比較により、修正と成功の因果関係も確認できました。

学んだこと

1. 「サポートされているfield」と「任意の値が受理される」は別

maxItemsは公式にサポートされています。しかし、大きな値を含むschemaが必ず受理されるわけではありません。

2. structured outputの制限は、出力サイズだけではない

maxItemsは実際に24件を生成する指示ではなく上限ですが、その値自体がschema complexityへ影響する場合があります。

3. genericなHTTP 400にはdelta debuggingが有効

schema全体を推測で書き換えるより、成功する最小構造へfieldを一つずつ追加するほうが確実です。

4. 成功checkpointが試行回数を減らす

成功Stageを毎回再実行しないことで、rate limit、費用、待ち時間を抑えられました。

5. 本番ユーザー向けHostingと実API診断環境は分離すべき

今回の問題は実Geminiを呼ばなければ再現できません。しかし、本番ユーザー向けサイトで試行錯誤する必要はありません。App CheckとAuthを維持したFirebase Hosting preview channelなど、本番相当の診断環境を用意するのが次の改善です。

6. ログは詳細であるほどよいとは限らない

音声やpromptを保存すれば調査しやすくなりますが、privacy boundaryを破ってしまいます。Stage、schema分類、HTTP statusだけでも、適切に実験を設計すれば原因を絞り込めます。

まとめ

今回のHTTP 400は、課金、クォータ、認証、App Check、音声形式、enum、nested object、recommendationsの問題ではありませんでした。

原因は、Firebase AI Logicへ送ったproduction response schemaの大きなmaxItems値でした。

同じschema構造 + productionの大きなmaxItems → HTTP 400
同じschema構造 + maxItemsを最大4へ制限 → 成功

公式ドキュメントからschema complexityの可能性は予測できますが、具体的な閾値は分かりません。実環境で安全な段階診断を行い、成功するschemaとの差分を測定する必要がありました。

Structured Outputで説明のないHTTP 400に遭遇した場合は、fieldの対応可否だけでなく、property数、nesting、enum数、required数、そしてmaxItemsなどの制約値も含めて、schemaを段階的に縮小してみる価値があります。

Firebase × Gemini API の落とし穴:「Cloud Billing は有効なのに 429 エラー」の正体と解決法

Firebase や Google Cloud を使って Gemini API をアプリに組み込む際、誰もが一度は遭遇する不気味なエラーがあります。

HTTP 429: RESOURCE_EXHAUSTED
Check billing and quota settings

Google Cloud のコンソールを開いて確認してみても、

  • ✅ クレジットカードは登録されている(billingEnabled: true
  • ✅ API も有効化されている(generativelanguage.googleapis.com / firebasevertexai.googleapis.com
  • ✅ Cloud Logging を見ても明確なクォータオーバーのログがない

「課金も有効だし、まだ数回しか叩いてないのになぜ??」 と頭を抱えることになります。

この記事では、このエラーの本当の原因と、1回で解決する手順を解説します。


結論:原因は「Google AI Studio 側の前払いクレジット不足」

結論から言うと、「Google Cloud 側の Billing 紐づけ」と「Google AI Studio 上の Gemini API 利用プラン」は別で管理されている のが原因です。

AI Studio(Gemini API)の仕様上、新規利用や特定のティア(Tier 1〜2)では 「前払い(Prepay)」制度 が適用されます。

Google Cloud の Billing アカウントがどれだけ正常でも、AI Studio 側でプリペイドクレジットが 0 円になっていると、API リクエストが即座に 429 エラーでブロック されます。

💡 補足 アプリ画面に表示される Check billing and quota settings というメッセージは、アプリ側が 429 エラーを一律で一般化した表示にすぎません。Google から「設定を見直せ」と具体的に指定されているわけではないため、ログ追跡で迷子になりがちです。


🛠️ 解決手順(3ステップ)

1. Google AI Studio の Dashboard を開く

  1. ブラウザで Google AI Studio にアクセスし、Firebase/GCP と同じ Google アカウントでログインします。
  2. 左側メニューの 「MANAGE」 > 「Dashboard」 をクリックします。

2. 対象プロジェクトをインポートする

  1. 画面右上の 「☁️ プロジェクトをインポート」 をクリックします。
  2. 利用中の Firebase プロジェクト(例: your-project-id)を選択してインポートします。

3. 前払いクレジットを購入(チャージ)する

  1. インポートしたプロジェクトのステータス欄に 「🔴 クレジットなし」 と表示されているか確認します。
  2. 「クレジットを購入」 をクリックし、必要な金額をチャージします(※最低チャージ額は約 $10 / 約 2,000 円〜)。

❓ 2,000円もチャージして大丈夫?(従量課金との違い)

「テスト利用したいだけなのに 2,000 円も払うの?」と不安になるかもしれませんが、ご安心ください。

  • 一括で 2,000 円が引かれるわけではありません。
  • チャージした金額はプール(デポジット)され、API を呼び出した分だけ数銭〜数円単位で引き落とされます。
  • 例えば Gemini 1.5 Flash などの軽量モデルの場合、数千文字のやり取りでも 1 回あたり 0.00数円レベル です。

個人開発やテスト利用であれば、最低額の 2,000 円分をチャージしておけばかなり長い期間使い続けることができます。


📊 チェックリスト(ハマった時の振り返り)

確認項目 状態 影響
GCP Billing 有効 クレジットカード決済の受け皿(必須)
AI Studio インポート 未実施 AI Studio 側でプロジェクトを認識できない
AI Studio ステータス 🔴 クレジットなし HTTP 429 エラーで即時遮断(今回の犯人)
AI Studio ステータス 🟢 正常 API 利用可能(429 解消)

まとめ

Firebase で AI 機能を開発していて「設定は合っているはずなのに 429 になる」ときは、コードや Logging を掘る前に Google AI Studio の Dashboard を確認しましょう。

「プロジェクトをインポートして少額チャージする」 これだけで一発解決することがほとんどです!

Local の claude code を MCP サーバーとして起動して Desktop 版から接続する

Local の claude code を MCP サーバーとして起動して Desktop 版から接続する

背景と課題

AI を活用した開発ワークフローにおいて、次のような非効率が生じていた。

  • Web 版 Claude でビジネス要件・ユーザーストーリーをもとに対話的な設計を行う
  • 設計結果を コピー して Claude Code(ターミナル) に貼り付けて実行する
  • 実行結果を コピー して再び Web 版 Claude に貼り付けて確認・次の設計へ

この「コピペの往復」を解消するため、Jira のコメント欄を中継地点として使う運用も試みたが、「コメントを読み込んでください」という指示を毎回記述する必要があり、根本的な解決にはならなかった。

本質的な問題は、設計(Web/Desktop Claude)と実行(Claude Code)の間に手動の橋渡しが存在すること。


解決アプローチ

アーキテクチャの考え方

設計フェーズと実行フェーズの役割を明確に分離し、両者を直接接続する。

Claude Desktop(設計・対話・判断)
         ↓ MCP プロトコル(自動・コピペなし)
claude mcp serve(ローカル Mac 上で実行)
         ↓
ファイル操作 / コマンド実行 / BigQuery など
  • Claude Desktop がオーケストレーター(大局的な設計・ビジネス判断)
  • claude mcp serve がサブエージェント(実行のみ)

Claude Desktop 上での会話の中で「このファイルを修正して」「このコマンドを実行して」と指示すれば、Claude Code がローカル Mac 上で直接操作を行い、結果がそのまま会話に返ってくる。

なぜ Web 版ではなく Desktop 版か

Web 版 Claude から接続するには、ローカルサーバーを公開エンドポイントに変換するためのプロキシ(supergateway 等)と、ngrok などのトンネリングツールが必要になる。対して Claude Desktop は stdio トランスポートで claude mcp serve に直接接続できるため、追加ツール不要でシンプルに構成できる。


セットアップ手順

Step 3:Claude Desktop のインストール

claude.ai/download から macOS 版をダウンロードしてインストール。

Step 4:MCP 設定ファイルの作成

~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "claude-code": {
      "command": "/Users/yourname/.local/bin/claude",
      "args": ["mcp", "serve"]
    }
  }
}

command には Step 1 で確認した which claude の結果を入力する。

Step 5:接続確認

Claude Desktop を再起動後、新しい会話を開き、入力欄左下の「+」→「Connectors」で claude-code のトグルが ON になっていることを確認する。


使い方

Claude Desktop 上で普通に会話するだけ。コピペは一切不要。

~/projects/xxx/main.py を読んで内容を説明して」

「このバグを直してファイルを保存して」

「このSQL を bq query で実行して結果を見せて」


注意事項

  • 設定ファイルを変更したら Claude Desktop の再起動が必要
  • command のパスはメンバーごとに異なる場合があるため、各自 which claude で確認すること
  • 操作はローカル Mac 上で直接実行される。影響の大きい操作には確認ダイアログが表示される

【完全版】Cloud Identity Free 無料ユーザー作成マニュアル


【完全版】Cloud Identity Free 無料ユーザー作成マニュアル

ステップ 0:無料ライセンスを「購入」する

まず、Google から無料のライセンス枠をもらいます。

  1. Google 管理コンソールにログインします。
  2. 左メニューの [お支払い] > [その他のサービスを利用できる機能] をクリックします。
  3. ※メニューがない場合は、画面上部の検索窓で「サービスを追加」や「Cloud Identity」と検索してください。

  4. 一覧の中から 「Cloud Identity」 カテゴリを探します。

  5. 「Cloud Identity Free」[開始] または [購入] ボタンをクリックします。
  6. ※似ている「Cloud Identity Premium」は有料なので選ばないでください。必ず Free を選びます。

  7. 金額が 「¥0 JPY」 であることを確認して、購入手続きを完了させます(支払いは発生しません)。

ステップ 1:無料ユーザー専用の「組織部門」を作る

有料ユーザーと区別するための「フォルダ」を作成します。

  1. [ディレクトリ] > [組織部門] を開きます。
  2. 「+(組織部門を作成)」 をクリックします。
  3. 以下の通り入力して作成します。
  4. 名前: Free Users (または Cloud Identity用 など)
  5. 親組織部門: 一番上の親組織(ドメイン名)を選択
  6. [作成] をクリック。

ステップ 2:自動課金を防ぐ設定をする(最重要)

このフォルダに入れたユーザーには、勝手に有料ライセンスを付与しない設定にします。

  1. [お支払い] > [ライセンスの設定] を開きます。
  2. ※メニューがない場合は、検索窓で「ライセンスの設定」と検索してください。

  3. 【重要】 画面左側の組織図から、ステップ1で作った 「Free Users」 をクリックして選択します。

  4. メイン画面の設定を変更します。
  5. Google Workspace (Enterprise/Business): ステータスを 「オフ」 に変更して保存。
  6. Cloud Identity Free: ステータスを 「オン」 に変更して保存。

ステップ 3:ユーザーを作成・移動する

最後に、ユーザーをこの組織に入れます。

  • 新規作成の場合:
  • ユーザー追加画面の「組織部門」で Free Users を選択して作成すれば、最初から無料になります。

  • 作成済みの場合:

  • ユーザー一覧から対象者を選び、[その他] > [組織部門の変更]Free Users に移動させます。

✅ 最終確認 作成したユーザーの詳細画面を開き、[ライセンス] セクションを見てください。

  • Google Workspace ...[割り当てなし]
  • Cloud Identity Free[割り当て済み]

になっていれば成功です!

Gmailが使えないはずの Cloud Identity Free ユーザがメール認証をする!

まだCloud Identity Freeのユーザ自体の登録方法は次の記事を参照してください。

nakano-tomofumi.hatenablog.com


🚀 【コスト削減】メール認証対応・無料ユーザーの作り方

この構成なら、Slack、Zoom、SmartHR、勤怠システムなどの「メール認証(アクティベーション)」が必要なサービスにも、無料ユーザーで対応できます。

1. 仕組みの概要

  • ID: Cloud Identity Free(無料)
  • メール箱: Google グループ(Web掲示板として利用)
  • 配送: 受信ルーティング(個人の宛先をグループへ転送)

2. 設定の4ステップ(完全版)

一度設定してしまえば、あとはルーチンワークです。

  1. ユーザー作成(Free)
  2. アルバイトの方のアカウント(例: tanaka@...)を Cloud Identity Free で作成します。

  3. 専用グループ作成(Inbox)

  4. その人専用(またはアルバイト共有)のグループ(例: tanaka-box@...)を作成します。

  5. メンバー追加と設定(★最重要)

  6. 作成したグループに、ユーザー tanaka を追加します。
  7. ここがキモです: 追加後、メンバー設定で「メールの配信」を「メールなし(Webのみ)」に変更します。
  8. ※これをしないと、グループからの通知メールが tanaka(メール機能なし)に飛んでエラーになります。

  9. 受信ルーティング設定

  10. 管理コンソール > Gmail > ルーティング設定にて、「宛先が tanaka@... なら、tanaka-box@... に変更する」というルールを入れます。

3. ユーザー(アルバイト)への案内方法

アルバイトの方には、以下の手順を伝えるだけでOKです。

「Slackなどの登録メールが届いたら、Gmailではなく、以下のURL(Google グループ)を開いて確認してください。」 URL: https://groups.google.com/a/あなたのドメイン/g/グループ名


💡 この運用のメリット・デメリット

メリット(◎)

  • コスト 0円: 何人増えても無料です。
  • セキュリティ: 退職時は Google アカウントを停止するだけで、全てのSaaSへのログインも止まります。
  • 履歴管理: グループの Web 画面にはメールがずっと残るため、「あの時の指示メール」を後から検索可能です。

デメリット(△)

  • プッシュ通知がない: スマホに「ピコン」と通知は来ません。能動的に見に行く必要があります。
  • 日常会話には不向き: チャットのようなリアルタイムのやり取りには向きません(日常連絡は Slack や Chat を使う運用なら問題なし)。

GCP L4 難民

背景

最近、L4 が人気なのか、余っていない。どこが空いているか、見つける。 さらに現在のブートディスクからスナップショットを作成して、別のzone で復元し、前のブートディスクを削除する。

チェックスクリプト

#!/bin/bash
# チェック対象のゾーン
ZONES=("us-west1-a" "us-west1-b" "us-west1-c" "us-central1-c" "us-central1-a" "us-central1-b" )

# プロジェクト ID
PROJECT="your-project-id"

# 共通パラメータ
MACHINE="g2-standard-4"
GPU_TYPE="nvidia-l4"
IMAGE_FAMILY="ubuntu-2204-lts"
IMAGE_PROJECT="ubuntu-os-cloud"

for ZONE in "${ZONES[@]}"; do
  echo "===== Checking zone: $ZONE ====="
  if gcloud compute instances create test-l4-check \
    --project="$PROJECT" \
    --zone="$ZONE" \
    --machine-type="$MACHINE" \
    --accelerator="type=$GPU_TYPE,count=1" \
    --image-family="$IMAGE_FAMILY" \
    --image-project="$IMAGE_PROJECT" \
    --boot-disk-size=10GB \
    --maintenance-policy=TERMINATE \
    --provisioning-model=STANDARD \
    --quiet; then
      echo "$ZONE: L4 available"
      # 成功したらすぐ削除
      gcloud compute instances delete test-l4-check --zone "$ZONE" --quiet
  else
      echo "$ZONE: L4 unavailable"
  fi
  echo
done

スナップショットコマンド

こんなようなコマンドを使って、利用可能な別のzoneへ移っています 。 移ったあとは、古いインスタンスを削除するとよいでしょう。 (古いブートディスクの削除も忘れずに。下のコマンドの設定ではインスタンスが削除されると自動でブートディスクが削除されます。)

#!/bin/bash
# ============================================================
# GCP VM Migration Script
# Creates a snapshot of current VM's boot disk
# and recreates it in a new zone with auto cleanup.
# ============================================================

# ---- 設定 ----
SOURCE_ZONE="us-west1-b"
TARGET_ZONE="us-west1-a"

PROJECT="your-project-id"
VM_BASE_NAME="your-vm-name"
BOOT_DISK_TYPE="pd-balanced"
MACHINE_TYPE="g2-standard-4"

SOURCE_DISK="${VM_BASE_NAME}-${SOURCE_ZONE}"    # はじめは、元のマシン名にする必要がある。
TARGET_VM_NAME="${VM_BASE_NAME}-${TARGET_ZONE}"

# ---- スナップショット名 ----
SNAPSHOT_NAME="${SOURCE_DISK}-snapshot-$(date +%Y%m%d-%H%M)"

echo "🔹 Creating snapshot: ${SNAPSHOT_NAME} from ${SOURCE_DISK} ..."
gcloud compute disks snapshot "$SOURCE_DISK" \
  --snapshot-names="$SNAPSHOT_NAME" \
  --zone="$SOURCE_ZONE" \
  --project="$PROJECT" \
  --storage-location=us \
  --quiet

if [ $? -ne 0 ]; then
  echo "❌ Snapshot creation failed."
  exit 1
fi

# ---- スナップショットがREADYになるまで待機 ----
echo "⏳ Waiting for snapshot to become ready..."
while true; do
  STATUS=$(gcloud compute snapshots describe "$SNAPSHOT_NAME" \
    --project="$PROJECT" \
    --format="value(status)")
  if [ "$STATUS" == "READY" ]; then
    echo "✅ Snapshot is ready."
    break
  fi
  echo "  → Current status: $STATUS"
  sleep 10
done

# ---- 新しいVMの作成 ----
BOOT_DISK_NAME="${TARGET_VM_NAME}-boot-$(date +%Y%m%d)"
echo "🚀 Creating new VM (${TARGET_VM_NAME}) in ${TARGET_ZONE} ..."
gcloud compute instances create "$TARGET_VM_NAME" \
  --zone="$TARGET_ZONE" \
  --machine-type="$MACHINE_TYPE" \
  --boot-disk-type="$BOOT_DISK_TYPE" \
  --boot-disk-device-name="$BOOT_DISK_NAME" \
  --source-snapshot="$SNAPSHOT_NAME" \
  --boot-disk-auto-delete \
  --maintenance-policy=TERMINATE \
  --restart-on-failure \
  --project="$PROJECT" \
  --quiet

if [ $? -eq 0 ]; then
  echo "✅ VM created successfully in ${TARGET_ZONE}!"
else
  echo "❌ VM creation failed."
  exit 1
fi

# ---- VMの状態とIPを確認 ----
echo "🔎 Checking VM status and IP..."
gcloud compute instances describe "$TARGET_VM_NAME" \
  --zone="$TARGET_ZONE" \
  --project="$PROJECT" \
  --format="table(name,status,networkInterfaces[0].accessConfigs[0].natIP)"

Ubuntu 22.04 + GCP L4(g2)向け / A1111 用の xFormers 導入手順

0. 前提チェック

nvidia-smi     # L4 GPU が認識されているか確認

1. NVIDIA CUDA リポジトリ追加 & nvcc 導入

# リポジトリキー登録(Ubuntu 22.04 / x86_64)
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update

# CUDA Toolkit 12.4(nvcc を含む)を導入
sudo apt-get install -y cuda-toolkit-12-4

# 環境変数を追加(恒久化)
echo 'export CUDA_HOME=/usr/local/cuda-12.4' >> ~/.bashrc
echo 'export PATH=$CUDA_HOME/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc

# 確認
nvcc --version

2. A1111 venv を有効化

cd ~/stable-diffusion-webui
source venv/bin/activate

3. Torch / torchvision を cu124 に固定

# A1111 が勝手に最新版を入れないよう固定
echo 'export TORCH_COMMAND="pip install --index-url https://download.pytorch.org/whl/cu124 torch==2.6.0 torchvision==0.21.0"' >> ./webui-user.sh

# クリーンインストール
pip uninstall -y xformers torch torchvision torchaudio triton
pip cache purge

pip install --index-url https://download.pytorch.org/whl/cu124 \
  torch==2.6.0 torchvision==0.21.0

確認:

python - <<'PY'
import torch, torchvision
print("torch:", torch.__version__, "cuda:", torch.version.cuda)
print("torchvision:", torchvision.__version__)
PY

4. xFormers を依存無視でソースビルド

ポイント: --no-deps を付けないと torch>=2.8 を強制される

# ビルドツール
sudo apt-get install -y build-essential cmake ninja-build python3-dev
pip install -U pip setuptools wheel cmake ninja

# L4 (Compute Capability 8.9) に限定
export TORCH_CUDA_ARCH_LIST="8.9"
# ビルドが重い場合は並列数を制限
# export MAX_JOBS=4

# xFormers ビルド
pip install -v --no-cache-dir --no-build-isolation --no-binary xformers --no-deps xformers==0.0.32.post2

5. 動作確認

python - <<'PY'
import torch, xformers
print("torch:", torch.__version__, "cuda:", torch.version.cuda, "avail:", torch.cuda.is_available())
print("xformers import OK")
PY

6. A1111 を起動

xFormers 有効

./webui.sh --listen --port 7860 --xformers

SDPA で運用(おすすめ・十分速い)

./webui.sh --listen --port 7860
# → Settings → Optimization → Cross attention を SDPA に

トラブルシュート

  • torch が 2.8 に戻る → --no-deps を忘れていないか確認
  • nvcc が無い → CUDA Toolkit が未導入、または PATH が未設定
  • ビルドエラー(リソース不足) → export MAX_JOBS=4
  • 不要な警告 (pytorch-lightning → torchmetrics) →
  • 残すなら:pip install torchmetrics>=0.7.0
  • 不要なら:pip uninstall -y pytorch-lightning