🗄

Vertex AI: Gemini API の Context caching の紹介

2024/07/10に公開

何の話かと言うと

Google Cloud で利用可能なマルチモーダル対応の基盤モデルである Gemini 1.5 Pro / Flash に対して、Context caching の機能が利用可能になりました。これがどのような機能で何の役に立つのかを実際の使用例とあわせて、わかりやすく説明します。

Context caching のない時

Gemini はマルチモーダル対応モデルなので、プロンプトに動画などのメディアコンテンツを埋め込むことができます。具体的には、次のように Python のリスト形式でプロンプトを構成します。

prompt = [
    '[動画ファイル]',
    Part.from_uri(uri=f'{BUCKET}/{movie_file}', mime_type='video/mp4'),
    '動画の文字起こしをしてください。'
]

このプロンプトを Gemini API に投入すると、次の図のように基盤モデルからの応答が得られます。この際、入力トークンと出力トークンに対してコストが発生します。具体的な価格は、公式ドキュメントで確認できます。シンプルですね!


マルチモーダルプロンプトを Gemini API で処理する

Context caching のある時

Context caching を利用すると、プロンプトの一部分を事前にクラウド上のキャッシュ領域にアップロードしておくことができます。先ほどの例であれば、前半の動画ファイルを含む部分をアップロードするような使い方になります。以下の内容をキャッシュ領域に保存するイメージです。

prompt_cached = [
    '[動画ファイル]',
    Part.from_uri(uri=f'{BUCKET}/{movie_file}', mime_type='video/mp4')
]

そして、次の図のように、キャッシュ領域から取り出したプロンプトとクライアントから入力した追加のプロンプトを結合して、基盤モデルに投入することができます。なんだか複雑ですね…。


Context caching を使用する場合のイメージ図

Context caching のメリット

それでは、Context caching を使うと何が嬉しいのでしょうか? 端的に言うとコストの最適化です。

先ほどの図にあるように、Context caching を使う場合、次の 4 つのコストが発生します。

  • 入力トークンにかかるコスト
  • キャッシュからの入力トークンにかかるコスト
  • キャッシュの保存にかかるコスト
  • 出力トークンにかかるコスト

この際、「キャッシュからの入力トークンにかかるコスト」は、「入力トークンにかかるコスト」の25%に設定されており、キャッシュから入力した方がコストが下がります。ただし、それとは別に「キャッシュの保存にかかるコスト」があるので、キャッシュ上のプロンプトを 1 回使うだけの場合、Context caching を利用するコストメリットはありません。短時間の間にキャッシュに保存したプロンプトを繰り返し利用するようなケースで、コストメリットが生まれます。具体的な価格は、公式ドキュメントで確認できます。なるほどー。

先ほどの例では、動画ファイルの文字起こしを行う想定のプロンプトを利用していました。同じ動画ファイルに対して文字起こしの他に、サマリーの生成、ストーリーボードの生成、テロップ文字の抽出など、複数の処理を行う場合に有効活用できそうです。

Context caching の制限事項

本記事執筆時点では、次のような制限事項があります。

  • リージョンは us-central1 のみに対応(Gemini API のリージョンに us-central1 を指定する必要がある)
  • キャッシュに保存するプロンプトは、32,769 トークン以上のサイズが必要
  • プロンプトの先頭部分のみをキャッシュに保存可能(キャッシュから取り出したプロンプトとクライアントから入力したプロンプトの順序を制御することはできない)

Context caching の利用例

事前準備

ここでは、次の動画ファイルを Gemini 1.5 Flash で処理する例を紹介します。

この動画ファイルを mp4 に変換したものが Cloud Storage のバケットに事前に保存されているものとします。

この後の作業は、Vertex AI Workbench のノートブック上で行います。Workbench 環境を準備する方法は、次の記事の事前準備を参考にしてください。

はじめに、Client SDK を最新バージョンにアップデートして、Kernel をリスタートします。

!pip install --upgrade google-cloud-aiplatform
import IPython
app = IPython.Application.instance()
_ = app.kernel.do_shutdown(True)

次に必要なモジュールをインポートします。この記事の執筆時点では、次のように preview 版のモジュールが必要になります。

import datetime, vertexai
from vertexai.preview.generative_models import GenerativeModel, Part
from vertexai.preview import caching

そして、プロジェクト ID などの変数をセットして、Client SDK を初期化します。[Project ID][Bucket Name][Move File Path] の部分は実際に使用する環境にあわせて入力してください。また、この例にあるように、location には us-central1 を指定する必要があります。

PROJECT_ID = '[Project ID]'
BUCKET = 'gs://[Buket Name]'
movie_file = '[Movie File Path]'
vertexai.init(project=PROJECT_ID, location='us-central1')

キャッシュへのアップロード

キャッシュにアップロードするプロンプトを次のように定義します。

