Generation API
Generation API 是 runtime 狀態轉換最明顯之處:cache 增長、停止條件與每 token 控制流。它們是自迴歸 decode 表面。
| 函式 | 用途 |
|---|---|
generate | 阻塞式批次 decode。完成時回傳完整 prompt+延續。 |
generate_stream | 帶每 token callback 的 decode;呼叫端可觀察並於串流中途中止。 |
generate_batch | 在一個 Model* 上循序 decode 一批 prompt。 |
generate_speculative | 鏈式自 speculative decoding — 在 L_draft 草擬、在 L_verify 驗證,零輔助權重。 |
generate_tree_speculative | 樹狀分支自 speculative,每步具備 K 寬度候選。 |
所有 generation 函式共用相同的底層基礎:
- 依
prompt_len + max_tokens配置全新的RuntimeKVCache。 - 以配置的 loop 深度對 prompt 進行 prefill。
- 執行具明確停止策略(EOS、stop-sequence 結尾匹配、callback 中止、grammar 死路、
max_tokens)的每 token decode loop。 - 回傳前釋放 cache。
所有 generation 函式都不會取得 cache 所有權 — 它們總是在函式退出時於內部清理。PrefixCache / ResumeHandle(見 快取重用 API)才是跨呼叫重用狀態的 opt-in 路徑。
generate
std::vector<int32_t> generate(Model* model,
const int32_t* prompt,
int prompt_len,
const GenerateConfig& config);
標準阻塞式自迴歸 decode。對 prompt 執行 prefill,然後每次取樣一個 token 直到停止條件觸發。
參數
| 名稱 | 型別 | 預設 | 描述 |
|---|---|---|---|
model | Model* | — | 已載入的模型指標。不得為 nullptr。 |
prompt | const int32_t* | — | Prompt token-id 陣列,長度為 prompt_len。Token id 必須滿足 0 <= t < vocab_size。 |
prompt_len | int | — | Prompt 中的 token 數量。必須滿足 1 <= prompt_len + config.max_tokens <= max_seq_len。 |
config | const GenerateConfig& | — | Decode 配置;見下方 GenerateConfig。 |
回傳
std::vector<int32_t> — 完整 token 流,prompt 在前、decoded token 在後。長度為 prompt_len + emitted,其中 emitted <= config.max_tokens,依停止策略而定。
例外 / 錯誤條件
致命錯誤時,runtime 會向 stderr 寫出訊息並中止。條件:
model == nullptr。prompt_len < 1或prompt_len + config.max_tokens > max_seq_len。RuntimeKVCache配置期間記憶體不足。- 無效的取樣參數(
temperature < 0、取樣啟用時top_k <= 0、beam_size > 1卻搭配不一致的 beam-path 旋鈕)。
範例
tinyloop::GenerateConfig cfg;
cfg.max_tokens = 64;
cfg.loops = 8;
cfg.temperature = 0.8f;
cfg.top_k = 50;
cfg.stop_sequences = {{13}}; // 在 "." 停止(GPT-2 中 "." 的 id)
auto out = tinyloop::generate(model, prompt.data(), prompt.size(), cfg);
// out = concat(prompt, generated_tokens)
generate_stream
std::vector<int32_t> generate_stream(Model* model,
const int32_t* prompt,
int prompt_len,
const GenerateConfig& config,
const GenerateTokenCallback& on_token,
GenerateStats* stats = nullptr);
Decode 語義與 generate 相同,但每 token 放出後會同步呼叫 on_token,並可選擇性地將每階段耗時填入 GenerateStats struct。decode 結束時回傳值仍是完整的 token 流。
參數
| 名稱 | 型別 | 預設 | 描述 |
|---|---|---|---|
model | Model* | — | 已載入的模型指標。 |
prompt | const int32_t* | — | Prompt token id。 |
prompt_len | int | — | 1 <= prompt_len + max_tokens <= max_seq_len。 |
config | const GenerateConfig& | — | 見 GenerateConfig。 |
on_token | const GenerateTokenCallback& | — | 每放出一個 token 呼叫一次的 callback。簽章:bool(int32_t token, int position)。回傳 false 可在包含此 token 後中止 decode。 |
stats | GenerateStats* | nullptr | 選擇性的統計輸出。欄位包括 prefill_ms、first_token_ms、decode_ms、tokens_emitted、stop_reason。 |
回傳
std::vector<int32_t> — 與 generate 相同。
Callback 語義
on_token從 decode 執行緒同步呼叫。它在緊湊的 decode loop 中執行;請避免在 callback 中進行阻塞工作(磁碟 I/O、網路傳送),否則請以有界佇列包裝。- Callback 以順序呼叫,每個放出的 token 呼叫一次。
- 回傳
false會乾淨地中止:最後一個 token 仍會包含在回傳 vector 中;cache 清理仍會執行。 - Callback 不會收到 logits。如需 logit 層級觀察,請在 decode loop 之外使用
score系列。
GenerateStats 欄位
| 欄位 | 型別 | 描述 |
|---|---|---|
prefill_ms | double | Prefill 階段(prompt → 第一個 token 就緒)的 wall-clock 時間。 |
first_token_ms | double | 從呼叫進入到第一個 token 放出的 wall-clock 時間。 |
decode_ms | double | 穩態 decode loop 的 wall-clock 時間。 |
tokens_emitted | int | 放出的 token 數(不含 prompt)。 |
stop_reason | enum | EOS、STOP_SEQUENCE、MAX_TOKENS、CALLBACK_ABORT、GRAMMAR_DEAD_END 其中之一。 |
loops_used | int | 實際使用的有效 loop 深度(對自適應模式有意義)。 |
範例
tinyloop::GenerateStats stats;
auto out = tinyloop::generate_stream(
model, prompt.data(), prompt.size(), cfg,
[](int32_t tok, int pos) {
std::cout << detokenize(tok) << std::flush;
return true; // 繼續;回傳 false 以中止
},
&stats);
std::cerr << "decode rate: "
<< stats.tokens_emitted / (stats.decode_ms / 1000.0)
<< " tok/s\n";
generate_batch
std::vector<int32_t> generate_batch(Model* model,
const std::vector<std::vector<int32_t>>& prompts,
const GenerateConfig& config);
循序批次 decode。每個 prompt 以全新 cache decode 至完成,然後下一個才開始。回傳值是所有輸出的扁平串接(每個 prompt 的 prompt + 延續連接在一起)。
參數
| 名稱 | 型別 | 預設 | 描述 |
|---|---|---|---|
model | Model* | — | 已載入的模型指標。 |
prompts | const std::vector<std::vector<int32_t>>& | — | Prompt 批次。每一個內部 vector 是一個 prompt。 |
config | const GenerateConfig& | — | 套用於每個 prompt 的共享配置。目前不支援 per-prompt 配置 — 如有需要,請直接在迴圈中呼叫 generate。 |
回傳
std::vector<int32_t> — 所有輸出的扁平串接。要還原每個 prompt 的邊界,呼叫端必須追蹤每個 prompt 的長度與每個結果放出的 token 數(可由 prompt 長度加 config 的 max_tokens 上限,再減去早期停止數取得)。
備註
- 這不是 continuous batching。 TinyLoop 在同一個
Model*上一次處理一個 prompt。真正的批次 prefill + paged attention 是多週的重構,目前不在範圍內。 - 每 prompt 的早期停止(EOS、stop sequences)運作方式與
generate相同。 - 對於並行批次服務,請每個 worker thread 使用一個
Model*(較高 VRAM)或外部的 lock-per-model 方案 — 指引與 生命週期與記憶體模型 相同。
generate_speculative
std::vector<int32_t> generate_speculative(Model* model,
const int32_t* prompt,
int prompt_len,
const SpeculativeConfig& config);
鏈式自 speculative decoding。以較便宜的 L_draft 草擬 draft_ahead 個 token,在單次 L_verify forward 中驗證,接受最長匹配的 prefix,並推進 cache。不需要輔助草擬模型 — 草擬與驗證都使用相同的共享 loop block,只是在不同深度。
參數
| 名稱 | 型別 | 預設 | 描述 |
|---|---|---|---|
model | Model* | — | 已載入的模型指標。 |
prompt | const int32_t* | — | Prompt token id。 |
prompt_len | int | — | 1 <= prompt_len + max_tokens <= max_seq_len。 |
config | const SpeculativeConfig& | — | 見 SpeculativeConfig。 |
回傳
std::vector<int32_t> — 形狀與 generate 相同:prompt 後接 decoded token。
SpeculativeConfig
| 欄位 | 型別 | 預設 | 描述 |
|---|---|---|---|
max_tokens | int | 64 | 總放出上限(不含 prompt)。 |
draft_loops | int | 2 | 便宜的草擬 pass loop 深度。較低 = 較快草擬、較多拒絕。 |
verify_loops | int | 8 | 驗證 pass loop 深度。通常等於 model.default_loops。 |
draft_ahead | int | 4 | 每次驗證循環草擬的 token 數。較大 = 每次驗證有更多並行性,但 miss 時浪費的工作也更多。 |
temperature | float | 0.0 | 貪婪接受是被充分描述的路徑;stochastic speculative 仍屬實驗性。 |
seed | uint64_t | 0 | Stochastic 模式的 RNG 種子。temperature == 0 時忽略。 |
接受語義
- 草擬在
draft_loops下貪婪產生draft_ahead個 token。 - 驗證在
verify_loops下對整個草擬序列執行一次。 - 在
verify_loops下其argmax與草擬相符的最長 prefix 會被接受。 - 首個不符的位置會從驗證分佈貢獻一個 bonus token;cache 推進到該點,下一輪循環開始。
備註
- Forward pass 減少,不是 wall-clock。 測得的接受率對應的是較少的全深度
forward_with_cache呼叫,而非等比例的 wall-clock 加速 — 草擬 pass 仍需耗費計算。在調校良好的配置下,407M checkpoint 的實際 wall-clock 加速為 2.0–2.8×(見論文 §6)。 - 貪婪是安全模式。 Stochastic speculative decode 引入額外細節(取樣下草擬/驗證分佈不符),目前實作並未在所有參數化下提供保證。
- β 敏感。 在高 β 模型(β > 1)上,草擬與驗證分佈發散更快,接受率崩潰,甚至可能產生比 baseline 更慢的 decode。請在為某 checkpoint 承諾使用 speculative 前先做實證測試。
generate_tree_speculative
std::vector<int32_t> generate_tree_speculative(Model* model,
const int32_t* prompt,
int prompt_len,
const TreeSpeculativeConfig& config);
樹狀分支 speculative decoding。不採用線性 token 鏈,而是每步草擬 K 個候選形成一棵樹,然後以單次 tree-mask gated forward 驗證整棵樹。接受最長的匹配路徑。
參數
| 名稱 | 型別 | 預設 | 描述 |
|---|---|---|---|
model | Model* | — | 已載入的模型指標。 |
prompt | const int32_t* | — | Prompt token id。 |
prompt_len | int | — | 1 <= prompt_len + max_tokens <= max_seq_len。 |
config | const TreeSpeculativeConfig& | — | 擴充自 SpeculativeConfig,加上 K_branches 與 tree_depth。 |
回傳
std::vector<int32_t> — 完整輸出序列。
TreeSpeculativeConfig
| 欄位 | 型別 | 預設 | 描述 |
|---|---|---|---|
max_tokens | int | 64 | 總放出上限。 |
draft_loops | int | 2 | 草擬 pass loop 深度。 |
verify_loops | int | 8 | 驗證 pass loop 深度。 |
draft_ahead | int | 4 | 樹深度 — 每個循環的連續草擬步數。 |
K_branches | int | 2 | 每個草擬步保留的候選 token 數。樹的總大小約為 K_branches^draft_ahead。 |
temperature | float | 0.0 | 貪婪是位元精確路徑(與非 speculative 輸出一致);取樣屬實驗性。 |
備註
- 架構獨有。 在草擬與驗證深度使用相同權重的樹狀 speculative decoding,只有在共享 loop block 時才可能。主流 runtime 需要輔助草擬 head(Medusa)或小型草擬模型(EAGLE)才能達到類似吞吐量。
temperature == 0時位元精確。 在相同種子與K_branches == 2下,貪婪 tree-speculative 產生的 token 與generate_speculative以draft_ahead=1+ 鏈式 decode 相同。此點已在多個 prompt 上驗證測試過。- 吞吐量甜蜜點在 407M artifact 上為
K_branches=2, draft_ahead=2, draft_loops=2, verify_loops=8。較高的K很快就不再划算(樹質量爆炸而品質增益有限)。
GenerateConfig
由 generate、generate_stream 與 generate_batch 共享。
| 欄位 | 型別 | 預設 | 描述 |
|---|---|---|---|
max_tokens | int | 64 | 放出 token 的上限。 |
loops | int | model.default_loops | Loop 深度。明確設定可覆寫 checkpoint 訓練的預設值。 |
temperature | float | 0.0 | Softmax temperature。0.0(或 <= 0.01)代表貪婪 argmax。必須 >= 0。 |
top_k | int | 50 | 取樣前保留前 k 個 token。取樣啟用時必須 >= 1。temperature == 0 時忽略。 |
top_p | float | 1.0 | Nucleus sampling 閾值。1.0 表示停用。在 top_k 之後套用。 |
cache_window | int | 0 | KV cache 的滑動視窗上限。0 代表無上限(受 max_seq_len 限制)。 |
repetition_penalty | float | 1.0 | HuggingFace 慣例的懲罰,在 temperature/top-k/top-p 前套用。1.0 代表停用。 |
stop_sequences | std::vector<std::vector<int32_t>> | {} | 在放出輸出尾部匹配時終止 decode 的 token-id 序列。匹配的 token 仍會包含在回傳流中。 |
beam_size | int | 0 | 0 或 1 = 標準取樣。>= 2 切換為確定性 beam search;取樣旋鈕會被忽略。 |
length_penalty | float | 1.0 | 僅 beam search 使用。以 log_prob / max(1, len)^length_penalty 排序 beam。 |
early_exit_patience | int | 0 | 0 停用自適應深度早退。>0 啟用:在 min_L 之後連續 patience 次穩定探測後退出。 |
early_exit_min_L | int | 2 | 早退的首探測底線。 |
early_exit_peek_every | int | 1 | min_L 之後的探測頻率。 |
early_exit_logit_margin | float | 0.0 | >0 將穩定性判斷由 hidden-L2 切換為 logit-margin。對高 β 模型的實證正確訊號。 |
sample_seed | uint64_t | 0 | Stochastic 取樣的 RNG 種子。給定種子 + config + 模型即為確定性。 |
grammar | Grammar* | nullptr | 選擇性的受限 decoding 文法。grammar 建構請參見 受限 decoding 參考。 |
正典 decode 狀態機
對於 generate 與 generate_stream,內部狀態機為:
- 配置大小為
prompt_len + config.max_tokens(若設定cache_window則再做調整)的RuntimeKVCache。 - 以
config.loops透過prefill_with_kv_cache預填 prompt。 - 從 post-prefill hidden state 產生第一個 token 的 logits。
- 對每個 token 在 loop 中:
a. 對已放出位置的 logits 套用
repetition_penalty。 b. 若設定grammar則套用遮罩;若遮罩全為零,則以GRAMMAR_DEAD_END停止。 c. 取樣下一個 token(貪婪或 stochastic)。 d. 將 token 附加到輸出 vector。 e. 以config.loops透過decode_with_kv_cache*推進 cache。 f. 檢查停止條件:EOS、stop-sequence 結尾匹配、callback 中止、max_tokens上限。 - 回傳完整 vector。
Speculative 變體以 draft-then-verify 子迴圈取代單步 decode;cache 推進以多 token chunk 而非每次一 token 進行。
取捨與限制
- 無 continuous batching。 批次大小是邏輯上的,不是並行的。真正的吞吐量批次需要 paged-attention 重構。
- 同一
Model*非可重入。 可變工作 buffer 阻止同一載入模型上無外部同步的並行 generate 呼叫。 - 確定性品質保證在貪婪設定下最強。 取樣的可重現性取決於明確的
sample_seed配置;跨平台數值可重現性無法保證(float 累加順序取決於 CUDA stream 排程)。 - Speculative 模式對 β 敏感。 見上方各函式備註;在生產中承諾使用 speculative decode 前,必須對每個 checkpoint 做實證測試。
另見
- 生命週期與記憶體模型 — 誰擁有 cache 與 buffer。
- 快取重用與狀態轉換 API —
PrefixCache、ResumeHandle、PrefixPool。 - Scoring API — 無取樣的 forward。
- 受限 decoding — Grammar 參考(Python API 頁面;C++ 表面與之對應)。