Scoring API
Scoring 呼叫執行 forward 計算,但不進入自迴歸取樣迴圈。它們是評估、排序、不確定性估計,以及任何需要取得 logits 而不需要 decode loop 的工作負載之 C++ 進入點。
| 函式 | 用途 |
|---|---|
score | 在固定 loop 深度下取得完整的 [seq_len, vocab] logits。 |
score_last_token | 僅輸出最後一個位置的 logits [vocab] — 最低記憶體流量。 |
score_logit_lens | 每個 loop 深度的最終 logits [n_loops, vocab],用於深度演化分析。 |
score_with_uncertainty | 兩個深度的 forward,附帶每 token 的 KL 不確定性訊號。 |
score_with_consistency_escalation | 自適應深度 forward:當低深度與高深度不一致時提升 loop 次數。 |
除非另有說明,本節中每一個 scoring 函式:
- 定義於
include/tinyloop.h的namespace tinyloop下。 - 會修改
Model內部工作 buffer(buffers.main、buffers.norm、scratch);若無外部序列化,對同一個Model*的並行呼叫是不安全的。 - 不會推進任何 decode cache。除非 API 明確使用 cache helpers,否則不會配置或附加到
RuntimeKVCache。 - 在違反不變條件時(null 指標、維度越界、GPU OOM)會向 stderr 寫出診斷並中止。呼叫端應將這些視為致命錯誤:runtime 不會 unwind。
- 結果寫入由呼叫端配置的輸出 buffer;logits 本身不會進行 heap 配置。
score
void score(Model* model,
const int32_t* tokens,
int seq_len,
float* logits_out,
int n_loops = 8);
在固定 loop 深度下,為給定的 prompt 計算完整的 [seq_len, vocab_size] logit 張量。
參數
| 名稱 | 型別 | 預設 | 描述 |
|---|---|---|---|
model | Model* | — | 已載入的模型指標。不得為 nullptr。 |
tokens | const int32_t* | — | Token-id 陣列,長度為 seq_len。Token id 必須滿足 0 <= t < vocab_size。 |
seq_len | int | — | 要評分的 token 數量。必須滿足 1 <= seq_len <= max_seq_len。 |
logits_out | float* | — | 輸出 buffer,連續記憶體,大小為 seq_len * vocab_size * sizeof(float)。呼叫端配置;無需預先歸零。 |
n_loops | int | 8 | 共享 loop block 的執行次數。必須滿足 1 <= n_loops <= 64。較高的值以線性計算代價換取品質提升(至飽和為止)。 |
回傳
void。結果以 row-major 順序寫入 logits_out:元素 (t, v) 位於偏移量 t * vocab_size + v。
例外 / 錯誤條件
Runtime 會向 stderr 寫出訊息並在以下情況中止:
model == nullptr。seq_len < 1或seq_len > max_seq_len(於load_model時指定)。n_loops < 1或n_loops > 64。- Prefill 期間或內部 scratch 輸出 buffer 配置時發生 GPU 記憶體不足。
備註
- 記憶體佔用。 在所有 scoring 變體中,
score的輸出佔用最大。當vocab_size = 50257、seq_len = 2048時,光輸出張量在 FP32 下就約 393 MB。在 decode 熱路徑請優先使用score_last_token。 - 計算為主導成本。 對於典型形狀(
d = 2048、L = 8、seq_len = 1024),loop-block GEMM 佔 wall-clock 的 90 % 以上;head projection 是次要成本。 prefill_chunk互動。 當模型以prefill_chunk > 0載入時,score受 chunk 列預算約束:seq_len超過 chunk 大小的呼叫將以多個 pass 執行,可降低尖峰 VRAM,但換來 kernel launch overhead。
範例
std::vector<int32_t> tokens = tokenize("The quick brown fox");
std::vector<float> logits(tokens.size() * vocab_size);
tinyloop::score(model, tokens.data(), tokens.size(), logits.data(), /*n_loops=*/8);
// logits[(t * vocab_size) + v] 是位置 t 上 token v 的未歸一化 logit
另見
score_last_token— 若只需要最後位置。score_logit_lens— 取得深度解析的最終 logits。
score_last_token
void score_last_token(Model* model,
const int32_t* tokens,
int seq_len,
float* logits_out,
int n_loops = 8);
僅在最後一個 token 位置計算 logits。適用於排序、為候選延續評分,以及每位置 logits 會被浪費的 decode helper。
參數
| 名稱 | 型別 | 預設 | 描述 |
|---|---|---|---|
model | Model* | — | 已載入的模型指標。不得為 nullptr。 |
tokens | const int32_t* | — | Token-id 陣列,長度為 seq_len。 |
seq_len | int | — | Token 數量,1 <= seq_len <= max_seq_len。 |
logits_out | float* | — | 輸出 buffer,大小為 vocab_size * sizeof(float)。 |
n_loops | int | 8 | 共享 block 迭代次數,1 <= n_loops <= 64。 |
回傳
void。logits_out[v] 是最後位置上 token v 的未歸一化 logit。
例外 / 錯誤條件
與 score 相同的錯誤路徑。越界的 n_loops、model 為 null,或 seq_len 不在 [1, max_seq_len] 之內皆為致命。
備註
- 記憶體優勢。 輸出 buffer 為
vocab_size個 float(幾百 KB),相較於score的seq_len * vocab_size。這是降低 scoring 工作負載中 host↔device 傳輸最主要的手段。 - 和
score具有相同的 forward 成本(節省完全在於輸出形狀與 forward 後的複製);若同時需要完整 logits 與最後 token 捷徑,只需呼叫score一次而非兩者皆呼叫。 - 常用於在 TinyLoop 內建的 Generation API 之外實作自訂 decode loop。
範例
std::vector<float> next_logits(vocab_size);
tinyloop::score_last_token(model, prompt.data(), prompt.size(), next_logits.data(), 8);
int next_token = std::distance(next_logits.begin(),
std::max_element(next_logits.begin(), next_logits.end()));
score_logit_lens
void score_logit_lens(Model* model,
const int32_t* tokens,
int seq_len,
float* logits_out,
int n_loops = 8);
在每一個從 1 到 n_loops 的 loop 深度上,產生最後 token 的 logits,排列為 [n_loops, vocab_size]。這是 logit-lens 原語:它讓呼叫端觀察模型預測如何隨著更多 loop 迭代而演化。
參數
| 名稱 | 型別 | 預設 | 描述 |
|---|---|---|---|
model | Model* | — | 已載入的模型指標。 |
tokens | const int32_t* | — | Token id,長度為 seq_len。 |
seq_len | int | — | 1 <= seq_len <= max_seq_len。 |
logits_out | float* | — | 輸出 buffer,大小為 n_loops * vocab_size * sizeof(float)。 |
n_loops | int | 8 | Loop 深度上限。深度 1, 2, ..., n_loops 的 logits 皆會寫出。 |
回傳
void。對於 l = 1, ..., n_loops,logits_out[(l-1) * vocab_size + v] 是 l 次 loop 迭代後最後位置上 token v 的 logit。
例外 / 錯誤條件
與 score 相同。此外,n_loops 為 0 視為無效(至少需要要求一個深度)。
備註
- 成本對
n_loops呈超線性。 底層實作並非在所有配置下對各深度 hidden state 進行 memoize;請視其為分析原語,而非熱路徑服務原語。 - 搭配
score_with_uncertainty使用。 Logit-lens 輸出是 KL 為基礎的不確定性啟發式的原始素材。 - 在訓練過的 looped 模型上進行的深度演化分析通常顯示非單調的 argmax 軌跡 — 一個 token 可能在
l=3看似「決定」,在l=4翻轉,到l=8又重新穩定。詳見專案論文的討論。
score_with_uncertainty
void score_with_uncertainty(Model* model,
const int32_t* tokens,
int seq_len,
float* logits_hi_out,
float* kl_out,
int L_lo = 4,
int L_hi = 8);
在兩個不同 loop 深度(L_lo 與 L_hi)執行 forward,並同時輸出高深度 logits 與兩個分佈間逐位置的 KL divergence 作為不確定性訊號。
參數
| 名稱 | 型別 | 預設 | 描述 |
|---|---|---|---|
model | Model* | — | 已載入的模型指標。 |
tokens | const int32_t* | — | Token id,長度為 seq_len。 |
seq_len | int | — | 1 <= seq_len <= max_seq_len。 |
logits_hi_out | float* | — | L_hi logits 的輸出 buffer,形狀 [seq_len, vocab_size]。 |
kl_out | float* | — | 逐位置 KL divergence 的輸出 buffer,形狀為 [seq_len] 個 float。每個值為 KL(softmax(logits_hi) || softmax(logits_lo))。 |
L_lo | int | 4 | 低深度 loop 次數。1 <= L_lo < L_hi。 |
L_hi | int | 8 | 高深度 loop 次數。L_lo < L_hi <= 64。 |
回傳
void。高深度 logits 寫入 logits_hi_out;逐位置 KL 值寫入 kl_out。
例外 / 錯誤條件
與 score 相同,另加:
L_lo >= L_hi無效。L_lo或L_hi不在[1, 64]內皆無效。
備註
- 兩次 forward pass。 計算成本大約為
L_lo + L_hi次 loop-block 運算,相較於單一深度 score 的L_hi。請為額外計算保留預算。 - KL 為 high-to-low。 依慣例此呼叫回報
KL(p_hi || p_lo)— 高 KL 意謂低深度分佈被高深度分佈「驚訝到」,是有用的「需要更多思考」訊號。 - 對於想進行自適應計算但不需要明確 KL 檢視的工作負載,請見
score_with_consistency_escalation。
score_with_consistency_escalation
void score_with_consistency_escalation(Model* model,
const int32_t* tokens,
int seq_len,
float* logits_out,
int* escalated_out,
int* L_used_out,
int L_lo = 8,
int L_hi = 16,
int escalate_L = 32);
自適應深度 scoring:首先以 L_lo 執行。對每個位置,若與 L_hi 的便宜一致性檢查不符,則僅將該位置重跑到 escalate_L。適用於希望在困難 token 上取得高品質、而不必在每處都付出 escalate_L 成本的工作負載。
參數
| 名稱 | 型別 | 預設 | 描述 |
|---|---|---|---|
model | Model* | — | 已載入的模型指標。 |
tokens | const int32_t* | — | Token id,長度為 seq_len。 |
seq_len | int | — | 1 <= seq_len <= max_seq_len。 |
logits_out | float* | — | 輸出 buffer,形狀 [seq_len, vocab_size]。包含每個位置實際使用的 logits(若一致則為 L_lo,若提升則為 escalate_L)。 |
escalated_out | int* | — | 輸出 buffer,形狀 [seq_len]。每個位置寫入 0/1:1 代表該位置已提升至 escalate_L,否則為 0。 |
L_used_out | int* | — | 輸出 buffer,形狀 [seq_len]。寫入每個位置實際使用的 loop 深度。 |
L_lo | int | 8 | 第一輪 loop 深度。 |
L_hi | int | 16 | 一致性檢查 loop 深度。必須滿足 L_lo < L_hi。 |
escalate_L | int | 32 | 提升位置所使用的 loop 深度。必須滿足 L_hi < escalate_L <= 64。 |
回傳
void。主要 logits 寫入 logits_out;逐位置提升旗標寫入 escalated_out;逐位置使用的 loop 深度寫入 L_used_out。
例外 / 錯誤條件
與 score 相同,另加:
L_lo、L_hi、escalate_L任一不在[1, 64]之內。- 順序違反:必須滿足
L_lo < L_hi < escalate_L。
備註
- 最壞情況成本是每個位置都提升時的
(L_hi + escalate_L) * seq_len次 loop-block 運算。實務上,訓練良好的模型其提升率通常較低低於 20 %。 - 非確定性繼承自 sampling-free forward:給定
tokens、L_lo、L_hi,提升決策是確定性的,但實務提升率取決於模型。 - 該檢查比較
L_lo與L_hi分佈在每個位置的 argmax 一致性;若需要更豐富的一致性訊號(完整 KL),請使用score_with_uncertainty。
Scoring 路徑中的狀態轉換
對所有 score 變體:
- Token 嵌入至模型 buffer 空間。
- Pre-block 執行(
n_pre_blocks)。 - 共享 loop block 重複
n_loops次。 - 依所請求的輸出模式進行最終 LN + head projection。
除非 API 明確使用 cache helpers,否則不會推進 decode cache。模型的 buffers.main 每次呼叫都會被覆寫;希望在多次 scoring 呼叫間保留 residual stream 的呼叫端,必須透過較低階 API 自行做快照(不屬於公開 scoring 表面)。
效能模型
| 輸出模式 | 輸出位元組 | Forward 成本 | 典型用途 |
|---|---|---|---|
score | seq_len * vocab * 4 | 1× forward | 離線評估 |
score_last_token | vocab * 4 | 1× forward | Decode helpers、排序 |
score_logit_lens | n_loops * vocab * 4 | ~1.5× forward | 可解釋性分析 |
score_with_uncertainty | (seq_len + 1) * vocab * 4 | ~2× forward | 主動學習、RLHF scoring |
score_with_consistency_escalation | seq_len * vocab * 4 + 2 * seq_len * 4 | L_hi + β * escalate_L forward | 生產級自適應計算 |
β 是實證提升率;在訓練良好的 1B-effective 模型上通常介於 0.05 至 0.20。
取捨與注意事項
- API 基於 token id;tokenizer 語義外於 TinyLoop。
- 一致性/不確定性 API 依設計增加額外 forward pass — 以吞吐量換取自適應品質訊號。請視為 opt-in。
- 對於
prefill_chunk > 0的配置,score 類 API 必須遵守 chunk 列預算(見 生命週期與記憶體模型)。 - 整個過程中,輸出 buffer 所有權皆由呼叫端管理 — TinyLoop 絕不會自行配置 logit 張量。