prompt_cached = [
    '[movie file]',
    Part.from_uri(f'{BUCKET}/{movie_file}', mime_type='video/mp4'),
]

次のコマンドで、キャッシュにアップロードします。この例からわかるように、この時点で、使用する基盤モデルも model_name オプションで指定します。その他には、システムインストラクションも system_instruction オプションで指定できます。変数 cached_content には、キャッシュされた内容を表すオブジェクトが保存されます。

cached_content = caching.CachedContent.create(
    model_name='gemini-1.5-flash-001',
    contents=prompt_cached,
    system_instruction='You are a video content analyst. Work on the following tasks.',
    ttl=datetime.timedelta(minutes=60)
)

また、最後の ttl オプションは、キャッシュの保持期間です。この時間(今の場合は 60 分)を経過するとキャッシュは自動的に削除されます。設定可能な値の上限はないので、極端に大きな値を設定すれば、実質的に無限大に設定することもできます。また、一度設定した時間を後から更新して、延長することも可能です。

キャッシュに保存したプロンプトには、ユニークな ID が自動で割り当てられます。これは、次のコマンドで確認できます。

cached_content.name

[出力]

'2753810434648702976'

また、現在キャッシュに保存されているプロンプトの一覧は、次のコマンドで表示されます。

caching.CachedContent.list()

[出力]

[<vertexai.caching._caching.CachedContent object at 0x7f83a2571ed0>: {
   "name": "projects/[Project Number]/locations/us-central1/cachedContents/2753810434648702976",
   "model": "projects/[Project ID]/locations/us-central1/publishers/google/models/gemini-1.5-flash-001",
   "createTime": "2024-07-10T02:56:24.259968Z",
   "updateTime": "2024-07-10T02:56:24.259968Z",
   "expireTime": "2024-07-10T03:56:24.251354Z"
 }]

name 属性の末尾が、先ほど確認した ID に一致しています。

今は実行する必要ありませんが、既存のキャッシュを再利用する際は、次のコマンドでキャッシュの内容を表すオブジェクトが取得できます。

cached_content = caching.CachedContent(
    cached_content_name='projects/[Project Number]/locations/us-central1/cachedContents/2753810434648702976')

キャッシュに保存したプロンプトの利用

それでは、キャッシュに保存したプロンプトを用いてみましょう。まず、変数 cached_content に用意した内容を用いて、モデルのオブジェクトを取得します。

model = GenerativeModel.from_cached_content(cached_content=cached_content)

このモデルに追加のプロンプトを与えると、キャッシュに保存したプロンプトと結合したものがモデルに対する入力になります。ここでは、動画のサマリーを生成してみましょう。

prompt = '''\
[tasks]
A. Summarize important topics of the video.

[conditions]
A. The summary has more than three sentences.
A. Try best to include as much detail as possible so that they clearly explain the sequence of the story.

[format instruction]
Output in Japanese. Use plaintext not markdown.
'''
response = model.generate_content(prompt, stream=False)
print(response.text)

[出力]

この動画は、若い女性がハワイを旅行した様子をGoogle Pixelで撮影した映像です。空港に到着
した彼女は、青い空と青い海、ヤシの木など、ハワイらしい風景をGoogle Pixelで撮影して楽しん
でいます。ファーマーズマーケットではGoogle Pixelのカメラ機能を使ってランブータンなどの
珍しい果物を調べ、地元の人と交流し、その美味しさを体験しました。その後、ハワイの熱帯植
物園を訪れ、さまざまな植物をGoogle Pixelで撮影しました。また、乗馬体験では、Google Pixel
を使って自撮りをして、ハワイの雄大な自然を満喫しました。そして夜は、レストランで食事を
楽しみながらGoogle Pixelでメニューを調べ、周囲の美しい景色も撮影しました。このように
Google Pixelを使って、彼女はハワイ旅行の思い出を鮮やかに記録し、楽しんでいます。 

前述のように、キャッシュに保存した内容は繰り返し使わないとコストメリットが出ません。ここでは、もうひとつの例として、動画の内容に対する QA を行います。

prompt = '''\
[task]
A. Find the most relevant scene with timestamp in the movie that can be used to answer the question.
B. Create a summary of the selected scene.
C. Create an answer to the question based on the selected scene.

[format instruction]
In Japanese. In plaintext, no markdowns. In the following format:

- Beginning of the output (don't show this line.)
[回答に用いたシーン]
<Timestamp in mm:ss - mm:ss>
<summary of the scene>

[回答]
<Answer to the question>
- End of the output (don't show this line.)

[question]
この動画に登場する人は、Google Pixel を使って何を調べましたか?
'''
response = model.generate_content(prompt, stream=False)
print(response.text)

[出力]

