Skip to content

context 的 token 預算應是 per-model 參數而非全域寫死 200:7 個 engine 只有 2 個吃 prompt,其餘照樣截斷且照印 injected(N) #164

Description

@kiki830621

Problem

Original text(使用者原話,2026-08-07):
「應該要分開,當作重要的模型參數來考慮」

PromptRenderer.defaultTokenBudget = 200全域寫死的單一常數,套用到所有 backend。但 prompt 能力是逐模型的性質,差異極大:多數 backend 根本不吃 prompt,而吃的那些上限也不同。目前的設計把「Whisper 的限制」當成「所有引擎的限制」。

Type

feature

Priority

P1。這不只是設計潔癖 —— 它讓 context biasing 在預設路由下靜默失效,而 CLI 還印出成功訊息(見下方 Impact)。

Actual(現況,逐項在原始碼查證)

一、預算是單一全域常數,無任何 per-model 概念

// Sources/BestASRKit/Context/PromptRenderer.swift
public static let defaultTokenBudget = 200

public static func render(_ context: LoadedContext,
                          tokenBudget: Int = defaultTokenBudget) -> Rendered

參數位存在,但全 codebase 無任何呼叫點傳非預設值 —— 只有一個 call site:

Sources/BestASRKit/CommandCore.swift:103
    return ContextBundle(loaded: loaded, rendered: PromptRenderer.render(loaded))

二、7 個引擎中只有 2 個會消費 prompt

引擎 原始碼中 prompt 出現次數
WhisperKitEngine 12
WhisperCppEngine 5
ParakeetEngine 0
AppleSpeechEngine 0
ChineseFamilyEngine(paraformer/sensevoice) 0
ExternalProcessEngine 0
Engine(protocol) 0

對那 5 個引擎,整套 render 照跑、截斷照發生、injected (N) 照印 —— 然後產物被丟棄。

三、連「吃 prompt 的那兩個」限制也不同,且無一由 renderer 感知

  • WhisperKitEngine.clampedPromptTokens(_:limit:224) —— tokenizer 實測的 224,取 suffix(224)
  • WhisperCppEngine —— 無自訂 clamp,直接 --prompt <字串> 交給 whisper-cli,理論上可超過 200

註解本身寫明 200 是為 Whisper 調的:

/// ~200-token budget, below Whisper's ~224 practical prompt limit; the
/// WhisperKit engine additionally clamps encoded tokens as a safety net.

設計本身很嚴謹 —— 問題是它被無條件套用到不是 Whisper 的東西

四、suffix(224) 的方向與 renderer 的優先序相反

PromptRenderer 依 names → terms → phrases 排序,最重要的排最前clampedPromptTokens 溢出時取 suffix(224)砍掉的正是最前面的人名。目前 200 < 224 撞不到,但兩個機制的優先方向不一致,是潛在的坑。

Impact

這不是理論問題 —— 實測已經咬到。 2026-08-07 轉錄 IMPS 2026 會議錄音(10 檔約 7.3 小時)時:

recommendhigh profile 選出 fluid-parakeet 0.6b-v3(「ranked #1 of 20 benchmarked candidates」,WER 6.1%)—— 也就是唯一完全不吃 prompt 的那一類。CLI 照樣印:

Context: <…>/.bestasr/context
  injected (49): Che Cheng, Che, Hau-Hung Yang, …, Joint Thurstonian, …
  truncated (53): criterion-related validity, …, Q-matrix, …

看起來 context biasing 生效了。實際上完全沒有:

目標詞 無 context 探針 parakeet + context whisper 無 context whisper + context
Joint Thurstonian 「during the thumbia」 「during Sonya」 「joint adornment models」 「Joint Thornia Models」
Likert 「the request scale」 「the scale or L F」

Joint ThurstonianLikert scale 都在已注入的 49 個裡,parakeet 命中數 0。同一段音訊換 whisper.cpp large-v3-turbo,context 讓輸出從「joint adornment」修到「Joint Thornia」並辨識為專有名詞 —— 證明 context 本身有效,失效的是 backend。

三個後果:

  1. 使用者被誤導injected (N) 讀起來是「已生效」,實際上對 5/7 引擎是 no-op。使用者會據此去精簡詞表 —— 白工。
  2. benchmark 路由與 context 目標衝突。routing 只看 WER,於是在有 context_dir 的情況下仍可能選中不吃 prompt 的引擎。而該 benchmark 語料顯然不像會議廳+專業術語音訊 —— 排名第一的模型在這批素材上可讀性最差。
  3. 截斷是真的發生的。實測 102 個項目只注入 49、截斷 53:42 個人名吃掉 171 tokens(85.5%),52 個術語只擠進 7 個,8 個 phrases 因 exhausted flag 被整類丟棄。對 parakeet 而言這整段計算與取捨都毫無意義。

Expected

把 prompt 預算視為引擎能力宣告,而非全域常數:

  • Engine protocol 增加 prompt 能力宣告,兩態即可:不支援 / 支援且上限 N(兩個 whisper backend 實測上限皆為 224;若日後接入上限未知的後端再擴為三態)
  • PromptRenderer.render 由所選引擎取得 budget,而非 defaultTokenBudget
  • 引擎不支援 prompt 時整段跳過 render,並明確告知使用者「此後端不支援 context biasing」——不要injected (N)
  • WhisperCppEngine 的上限明確設為 224whisper-cli 文件的 n_text_ctx/2;並非「無上限」——先前以為它沒有 clamp 所以可高於 200,實測其說明明文標示上限,與 WhisperKit 一致)
  • 檢視 clampedPromptTokenssuffix 方向是否應改為 prefix,以與 renderer 的優先序一致
  • recommend / routing 在偵測到 context 存在時,把「是否支援 prompt」納入選型考量;至少在選中不支援的引擎時發出警告

