跳至主要内容

批次化與整合模式

本頁涵蓋生產等級的整合考量:如何批次化請求、如何結構化服務,以及如何組合 runtime 模式而不踩到未定義行為。TinyLoop 的批次為靜態 — N 個 prompt 以 lockstep decode,一個 Model* 一次。目前不支援 continuous batching(paged attention)。

整合決策矩陣

需求...使用
每請求最低延遲單請求 generate_stream
相同形狀下最高吞吐以 lockstep prompt 使用 generate_batch
多請求共享 system promptPrefixCache + generate_from_prefix_cache
多租戶且有多個已知 scaffoldPrefixPool + generate_with_pool
低深度草稿 → 高深度最終ResumeHandle + resume_generate
零輔助參數的 decode 加速generate_speculative

每一項皆為明確的 runtime API — TinyLoop 中沒有隱藏的排程器會為你挑選。

靜態批次生成

std::vector<int32_t> generate_batch(Model* model,
const std::vector<std::vector<int32_t>>& prompts,
const GenerateConfig& config);

N = prompts.size() 條 lane 上做靜態 lockstep 批次生成。

執行形狀

  1. 每 lane prefill(循序)。 每個 prompt 一次一個進行 prefill,填滿其專屬 cache 區域。目前實作不跨 lane 並行 prefill。
  2. Lockstep decode。 所有 lane 透過批次 attention 與 GEMM kernel 並行每步一個 token。已停止的 lane(EOS、stop-sequence、max-tokens)仍參與 kernel 形狀,但其輸出被丟棄。
  3. 每 lane 主機端取樣。 每步 decode 後,取樣、repetition penalty 與 grammar masking 在 host 端對每個 lane 各自套用。

這是吞吐量導向的原語,並非完整 continuous batching。Lane 無法於 decode 中途加入;批次形狀在呼叫進入時即固定。

目前限制(由 src/generate.cpp 強制)

這些是明確的 runtime 契約,而非偶然行為。違反時會中止並寫出 stderr 診斷。

限制強制時機理由
所有 prompt 皆須滿足 prompt_len + max_tokens <= max_seq_len呼叫進入時每 lane 靜態 cache 配置。
批次模式中拒絕 cache_window > 0呼叫進入時Ring-buffer 語義目前與 lockstep kernel 難以乾淨組合。
批次模式中拒絕 beam_size > 1呼叫進入時Beam × batch 會將 cache 記憶體乘以 beam_size * batch;目前不支援。
所有 lane 必須共享 loopstemperaturetop_ktop_prepetition_penalty呼叫進入時每 lane 發散取樣無法高效批次化。
每 lane stop_sequencesgrammar 允許RuntimeMasking 於 host 端每 lane 套用。
Store-h KV cache 模式:完全支援Runtime所有 lane 使用相同 cache 模式。

何時靜態批次有幫助、何時有害

有幫助:

  • 長度相近的 prompt(例:分類、短生成)。
  • 穩態高吞吐量場景,lane 發散度低。

有害:

  • Prompt 長度變化大 — 短 lane 等待長 lane 到達 EOS。
  • 互動式工作負載 — 單一慢 lane 阻塞其他 lane 的首 token 延遲。

典型生產模式是維護多個批次大小,並將請求路由至形狀能容納它的最小批次。

整合模式

TinyLoop 是執行原語,不是服務框架。下列模式是經過驗證、能安全組合 runtime API 的編排。

模式 1 — 低複雜度單租戶服務

最簡單的正確結構:

  • 每個 process 一個 Model*(不跨 thread 共享)。
  • 外部請求佇列(例:stdin、HTTP endpoint、訊息佇列)。
  • 一個 worker thread 抽取佇列並為每個請求呼叫 generate_stream
// Pseudocode
tinyloop::Model* model = tinyloop::load_model("model.tinyloop", 2048);
std::queue<Request> queue;