[回答に用いたシーン]
00:16 - 00:20
女性が、赤い果実の入った袋をGoogle Pixel のカメラで撮影したところ、画面に
「ランブータン」と表示され、ランブータンの説明が表示されます。

[回答]
この動画に登場する人は、Google Pixel を使って、赤い果実が何であるかを調べました。

「この動画に登場する人は、Google Pixel を使って何を調べましたか?」という質問に対して、回答の根拠となるシーンの説明と、それを用いた回答が得られました。このように、特定の動画にQAを繰り返すような場合は、特に Context caching が有効活用できるでしょう。

キャッシュの削除

利用が終わったキャッシュは、次のコマンドで強制的に削除することができます。

cached_content.delete()

複数メディアの同時利用

ここまで、動画ファイルをキャッシュに保存する例で説明しましたが、Gemini 1.5 Pro / Flash で利用可能なメディアはすべてキャッシュに保存することができます。また、複数のメディアを含むプロンプトを保存することも可能です。たとえば、次の図のように、複数の画像とテキストが混ざったプロンプトを考えます。


複数の画像とテキストが混ざったプロンプト

これは、Python のコードでは次のように表現されるでしょう。対応する画像ファイルは、事前に Cloud Storage のバケットに保存してある想定です。

prompt_cached = [
    '[reference information]',
    Part.from_uri(f'{BUCKET}/01_chrome_dino.png', mime_type='image/png'),
    'This character is "クロームの恐竜".',
    Part.from_uri(f'{BUCKET}/02_google_color.png', mime_type='image/png'),
    'These four colors are "Googleカラー".',
    Part.from_uri(f'{BUCKET}/03_gbike.png', mime_type='image/png'),
    'The bicycle with "Googleカラー" is "Googleバイク".'
]

これを先ほどと同様に、caching.CachedContent.create() メソッドに指定すれば OK です。(ただし、この例ではトークン数が少ないため前述の最低トークン数の制限でエラーになります。実際には、もっと大量の画像とその説明文を用意する必要があります。)

cached_content = caching.CachedContent.create(
    model_name='gemini-1.5-flash-001',
    contents=prompt_cached,
    system_instruction='You are a fashion advisor. Work on the following tasks.',
    ttl=datetime.timedelta(minutes=60)
)

これに次のプロンプトを追加して、Tシャツの絵柄の説明文を生成します。ハイライト部分にあるように、先ほどの説明文を参照するように指示しています。


画像の参考情報を利用するプロンプトの例

実際のコードと出力例は次のようになります。

prompt = [
    """\
[task]
A. Describe the printed patterns and illustrations of each item.

[condition]
A. Use the reference information to make descriptions.
A. Each description has a few short sentences. Try best to include as much detail as possible.
A. Be cautious on number of items.

[format instruction]
In Japanese. In plain text, no markdowns.
Output only descriptions in a bullet list starting with '-'.

[items]
""",
    Part.from_uri(f'{BUCKET}/t_shirt1.png', mime_type='image/png'),
    Part.from_uri(f'{BUCKET}/t_shirt2.png', mime_type='image/png'),
    Part.from_uri(f'{BUCKET}/t_shirt3.png', mime_type='image/png'),
    Part.from_uri(f'{BUCKET}/t_shirt4.png', mime_type='image/png')
]
    
response = model.generate_content(prompt, stream=False)
print(response.text)

[出力]

- このセーターは、白をベースに、青とグレーの模様でデザインされています。模様には、「クロームの
恐竜」とサボテンが描かれています。
- このTシャツは、ピンクから黄色、白、青へとグラデーションで染められています。Tシャツの裾には、
「Google」の文字が白で印刷されています。
- このサイクルジャージは、黒をベースに、Googleカラーの水平のストライプがデザインされています。
ストライプは、青、赤、黄色、緑の順に配置されています。ジャージの左胸には、「Google」のロゴが
白で印刷されています。
- このTシャツは、白をベースに、Googleバイクをモチーフにデザインされています。バイクのフレーム、
車輪、サドル、ハンドルバー、バスケットがカラフルなGoogleカラーで印刷されています。

「クロームの恐竜」「Googleカラー」「Googleバイク」など、キャッシュ上のプロンプトで示した名称を使った説明文が生成されていることがわかります。

まとめ

本文で説明したように、Context caching の利用目的は、トークン数が大きなコンテンツを繰り返し利用する際のコストを最適化する点にあります。特に Gemini 1.5 Pro / Flash では、2Mトークン(Pro)、1Mトークン(Flash)のロングコンテキストが使用できるので、プロンプトに大量のデータを含めることで応答の品質を上げるアプローチが増えると思われます。同一のデータを繰り返し利用する際は、Context caching を検討するとよいでしょう。

本記事で紹介した以外の機能については、次の公式ドキュメントを参考にしてください。

Google Cloud Japan

Discussion