跳至主要内容

Prefix cache

當很多請求共用相同的 prompt 前綴(例如 RAG system prompt、chat template、few-shot preamble)時,只做一次前綴 prefill,然後讓後續 decode 重用它,就可以避免每個請求都重跑一次 L 次 loop iteration 的 prefill。

這個原語叫做 PrefixCache。它是一個 opaque object,內部持有共享 prompt 在 loop block 所有 L 次迭代下的 KV 狀態。一個 PrefixCache 可以跨很多次 generate_from_prefix_cache 呼叫持續存在,也能支援同一前綴下多個並發或串行 decode。

Model.build_prefix_cache(prompt, loops, cache_window=0)

prefix = model.build_prefix_cache(prompt, loops=8, cache_window=0)

這個呼叫會對 prompt 在指定 loops 下跑完整 prefill,並回傳一個 PrefixCache handle,其 K/V tensors 會覆蓋 shared loop block 的每一次 loop iteration。這一步不會 decode;它只建立好一個可被一或多個 generate_from_prefix_cache 消費的前綴狀態。

參數

名稱型別預設說明
promptnp.ndarray[int32]共享前綴 token ids,形狀為 [seq_len]。後續所有 generate_from_prefix_cache 呼叫都必須使用相同的 loops 值,因為前綴是 depth-specialized。
loopsintmodel.config()["default_loops"]建立前綴時使用的 loop 深度。
cache_windowint0Sliding-window 上限。0 代表完整快取(不淘汰);>0 則只保留最後 cache_window 個 token 的 KV,會失去更早位置資訊。

回傳

tinyloop_py.PrefixCache,一個 opaque object。其內部狀態包含:

  • 一份完整的 RuntimeKVCache,容量為 seq_len + (max_new_tokens from callers),並且在全部 n_pre_blocks + loops 個 cache layer 中都已填滿到 seq_len
  • 一份 prompt token array 的拷貝(因此呼叫端原始 buffer 後續可自由修改)
  • 一個 loop 深度欄位(後續 generate_from_prefix_cache 會用它驗證參數)

其記憶體成本對 FP16 KV 而言約為 seq_len × D × (n_pre_blocks + loops) × 2 bytes。例如 seq_len=1024, D=2048, L=8 時約為 80 MB。若開啟 TINYLOOP_KV_H_MODE=1 TINYLOOP_KV_INT4=1,則可降到約 14 MB(詳見 KV cache modes)。

可能拋出的錯誤

  • RuntimeErrormodel 已關閉、cache allocation OOM、或 seq_len > max_seq_len
  • ValueErrorloops < 1cache_window < 0

生命週期

只要你的共享工作負載還存在,就保留這個 PrefixCache。它由 Python 參考計數管理;底層 GPU 記憶體會在 handle 離開作用域或被顯式覆蓋時釋放。它不像 Model 那樣提供 close();交給 GC 處理即可。

常見兩種模式:

  1. 模組層級 cache。 啟動時建一次,之後每個請求都重用。最適合固定 system prompt 的情境。
  2. 按 prefix 分配 cache。 維護一個 {prefix_hash: PrefixCache} 的字典,當數量或 VRAM 超出預算時再依 LRU 淘汰。

Model.generate_from_prefix_cache(prefix, max_tokens=32, loops=8, ...)

這個 API 會從 prefix 內已快取的狀態開始 decode。prefill skip 是隱含的,所以第一個新 token 的產生不需要再把 prompt 重跑一遍。

generated = model.generate_from_prefix_cache(
prefix,
max_tokens=32,
loops=8,
temperature=0.0,
top_k=50,
top_p=1.0,
cache_window=0,
repetition_penalty=1.0,
stop_sequences=[],
)

參數

名稱型別預設說明
prefixPrefixCachebuild_prefix_cache 回傳。
loopsint必須與建立 prefix 時的 loops 完全相同。binding 會驗證,不符就直接 raise。
max_tokenstemperaturetop_ktop_pcache_windowrepetition_penaltystop_sequencesModel.generate 相同。

回傳

List[int],也就是完整 token stream:prefix + decode 後的 token。總長度為 len(prefix.prompt) + emitted

可能拋出的錯誤

  • RuntimeErrormodel 已關閉、OOM、或 prefix 與本次呼叫的 loops 不一致
  • ValueErrormax_tokens < 1