Residue

「哪個模型在會議廳音訊上最好」不是本 issue 能解的。本 issue 只處理能力宣告的正確性 —— 讓系統不再宣稱做了它沒做的事。benchmark 語料是否該擴充到會議/演講類音訊,是另一條線(現行語料顯然讓 parakeet 在此類素材上被高估)。

相關

Clarity Surface(idd-clarify run 2026-08-06T18:55:06Z)

Type Source Suggested canonical Status
ambiguity 「應該要分開,當作重要的模型參數來考慮」 「分開」有兩種讀法:(a) budget 數值依模型不同(本 issue 採此解);(b) 連 render 是否執行、能力宣告本身都依模型分流(Expected 第 3 點涵蓋)。兩者相容但工作量差一級,實作前宜確認範圍 resolved @ 2026-08-06T20:04Z(reason: scope = (a)+(b) 兩者皆含。依據為本 issue Expected 自身已同時列出兩層 —— 第 2 項「render 由所選引擎取得 budget」屬 (a),第 1 項「Engine protocol 增加 prompt 能力宣告」與第 3 項「不支援時整段跳過 render」屬 (b)。Complexity 即按此範圍評定。⚠ 此為 diagnose 階段的明示假設:若原意僅為 (a),請據此收窄 Expected 並重評 —— 範圍收窄會使 #129 硬閘的觸發理由改變)
missing-context 「支援但上限未知(交由後端自行截斷)」 已解whisper-cli --help 明文 --prompt PROMPT initial prompt (max n_text_ctx/2 tokens);Whisper 模型 n_text_ctx = 448224,與 WhisperKitEngine.clampedPromptTokens 的 224 相同。兩個 whisper backend 上限一致,第三態(上限未知)可能不需要 resolved @ 2026-08-06T19:00Z(reason: whisper-cli 說明文件實測確認上限為 n_text_ctx/2 = 224)
terminology 「prompt」 本 issue 全文指 ASR decoder 的 conditioning text(Whisper 的 initial_prompt / promptTokens),非 LLM 意義的 prompt。混淆會誤導實作者往錯的抽象層找 dismissed @ 2026-08-06T20:04Z(reason: 該列本身即為正確的 canonical 定義,且本 issue 全文用法與之一致(WhisperKitEngine.clampedPromptTokens / whisper-cli --prompt 皆為 decoder conditioning text)。無待決事項,保留作為術語註記)

Current Status

Phase: proposed(Spectra tier)

欄位
Diagnosis #164 comment(2026-08-07)
Conflict Class C_shared_module_coordEngines/Engine.swift public protocol + 6 conformer
Complexity Spectra(Layer 2 + Layer 3 皆命中:public protocol +4th requirement × 6 conformer;跨 ≥4 子系統且設計空間互斥)
排序 必須排在 #165 之後(先收斂 spawn seam,再擴 protocol)
明示假設 scope = (a)+(b)(依 Expected 自身涵蓋範圍)。若原意僅 (a),Layer 2 不命中、複雜度降為 Plan/Simple —— 收窄 Expected 後重跑即可
設計建議 能力宣告採二態(第三態目前無 instance);不給預設實作(給預設等於允許沉默,與本 issue 初衷相反)
提案 per-engine-prompt-capability 已建立、validated、parked(摘要
⚠️ 存放位置 parked change 在 .git/spectra-app/changes/不受 git 追蹤、不會 push —— 關鍵決策已鏡射於上述 comment
下一步 /spectra-apply per-engine-prompt-capability(自動 unpark 並開始實作)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions