1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LLMの固定プロンプトを前に置くとレイテンシが変わる理由

1
Posted at

LLMの固定プロンプトを前に置くとレイテンシが変わる理由

同じモデルへ、同じ程度の長さとほぼ同じ情報を渡しても、並べる順番だけで入力コストや応答開始までの時間が変わることがあります。
見るべきなのはプロンプト全体の類似度ではなく、先頭からどこまで同じprefixを再利用できるかです。
OpenAIのPrompt Cachingは、再利用可能なprefixのKV stateを保持します。固定情報を前、毎回変わる情報を後ろに置くと、この仕組みを性能設計として扱えます。

キャッシュされるのは「内容」ではなくprefix

LLMの固定プロンプトを前に置くとレイテンシが変わる理由

2026年9月30日時点のOpenAI公式ドキュメントでは、対応モデルのPrompt Cachingは共通するprompt prefixを再利用します。

Prompt caching

モデルが入力を処理するとき、過去のトークンを参照するためのkey-value states、いわゆるKV stateを計算します。Prompt Cachingが保持するのは、単に「以前この文章を見た」という情報ではなく、再利用可能なprefixについて計算されたKV stateです。

たとえば、毎回次の三つを送るアプリを考えます。

固定の開発者向け指示
固定の長い参照情報
今回のユーザー入力

ユーザー入力だけが毎回変わるなら、前半は安定しています。

request A:
[固定][固定][質問A]
^^^^^^^^^^
再利用候補

request B:
[固定][固定][質問B]
^^^^^^^^^^
同じprefix

これを逆にします。

request A:
[質問A][固定][固定]

request B:
[質問B][固定][固定]
 ^
 ここで不一致

後半にはまったく同じ固定情報があります。それでも先頭で差が生じるので、「固定部分が同じだから、その部分だけキャッシュされる」とは考えられません。

Prompt Cachingでは、同じ情報が存在する場所より、先頭から一致が続く境界を見る必要があります。

この性質はRAGでも効いてきます。長い共通instructionsや安定したtool definitionsの前に、検索のたびに変わる文書や日時、ユーザー固有情報を差し込むと、意味としては同じ構成でも再利用可能なprefixを早い位置で壊します。

なお、キャッシュ判定には本文だけでなくmodelやtools、Structured Outputsの設定なども関係します。toolのschemaや順序を毎回組み立て直す設計も確認対象です。

GPT-5.6ではbreakpointを設計できる

GPT-5.6以降では、キャッシュ可能なprefixの最小長は1,024 visible input tokensです。また、implicit cachingに加えてexplicit cache breakpointを指定できます。

ここが「固定情報を前へ置く」だけでは終わらないポイントです。

たとえば、

[固定instructions][固定reference][毎回変わるRAG文書][質問]
                              ^
                         breakpoint

という境界を作れば、変化しない部分を明示的な再利用単位にできます。

Responses APIでは、固定部分のcontent blockへprompt_cache_breakpointを付け、prompt_cache_options.modeをexplicitにできます。

from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-5.6",
    prompt_cache_options={"mode": "explicit"},
    input=[
        {
            "role": "developer",
            "content": [
                {
                    "type": "input_text",
                    "text": "ここに十分な長さの固定instructionsと参照情報",
                    "prompt_cache_breakpoint": {"mode": "explicit"},
                }
            ],
        },
        {
            "role": "user",
            "content": "毎回変化する入力",
        },
    ],
)

explicit modeでは、指定したbreakpointだけを利用します。変化するsuffixまでキャッシュへ書き込みたくない場合に、境界をアプリ側で決められます。

これはGPT-5.6以降ではコスト面でも意味があります。公式ドキュメントでは、GPT-5.6以降のcache writeは通常のuncached input rateの1.25倍、cache readは0.1倍とされています。したがって、「キャッシュできるものは何でも書く」ではなく、再利用されるprefixを選ぶ必要があります。

一方で、短い固定instructionsを無理に長くして1,024 tokensを超えれば必ず得になる、という話でもありません。追加した入力自体にコストがあります。固定prefixの長さ、再利用回数、writeとreadを実測して判断する必要があります。

Pythonでcached tokensとTTFTを測る

順序による違いは、感覚ではなく計測した方が早いです。

ここではPython 3.12以降とOpenAI Python SDK 2系を前提にします。

openai>=2.0.0,<3

OPENAI_API_KEYを環境変数へ設定したうえで、次のスクリプトを実行します。固定部分は1,024 tokensを十分に超える実データへ置き換えてください。

import csv
import time
from openai import OpenAI

client = OpenAI()
MODEL = "gpt-5.6"

stable = ("固定された業務ルールと参照情報です。" * 600)

def run(label: str, dynamic: str):
    started = time.perf_counter()
    first_token_at = None
    completed = None

    stream = client.responses.create(
        model=MODEL,
        input=[
            {"role": "developer", "content": stable},
            {"role": "user", "content": dynamic},
        ],
        stream=True,
    )

    for event in stream:
        if event.type == "response.output_text.delta":
            if first_token_at is None:
                first_token_at = time.perf_counter()
        elif event.type == "response.completed":
            completed = event.response

    ttft = first_token_at - started if first_token_at else None
    usage = completed.usage
    cached = usage.input_tokens_details.cached_tokens

    with open("cache.csv", "a", newline="") as f:
        csv.writer(f).writerow([
            label,
            usage.input_tokens,
            cached,
            round(ttft, 3) if ttft is not None else "",
        ])

for i in range(3):
    run(f"stable-first-{i}", f"今回だけ変わる質問です。番号={i}")

このコードでは、最初のresponse.output_text.deltaを受信するまでをTTFTの観測値として記録しています。ネットワーク時間なども含むクライアント側の測定なので、モデル内部の処理時間そのものではありません。同じ環境で相対比較するための値と考えるのが安全です。

usage.input_tokens_details.cached_tokensも同時に記録します。

次に比較用として、入力を逆転させます。

input=[
    {"role": "developer", "content": dynamic},
    {"role": "user", "content": stable},
]

ただし、ここで一つ注意があります。GPT-5.6以降のimplicit cachingでは、単に二つのリクエストが長い共通prefixを持っているだけで、狙った短い境界が必ず保存されるとは限りません。

固定部分と可変部分の境界を検証したいなら、固定developer messageの末尾へexplicit breakpointを置く方が実験条件を明確にできます。

input=[
    {
        "role": "developer",
        "content": [{
            "type": "input_text",
            "text": stable,
            "prompt_cache_breakpoint": {"mode": "explicit"},
        }],
    },
    {"role": "user", "content": dynamic},
]

同時に、

prompt_cache_options={"mode": "explicit"}

を指定します。

これなら「固定prefixをここまで保存する」という条件そのものをコードで固定できます。

測る対象は生成完了までの総時間より、まずcached_tokensとTTFTです。出力が長いと生成時間の揺れが支配的になり、入力prefixを変えた効果が見えにくくなるからです。

プロンプト構造を回帰テストする

この問題で厄介なのは、機能テストが通ってしまうことです。

たとえばRAGアプリで次の変更をしても、回答内容は正常かもしれません。

変更前:
[共通instructions]
[tool definitions]
[固定reference]
[検索結果]
[user input]

変更後:
[検索結果]
[共通instructions]
[tool definitions]
[固定reference]
[user input]

意味として必要な情報は全部残っています。しかし、先頭に毎回変わる検索結果を移したことで、prefixの再利用条件は別物になります。

そこでLLMアプリの性能テストでは、回答品質だけでなくキャッシュ利用も観測対象にします。

最低限、次をCSVやメトリクス基盤へ残します。

model
input_tokens
cached_tokens
TTFT
prompt構造のバージョン

CIでAPIを毎回呼ぶかどうかは、コストとテストの安定性を見て決めるべきです。ネットワークを含むTTFTへ厳密な閾値を設定すると、キャッシュ以外の揺らぎで落ちやすくなります。

私なら、構造に対するテストと実APIによる性能計測を分けます。

前者では「固定instructionsの前に可変RAG文書を置かない」「tool definitionsの生成順を安定させる」といった構造上の不変条件を確認します。後者では複数回計測し、cached_tokensが想定した境界まで到達しているかを観測します。

キャッシュミスが発生した場合、GPT-5.6以降では公式のPrompt Cache Diagnosticsも利用できます。model、tools、settings、inputなど、どこで期待したprefixとの一致が崩れたかを調べる用途です。

Prompt cache diagnostics

プロンプトは文章であると同時に、LLMアプリでは実行時のデータ構造でもあります。固定部分を前方へ集め、変化する情報を後方へ送り、必要ならbreakpointを置く。ただし、キャッシュを優先してプロンプトの意味や回答品質を壊しては本末転倒です。最終的には評価結果、cached_tokens、TTFT、実際の入力コストを同じ変更単位で測るのがよいと考えています。

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?