生命週期與記憶體模型
本頁是 TinyLoop C++ 呼叫端的權威記憶體/所有權契約。
生命週期進入點
Model* load_model(const std::string& path, int max_seq_len = 2048, int prefill_chunk = 0);
void free_model(Model* model);
size_t vram_usage_bytes(Model* model);
內部配置階段(load_model)
load_model 不是輕量建構子。內部執行以下步驟:
- header 解析 + 格式驗證(
MAGIC、VERSION_MAX、維度) - 量化權重上傳(
QuantWeight:打包 bytes + FP16 scales/zeros) - 針對熱路徑 GEMM 的可選 FP16 cache(由 env 控制)
BufferPool配置:main(FP32 residual,永遠是全序列寬)norm、scratch1、attn_down(FP16 暫存;全序列寬或 chunk 寬)- 延遲配置的
logit_row
- h-mode 路徑的可選重建 scratch
任何配置失敗都會使模型建立致命錯誤。呼叫端收到 nullptr,不得繼續使用該模型指標。
模型擁有的記憶體
Model 擁有長期存活的 GPU 記憶體:
- 量化權重 + metadata
- 輸出/head 張量
BufferPool可重用活化- 可選的 FP16 權重 cache
Model 不永久擁有每個請求的 decode cache。那些由 generation/prefix/resume API 建立。
每請求 / handle 擁有的記憶體
RuntimeKVCache
由 generation 系列呼叫與 prefix/warm-start builder 配置。層儲存模式依 env/runtime flag 而定:
- FP16 K/V
- INT8 K/V
- store-h FP16
- store-h INT8
- store-h INT4
PrefixCache / ResumeHandle / PrefixPool
跨呼叫擁有 cache 狀態的不透明 handle。
PrefixCache:固定 loop 深度下預填充的 prefix 狀態。ResumeHandle:residual 快照 + 過量配置的 cache,用於深度升級。PrefixPool:多個 prefix cache + LRU 行為。
可變狀態與 thread-safety 含義
Model 有可變工作 buffer(buffers.main、buffers.norm、scratch buffer)。每一次 forward 都會修改它們。
後果:
- 在同一
Model*上並行呼叫 forward 是不安全的,除非外部序列化。 - 綁定到單一模型的 handle(尤其是
ResumeHandle)假設沒有外來 forward 污染其預期的 residual/cache 演化。 - 較安全的並行模式:
- 每個模型一個鎖
- 每個 worker 一個模型(較高 VRAM)
- 每個 replica 一個 process
prefill_chunk 與 scratch 駐留
prefill_chunk > 0 改變記憶體行為:
main:仍然是max_seq_len × D × sizeof(float)- scratch buffer(
norm、scratch1、attn_down):依 chunk 列數配置,而非全序列列數
取捨:
- 長上下文下 VRAM 佔用較低
- 因 chunked 執行,prefill 期間的啟動/driver overhead 較高
- score 類 API 受 chunk 列預算限制
為什麼這樣設計記憶體
TinyLoop 優先考量:
- 單次模型載入後的穩定高吞吐
- 避免熱路徑重複配置的波動
- 透過 runtime mode flag 明確控制 VRAM-vs-延遲取捨
相較於完全無狀態 API,這產生更高的持續效能,但要求呼叫端對所有權與並行有更強的紀律。
實務整合檢查清單
在 production C++ 整合前:
- 選定一種並行模型(鎖 vs 每 worker 一模型)。
- 由 VRAM 預算決定 max sequence 預算 + chunk 策略。
- 選定 cache 模式(FP16/INT8/store-h 變體)並記錄品質/延遲政策。
- 把所有不透明 handle 當作持有 VRAM 的物件;確定性釋放。
- 在
load_model失敗與 OOM 路徑周圍接入健康探針。