std::thread worker([&]() {
while (running) {
Request req = queue.pop_blocking();
tinyloop::GenerateConfig cfg = build_config(req);
auto out = tinyloop::generate_stream(model, req.prompt.data(),
req.prompt.size(), cfg,
[&](int32_t tok, int pos) {
req.response.push_token(tok);
return !req.cancelled;
});
req.response.finish(out);
}
});

tinyloop::free_model(model);

取捨:最容易推理;最大吞吐量為 1× 單請求吞吐量;無 prefix 重用;延遲可預測。

模式 2 — 以分桶提升吞吐量的服務

當有持續流量且能為多個模型實例預算 VRAM 時:

  • N 個 process 組成的 worker pool,每個具備自己的 Model*
  • 派發器按相容形狀(loopsmax_tokens 帶、cache_modebeam_size)將請求分桶。
  • 每桶:若桶內 >= min_batch_size 個就緒請求,呼叫 generate_batch;否則退回 generate_stream

需調校的關鍵參數:

  • min_batch_size — 低於此值,靜態批次的 prefill 成本會超過 decode 的收益。
  • max_tokens 為基準的桶邊界 — 緊湊的區間可減少短 lane 的浪費 decode 週期。
  • Worker 數量 — 平衡 VRAM 預算與佇列深度。

模式 3 — 共享 scaffold 的多租戶服務

當多個請求共享共同的 system prompt / scaffold 時:

  • 在 process 啟動時將 scaffold 註冊到 PrefixPool
  • 透過 generate_with_pool 服務請求 — 池匹配最長已註冊 prefix 並略過其 prefill 成本。
  • 全新 prompt(無匹配)乾淨地降級為標準 prefill,無任何代價。
auto* pool = tinyloop::create_prefix_pool(32);
tinyloop::register_prefix(pool, model, system_prompt.data(),
system_prompt.size(), /*n_loops=*/8);

// 於請求 handler 中
auto out = tinyloop::generate_with_pool(model, pool, prompt.data(),
prompt.size(), cfg);

取捨:當 prefix 共享率 >50 % 時會有可測得的收益;低於此值時,雜湊查找與潛在的逐出波動可能抵銷收益。

模式 4 — 具 warm-start 升級的品質分層服務

對於想要「快速草稿,按需升級」的工作負載:

  1. 從 prompt 建構 L_used = 4ResumeHandle
  2. resume_generate(handle, 4, cfg) 提供草稿。
  3. 若使用者點擊「精煉」,呼叫 resume_generate(handle, 16, cfg) — 前 4 次迭代被略過。

這是 warm-start 架構原語的運維形式。實測 wall-clock 節省為 407M artifact 上單次升級 32.5 %;每次建構的升級次數越多,攤平效益越好。

模式 5 — 具 β 感知 fallback 的 speculative

若你的 checkpoint 為 β ≤ 1(transparent 或 bottleneck 範圍),自 speculative decode 幾乎是免費的 2× 吞吐量提升:

tinyloop::SpeculativeConfig sc;
sc.draft_loops = 2;
sc.verify_loops = 8;
sc.draft_ahead = 4;
auto out = tinyloop::generate_speculative(model, prompt.data(),
prompt.size(), sc);

在高 β checkpoint(β > 1)上接受率下降,可能使 speculative 比 baseline 更慢。在生產承諾使用 speculative 前務必先 benchmark。安全模式為:

  • 啟動時以 5–10 個代表性 prompt 進行小型 benchmark,比較 generategenerate_speculative 的 wall-clock。
  • 若 speculative 未達 >1.3× 加速,則對該 checkpoint 退回標準 decode。

組合 runtime 模式

TinyLoop 並未對每一種 runtime 模式組合強制互斥。有些組合可用,有些以明確錯誤拒絕,還有些為未定義。下方矩陣為目前已驗證的表面:

模式單一 decode批次 decodeSpeculativeTree-speculativePrefix 重用Warm-start
FP16 cache
INT8 cache⚠ 部分
INT4 cache(store-h INT4)⚠ 部分⚠ 部分⚠ 部分
Grammar✅(每 lane)
cache_window > 0⚠ 部分⚠ 部分
Beam search

✅ = 完全測試。⚠ 部分 = 目前 commit 可運作但未詳盡測試。❌ = runtime 明確拒絕。

請將此矩陣視為承重 — 拒絕組合由 src/generate.cpp 守衛,越過它們即為未定義行為。

Thread-safety 與並行摘要

  • 單一 Model* 非可重入。 在同一 Model* 上的任兩次 forward pass 必須於外部序列化。否則可變工作 buffer(buffers.mainbuffers.norm、scratch)會產生競爭。
  • Handle 綁定於模型。 以模型 A 建構的 PrefixCacheResumeHandlePrefixPool 不得用於模型 B。runtime 不強制檢查 — 屬契約錯誤。
  • Handle 非可重入。 對同一 handle 進行兩次 generate_from_prefix_cache 呼叫會在複製路徑上彼此競爭。請於外部序列化。
  • 並行 build + decode(一個 thread 建構新的 PrefixCache 而另一 thread 正在 decode)同樣不安全:build 會在模型的可變 buffer 上執行完整 forward。

安全模式:

  • 每 worker 一個模型。 每個 thread/process 一個 Model*,各自具備自己的 handle 集合。
  • Lock-per-model。 單一 Model* 以 mutex 保護;build 與 decode 呼叫序列化。
  • Replica pool。 N 個 process,各載入自己的模型,透過外部派發器均衡。

為什麼 TinyLoop 將此以 API 形式暴露,而非隱藏排程器邏輯

排程策略視產品而定:

  • 延遲敏感產品選擇小批次的 generate_stream
  • 吞吐量敏感產品以分桶使用 generate_batch
  • 成本敏感產品加入 PrefixPool 以減少重複 prefill。
  • 品質分層產品組合 ResumeHandle + speculative。

若以單一 serve() API 隱藏這些選擇的框架會作出強烈隱含假設(例:統一延遲預算、單一租戶),這對大多數真實部署都不成立。TinyLoop 的立場是:runtime 原語應明確,而 SLO 策略存在於整合程式碼(產品層)中。

實務整合檢查清單

在以 C++ 服務部署 TinyLoop 前:

  1. 決定並行模型 — lock-per-model vs model-per-worker。在程式碼中文件化。
  2. 設定 VRAM 預算 — 每模型 + 每 handle + decode cache。若預算無法滿足則中止啟動。
  3. 限制存活 handle 數PrefixCache + ResumeHandle + PrefixPool 總項目數。以上限逐出,而非以記憶體壓力逐出(記憶體壓力下的逐出會破壞延遲)。
  4. 針對每個 checkpoint 為 speculative 做 benchmark — 不要假設會加速;永遠驗證。
  5. 加入模式護欄 — 於派發器層拒絕產品不需的組合(在 runtime 自有拒絕之上做深度防禦)。
  6. 接上健康探針load_model 失敗、建構時 OOM、反覆 decode 中止應通知運維人員,而非僅記錄 log。
  7. 依部署明確選擇貪婪 vs stochastic — 生產確定性可重現性依賴明確種子與貪婪模式。
  8. 接入 GenerateStats — 每請求記錄 prefill_msfirst_token_msdecode_msstop_reason。這些是唯一能快速捕捉 runtime 模式誤配置的訊號。

取捨

  • 靜態批次提升吞吐但可能損害尾端延遲。 短 lane 等待長 lane。
  • 明確整合帶來控制但需要編排工程。 產品團隊承擔排程策略,而非 TinyLoop。
  • 模式組合需要在呼叫端程式碼設護欄 — runtime 僅守護明顯的不可能,而非每個產品專屬不變式。
  • VRAM 預算為手動。 無內建記憶體壓力逐出;handle 生命週期是產品責任。

另見