實際發生了什麼

內部流程如下:

  1. 複製 prefixRuntimeKVCache。在 h 模式下這很便宜,主要是 per-layer pointer copy;在 K/V 模式下則是完整 cudaMemcpy。這樣每次呼叫都拿到自己的 scratch-safe 副本,因此多個並發呼叫共享同一個 prefix 時不會競態。
  2. Decode max_tokens 個 token,像平常一樣重用剛複製出的 cache。
  3. 丟棄 這份 clone。原本的 prefix 完全不變。

第 (1) 步就是你能讓一個 PrefixCache 同時供很多個請求使用的關鍵:每次呼叫都有自己的 KV 副本。主要成本是 clone 的記憶體配置,但相比它省下的 prefill 計算,通常非常划算。

多請求吞吐的實測

由於 loop block 會在每次 forward 中套用 L 次,TinyLoop 的 prefix cache 會在這 L 次迭代的每一層都保存 K/V。當多個 decode 呼叫共享同一個 PrefixCache 時,就能避免把整段 L-iteration prefill 重做 N − 1 次。絕對節省量是 (N − 1) × prefill_cost;理論上的速度提升會漸近於 (prefill + decode) / (prefill / N + decode)

以下為 2026-04-17 在 model_407m_gptq_int4.tinyloop / H100 上,以 tests/bench_prefix_cache.py 量測的結果(NO_CACHE 每次請求都重建整段 prefix;SHARED 則只建一次 prefix,之後 N 次 continuation 全部重用;兩者都在相同 loop 深度 L = 8 下運行):

backendprefixNNO_CACHESHAREDspeedup
INT4 scalar(預設)500 tok1012 847 ms5 415 ms2.37×
INT4 scalar(預設)1024 tok1023 852 ms8 329 ms2.86×
INT4 scalar(預設)1024 tok50104 605 ms34 531 ms3.03×
FP16 body tensor core ¹1024 tok105 123 ms4 732 ms1.08×

¹ TINYLOOP_EXPERIMENTAL_FP16_BODY=1。在 tensor core 路徑下,prefill 本來就已接近 memory-bound,因此 prefix-share 的相對收益會變小。

實務建議

  • 在預設 INT4-scalar body 下,對長前綴(大於約 500 token)的 RAG / 多使用者共享前綴工作負載,預期可拿到 2–3× 吞吐提升
  • FP16-body tensor-core 路徑 的相對收益會比較小,因為 prefill 本來就很便宜。prefix cache 仍然正確、也仍然便宜,只是沒有那麼戲劇性。
  • 重用同一個 PrefixCache 不要每個請求都重建一次,否則就失去 prefix cache 的意義。
  • loops 必須一致。L=8 建好的 prefix,不能直接拿去服務 L=16 的 decode。若你需要多個深度,請使用 warm-start mid-loop,或維護一份「每個深度各一個 PrefixCache」。

與其他 API 的關係

  • Prefix cache vs warm-start mid-loop prefix cache 是為了在相同 L 下,讓很多請求共用一段 prompt;warm-start 則是在單一請求內,讓不同 L 之間共用同一段 prompt。兩者可以在概念上組合,但 API 還沒接通(已列為後續事項)。
  • Prefix cache vs speculative decode speculative decode 是在一次生成過程中,用同一權重的兩個深度互相配合;prefix cache 則是讓不同生成共用相同深度下的前綴狀態。兩者是正交能力,可以疊加。
  • Prefix cache 與 KV cache modes prefix cache 會繼承 generate 所使用的 KV 儲存模式(FP16、INT8,或三種 h 模式)。h 模式可讓 prefix cache 大約再縮一半;若使用 INT4-h + fp16_body,前綴共享與延遲降低可以雙重疊加。

錯誤條件

錯誤原因解法
RuntimeError: model was closed在已關閉的 Model 上呼叫重新建立 Model
RuntimeError: loops mismatchgenerate_from_prefix_cacheloops 與建構時不同對齊參數,或在目標深度重建 prefix
RuntimeError: out of memory前綴太長 / cache 預算不足縮短 seq_len、降低 L、開啟 INT4-h、或限制 cache_window
ValueError: max_tokens < 1字面意思改成正整數