3.7 KiB
3.7 KiB
KVキャッシュを中心とした設計
LLMのKVキャッシュのヒット率を重要なメトリクスであるとし、APIレベルでキャッシュ操作を中心とした設計を行う。
前提
リクエスト間キャッシュ(Context Caching)は、複数のリクエストで同じ入力トークン列が繰り返された際、プロバイダ側が計算済みの状態を再利用することでレイテンシと入力コストを下げる仕組みである。 キャッシュは主に先頭一致 (Common Prefix) によってHitするため、前提となるシステムプロンプトや、会話ログの過去部分(前方)を変化させると、以降のキャッシュは無効となる。
要件
-
前方不変性の保証 (Prefix Immutability)
- 後方に会話が追加されても、前方のデータ(システムプロンプトや確定済みのメッセージ履歴)が変化しないことをAPIレベルで保証する。
- これにより、意図しないキャッシュミス(Cache Miss)を防ぐ。
-
データ上の再現性
- コンテキストのデータ構造が同一であれば、生成されるリクエスト構造も同一であることを保証する。
- シリアライズ結果のバイト単位の完全一致までは求めないが、論理的なリクエスト構造は保たれる必要がある。
アプローチ: Type-state Pattern
RustのType-stateパターンを利用し、Workerの状態によって利用可能な操作をコンパイル時に制限する。
1. 状態定義
Mutable(初期状態)- 自由な編集が可能な状態。
- システムプロンプトの設定・変更が可能。
- メッセージ履歴の初期構築(ロード、編集)が可能。
Locked(キャッシュ保護状態)- キャッシュの有効活用を目的とした、前方不変状態。
- システムプロンプトの変更不可。
- 既存メッセージ履歴の変更不可(追記のみ許可)。
- 実行(
run)はこの状態で行うことを推奨する。
2. 状態遷移とAPIイメージ
Worker 自身がコンテキスト(履歴)のオーナーとなり、状態によってアクセサを制限する。
// 1. Mutable状態で初期化
let mut worker: Worker<Mutable> = Worker::new(client);
// 2. コンテキストの構築 (Mutableなので自由に変更可)
worker.set_system_prompt("You are a helpful assistant.");
worker.history_mut().push(initial_message);
// 3. ロックしてLocked状態へ遷移
// これにより、ここまでのコンテキストが "Fixed Prefix" として扱われる
let mut locked_worker: Worker<Locked> = worker.lock();
// 4. 利用 (Locked状態)
// 実行は可能。新しいメッセージは履歴の末尾に追記される。
// 前方の履歴やシステムプロンプトは変更できないため、キャッシュヒットが保証される。
locked_worker.run(new_user_input).await?;
// NG操作 (コンパイルエラー)
// locked_worker.set_system_prompt("New prompt");
// locked_worker.history_mut().clear();
3. 実装への影響
現在の Worker 実装に対し、以下の変更が必要となる。
- 状態パラメータの導入:
Worker<S: WorkerState>の導入。 - コンテキスト所有権の委譲:
runメソッドの引数でコンテキストを受け取るのではなく、Worker内部にhistory: Vec<Message>を保持し管理する形へ移行する。 - APIの分離:
Mutable特有のメソッド(setter等)と、Lockedでも使えるメソッド(実行、参照等)をトレイト境界で分離する。