voidful commited on
Commit
86b8987
·
verified ·
1 Parent(s): f70a24d

Use Breeze25 for efficient semantic verification

Browse files
README.md CHANGED
@@ -11,6 +11,7 @@ license: apache-2.0
11
  short_description: 台灣華語與中英混合文字轉語音
12
  models:
13
  - OpenFormosa/BlueMagpie-TTS
 
14
  ---
15
 
16
  # BlueMagpie-TTS Demo
@@ -41,10 +42,11 @@ Mamba2 所需的 Triton runtime 以
41
  `v2.3.2.post1` 的 Apache-2.0 上游原始碼隨 Space 保存(不綁定
42
  `selective_scan_cuda` 編譯產物),
43
  避免 Space 重啟後在沒有程式版本變更的情況下改變模型或 speaker embedding 空間。
44
- 候選語意驗證固定使用 Whisper large-v3-turbo revision
45
- `41f01f3fe87f28c78e2fbf8b568835947dd65ed9`;exact-assembled whole-output
46
- 語意驗證另固定使用 Whisper large-v3 revision
47
- `06f233fe06e710322aca913c1bc4249a0d71fce1`,兩者必須同時過。
 
48
 
49
  | 設定 | 值 |
50
  |---|---:|
@@ -62,16 +64,16 @@ Mamba2 所需的 Triton runtime 以
62
  | Hard stop | native-pace target steps, independent of playback pace; URL/email chunks use a separate conservative endpoint-duration counter (ASCII alnum run ÷ 2) |
63
  | Pace correction | derive total/active pace after completion; probe the corrected waveform and, when needed, re-render once from the untouched model waveform toward 3.95 active units/sec (ordinary `1536/384`; URL/email code-switch `2048/512`); no active-interval slicing |
64
  | Pace / speaker eligibility duration | union of active 25 ms RMS frames at 10 ms hop; internal pauses excluded |
65
- | Semantic verification | orthographic whole CER + tone-aware acoustic first/last 6 exact + no lexical tail; range-proven URL/email locals and every exact whole output require turbo/full-large-v3 hard intersection; fail closed |
66
  | Local endpoint verification | internal chunk first/last 6 may contain at most one substitution and zero deletion; absolute request onset/ending remain exact |
67
- | Acoustic quality gate | pinned CPU SQUIM objective SHA-256 `2c54586fea83fb5eb5394d710038ee89f55cab7011a5bf730bebed4c8777e828`; hard STOI ≥ 0.60 / PESQ ≥ 1.12; at ≥1.50 s, strict preferred return requires STOI ≥ 0.72 / PESQ ≥ 1.20 and speaker 0.25 / 0.05. A single-chunk exact joined waveform may also stop at the latency tier speaker 0.23 / 0.08, but only after both ASRs, preferred SQUIM, hard release speaker, pace, endpoint, and echo/smearing gates pass; local proxies cannot use this tier. SI-SDR is bounded soft evidence only |
68
  | Local speaker verification | frozen request centroid + bounded window begin/end directional drift |
69
  | Local hard speaker gate | similarity ≥ 0.10 and directional drop ≤ 0.10 |
70
  | Joined/final release-parity speaker guard | full-audio similarity ≥ 0.105; disjoint first/last-third drop ≤ 0.095 |
71
  | Local long speaker evidence | one ECAPA batch over up to four 3-second windows + begin/end |
72
  | Candidate cascade | exactly one same-seed whole trajectory, then deterministic single-chunk refills for zero/low-coverage rows only; a complete rank-1 path is checked immediately, and a hard-pass/preferred-miss path gets one bounded incremental replacement check while one final-search slot remains reserved; a fully verified single-chunk exact waveform returns immediately instead of filling the preferred candidate pool |
73
  | Whole-trajectory qualification | assemble with production RMS/fade/pause/crossfade/speed, then whole-output gate |
74
- | Sequence fallback | ragged DP with up to 3 culprit-diverse paths; boundary-only local rejects may enter through a frozen 0.15 cap, then every exact assembly must pass turbo + full large-v3 and the stricter 0.105/0.095 whole gate |
75
  | Final output gate | re-verify joined/faded/RMS-matched/speed-adjusted whole waveform; fail closed |
76
  | Runtime budget | ordinary non-network requests up to 48 speech units stop after at most 10 generated TTS chunks; other requests retain the 32-chunk hard cap. Every request is also capped at 800 generated speech units; NFE remains fixed at 10 |
77
  | Maximum chunk | ordinary request 80 speech units; every chunk in a URL/email request 36 units |
@@ -97,13 +99,14 @@ exact 32-entry grammar: `letter-a` through `letter-z` are spoken as
97
  `symbol-dot`, `symbol-hyphen`, `symbol-underscore`, `symbol-colon` and
98
  `symbol-slash` are respectively `網路符號小老鼠`, `網路符號點`,
99
  `這個符號叫做橫線`, `這個符號叫做底線`, `這個符號叫做冒號` and
100
- `網路符號斜線`. Its recorded Whisper, ECAPA and SQUIM values are
101
  **attestations only**: successfully loading the manifest never sets
102
  `runtime_quality_verified`, never runs those evaluators, and is not a release
103
- pass. A release evaluator must independently load the pinned Whisper
104
- large-v3-turbo, Whisper large-v3 (and the frozen third ASR where required),
105
- ECAPA and SQUIM artifacts, recompute their evidence from the exact assembled
106
- PCM16 delivered to the user, and apply the unchanged frozen release gates.
 
107
  Silent PCM accompanied by invented passing numbers is rejected and can never
108
  be promoted by the asset loader.
109
 
@@ -142,7 +145,8 @@ added, preserving every frozen asset byte.
142
  `root seed + global generation offset`,不會重新生成其他已覆蓋 chunks。三種 policy 都只改 native-duration endpoint estimate,
143
  margin 固定 +1 latent step、`min_len` 固定為 2,不會為了播放目標語速硬撐 generation loop。
144
  唯一例外是 completion-headroom 的一至兩單位短句:其 sparse retry 將 hard cap 下限提高到 5 steps,
145
- 讓第一個 eligible weak-stop 有一次真正的 threshold 判定機會;所有額外內容仍須通過 ASR 與 tail=0。
 
146
  選中完整 trajectory 或 coverage-sequence-DP 混合路徑後,log 會記錄每個 chunk 的 candidate、seed、policy、實際 CFG,
147
  並記錄每個已嘗試 row-local ordinal 的 schedule CFG。Ordinal 0 與正偶數使用 primary CFG 3.0,
148
  正奇數使用 alternate CFG 2.0;含 URL/email component 的 chunk 仍有 CFG 3.0 的內容安全下限。
@@ -157,13 +161,13 @@ speech-unit counter;只有 URL/email-conditioned native endpoint estimate 對
157
  終端 outcome 另輸出 content-free canonical evidence schema v6,包含 original chunk indices、
158
  row-local ordinal、network provenance、CFG contract、32/800 預算用量與 selected-path 交叉檢查,
159
  每個 chunk 的 stop reason、pre-fade endpoint energy、generated/hard-stop steps,
160
- 每個 local row 的 independent-large-v3 attempted/pass/proof-count/result attestation,以及
161
- local、joined、independent-large-v3 與 final gate 的 bounded SQUIM scalar,
162
  不包含 target/transcript/audio/embedding。
163
  為了讓 release contract 與正式量測一致,UI 不提供其他 primary CFG。
164
  內部 hosted evaluator 可注入 `[0, 2^31)` 的固定 root seed 以重現結果;UI 不暴露這個參數,
165
  一般請求仍只在未注入 seed 時使用系統亂數。
166
- 初始完整 same-seed trajectory 永遠優先;通過 turbo + full large-v3 exact-whole gate 就立即回傳,
167
  不會執行 refill 或 DP。若失敗,之後只做單 chunk refill,不再生成第二條完整 trajectory。
168
  Ragged DP 可混合已通過 hard gate 的 chunks,
169
  以及下述唯一 boundary-only、drop 不超過 0.15 的 DP-only chunks。切段保留標點,並依逗號、分號或句末標點
@@ -177,23 +181,22 @@ Ragged DP 可混合已通過 hard gate 的 chunks,
177
  逐 sample 冪等,因此驗證的就是使用者收到的 PCM codes;同時不讓 Gradio 對 float ndarray
178
  再做 peak normalization,以免額外放大底噪。
179
  完成 RMS matching、edge fade、pause、crossfade 與使用者 speed 後,服務會再對最終整段 waveform
180
- 執行 normalized target 的 prefix/whole/suffix/tail、pace 與 speaker anchor/boundary gate,並要求
181
- turbo full large-v3 的語意 hard intersection;這post-join gate 失敗時不會回傳先前已通過的
182
- chunk 音訊。除此之外,每個帶有 planner range proof 的 URL/email local chunk 在進入 coverage pool
183
- 以前,也必須用完全相同的 `NetworkFragmentProof` 同時通過 turbo 與 pinned full large-v3;
184
- large-v3 reject 會成為不可被 boundary proxy 放寬的 semantic hard failure。一般 local chunk
185
- 維持 turbo gate;已被 turbo boundary hard gate 拒絕、原本就不可能進 pool network row
186
- 額外耗用 full large-v3Production assembler 產生的 exact whole candidate exact sequence path 仍全部
187
- 使用 dual-ASR。Initial whole rejection 會啟動低覆蓋 refill,sequence rejection 則只會前進到
188
- 下一條已排序的 exact path。
189
  此外,初始 same-seed trajectory 只有在每個 local chunk 都通過後,才會先用同一個
190
  `_assemble_trajectory_audio` 組成實際播放版本並跑整段 semantic/pace/speaker gate;joined gate
191
  失敗會取消該 whole trajectory 的資格,但保留已通過的 local evidence 供 sequence DP 使用。
192
- 這個額外 dual-ASR whole gate 只對 local turbo 全數通過的候選執行,DP 選定後仍會保留上述最終
193
  post-join gate。每個 request 只在 waveform float32 bytes、sample rate、normalized target 與 pinned
194
- verifier profile 完全相同時重用 full large-v3 evidence;任何 sample 改變都會重新驗證。ASR loader
195
- 異常、OOM、空 transcript 或無效 evidence 都 fail closed,不會降級為 turbo-only。同一 request
196
- 內若 release 與 semantic verifier 使用完全相同的精確 waveform、sample rate、Whisper
197
  model/revision、語言、token cap 與 segmentation cap,會共用一次 transcript decode;cache
198
  不跨 request,exception 與無效結果不會寫入。
199
  初始 whole trajectory 未通過後,refill scheduler 會優先補 coverage=0 的 rows,再補不足 3 個
@@ -208,7 +211,7 @@ rank-1 path 時會立即再驗證一次;若首條 path 連 hard gate 都未通
208
  已通過 local hard gate 的 chunk 可直接進入 DP;唯一 rejection reason 是
209
  `boundary_speaker_drop`、且 drop 不超過 0.15 的 chunk 也可作為 DP-only 候選。Single-chunk
210
  request 可使用相同的 local proxy,但仍必須重新通過 exact whole-waveform 0.105/0.095 release guard。
211
- 若 exact joined waveform 已通過 dual-ASR,single-chunk preferred early return 以該 release
212
  ECAPA/SQUIM evidence 為準,不會因較粗的 local windowed boundary proxy 再生成多個無用候選。
213
  這個 boundary-only relaxation 不適用於 semantic、pace、speaker similarity、缺少 evidence 或 whole-trajectory
214
  accept,也不能在缺少整段 final verifier 時啟用。Local score 加上相鄰 ECAPA/RMS transition
@@ -248,10 +251,10 @@ spoken range;planner、Space runtime 與 local ASR 入口都會從 raw identif
248
  proof 完全相等。若 nested proof 被移除、range/rule/reordering 被修改,或非 naturalized identifier
249
  夾帶這組 provenance,會 fail closed。這個 naturalized phrase 在 generation planner 中是一個
250
  不可切 component;H07 因此是 35 public units 的單一 network chunk,沒有 artificial
251
- `network_internal` seam。兩個中文逗號只是模型 pause cue,Whisper 省略它們仍可通過;scheme cue、
252
  host、path 與 terminator 仍是 exact lexical protected range。
253
 
254
- 完整 identifier 仍保留為 joined、full-large-v3 與 final ASR 的 exact protected target。完整
255
  URL spoken proof 不超過 36 units 時仍保持單一 atomic chunk;URL 後的逗號也留在同一 chunk。
256
  Hosted profile 對 bounded email 只開放一��更窄的例外:同一 strong sentence 必須恰好只有一個
257
  email、email 前的 prose 至少 12 units、domain fragment 至少 8 units,且在 range-proven `@`/
@@ -278,9 +281,9 @@ URL 與後續英文 prose 應以空白或中文標點分隔;未分隔的 RFC p
278
  本身的一部分並納入 exact gate。Quoted email local-part 暫不支援,輸入時會直接 fail closed。
279
  URL identifier 目前只支援 ASCII;非 ASCII IRI 必須先轉成 ASCII/percent-encoded 形式,
280
  否則會因字母讀音碰撞而在 normalization 前 fail closed。
281
- 為避免 Whisper 自動句末標點和 URL 內容不可判定,URL 不接受以 `. , ! ? ; : '` 結尾;
282
  需要這些尾端符號時請使用 percent encoding。
283
- Whisper 常見的 `15点30分`/`15點30分` 會視為同一讀法;裸寫 `15點30` 只有在 target
284
  明確含同一個完整鐘點讀法時才作 ASR-only canonicalization。比分、秒數、重量、裸 `15點`、
285
  非法時間與真實漏字仍會被拒絕。Whole-output ASR 只使用 waveform evidence:若所有符合
286
  250 ms、兩側皆有 active speech 的 pause atoms 都能將音訊限制在 28 秒內,就保留
@@ -296,8 +299,8 @@ target-dependent fallback。每段至少 1.25 秒、硬上限 30 秒,每次驗
296
  normalization 不會修改送入模型的文字;既有同音代詞 canonicalization 仍保留。
297
  只有尚未 assemble 的 internal local chunk 允許首、尾 6 units 各最多一個 substitution,且
298
  deletion 必須為 0;由原始 `GenerationChunkSpec` 證明的 request 第一段 prefix 與最後一段
299
- suffix 仍必須 exact。Refill 依原始絕對 chunk provenance 套用相同角色。Joined turbo
300
- independent full-large-v3 與 final whole-waveform gate 全部維持 exact endpoint。
301
 
302
  自然 stop 在目前 checkpoint 上仍可能過快或錯過句尾。Demo 不再用播放目標語速決定生成長度;
303
  模型使用原生語速的安全上限,完成後才做保音高語速校正。缺少句末標點時只在模型輸入補上句號,
@@ -328,9 +331,11 @@ echo/smearing gate。Gate 聚合多個 active windows 的 24–180 ms 長 quefre
328
  delayed-copy comb peak 或 dense smearing signature;無效、非有限或缺少足夠 active-window
329
  evidence 都 fail closed。短於 1.0 秒時 estimator 不適用,仍由原有 strict semantic、endpoint
330
  與 SQUIM gate 負責。任何仍過快、被 stretch 破壞或具有可量測回音的候選均不會回傳。
331
- 每個 local row 先完成 Turbo ASR 與同一 active-duration pace gate;若此時已定為 semantic
332
- pace hard failure,便保留拒絕 evidence 並跳過 ECAPA、SQUIM、echo 與 F0。只有仍可能
333
- 回傳進入 DP 候選才支付完整 acoustic verification最終 speaker/SQUIM/echo 門檻不變。
 
 
334
  Median-F0 只在 multi-chunk lattice 真的出現兩條以上可選路徑、必須計算 transition ranking
335
  時,才對 retained DP candidates 延遲計算一次;單一路徑、single/K32、semantic reject、
336
  joined 與 final waveform 不再支付無用的 pYIN latency。真正進入多路徑排序時,pYIN 演算法與
 
11
  short_description: 台灣華語與中英混合文字轉語音
12
  models:
13
  - OpenFormosa/BlueMagpie-TTS
14
+ - MediaTek-Research/Breeze-ASR-25
15
  ---
16
 
17
  # BlueMagpie-TTS Demo
 
42
  `v2.3.2.post1` 的 Apache-2.0 上游原始碼隨 Space 保存(不綁定
43
  `selective_scan_cuda` 編譯產物),
44
  避免 Space 重啟後在沒有程式版本變更的情況下改變模型或 speaker embedding 空間。
45
+ 所有內容驗證固定使用 `MediaTek-Research/Breeze-ASR-25` revision
46
+ `cffe7ccb404d025296a00758d0a33468bec3a9d0`,model weight SHA-256 為
47
+ `c5d952b3bc03ea277209aff0ef5b5c4c055d74449ff794c02d8f4e315fdef6b6`。
48
+ 、reference 與 long-form request 共用同一個固定中文、greedy、transcribe
49
+ decoder profile;同一 request 的 exact waveform 只解碼一次。
50
 
51
  | 設定 | 值 |
52
  |---|---:|
 
64
  | Hard stop | native-pace target steps, independent of playback pace; URL/email chunks use a separate conservative endpoint-duration counter (ASCII alnum run ÷ 2) |
65
  | Pace correction | derive total/active pace after completion; probe the corrected waveform and, when needed, re-render once from the untouched model waveform toward 3.95 active units/sec (ordinary `1536/384`; URL/email code-switch `2048/512`); no active-interval slicing |
66
  | Pace / speaker eligibility duration | union of active 25 ms RMS frames at 10 ms hop; internal pauses excluded |
67
+ | Semantic verification | one pinned Breeze-ASR-25 decode; orthographic whole CER + tone-aware acoustic first/last 6 exact + no lexical tail; range-proven URL/email locals and every exact whole output retain separate hard projections over the cached transcript; fail closed |
68
  | Local endpoint verification | internal chunk first/last 6 may contain at most one substitution and zero deletion; absolute request onset/ending remain exact |
69
+ | Acoustic quality gate | pinned CPU SQUIM objective SHA-256 `2c54586fea83fb5eb5394d710038ee89f55cab7011a5bf730bebed4c8777e828`; hard STOI ≥ 0.60 / PESQ ≥ 1.12; at ≥1.50 s, strict preferred return requires STOI ≥ 0.72 / PESQ ≥ 1.20 and speaker 0.25 / 0.05. A single-chunk exact joined waveform may also stop at the latency tier speaker 0.23 / 0.08, but only after Breeze25 semantics, preferred SQUIM, hard release speaker, pace, endpoint, and echo/smearing gates pass; local proxies cannot use this tier. SI-SDR is bounded soft evidence only |
70
  | Local speaker verification | frozen request centroid + bounded window begin/end directional drift |
71
  | Local hard speaker gate | similarity ≥ 0.10 and directional drop ≤ 0.10 |
72
  | Joined/final release-parity speaker guard | full-audio similarity ≥ 0.105; disjoint first/last-third drop ≤ 0.095 |
73
  | Local long speaker evidence | one ECAPA batch over up to four 3-second windows + begin/end |
74
  | Candidate cascade | exactly one same-seed whole trajectory, then deterministic single-chunk refills for zero/low-coverage rows only; a complete rank-1 path is checked immediately, and a hard-pass/preferred-miss path gets one bounded incremental replacement check while one final-search slot remains reserved; a fully verified single-chunk exact waveform returns immediately instead of filling the preferred candidate pool |
75
  | Whole-trajectory qualification | assemble with production RMS/fade/pause/crossfade/speed, then whole-output gate |
76
+ | Sequence fallback | ragged DP with up to 3 culprit-diverse paths; boundary-only local rejects may enter through a frozen 0.15 cap, then every exact assembly must pass cached Breeze25 semantic/release projections and the stricter 0.105/0.095 whole gate |
77
  | Final output gate | re-verify joined/faded/RMS-matched/speed-adjusted whole waveform; fail closed |
78
  | Runtime budget | ordinary non-network requests up to 48 speech units stop after at most 10 generated TTS chunks; other requests retain the 32-chunk hard cap. Every request is also capped at 800 generated speech units; NFE remains fixed at 10 |
79
  | Maximum chunk | ordinary request 80 speech units; every chunk in a URL/email request 36 units |
 
99
  `symbol-dot`, `symbol-hyphen`, `symbol-underscore`, `symbol-colon` and
100
  `symbol-slash` are respectively `網路符號小老鼠`, `網路符號點`,
101
  `這個符號叫做橫線`, `這個符號叫做底線`, `這個符號叫做冒號` and
102
+ `網路符號斜線`. Its recorded Breeze25, ECAPA and SQUIM values are
103
  **attestations only**: successfully loading the manifest never sets
104
  `runtime_quality_verified`, never runs those evaluators, and is not a release
105
+ pass. The glyph evidence boundary loads the same pinned Breeze25 weights with
106
+ two explicit decoder profiles—fixed `zh` primary and auto-language
107
+ confirmation—plus ECAPA and SQUIM, recomputes their evidence from the exact
108
+ assembled PCM16 delivered to the user, and applies the frozen release gates.
109
+ The two profiles are not represented as independent model weights.
110
  Silent PCM accompanied by invented passing numbers is rejected and can never
111
  be promoted by the asset loader.
112
 
 
145
  `root seed + global generation offset`,不會重新生成其他已覆蓋 chunks。三種 policy 都只改 native-duration endpoint estimate,
146
  margin 固定 +1 latent step、`min_len` 固定為 2,不會為了播放目標語速硬撐 generation loop。
147
  唯一例外是 completion-headroom 的一至兩單位短句:其 sparse retry 將 hard cap 下限提高到 5 steps,
148
+ 讓第一個 eligible weak-stop 有一次真正的 threshold 判定機會;所有額外內容仍須通過 Breeze25
149
+ semantic gate 與 tail=0。
150
  選中完整 trajectory 或 coverage-sequence-DP 混合路徑後,log 會記錄每個 chunk 的 candidate、seed、policy、實際 CFG,
151
  並記錄每個已嘗試 row-local ordinal 的 schedule CFG。Ordinal 0 與正偶數使用 primary CFG 3.0,
152
  正奇數使用 alternate CFG 2.0;含 URL/email component 的 chunk 仍有 CFG 3.0 的內容安全下限。
 
161
  終端 outcome 另輸出 content-free canonical evidence schema v6,包含 original chunk indices、
162
  row-local ordinal、network provenance、CFG contract、32/800 預算用量與 selected-path 交叉檢查,
163
  每個 chunk 的 stop reason、pre-fade endpoint energy、generated/hard-stop steps,
164
+ 每個 local row 的 secondary semantic projection attempted/pass/proof-count/result attestation,以及
165
+ local、joined、secondary semantic projection 與 final gate 的 bounded SQUIM scalar,
166
  不包含 target/transcript/audio/embedding。
167
  為了讓 release contract 與正式量測一致,UI 不提供其他 primary CFG。
168
  內部 hosted evaluator 可注入 `[0, 2^31)` 的固定 root seed 以重現結果;UI 不暴露這個參數,
169
  一般請求仍只在未注入 seed 時使用系統亂數。
170
+ 初始完整 same-seed trajectory 永遠優先;通過 Breeze25 exact-whole semantic/release gate 就立即回傳,
171
  不會執行 refill 或 DP。若失敗,之後只做單 chunk refill,不再生成第二條完整 trajectory。
172
  Ragged DP 可混合已通過 hard gate 的 chunks,
173
  以及下述唯一 boundary-only、drop 不超過 0.15 的 DP-only chunks。切段保留標點,並依逗號、分號或句末標點
 
181
  逐 sample 冪等,因此驗證的就是使用者收到的 PCM codes;同時不讓 Gradio 對 float ndarray
182
  再做 peak normalization,以免額外放大底噪。
183
  完成 RMS matching、edge fade、pause、crossfade 與使用者 speed 後,服務會再對最終整段 waveform
184
+ 執行 normalized target 的 prefix/whole/suffix/tail、pace 與 speaker anchor/boundary gate。內容只由
185
+ 固定 Breeze25 profile 解碼一次,再由 release semantic-only 兩hard projection 檢查同一份
186
+ exact transcript;這個 post-join gate 失敗時不會回傳先前已通過的 chunk 音訊。除此之外,每個
187
+ 帶有 planner range proof 的 URL/email local chunk 在進入 coverage pool 以前,也必須用完全相同的
188
+ `NetworkFragmentProof` 通過 primary secondary semantic projection;secondary reject 仍是不可被
189
+ boundary proxy 放寬semantic hard failure。兩個 projection 透過 exact-waveform cache 共用一次
190
+ Breeze25 decode,再載入或執行第二個 ASR 模型Initial whole rejection 會啟動低覆蓋 refill,
191
+ sequence rejection 則只會前進到下一條已排序的 exact path。
 
192
  此外,初始 same-seed trajectory 只有在每個 local chunk 都通過後,才會先用同一個
193
  `_assemble_trajectory_audio` 組成實際播放版本並跑整段 semantic/pace/speaker gate;joined gate
194
  失敗會取消該 whole trajectory 的資格,但保留已通過的 local evidence 供 sequence DP 使用。
195
+ 這個額外 whole gate 只對 local Breeze25 全數通過的候選執行,DP 選定後仍會保留上述最終
196
  post-join gate。每個 request 只在 waveform float32 bytes、sample rate、normalized target 與 pinned
197
+ verifier profile 完全相同時重用 evidence;任何 sample 改變都會重新驗證。ASR loader
198
+ 異常、OOM、空 transcript 或無效 evidence 都 fail closed,不會降級為未驗證輸出。同一 request
199
+ 內若 release 與 semantic verifier 使用完全相同的精確 waveform、sample rate、Breeze25
200
  model/revision、語言、token cap 與 segmentation cap,會共用一次 transcript decode;cache
201
  不跨 request,exception 與無效結果不會寫入。
202
  初始 whole trajectory 未通過後,refill scheduler 會優先補 coverage=0 的 rows,再補不足 3 個
 
211
  已通過 local hard gate 的 chunk 可直接進入 DP;唯一 rejection reason 是
212
  `boundary_speaker_drop`、且 drop 不超過 0.15 的 chunk 也可作為 DP-only 候選。Single-chunk
213
  request 可使用相同的 local proxy,但仍必須重新通過 exact whole-waveform 0.105/0.095 release guard。
214
+ 若 exact joined waveform 已通過 Breeze25 semantic/release projections,single-chunk preferred early return 以該 release
215
  ECAPA/SQUIM evidence 為準,不會因較粗的 local windowed boundary proxy 再生成多個無用候選。
216
  這個 boundary-only relaxation 不適用於 semantic、pace、speaker similarity、缺少 evidence 或 whole-trajectory
217
  accept,也不能在缺少整段 final verifier 時啟用。Local score 加上相鄰 ECAPA/RMS transition
 
251
  proof 完全相等。若 nested proof 被移除、range/rule/reordering 被修改,或非 naturalized identifier
252
  夾帶這組 provenance,會 fail closed。這個 naturalized phrase 在 generation planner 中是一個
253
  不可切 component;H07 因此是 35 public units 的單一 network chunk,沒有 artificial
254
+ `network_internal` seam。兩個中文逗號只是模型 pause cue,Breeze25 省略它們仍可通過;scheme cue、
255
  host、path 與 terminator 仍是 exact lexical protected range。
256
 
257
+ 完整 identifier 仍保留為 joined 與 final Breeze25 ASR 的 exact protected target。完整
258
  URL spoken proof 不超過 36 units 時仍保持單一 atomic chunk;URL 後的逗號也留在同一 chunk。
259
  Hosted profile 對 bounded email 只開放一��更窄的例外:同一 strong sentence 必須恰好只有一個
260
  email、email 前的 prose 至少 12 units、domain fragment 至少 8 units,且在 range-proven `@`/
 
281
  本身的一部分並納入 exact gate。Quoted email local-part 暫不支援,輸入時會直接 fail closed。
282
  URL identifier 目前只支援 ASCII;非 ASCII IRI 必須先轉成 ASCII/percent-encoded 形式,
283
  否則會因字母讀音碰撞而在 normalization 前 fail closed。
284
+ 為避免 Breeze25 自動句末標點和 URL 內容不可判定,URL 不接受以 `. , ! ? ; : '` 結尾;
285
  需要這些尾端符號時請使用 percent encoding。
286
+ Breeze25 常見的 `15点30分`/`15點30分` 會視為同一讀法;裸寫 `15點30` 只有在 target
287
  明確含同一個完整鐘點讀法時才作 ASR-only canonicalization。比分、秒數、重量、裸 `15點`、
288
  非法時間與真實漏字仍會被拒絕。Whole-output ASR 只使用 waveform evidence:若所有符合
289
  250 ms、兩側皆有 active speech 的 pause atoms 都能將音訊限制在 28 秒內,就保留
 
299
  normalization 不會修改送入模型的文字;既有同音代詞 canonicalization 仍保留。
300
  只有尚未 assemble 的 internal local chunk 允許首、尾 6 units 各最多一個 substitution,且
301
  deletion 必須為 0;由原始 `GenerationChunkSpec` 證明的 request 第一段 prefix 與最後一段
302
+ suffix 仍必須 exact。Refill 依原始絕對 chunk provenance 套用相同角色。Joined、semantic-only
303
+ projection 與 final whole-waveform Breeze25 gate 全部維持 exact endpoint。
304
 
305
  自然 stop 在目前 checkpoint 上仍可能過快或錯過句尾。Demo 不再用播放目標語速決定生成長度;
306
  模型使用原生語速的安全上限,完成後才做保音高語速校正。缺少句末標點時只在模型輸入補上句號,
 
331
  delayed-copy comb peak 或 dense smearing signature;無效、非有限或缺少足夠 active-window
332
  evidence 都 fail closed。短於 1.0 秒時 estimator 不適用,仍由原有 strict semantic、endpoint
333
  與 SQUIM gate 負責。任何仍過快、被 stretch 破壞或具有可量測回音的候選均不會回傳。
334
+ 每個 local row 先計算同一個不可繞過的 active-duration pace gate;超過
335
+ `4.85 + 0.05` CPS 的候選會直接保留 fail-closed 拒絕 evidence,不再支付 Breeze25、ECAPA、
336
+ SQUIM、echo F0 成本。其餘候選才 Breeze25 驗證內容再對 semantic hard failure
337
+ 同樣停止後續 acoustic work。只有仍可能回傳或進入 DP 的候選才支付完整 acoustic
338
+ verification,最終內容、speaker、SQUIM、pace 與 echo 門檻均不變。
339
  Median-F0 只在 multi-chunk lattice 真的出現兩條以上可選路徑、必須計算 transition ranking
340
  時,才對 retained DP candidates 延遲計算一次;單一路徑、single/K32、semantic reject、
341
  joined 與 final waveform 不再支付無用的 pYIN latency。真正進入多路徑排序時,pYIN 演算法與
app.py CHANGED
@@ -169,13 +169,12 @@ from production import (
169
  )
170
  from quality_runtime import (
171
  BASE_GENERATION_POLICY,
 
 
 
 
172
  GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS as QUALITY_GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS,
173
  SAFE_DURATION_GENERATION_POLICY,
174
- VERIFICATION_WHISPER_MODEL_ID,
175
- VERIFICATION_WHISPER_REVISION,
176
- WHISPER_MAX_VERIFICATION_SEGMENTS,
177
- WHISPER_MODEL_ID,
178
- WHISPER_REVISION,
179
  CandidateGenerationContext,
180
  CandidateGenerationEvidence,
181
  CandidateVerification,
@@ -208,8 +207,7 @@ from quality_runtime import (
208
  run_coverage_adaptive_cascade,
209
  speaker_evidence_from_audio,
210
  squim_objective_evidence_from_audio,
211
- transcribe_verification_whisper,
212
- transcribe_whisper,
213
  trajectory_gate_evidence,
214
  verify_candidate,
215
  verify_trajectory,
@@ -286,9 +284,9 @@ QUALITY_PREFERRED_MIN_SQUIM_PESQ = 1.20
286
  QUALITY_PREFERRED_SQUIM_MIN_DURATION_SECONDS = 1.50
287
  QUALITY_PREFERRED_MIN_SPEAKER_SIMILARITY = 0.25
288
  QUALITY_PREFERRED_MAX_BOUNDARY_SPEAKER_DROP = 0.05
289
- # A single-chunk waveform that has already passed both ASR checks and every
290
- # hard release gate may stop the cascade at this exact-only tier. Local chunk
291
- # proxies and multi-chunk eager paths remain on the stricter preferred tier.
292
  QUALITY_FAST_EXACT_MIN_SPEAKER_SIMILARITY = 0.23
293
  QUALITY_FAST_EXACT_MAX_BOUNDARY_SPEAKER_DROP = 0.08
294
  QUALITY_MAX_SEQUENCE_PATHS = 3
@@ -308,31 +306,37 @@ if (
308
  GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS
309
  != QUALITY_GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS
310
  ):
311
- raise RuntimeError("glyph-hybrid Whisper segment caps disagree")
312
- VERIFICATION_ASR_PROFILE = (
313
- f"{VERIFICATION_WHISPER_MODEL_ID}@{VERIFICATION_WHISPER_REVISION}:"
 
314
  f"greedy:zh:short{QUALITY_PREFIX_SUFFIX_UNITS}:cer{QUALITY_MAX_CER}:"
315
  f"prefix{QUALITY_PREFIX_SUFFIX_UNITS}=0:"
316
  f"suffix{QUALITY_PREFIX_SUFFIX_UNITS}=0:tail=0:"
317
  f"max_tokens{QUALITY_FINAL_ASR_MAX_NEW_TOKENS}"
318
  )
319
- TURBO_TRANSCRIPT_PROFILE = (
320
- f"{WHISPER_MODEL_ID}@{WHISPER_REVISION}:greedy:zh:transcribe:"
 
321
  f"max_tokens{QUALITY_FINAL_ASR_MAX_NEW_TOKENS}:"
322
- f"segments{WHISPER_MAX_VERIFICATION_SEGMENTS}"
323
  )
324
- LARGE_V3_TRANSCRIPT_PROFILE = (
325
- f"{VERIFICATION_WHISPER_MODEL_ID}@{VERIFICATION_WHISPER_REVISION}:"
326
- f"greedy:zh:transcribe:max_tokens{QUALITY_FINAL_ASR_MAX_NEW_TOKENS}:"
327
- f"segments{WHISPER_MAX_VERIFICATION_SEGMENTS}"
328
- )
329
- RELEASE_TURBO_VERIFICATION_PROFILE = (
330
- f"{TURBO_TRANSCRIPT_PROFILE}:release-speaker-squim-echo-wsola-v2:"
331
  f"pace{QUALITY_MAX_PACE_CPS}"
332
  )
333
- RELEASE_LARGE_V3_VERIFICATION_PROFILE = (
334
- f"{LARGE_V3_TRANSCRIPT_PROFILE}:release-speaker-squim-echo-wsola-v2:"
335
- f"pace{QUALITY_MAX_PACE_CPS}"
 
 
 
 
 
 
 
 
 
336
  )
337
 
338
 
@@ -340,15 +344,15 @@ print(f"[BlueMagpie] downloading model from {REPO_ID}@{MODEL_REVISION} ...")
340
  MODEL_DIR = snapshot_download(REPO_ID, revision=MODEL_REVISION)
341
  print(f"[BlueMagpie] caching speaker encoder from {ECAPA_REPO_ID}@{ECAPA_REVISION} ...")
342
  ECAPA_DIR = snapshot_download(ECAPA_REPO_ID, revision=ECAPA_REVISION)
343
- print(f"[BlueMagpie] caching quality ASR from {WHISPER_MODEL_ID}@{WHISPER_REVISION} ...")
344
- ASR_DIR = snapshot_download(WHISPER_MODEL_ID, revision=WHISPER_REVISION)
345
- print(
346
- "[BlueMagpie] caching independent final ASR from "
347
- f"{VERIFICATION_WHISPER_MODEL_ID}@{VERIFICATION_WHISPER_REVISION} ..."
348
  )
349
- VERIFICATION_ASR_DIR = snapshot_download(
350
- VERIFICATION_WHISPER_MODEL_ID,
351
- revision=VERIFICATION_WHISPER_REVISION,
 
352
  )
353
  tokenizer = PreTrainedTokenizerFast(tokenizer_file=os.path.join(MODEL_DIR, "tokenizer.json"))
354
  print(f"[BlueMagpie] loading model on device={DEVICE} ...")
@@ -1093,7 +1097,7 @@ def _verify_trajectory_audio(
1093
  asr_max_new_tokens: int = 128,
1094
  *,
1095
  release_speaker_gate: bool = False,
1096
- transcriber=transcribe_whisper,
1097
  semantic_only: bool = False,
1098
  network_fragment_proofs: (
1099
  tuple[tuple[NetworkFragmentProof, ...], ...] | None
@@ -1144,8 +1148,53 @@ def _verify_trajectory_audio(
1144
  strict=True,
1145
  )
1146
  ):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1147
  prepared = prepare_candidate_audio(
1148
- audio,
1149
  SR,
1150
  transcriber=lambda waveform, sample_rate: transcriber(
1151
  waveform,
@@ -1166,10 +1215,6 @@ def _verify_trajectory_audio(
1166
  prepared_waveforms.append(None)
1167
  continue
1168
  waveform = prepared.waveform
1169
- try:
1170
- duration = active_voiced_duration_seconds(waveform, SR)
1171
- except ValueError:
1172
- duration = 0.0
1173
  transcript = prepared.transcript_text
1174
  if fragment_proofs:
1175
  fragment_evidence = canonicalize_asr_network_fragments(
@@ -1184,11 +1229,6 @@ def _verify_trajectory_audio(
1184
  if fragment_evidence.passed
1185
  else ""
1186
  )
1187
- pace_cps = (
1188
- count_speech_units(chunk) / duration * float(playback_speed)
1189
- if duration > 0.0
1190
- else None
1191
- )
1192
  semantic_observation = CandidateObservation(
1193
  target_text=chunk,
1194
  transcript_text=transcript,
@@ -1291,7 +1331,7 @@ def _verify_trajectory_audio(
1291
  begin_speaker_similarity=begin_similarity,
1292
  end_speaker_similarity=end_similarity,
1293
  pace_cps=(
1294
- count_speech_units(chunk) / duration * float(playback_speed)
1295
  if duration > 0.0
1296
  else None
1297
  ),
@@ -1346,9 +1386,9 @@ def _verify_independent_whole_audio(
1346
  anchor: np.ndarray,
1347
  cache: WholeWaveformVerificationCache,
1348
  *,
1349
- transcriber=transcribe_verification_whisper,
1350
  ):
1351
- """Verify exact assembled audio with the independent full large-v3 ASR."""
1352
 
1353
  def verifier(exact_waveform, sample_rate, exact_target):
1354
  if sample_rate != SR:
@@ -1367,7 +1407,7 @@ def _verify_independent_whole_audio(
1367
  waveform,
1368
  SR,
1369
  target_text,
1370
- VERIFICATION_ASR_PROFILE,
1371
  verifier,
1372
  )
1373
 
@@ -1437,43 +1477,45 @@ def _verify_release_whole_audio(
1437
  )
1438
 
1439
 
1440
- def _transcribe_glyph_hybrid_turbo(
1441
  audio: np.ndarray,
1442
  sample_rate: int,
1443
  *,
1444
  language: str = "zh",
1445
  task: str = "transcribe",
1446
  max_new_tokens: int = 128,
 
1447
  ) -> str:
1448
- """Run Turbo with the bounded 128-asset + 32-carrier V5 cap."""
1449
 
1450
- return transcribe_whisper(
1451
  audio,
1452
  sample_rate,
1453
  language=language,
1454
  task=task,
1455
  max_new_tokens=max_new_tokens,
1456
- max_verification_segments=GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS,
1457
  )
1458
 
1459
 
1460
- def _transcribe_glyph_hybrid_independent(
1461
  audio: np.ndarray,
1462
  sample_rate: int,
1463
  *,
1464
- language: str = "zh",
1465
  task: str = "transcribe",
1466
  max_new_tokens: int = 128,
 
1467
  ) -> str:
1468
- """Run independent large-v3 with the same V5-only segment cap."""
1469
 
1470
- return transcribe_verification_whisper(
1471
  audio,
1472
  sample_rate,
1473
  language=language,
1474
  task=task,
1475
  max_new_tokens=max_new_tokens,
1476
- max_verification_segments=GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS,
1477
  )
1478
 
1479
 
@@ -1527,9 +1569,11 @@ _GLYPH_HYBRID_EVIDENCE_ADAPTER = HardenedGlyphHybridAdapter(
1527
  asr_max_verification_segments=(
1528
  GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS
1529
  ),
1530
- turbo_transcriber=_transcribe_glyph_hybrid_turbo,
1531
- verification_transcriber=(
1532
- _transcribe_glyph_hybrid_independent
 
 
1533
  ),
1534
  )
1535
  )
@@ -1748,32 +1792,35 @@ def _verification_metric_log_fields(verification) -> str:
1748
 
1749
 
1750
  def _verify_network_local_asr_intersection(
1751
- turbo_verification,
1752
  trajectory: tuple[np.ndarray, ...],
1753
  chunks: tuple[str, ...],
1754
  anchor: np.ndarray,
1755
  proof_rows: tuple[tuple[NetworkFragmentProof, ...], ...] | None,
1756
  *,
1757
  candidate_seed: int,
 
1758
  ):
1759
- """Require pinned large-v3 semantics before retaining network locals.
1760
-
1761
- Only proof-bearing rows are sent through the independent model. Their
1762
- exact ``NetworkFragmentProof`` tuples are reused unchanged, so neither ASR
1763
- can borrow a whole URL/email proof for a different local range. The
1764
- intersection helper keeps turbo speaker/SQUIM/pace/artifact evidence while
1765
- projecting every independent rejection into a hard semantic local failure.
 
 
1766
  """
1767
 
1768
  if proof_rows is None:
1769
- return turbo_verification, tuple(
1770
  LocalIndependentGateEvidence(False, None, 0, None)
1771
  for _ in chunks
1772
  )
1773
  if (
1774
  len(proof_rows) != len(chunks)
1775
  or len(trajectory) != len(chunks)
1776
- or len(turbo_verification.candidate_results) != len(chunks)
1777
  ):
1778
  raise ValueError("network local verification provenance does not align")
1779
  independent_evidence = [
@@ -1791,7 +1838,7 @@ def _verify_network_local_asr_intersection(
1791
  if (
1792
  proofs
1793
  and local_candidate_has_coverage_eligibility(
1794
- turbo_verification.candidate_results[index],
1795
  max_local_boundary_speaker_drop=(
1796
  SEQUENCE_FALLBACK_MAX_LOCAL_BOUNDARY_SPEAKER_DROP
1797
  ),
@@ -1799,7 +1846,7 @@ def _verify_network_local_asr_intersection(
1799
  )
1800
  )
1801
  if not selected_indices:
1802
- return turbo_verification, tuple(independent_evidence)
1803
  independent_proof_rows = tuple(
1804
  proof_rows[index] for index in selected_indices
1805
  )
@@ -1809,7 +1856,7 @@ def _verify_network_local_asr_intersection(
1809
  anchor,
1810
  1.0,
1811
  QUALITY_FINAL_ASR_MAX_NEW_TOKENS,
1812
- transcriber=transcribe_verification_whisper,
1813
  semantic_only=True,
1814
  network_fragment_proofs=independent_proof_rows,
1815
  )
@@ -1823,7 +1870,7 @@ def _verify_network_local_asr_intersection(
1823
  )
1824
  comparison = result.comparison
1825
  print(
1826
- "[BlueMagpie] network local independent "
1827
  f"seed={candidate_seed} local_chunk_index={primary_index} "
1828
  f"proof_count={len(proof_rows[primary_index])} "
1829
  f"passed={result.passed} cer={comparison.cer:.6f} "
@@ -1834,7 +1881,7 @@ def _verify_network_local_asr_intersection(
1834
  )
1835
  return (
1836
  intersect_local_semantic_verification(
1837
- turbo_verification,
1838
  independent_verification,
1839
  selected_indices,
1840
  ),
@@ -1852,8 +1899,8 @@ def _qualify_candidate_trajectory_audio(
1852
  *,
1853
  candidate_seed: int,
1854
  chunk_specs: tuple[GenerationChunkSpec, ...] | None = None,
1855
- release_transcriber=transcribe_whisper,
1856
- independent_transcriber=transcribe_verification_whisper,
1857
  ):
1858
  """Run whole-output qualification only after every local chunk passes."""
1859
 
@@ -1863,6 +1910,8 @@ def _qualify_candidate_trajectory_audio(
1863
  chunks,
1864
  anchor,
1865
  playback_speed,
 
 
1866
  network_fragment_proofs=proof_rows,
1867
  local_endpoint_roles=_local_endpoint_role_rows(chunks, chunk_specs),
1868
  )
@@ -1874,6 +1923,7 @@ def _qualify_candidate_trajectory_audio(
1874
  anchor,
1875
  proof_rows,
1876
  candidate_seed=candidate_seed,
 
1877
  )
1878
  )
1879
  if not local_verification.passed:
@@ -1894,7 +1944,7 @@ def _qualify_candidate_trajectory_audio(
1894
  anchor,
1895
  independent_cache,
1896
  transcriber=release_transcriber,
1897
- verifier_profile=RELEASE_TURBO_VERIFICATION_PROFILE,
1898
  )
1899
  qualified = qualify_trajectory_with_joined_output(
1900
  local_verification,
@@ -1920,18 +1970,18 @@ def _qualify_candidate_trajectory_audio(
1920
  independent_cache,
1921
  transcriber=independent_transcriber,
1922
  )
1923
- dual_qualified = qualify_trajectory_with_joined_output(
1924
  qualified,
1925
  independent_verification,
1926
  )
1927
- if not dual_qualified.passed:
1928
  print(
1929
- "[BlueMagpie] candidate independent whole output rejected "
1930
  f"seed={candidate_seed} "
1931
  f"{_verification_metric_log_fields(independent_verification)}"
1932
  )
1933
  return CandidateVerification(
1934
- dual_qualified,
1935
  independent_local_results=independent_local_results,
1936
  joined_output=joined_evidence,
1937
  independent_output=trajectory_gate_evidence(independent_verification),
@@ -1946,8 +1996,9 @@ def _verify_refill_candidate_trajectory_audio(
1946
  chunk_specs: tuple[GenerationChunkSpec, ...] | None = None,
1947
  *,
1948
  candidate_seed: int,
 
1949
  ):
1950
- """Apply strict dual-ASR local gates to one safe-duration refill."""
1951
 
1952
  if len(trajectory) != 1 or len(chunks) != 1:
1953
  return verify_trajectory(())
@@ -1957,6 +2008,8 @@ def _verify_refill_candidate_trajectory_audio(
1957
  chunks,
1958
  anchor,
1959
  playback_speed,
 
 
1960
  network_fragment_proofs=proof_rows,
1961
  local_endpoint_roles=_local_endpoint_role_rows(chunks, chunk_specs),
1962
  )
@@ -1968,6 +2021,7 @@ def _verify_refill_candidate_trajectory_audio(
1968
  anchor,
1969
  proof_rows,
1970
  candidate_seed=candidate_seed,
 
1971
  )
1972
  )
1973
  return CandidateVerification(
@@ -1985,8 +2039,8 @@ def _verify_sequence_trajectory_audio(
1985
  independent_cache: WholeWaveformVerificationCache,
1986
  chunk_specs: tuple[GenerationChunkSpec, ...] | None = None,
1987
  *,
1988
- release_transcriber=transcribe_verification_whisper,
1989
- independent_transcriber=transcribe_verification_whisper,
1990
  ):
1991
  """Verify one ranked DP path after exact production assembly."""
1992
 
@@ -1996,22 +2050,20 @@ def _verify_sequence_trajectory_audio(
1996
  playback_speed,
1997
  chunk_specs,
1998
  )
1999
- # The release contract assigns local candidate coverage (including exact
2000
- # range-bound network fragments) to turbo and the exact assembled
2001
- # waveform to the separately pinned full large-v3 verifier. Do not let a
2002
- # redundant long-form turbo decode veto a path that passes that stronger
2003
- # assembled-waveform gate.
2004
  assembled_verification = _verify_release_whole_audio(
2005
  waveform,
2006
  whole_target_text,
2007
  anchor,
2008
  independent_cache,
2009
  transcriber=release_transcriber,
2010
- verifier_profile=RELEASE_LARGE_V3_VERIFICATION_PROFILE,
2011
  )
2012
  status = "verified" if assembled_verification.passed else "rejected"
2013
  print(
2014
- f"[BlueMagpie] sequence path large-v3 {status} "
2015
  f"rank={sequence_result.sequence_path_rank} "
2016
  f"chunk_candidates={sequence_result.chunk_candidate_indices} "
2017
  f"{_verification_metric_log_fields(assembled_verification)}"
@@ -2031,7 +2083,7 @@ def _verify_sequence_trajectory_audio(
2031
  )
2032
  independent_status = "verified" if independent_verification.passed else "rejected"
2033
  print(
2034
- f"[BlueMagpie] sequence path independent large-v3 {independent_status} "
2035
  f"rank={sequence_result.sequence_path_rank} "
2036
  f"chunk_candidates={sequence_result.chunk_candidate_indices} "
2037
  f"{_verification_metric_log_fields(independent_verification)}"
@@ -2385,15 +2437,10 @@ def _synthesize(
2385
  anchor = _speaker_anchor_array(centroid)
2386
  independent_cache = WholeWaveformVerificationCache()
2387
  transcript_cache = WholeWaveformTranscriptCache()
2388
- cached_turbo_transcriber = _cached_request_transcriber(
2389
- transcript_cache,
2390
- TURBO_TRANSCRIPT_PROFILE,
2391
- transcribe_whisper,
2392
- )
2393
- cached_large_v3_transcriber = _cached_request_transcriber(
2394
  transcript_cache,
2395
- LARGE_V3_TRANSCRIPT_PROFILE,
2396
- transcribe_verification_whisper,
2397
  )
2398
  generation_context_by_seed: dict[int, CandidateGenerationContext] = {}
2399
  generation_endpoint_evidence_by_seed: dict[
@@ -2734,8 +2781,8 @@ def _synthesize(
2734
  independent_cache,
2735
  candidate_seed=seed,
2736
  chunk_specs=generation_chunk_specs(seed, candidate_chunks),
2737
- release_transcriber=cached_turbo_transcriber,
2738
- independent_transcriber=cached_large_v3_transcriber,
2739
  ),
2740
  lambda trajectory, candidate_chunks, seed: (
2741
  _qualify_candidate_trajectory_audio(
@@ -2747,8 +2794,8 @@ def _synthesize(
2747
  independent_cache,
2748
  candidate_seed=seed,
2749
  chunk_specs=generation_chunk_specs(seed, candidate_chunks),
2750
- release_transcriber=cached_turbo_transcriber,
2751
- independent_transcriber=cached_large_v3_transcriber,
2752
  )
2753
  if len(chunks) == 1
2754
  else _verify_refill_candidate_trajectory_audio(
@@ -2758,6 +2805,7 @@ def _synthesize(
2758
  speed,
2759
  generation_chunk_specs(seed, candidate_chunks),
2760
  candidate_seed=seed,
 
2761
  )
2762
  ),
2763
  sequence_final_verifier=lambda sequence_result, candidate_chunks: (
@@ -2769,8 +2817,8 @@ def _synthesize(
2769
  speed,
2770
  independent_cache,
2771
  chunk_specs,
2772
- release_transcriber=cached_large_v3_transcriber,
2773
- independent_transcriber=cached_large_v3_transcriber,
2774
  )
2775
  ),
2776
  generation_evidence_factory=candidate_generation_evidence,
@@ -2895,16 +2943,8 @@ def _synthesize(
2895
  text,
2896
  anchor,
2897
  independent_cache,
2898
- transcriber=(
2899
- cached_large_v3_transcriber
2900
- if len(chunks) > 1
2901
- else cached_turbo_transcriber
2902
- ),
2903
- verifier_profile=(
2904
- RELEASE_LARGE_V3_VERIFICATION_PROFILE
2905
- if len(chunks) > 1
2906
- else RELEASE_TURBO_VERIFICATION_PROFILE
2907
- ),
2908
  )
2909
  final_evidence = trajectory_gate_evidence(final_verification)
2910
  independent_final_evidence = None
@@ -2916,7 +2956,7 @@ def _synthesize(
2916
  text,
2917
  anchor,
2918
  independent_cache,
2919
- transcriber=cached_large_v3_transcriber,
2920
  )
2921
  independent_final_evidence = trajectory_gate_evidence(
2922
  independent_final_verification
@@ -2941,8 +2981,8 @@ def _synthesize(
2941
  )
2942
  print(
2943
  "[BlueMagpie] final output rejected "
2944
- f"turbo={_verification_metric_log_fields(final_verification)} "
2945
- f"independent={independent_fields}"
2946
  )
2947
  raise gr.Error("最終合成結果未通過整段內容、語速與音色驗證,未回傳音訊。") from error
2948
  except (RuntimeError, ValueError) as error:
@@ -2960,8 +3000,8 @@ def _synthesize(
2960
  )
2961
  print(
2962
  "[BlueMagpie] final output verified "
2963
- f"turbo_score={final_verification.score:.6f} "
2964
- f"independent_score={independent_final_verification.score:.6f} "
2965
  f"independent_cache_entries={independent_cache.entry_count} "
2966
  f"transcript_cache_entries={transcript_cache.entry_count}"
2967
  )
@@ -3517,7 +3557,7 @@ def _synthesize_glyph_hybrid(
3517
  1.0,
3518
  QUALITY_FINAL_ASR_MAX_NEW_TOKENS,
3519
  release_speaker_gate=True,
3520
- transcriber=_transcribe_glyph_hybrid_turbo,
3521
  )
3522
  independent_final_verification = None
3523
  try:
@@ -3527,7 +3567,7 @@ def _synthesize_glyph_hybrid(
3527
  plan.canonical_target,
3528
  anchor,
3529
  independent_cache,
3530
- transcriber=_transcribe_glyph_hybrid_independent,
3531
  )
3532
  _require_glyph_final_verification(independent_final_verification)
3533
  except FinalOutputRejectedError as error:
@@ -3707,7 +3747,7 @@ HEADER = f"""
3707
  台灣華語與中英混合文字轉語音。模型版本:`{CHECKPOINT}`。
3708
 
3709
  執行環境固定於已驗證的 model revision `{MODEL_REVISION[:8]}`、ECAPA revision
3710
- `{ECAPA_REVISION[:8]}` 與 Whisper revision `{WHISPER_REVISION[:8]}`,避免服務重啟時
3711
  無聲變更權重、speaker embedding 或語意驗證空間。
3712
 
3713
  目前預設採用穩定推論設定:候選交錯 `CFG 3.0 / 2.0`(網路內容與極短句最低 3.0)、
@@ -3722,7 +3762,8 @@ same-seed 完整 trajectory;失敗後只補低覆蓋 chunks,再以 speaker/R
3722
  culprit-diverse exact paths;所有 chunk 首次具備 coverage 時先驗證 rank-1 exact path;若它已通過 hard gate
3723
  但僅未達 preferred tier,下一個新 rank-1 path 會在 bounded refill 中提前驗證,同時保留 completed-lattice
3724
  final slot。F0 只在 multi-chunk lattice 真正有替代路徑、需要 transition ranking 時才計算。單一 chunk 若 exact joined
3725
- 波形已通過 ASR、preferred SQUIM、release speaker、語速與 echo/smearing gate,會立即返回而不再填滿候選池。
 
3726
  NFE 固定為已驗證的 10;48 speech units 內的非網路互動 request 最多生成 10 個 TTS chunks,
3727
  其他 request 維持 32 個 hard cap,且一律不超過 800 generated speech units。語速校正使用 deterministic WSOLA,總 rate 不低於 0.90;不少於 1 秒的
3728
  候選與最終波形另須通過 echo/smearing gate。
 
169
  )
170
  from quality_runtime import (
171
  BASE_GENERATION_POLICY,
172
+ BREEZE25_MAX_VERIFICATION_SEGMENTS,
173
+ BREEZE25_MODEL_ID,
174
+ BREEZE25_REVISION,
175
+ BREEZE25_WEIGHT_SHA256,
176
  GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS as QUALITY_GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS,
177
  SAFE_DURATION_GENERATION_POLICY,
 
 
 
 
 
178
  CandidateGenerationContext,
179
  CandidateGenerationEvidence,
180
  CandidateVerification,
 
207
  run_coverage_adaptive_cascade,
208
  speaker_evidence_from_audio,
209
  squim_objective_evidence_from_audio,
210
+ transcribe_breeze25,
 
211
  trajectory_gate_evidence,
212
  verify_candidate,
213
  verify_trajectory,
 
284
  QUALITY_PREFERRED_SQUIM_MIN_DURATION_SECONDS = 1.50
285
  QUALITY_PREFERRED_MIN_SPEAKER_SIMILARITY = 0.25
286
  QUALITY_PREFERRED_MAX_BOUNDARY_SPEAKER_DROP = 0.05
287
+ # A single-chunk waveform that has passed both Breeze25 semantic projections
288
+ # and every hard release gate may stop the cascade at this exact-only tier.
289
+ # Local chunk proxies and multi-chunk eager paths remain on the stricter tier.
290
  QUALITY_FAST_EXACT_MIN_SPEAKER_SIMILARITY = 0.23
291
  QUALITY_FAST_EXACT_MAX_BOUNDARY_SPEAKER_DROP = 0.08
292
  QUALITY_MAX_SEQUENCE_PATHS = 3
 
306
  GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS
307
  != QUALITY_GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS
308
  ):
309
+ raise RuntimeError("glyph-hybrid Breeze25 segment caps disagree")
310
+ BREEZE25_SEMANTIC_VERIFICATION_PROFILE = (
311
+ f"{BREEZE25_MODEL_ID}@{BREEZE25_REVISION}:"
312
+ f"weights{BREEZE25_WEIGHT_SHA256}:"
313
  f"greedy:zh:short{QUALITY_PREFIX_SUFFIX_UNITS}:cer{QUALITY_MAX_CER}:"
314
  f"prefix{QUALITY_PREFIX_SUFFIX_UNITS}=0:"
315
  f"suffix{QUALITY_PREFIX_SUFFIX_UNITS}=0:tail=0:"
316
  f"max_tokens{QUALITY_FINAL_ASR_MAX_NEW_TOKENS}"
317
  )
318
+ BREEZE25_TRANSCRIPT_PROFILE = (
319
+ f"{BREEZE25_MODEL_ID}@{BREEZE25_REVISION}:"
320
+ f"weights{BREEZE25_WEIGHT_SHA256}:greedy:zh:transcribe:"
321
  f"max_tokens{QUALITY_FINAL_ASR_MAX_NEW_TOKENS}:"
322
+ f"segments{BREEZE25_MAX_VERIFICATION_SEGMENTS}"
323
  )
324
+ BREEZE25_RELEASE_VERIFICATION_PROFILE = (
325
+ f"{BREEZE25_TRANSCRIPT_PROFILE}:release-speaker-squim-echo-wsola-v2:"
 
 
 
 
 
326
  f"pace{QUALITY_MAX_PACE_CPS}"
327
  )
328
+ BREEZE25_SNAPSHOT_ALLOW_PATTERNS = (
329
+ "added_tokens.json",
330
+ "config.json",
331
+ "generation_config.json",
332
+ "merges.txt",
333
+ "model.safetensors",
334
+ "normalizer.json",
335
+ "preprocessor_config.json",
336
+ "special_tokens_map.json",
337
+ "tokenizer.json",
338
+ "tokenizer_config.json",
339
+ "vocab.json",
340
  )
341
 
342
 
 
344
  MODEL_DIR = snapshot_download(REPO_ID, revision=MODEL_REVISION)
345
  print(f"[BlueMagpie] caching speaker encoder from {ECAPA_REPO_ID}@{ECAPA_REVISION} ...")
346
  ECAPA_DIR = snapshot_download(ECAPA_REPO_ID, revision=ECAPA_REVISION)
347
+ BREEZE25_ASR_DIR = snapshot_download(
348
+ BREEZE25_MODEL_ID,
349
+ revision=BREEZE25_REVISION,
350
+ allow_patterns=BREEZE25_SNAPSHOT_ALLOW_PATTERNS,
 
351
  )
352
+ print(
353
+ "[BlueMagpie] cached content verifier from "
354
+ f"{BREEZE25_MODEL_ID}@{BREEZE25_REVISION} "
355
+ f"weights={BREEZE25_WEIGHT_SHA256[:12]}"
356
  )
357
  tokenizer = PreTrainedTokenizerFast(tokenizer_file=os.path.join(MODEL_DIR, "tokenizer.json"))
358
  print(f"[BlueMagpie] loading model on device={DEVICE} ...")
 
1097
  asr_max_new_tokens: int = 128,
1098
  *,
1099
  release_speaker_gate: bool = False,
1100
+ transcriber=transcribe_breeze25,
1101
  semantic_only: bool = False,
1102
  network_fragment_proofs: (
1103
  tuple[tuple[NetworkFragmentProof, ...], ...] | None
 
1148
  strict=True,
1149
  )
1150
  ):
1151
+ try:
1152
+ waveform = np.asarray(audio, dtype=np.float32).reshape(-1)
1153
+ if waveform.size == 0 or not np.isfinite(waveform).all():
1154
+ raise ValueError("candidate waveform is invalid")
1155
+ duration = active_voiced_duration_seconds(waveform, SR)
1156
+ except (TypeError, ValueError, OverflowError):
1157
+ observations.append(
1158
+ CandidateObservation(
1159
+ target_text=chunk,
1160
+ transcript_text="",
1161
+ audio_duration_seconds=0.0,
1162
+ pace_cps=None,
1163
+ )
1164
+ )
1165
+ artifacts.append(ChunkCandidateArtifact())
1166
+ prepared_waveforms.append(None)
1167
+ continue
1168
+ speech_units = count_speech_units(chunk)
1169
+ pace_cps = (
1170
+ speech_units / duration * float(playback_speed)
1171
+ if duration > 0.0
1172
+ else None
1173
+ )
1174
+ if (
1175
+ not semantic_only
1176
+ and (
1177
+ pace_cps is None
1178
+ or pace_cps
1179
+ > QUALITY_MAX_PACE_CPS + QUALITY_PREFLIGHT_PACE_MARGIN_CPS
1180
+ )
1181
+ ):
1182
+ # A candidate that cannot pass the immutable pace gate must never
1183
+ # be returned. Reject it before Breeze25, ECAPA, SQUIM and echo
1184
+ # work; later cascade candidates still face every unchanged gate.
1185
+ observations.append(
1186
+ CandidateObservation(
1187
+ target_text=chunk,
1188
+ transcript_text="",
1189
+ audio_duration_seconds=duration,
1190
+ pace_cps=pace_cps,
1191
+ )
1192
+ )
1193
+ artifacts.append(ChunkCandidateArtifact())
1194
+ prepared_waveforms.append(None)
1195
+ continue
1196
  prepared = prepare_candidate_audio(
1197
+ waveform,
1198
  SR,
1199
  transcriber=lambda waveform, sample_rate: transcriber(
1200
  waveform,
 
1215
  prepared_waveforms.append(None)
1216
  continue
1217
  waveform = prepared.waveform
 
 
 
 
1218
  transcript = prepared.transcript_text
1219
  if fragment_proofs:
1220
  fragment_evidence = canonicalize_asr_network_fragments(
 
1229
  if fragment_evidence.passed
1230
  else ""
1231
  )
 
 
 
 
 
1232
  semantic_observation = CandidateObservation(
1233
  target_text=chunk,
1234
  transcript_text=transcript,
 
1331
  begin_speaker_similarity=begin_similarity,
1332
  end_speaker_similarity=end_similarity,
1333
  pace_cps=(
1334
+ speech_units / duration * float(playback_speed)
1335
  if duration > 0.0
1336
  else None
1337
  ),
 
1386
  anchor: np.ndarray,
1387
  cache: WholeWaveformVerificationCache,
1388
  *,
1389
+ transcriber=transcribe_breeze25,
1390
  ):
1391
+ """Apply the semantic-only projection using the cached Breeze25 transcript."""
1392
 
1393
  def verifier(exact_waveform, sample_rate, exact_target):
1394
  if sample_rate != SR:
 
1407
  waveform,
1408
  SR,
1409
  target_text,
1410
+ BREEZE25_SEMANTIC_VERIFICATION_PROFILE,
1411
  verifier,
1412
  )
1413
 
 
1477
  )
1478
 
1479
 
1480
+ def _transcribe_glyph_hybrid_breeze_primary(
1481
  audio: np.ndarray,
1482
  sample_rate: int,
1483
  *,
1484
  language: str = "zh",
1485
  task: str = "transcribe",
1486
  max_new_tokens: int = 128,
1487
+ max_verification_segments: int = GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS,
1488
  ) -> str:
1489
+ """Run the fixed-zh Breeze25 profile with the V5 segment cap."""
1490
 
1491
+ return transcribe_breeze25(
1492
  audio,
1493
  sample_rate,
1494
  language=language,
1495
  task=task,
1496
  max_new_tokens=max_new_tokens,
1497
+ max_verification_segments=max_verification_segments,
1498
  )
1499
 
1500
 
1501
+ def _transcribe_glyph_hybrid_breeze_confirmation(
1502
  audio: np.ndarray,
1503
  sample_rate: int,
1504
  *,
1505
+ language: str | None = None,
1506
  task: str = "transcribe",
1507
  max_new_tokens: int = 128,
1508
+ max_verification_segments: int = GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS,
1509
  ) -> str:
1510
+ """Run the auto-language Breeze25 confirmation profile for V5."""
1511
 
1512
+ return transcribe_breeze25(
1513
  audio,
1514
  sample_rate,
1515
  language=language,
1516
  task=task,
1517
  max_new_tokens=max_new_tokens,
1518
+ max_verification_segments=max_verification_segments,
1519
  )
1520
 
1521
 
 
1569
  asr_max_verification_segments=(
1570
  GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS
1571
  ),
1572
+ breeze_primary_transcriber=(
1573
+ _transcribe_glyph_hybrid_breeze_primary
1574
+ ),
1575
+ breeze_confirmation_transcriber=(
1576
+ _transcribe_glyph_hybrid_breeze_confirmation
1577
  ),
1578
  )
1579
  )
 
1792
 
1793
 
1794
  def _verify_network_local_asr_intersection(
1795
+ primary_verification,
1796
  trajectory: tuple[np.ndarray, ...],
1797
  chunks: tuple[str, ...],
1798
  anchor: np.ndarray,
1799
  proof_rows: tuple[tuple[NetworkFragmentProof, ...], ...] | None,
1800
  *,
1801
  candidate_seed: int,
1802
+ transcriber=transcribe_breeze25,
1803
  ):
1804
+ """Project cached Breeze25 semantics before retaining network locals.
1805
+
1806
+ Only proof-bearing rows receive the duplicate semantic projection. Their
1807
+ exact ``NetworkFragmentProof`` tuples are reused unchanged, so neither
1808
+ semantic projection can borrow a whole URL/email proof for another local
1809
+ range. The
1810
+ intersection helper keeps primary speaker/SQUIM/pace/artifact evidence
1811
+ while projecting every semantic rejection into a hard local failure. The
1812
+ request-local transcriber cache prevents a second decode of exact PCM.
1813
  """
1814
 
1815
  if proof_rows is None:
1816
+ return primary_verification, tuple(
1817
  LocalIndependentGateEvidence(False, None, 0, None)
1818
  for _ in chunks
1819
  )
1820
  if (
1821
  len(proof_rows) != len(chunks)
1822
  or len(trajectory) != len(chunks)
1823
+ or len(primary_verification.candidate_results) != len(chunks)
1824
  ):
1825
  raise ValueError("network local verification provenance does not align")
1826
  independent_evidence = [
 
1838
  if (
1839
  proofs
1840
  and local_candidate_has_coverage_eligibility(
1841
+ primary_verification.candidate_results[index],
1842
  max_local_boundary_speaker_drop=(
1843
  SEQUENCE_FALLBACK_MAX_LOCAL_BOUNDARY_SPEAKER_DROP
1844
  ),
 
1846
  )
1847
  )
1848
  if not selected_indices:
1849
+ return primary_verification, tuple(independent_evidence)
1850
  independent_proof_rows = tuple(
1851
  proof_rows[index] for index in selected_indices
1852
  )
 
1856
  anchor,
1857
  1.0,
1858
  QUALITY_FINAL_ASR_MAX_NEW_TOKENS,
1859
+ transcriber=transcriber,
1860
  semantic_only=True,
1861
  network_fragment_proofs=independent_proof_rows,
1862
  )
 
1870
  )
1871
  comparison = result.comparison
1872
  print(
1873
+ "[BlueMagpie] network local Breeze25 projection "
1874
  f"seed={candidate_seed} local_chunk_index={primary_index} "
1875
  f"proof_count={len(proof_rows[primary_index])} "
1876
  f"passed={result.passed} cer={comparison.cer:.6f} "
 
1881
  )
1882
  return (
1883
  intersect_local_semantic_verification(
1884
+ primary_verification,
1885
  independent_verification,
1886
  selected_indices,
1887
  ),
 
1899
  *,
1900
  candidate_seed: int,
1901
  chunk_specs: tuple[GenerationChunkSpec, ...] | None = None,
1902
+ release_transcriber=transcribe_breeze25,
1903
+ independent_transcriber=transcribe_breeze25,
1904
  ):
1905
  """Run whole-output qualification only after every local chunk passes."""
1906
 
 
1910
  chunks,
1911
  anchor,
1912
  playback_speed,
1913
+ QUALITY_FINAL_ASR_MAX_NEW_TOKENS,
1914
+ transcriber=release_transcriber,
1915
  network_fragment_proofs=proof_rows,
1916
  local_endpoint_roles=_local_endpoint_role_rows(chunks, chunk_specs),
1917
  )
 
1923
  anchor,
1924
  proof_rows,
1925
  candidate_seed=candidate_seed,
1926
+ transcriber=independent_transcriber,
1927
  )
1928
  )
1929
  if not local_verification.passed:
 
1944
  anchor,
1945
  independent_cache,
1946
  transcriber=release_transcriber,
1947
+ verifier_profile=BREEZE25_RELEASE_VERIFICATION_PROFILE,
1948
  )
1949
  qualified = qualify_trajectory_with_joined_output(
1950
  local_verification,
 
1970
  independent_cache,
1971
  transcriber=independent_transcriber,
1972
  )
1973
+ semantic_qualified = qualify_trajectory_with_joined_output(
1974
  qualified,
1975
  independent_verification,
1976
  )
1977
+ if not semantic_qualified.passed:
1978
  print(
1979
+ "[BlueMagpie] candidate Breeze25 semantic projection rejected "
1980
  f"seed={candidate_seed} "
1981
  f"{_verification_metric_log_fields(independent_verification)}"
1982
  )
1983
  return CandidateVerification(
1984
+ semantic_qualified,
1985
  independent_local_results=independent_local_results,
1986
  joined_output=joined_evidence,
1987
  independent_output=trajectory_gate_evidence(independent_verification),
 
1996
  chunk_specs: tuple[GenerationChunkSpec, ...] | None = None,
1997
  *,
1998
  candidate_seed: int,
1999
+ transcriber=transcribe_breeze25,
2000
  ):
2001
+ """Apply strict cached-Breeze25 local gates to one safe-duration refill."""
2002
 
2003
  if len(trajectory) != 1 or len(chunks) != 1:
2004
  return verify_trajectory(())
 
2008
  chunks,
2009
  anchor,
2010
  playback_speed,
2011
+ QUALITY_FINAL_ASR_MAX_NEW_TOKENS,
2012
+ transcriber=transcriber,
2013
  network_fragment_proofs=proof_rows,
2014
  local_endpoint_roles=_local_endpoint_role_rows(chunks, chunk_specs),
2015
  )
 
2021
  anchor,
2022
  proof_rows,
2023
  candidate_seed=candidate_seed,
2024
+ transcriber=transcriber,
2025
  )
2026
  )
2027
  return CandidateVerification(
 
2039
  independent_cache: WholeWaveformVerificationCache,
2040
  chunk_specs: tuple[GenerationChunkSpec, ...] | None = None,
2041
  *,
2042
+ release_transcriber=transcribe_breeze25,
2043
+ independent_transcriber=transcribe_breeze25,
2044
  ):
2045
  """Verify one ranked DP path after exact production assembly."""
2046
 
 
2050
  playback_speed,
2051
  chunk_specs,
2052
  )
2053
+ # Local and assembled checks share one request-local Breeze25 transcript
2054
+ # cache. The semantic-only projection remains a separate hard gate over
2055
+ # the same exact transcript, never a second decoder invocation.
 
 
2056
  assembled_verification = _verify_release_whole_audio(
2057
  waveform,
2058
  whole_target_text,
2059
  anchor,
2060
  independent_cache,
2061
  transcriber=release_transcriber,
2062
+ verifier_profile=BREEZE25_RELEASE_VERIFICATION_PROFILE,
2063
  )
2064
  status = "verified" if assembled_verification.passed else "rejected"
2065
  print(
2066
+ f"[BlueMagpie] sequence path Breeze25 {status} "
2067
  f"rank={sequence_result.sequence_path_rank} "
2068
  f"chunk_candidates={sequence_result.chunk_candidate_indices} "
2069
  f"{_verification_metric_log_fields(assembled_verification)}"
 
2083
  )
2084
  independent_status = "verified" if independent_verification.passed else "rejected"
2085
  print(
2086
+ f"[BlueMagpie] sequence path Breeze25 projection {independent_status} "
2087
  f"rank={sequence_result.sequence_path_rank} "
2088
  f"chunk_candidates={sequence_result.chunk_candidate_indices} "
2089
  f"{_verification_metric_log_fields(independent_verification)}"
 
2437
  anchor = _speaker_anchor_array(centroid)
2438
  independent_cache = WholeWaveformVerificationCache()
2439
  transcript_cache = WholeWaveformTranscriptCache()
2440
+ cached_breeze25_transcriber = _cached_request_transcriber(
 
 
 
 
 
2441
  transcript_cache,
2442
+ BREEZE25_TRANSCRIPT_PROFILE,
2443
+ transcribe_breeze25,
2444
  )
2445
  generation_context_by_seed: dict[int, CandidateGenerationContext] = {}
2446
  generation_endpoint_evidence_by_seed: dict[
 
2781
  independent_cache,
2782
  candidate_seed=seed,
2783
  chunk_specs=generation_chunk_specs(seed, candidate_chunks),
2784
+ release_transcriber=cached_breeze25_transcriber,
2785
+ independent_transcriber=cached_breeze25_transcriber,
2786
  ),
2787
  lambda trajectory, candidate_chunks, seed: (
2788
  _qualify_candidate_trajectory_audio(
 
2794
  independent_cache,
2795
  candidate_seed=seed,
2796
  chunk_specs=generation_chunk_specs(seed, candidate_chunks),
2797
+ release_transcriber=cached_breeze25_transcriber,
2798
+ independent_transcriber=cached_breeze25_transcriber,
2799
  )
2800
  if len(chunks) == 1
2801
  else _verify_refill_candidate_trajectory_audio(
 
2805
  speed,
2806
  generation_chunk_specs(seed, candidate_chunks),
2807
  candidate_seed=seed,
2808
+ transcriber=cached_breeze25_transcriber,
2809
  )
2810
  ),
2811
  sequence_final_verifier=lambda sequence_result, candidate_chunks: (
 
2817
  speed,
2818
  independent_cache,
2819
  chunk_specs,
2820
+ release_transcriber=cached_breeze25_transcriber,
2821
+ independent_transcriber=cached_breeze25_transcriber,
2822
  )
2823
  ),
2824
  generation_evidence_factory=candidate_generation_evidence,
 
2943
  text,
2944
  anchor,
2945
  independent_cache,
2946
+ transcriber=cached_breeze25_transcriber,
2947
+ verifier_profile=BREEZE25_RELEASE_VERIFICATION_PROFILE,
 
 
 
 
 
 
 
 
2948
  )
2949
  final_evidence = trajectory_gate_evidence(final_verification)
2950
  independent_final_evidence = None
 
2956
  text,
2957
  anchor,
2958
  independent_cache,
2959
+ transcriber=cached_breeze25_transcriber,
2960
  )
2961
  independent_final_evidence = trajectory_gate_evidence(
2962
  independent_final_verification
 
2981
  )
2982
  print(
2983
  "[BlueMagpie] final output rejected "
2984
+ f"breeze25={_verification_metric_log_fields(final_verification)} "
2985
+ f"semantic_projection={independent_fields}"
2986
  )
2987
  raise gr.Error("最終合成結果未通過整段內容、語速與音色驗證,未回傳音訊。") from error
2988
  except (RuntimeError, ValueError) as error:
 
3000
  )
3001
  print(
3002
  "[BlueMagpie] final output verified "
3003
+ f"breeze25_score={final_verification.score:.6f} "
3004
+ f"semantic_projection_score={independent_final_verification.score:.6f} "
3005
  f"independent_cache_entries={independent_cache.entry_count} "
3006
  f"transcript_cache_entries={transcript_cache.entry_count}"
3007
  )
 
3557
  1.0,
3558
  QUALITY_FINAL_ASR_MAX_NEW_TOKENS,
3559
  release_speaker_gate=True,
3560
+ transcriber=_transcribe_glyph_hybrid_breeze_primary,
3561
  )
3562
  independent_final_verification = None
3563
  try:
 
3567
  plan.canonical_target,
3568
  anchor,
3569
  independent_cache,
3570
+ transcriber=_transcribe_glyph_hybrid_breeze_confirmation,
3571
  )
3572
  _require_glyph_final_verification(independent_final_verification)
3573
  except FinalOutputRejectedError as error:
 
3747
  台灣華語與中英混合文字轉語音。模型版本:`{CHECKPOINT}`。
3748
 
3749
  執行環境固定於已驗證的 model revision `{MODEL_REVISION[:8]}`、ECAPA revision
3750
+ `{ECAPA_REVISION[:8]}` 與 Breeze-ASR-25 revision `{BREEZE25_REVISION[:8]}`,避免服務重啟時
3751
  無聲變更權重、speaker embedding 或語意驗證空間。
3752
 
3753
  目前預設採用穩定推論設定:候選交錯 `CFG 3.0 / 2.0`(網路內容與極短句最低 3.0)、
 
3762
  culprit-diverse exact paths;所有 chunk 首次具備 coverage 時先驗證 rank-1 exact path;若它已通過 hard gate
3763
  但僅未達 preferred tier,下一個新 rank-1 path 會在 bounded refill 中提前驗證,同時保留 completed-lattice
3764
  final slot。F0 只在 multi-chunk lattice 真正有替代路徑、需要 transition ranking 時才計算。單一 chunk 若 exact joined
3765
+ 波形已通過 cached Breeze25 語意投影、preferred SQUIM、release speaker、語速與 echo/smearing gate,
3766
+ 會立即返回而不再填滿候選池。
3767
  NFE 固定為已驗證的 10;48 speech units 內的非網路互動 request 最多生成 10 個 TTS chunks,
3768
  其他 request 維持 32 個 hard cap,且一律不超過 800 generated speech units。語速校正使用 deterministic WSOLA,總 rate 不低於 0.90;不少於 1 秒的
3769
  候選與最終波形另須通過 echo/smearing gate。
glyph_assets.py CHANGED
@@ -307,6 +307,17 @@ class SquimAttestation:
307
 
308
  @dataclass(frozen=True)
309
  class EndpointAttestation:
 
 
 
 
 
 
 
 
 
 
 
310
  stop_reason: str
311
  turbo_cer: float
312
  large_v3_cer: float
@@ -317,6 +328,18 @@ class EndpointAttestation:
317
  maximum_tail_energy_ratio: float
318
  terminal_silence_samples: int
319
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
  @dataclass(frozen=True)
322
  class PcmMeasurements:
@@ -1116,10 +1139,13 @@ def _parse_endpoint(value: Any, *, context: str) -> EndpointAttestation:
1116
  )
1117
  if stop_reason not in ("stop_token", "verified_hard_cap"):
1118
  raise GlyphAssetError(f"{context}.stop_reason is not an allowed completion")
1119
- turbo_cer = _require_finite(
 
 
 
1120
  fields["turbo_cer"], context=f"{context}.turbo_cer", minimum=0.0
1121
  )
1122
- large_v3_cer = _require_finite(
1123
  fields["large_v3_cer"], context=f"{context}.large_v3_cer", minimum=0.0
1124
  )
1125
  prefix_cer = _require_finite(
@@ -1154,8 +1180,8 @@ def _parse_endpoint(value: Any, *, context: str) -> EndpointAttestation:
1154
  )
1155
  return EndpointAttestation(
1156
  stop_reason,
1157
- turbo_cer,
1158
- large_v3_cer,
1159
  prefix_cer,
1160
  suffix_cer,
1161
  extra_tail_units,
 
307
 
308
  @dataclass(frozen=True)
309
  class EndpointAttestation:
310
+ """Legacy v1 storage for the two Breeze ASR 25 decoder-profile CERs.
311
+
312
+ The serialized ``turbo_cer`` and ``large_v3_cer`` keys cannot be renamed
313
+ without invalidating every frozen v1 manifest hash. Migrated bundles reuse
314
+ those slots for Breeze primary fixed-zh and confirmation auto-language CER,
315
+ but historical values retain their archival meaning and are never accepted
316
+ as current Breeze runtime evidence. The hardened adapter requires the
317
+ official Breeze weight pin; new runtime evidence uses only honest profile
318
+ names.
319
+ """
320
+
321
  stop_reason: str
322
  turbo_cer: float
323
  large_v3_cer: float
 
328
  maximum_tail_energy_ratio: float
329
  terminal_silence_samples: int
330
 
331
+ @property
332
+ def breeze_primary_cer(self) -> float:
333
+ """Return the primary-profile slot for a migrated Breeze bundle."""
334
+
335
+ return self.turbo_cer
336
+
337
+ @property
338
+ def breeze_confirmation_cer(self) -> float:
339
+ """Return the confirmation slot for a migrated Breeze bundle."""
340
+
341
+ return self.large_v3_cer
342
+
343
 
344
  @dataclass(frozen=True)
345
  class PcmMeasurements:
 
1139
  )
1140
  if stop_reason not in ("stop_token", "verified_hard_cap"):
1141
  raise GlyphAssetError(f"{context}.stop_reason is not an allowed completion")
1142
+ # These two field names are frozen v1 wire compatibility only. See
1143
+ # ``EndpointAttestation``: their values now attest the two decoder
1144
+ # profiles over the same pinned Breeze ASR 25 weights.
1145
+ breeze_primary_cer = _require_finite(
1146
  fields["turbo_cer"], context=f"{context}.turbo_cer", minimum=0.0
1147
  )
1148
+ breeze_confirmation_cer = _require_finite(
1149
  fields["large_v3_cer"], context=f"{context}.large_v3_cer", minimum=0.0
1150
  )
1151
  prefix_cer = _require_finite(
 
1180
  )
1181
  return EndpointAttestation(
1182
  stop_reason,
1183
+ breeze_primary_cer,
1184
+ breeze_confirmation_cer,
1185
  prefix_cer,
1186
  suffix_cer,
1187
  extra_tail_units,
glyph_hybrid_adapter.py CHANGED
@@ -2,9 +2,10 @@
2
 
3
  The adapter is the only bridge from the hosted runtime's text-bearing objects
4
  to :mod:`glyph_hybrid_evidence`. It rebuilds the renderer plan, replays every
5
- model attempt, reruns both pinned ASRs plus ECAPA and SQUIM on exact public
6
- PCM, validates the immutable assembly, and only then returns content-free
7
- canonical JSON.
 
8
  """
9
 
10
  from __future__ import annotations
@@ -23,6 +24,7 @@ import glyph_ascii_v1 as glyph_ascii_module
23
  import glyph_assets as glyph_assets_module
24
  import glyph_hybrid_evidence as evidence
25
  import glyph_hybrid_plan as glyph_plan_module
 
26
  from glyph_assets import (
27
  AssetSegment,
28
  CarrierSegment,
@@ -35,25 +37,69 @@ from quality_runtime import (
35
  SQUIM_OBJECTIVE_SAMPLE_RATE,
36
  SQUIM_OBJECTIVE_WEIGHT_SHA256,
37
  SQUIM_OBJECTIVE_WINDOW_SECONDS,
38
- VERIFICATION_WHISPER_MODEL_ID,
39
- VERIFICATION_WHISPER_REVISION,
40
- WHISPER_ATTENTION_IMPLEMENTATION,
41
- WHISPER_MAX_MICROBATCH_SEGMENTS,
42
- WHISPER_MODEL_ID,
43
- WHISPER_RETURN_ATTENTION_MASK,
44
- WHISPER_REVISION,
45
- WHISPER_SAMPLE_RATE,
46
  release_speaker_evidence_from_audio,
47
  squim_objective_evidence_from_audio,
48
- transcribe_verification_whisper,
49
- transcribe_whisper,
50
  )
51
 
52
 
53
  ADAPTER_SCHEMA = "bluemagpie.glyph-hybrid.adapter.v1"
54
  GENERATION_POLICY_SCHEMA = "bluemagpie.glyph-generation-policy.v1"
55
- ASR_LANGUAGE = "zh"
56
  ASR_TASK = "transcribe"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
 
59
  class GlyphHybridAdapterError(RuntimeError):
@@ -84,10 +130,8 @@ class GlyphAdapterRuntime:
84
  renderer_builder: Callable[..., Any] = build_glyph_hybrid_plan
85
  assembly_verifier: Callable[..., None] = verify_hybrid_assembly
86
  carrier_conditioner: Callable[[CarrierSegment], bytes] = condition_carrier_v1
87
- turbo_transcriber: Callable[..., str] = transcribe_whisper
88
- verification_transcriber: Callable[..., str] = (
89
- transcribe_verification_whisper
90
- )
91
  speaker_measure: Callable[..., Any] = release_speaker_evidence_from_audio
92
  squim_measure: Callable[..., Any] = squim_objective_evidence_from_audio
93
 
@@ -173,9 +217,10 @@ def _artifact_pin(
173
  *,
174
  kind: str,
175
  config: Mapping[str, Any],
 
176
  ) -> evidence.ArtifactPin:
177
  return evidence.ArtifactPin(
178
- name=str(pin.name),
179
  kind=kind,
180
  uri=str(pin.uri),
181
  revision=str(pin.revision),
@@ -220,18 +265,16 @@ def _runtime_pins(
220
  revision=runtime.model_revision,
221
  label="BlueMagpie model",
222
  )
223
- turbo_pin = _select_hf_pin(
224
- tuple(bundle.evaluator_pins),
225
- repo_id=WHISPER_MODEL_ID,
226
- revision=WHISPER_REVISION,
227
- label="Whisper turbo",
228
- )
229
- verification_pin = _select_hf_pin(
230
  tuple(bundle.evaluator_pins),
231
- repo_id=VERIFICATION_WHISPER_MODEL_ID,
232
- revision=VERIFICATION_WHISPER_REVISION,
233
- label="Whisper large-v3",
234
  )
 
 
 
 
235
  ecapa_pin = _select_hf_pin(
236
  tuple(bundle.evaluator_pins),
237
  repo_id=runtime.ecapa_repo_id,
@@ -312,37 +355,41 @@ def _runtime_pins(
312
  "inference_steps": runtime.inference_steps,
313
  },
314
  )
315
- asr_config = {
316
  "schema": ADAPTER_SCHEMA,
317
- "language": ASR_LANGUAGE,
318
  "task": ASR_TASK,
319
  "max_new_tokens": runtime.asr_max_new_tokens,
320
  "max_verification_segments": (
321
  runtime.asr_max_verification_segments
322
  ),
323
- "sample_rate": WHISPER_SAMPLE_RATE,
324
- "attention_implementation": WHISPER_ATTENTION_IMPLEMENTATION,
325
- "return_attention_mask": WHISPER_RETURN_ATTENTION_MASK,
326
- "max_microbatch_segments": WHISPER_MAX_MICROBATCH_SEGMENTS,
327
  "do_sample": False,
328
  "num_beams": 1,
 
 
 
329
  }
330
- turbo = _artifact_pin(
331
- turbo_pin,
332
  kind="asr",
 
333
  config={
334
- **asr_config,
335
- "repo_id": WHISPER_MODEL_ID,
336
- "revision": WHISPER_REVISION,
337
  },
338
  )
339
- verification = _artifact_pin(
340
- verification_pin,
341
  kind="asr",
 
342
  config={
343
- **asr_config,
344
- "repo_id": VERIFICATION_WHISPER_MODEL_ID,
345
- "revision": VERIFICATION_WHISPER_REVISION,
346
  },
347
  )
348
  ecapa = _artifact_pin(
@@ -370,7 +417,14 @@ def _runtime_pins(
370
  "device": "cpu",
371
  },
372
  )
373
- return source_pins, model, turbo, verification, ecapa, squim
 
 
 
 
 
 
 
374
 
375
 
376
  def _trusted_bundle(
@@ -760,8 +814,8 @@ class HardenedGlyphHybridAdapter:
760
  (
761
  source_pins,
762
  model_pin,
763
- turbo_pin,
764
- verification_pin,
765
  ecapa_pin,
766
  squim_pin,
767
  ) = _runtime_pins(runtime, bundle)
@@ -975,8 +1029,8 @@ class HardenedGlyphHybridAdapter:
975
  )
976
 
977
  expected_pins = {
978
- turbo_pin.name: turbo_pin,
979
- verification_pin.name: verification_pin,
980
  ecapa_pin.name: ecapa_pin,
981
  squim_pin.name: squim_pin,
982
  }
@@ -1000,23 +1054,25 @@ class HardenedGlyphHybridAdapter:
1000
  key = pin.name, _sha256_bytes(pcm16_le), sample_rate
1001
  if key not in asr_cache:
1002
  waveform = _pcm_waveform(pcm16_le)
1003
- transcriber = (
1004
- runtime.turbo_transcriber
1005
- if pin == turbo_pin
1006
- else runtime.verification_transcriber
1007
- if pin == verification_pin
1008
- else None
1009
- )
1010
- if transcriber is None:
1011
  raise GlyphHybridAdapterError(
1012
  "ASR evaluator kind is not pinned"
1013
  )
1014
  transcript = transcriber(
1015
  waveform,
1016
  sample_rate,
1017
- language=ASR_LANGUAGE,
1018
  task=ASR_TASK,
1019
  max_new_tokens=runtime.asr_max_new_tokens,
 
 
 
1020
  )
1021
  if type(transcript) is not str:
1022
  raise GlyphHybridAdapterError(
@@ -1094,13 +1150,13 @@ class HardenedGlyphHybridAdapter:
1094
  return squim_cache[key]
1095
 
1096
  output_pcm = bytes(assembly.pcm16_le)
1097
- turbo_transcript = asr_evaluator(
1098
- turbo_pin,
1099
  output_pcm,
1100
  runtime.sample_rate,
1101
  )
1102
- verification_transcript = asr_evaluator(
1103
- verification_pin,
1104
  output_pcm,
1105
  runtime.sample_rate,
1106
  )
@@ -1118,8 +1174,8 @@ class HardenedGlyphHybridAdapter:
1118
  final_comparison = getattr(final_row, "comparison", None)
1119
  if (
1120
  final_comparison is None
1121
- or not turbo_transcript
1122
- or not verification_transcript
1123
  ):
1124
  raise GlyphHybridAdapterError(
1125
  "semantic evaluator output is unavailable"
@@ -1135,11 +1191,15 @@ class HardenedGlyphHybridAdapter:
1135
  runtime.gate_policy,
1136
  )
1137
  for scope, asr_pin, transcript in (
1138
- ("final", turbo_pin, turbo_transcript),
 
 
 
 
1139
  (
1140
  "independent",
1141
- verification_pin,
1142
- verification_transcript,
1143
  ),
1144
  ):
1145
  asr_call_id = f"asr-{scope}"
@@ -1243,8 +1303,8 @@ class HardenedGlyphHybridAdapter:
1243
  source_pins=source_pins,
1244
  model_pins=(model_pin,),
1245
  evaluator_pins=(
1246
- turbo_pin,
1247
- verification_pin,
1248
  ecapa_pin,
1249
  squim_pin,
1250
  ),
 
2
 
3
  The adapter is the only bridge from the hosted runtime's text-bearing objects
4
  to :mod:`glyph_hybrid_evidence`. It rebuilds the renderer plan, replays every
5
+ model attempt, reruns two explicitly distinct decoder profiles over the same
6
+ pinned Breeze ASR 25 weights plus ECAPA and SQUIM on exact public PCM,
7
+ validates the immutable assembly, and only then returns content-free canonical
8
+ JSON.
9
  """
10
 
11
  from __future__ import annotations
 
24
  import glyph_assets as glyph_assets_module
25
  import glyph_hybrid_evidence as evidence
26
  import glyph_hybrid_plan as glyph_plan_module
27
+ import quality_runtime as quality_runtime_module
28
  from glyph_assets import (
29
  AssetSegment,
30
  CarrierSegment,
 
37
  SQUIM_OBJECTIVE_SAMPLE_RATE,
38
  SQUIM_OBJECTIVE_WEIGHT_SHA256,
39
  SQUIM_OBJECTIVE_WINDOW_SECONDS,
 
 
 
 
 
 
 
 
40
  release_speaker_evidence_from_audio,
41
  squim_objective_evidence_from_audio,
 
 
42
  )
43
 
44
 
45
  ADAPTER_SCHEMA = "bluemagpie.glyph-hybrid.adapter.v1"
46
  GENERATION_POLICY_SCHEMA = "bluemagpie.glyph-generation-policy.v1"
 
47
  ASR_TASK = "transcribe"
48
+ BREEZE25_MODEL_ID = getattr(
49
+ quality_runtime_module,
50
+ "BREEZE25_MODEL_ID",
51
+ "MediaTek-Research/Breeze-ASR-25",
52
+ )
53
+ BREEZE25_REVISION = getattr(
54
+ quality_runtime_module,
55
+ "BREEZE25_REVISION",
56
+ "cffe7ccb404d025296a00758d0a33468bec3a9d0",
57
+ )
58
+ BREEZE25_PRIMARY_PIN_NAME = "breeze25-primary-zh"
59
+ BREEZE25_CONFIRMATION_PIN_NAME = "breeze25-confirmation-auto"
60
+ BREEZE25_PRIMARY_DECODER_PROFILE = "breeze25-greedy-zh-v1"
61
+ BREEZE25_CONFIRMATION_DECODER_PROFILE = "breeze25-greedy-auto-v1"
62
+ BREEZE25_PRIMARY_LANGUAGE = "zh"
63
+ BREEZE25_CONFIRMATION_LANGUAGE = None
64
+ BREEZE25_WEIGHT_SHA256 = getattr(
65
+ quality_runtime_module,
66
+ "BREEZE25_WEIGHT_SHA256",
67
+ "c5d952b3bc03ea277209aff0ef5b5c4c055d74449ff794c02d8f4e315fdef6b6",
68
+ )
69
+ BREEZE25_SAMPLE_RATE = getattr(
70
+ quality_runtime_module,
71
+ "BREEZE25_SAMPLE_RATE",
72
+ 16_000,
73
+ )
74
+ BREEZE25_ATTENTION_IMPLEMENTATION = getattr(
75
+ quality_runtime_module,
76
+ "BREEZE25_ATTENTION_IMPLEMENTATION",
77
+ "eager",
78
+ )
79
+ BREEZE25_RETURN_ATTENTION_MASK = getattr(
80
+ quality_runtime_module,
81
+ "BREEZE25_RETURN_ATTENTION_MASK",
82
+ True,
83
+ )
84
+ BREEZE25_MAX_MICROBATCH_SEGMENTS = getattr(
85
+ quality_runtime_module,
86
+ "BREEZE25_MAX_MICROBATCH_SEGMENTS",
87
+ 6,
88
+ )
89
+
90
+
91
+ def _unavailable_breeze25_transcriber(*args: Any, **kwargs: Any) -> str:
92
+ del args, kwargs
93
+ raise RuntimeError(
94
+ "the pinned Breeze ASR 25 runtime is unavailable"
95
+ )
96
+
97
+
98
+ transcribe_breeze25 = getattr(
99
+ quality_runtime_module,
100
+ "transcribe_breeze25",
101
+ _unavailable_breeze25_transcriber,
102
+ )
103
 
104
 
105
  class GlyphHybridAdapterError(RuntimeError):
 
130
  renderer_builder: Callable[..., Any] = build_glyph_hybrid_plan
131
  assembly_verifier: Callable[..., None] = verify_hybrid_assembly
132
  carrier_conditioner: Callable[[CarrierSegment], bytes] = condition_carrier_v1
133
+ breeze_primary_transcriber: Callable[..., str] = transcribe_breeze25
134
+ breeze_confirmation_transcriber: Callable[..., str] = transcribe_breeze25
 
 
135
  speaker_measure: Callable[..., Any] = release_speaker_evidence_from_audio
136
  squim_measure: Callable[..., Any] = squim_objective_evidence_from_audio
137
 
 
217
  *,
218
  kind: str,
219
  config: Mapping[str, Any],
220
+ logical_name: str | None = None,
221
  ) -> evidence.ArtifactPin:
222
  return evidence.ArtifactPin(
223
+ name=str(logical_name if logical_name is not None else pin.name),
224
  kind=kind,
225
  uri=str(pin.uri),
226
  revision=str(pin.revision),
 
265
  revision=runtime.model_revision,
266
  label="BlueMagpie model",
267
  )
268
+ breeze_weights_pin = _select_hf_pin(
 
 
 
 
 
 
269
  tuple(bundle.evaluator_pins),
270
+ repo_id=BREEZE25_MODEL_ID,
271
+ revision=BREEZE25_REVISION,
272
+ label="Breeze ASR 25",
273
  )
274
+ if getattr(breeze_weights_pin, "sha256", None) != BREEZE25_WEIGHT_SHA256:
275
+ raise GlyphHybridAdapterError(
276
+ "exact Breeze ASR 25 weight pin is unavailable"
277
+ )
278
  ecapa_pin = _select_hf_pin(
279
  tuple(bundle.evaluator_pins),
280
  repo_id=runtime.ecapa_repo_id,
 
355
  "inference_steps": runtime.inference_steps,
356
  },
357
  )
358
+ shared_asr_config = {
359
  "schema": ADAPTER_SCHEMA,
 
360
  "task": ASR_TASK,
361
  "max_new_tokens": runtime.asr_max_new_tokens,
362
  "max_verification_segments": (
363
  runtime.asr_max_verification_segments
364
  ),
365
+ "sample_rate": BREEZE25_SAMPLE_RATE,
366
+ "attention_implementation": BREEZE25_ATTENTION_IMPLEMENTATION,
367
+ "return_attention_mask": BREEZE25_RETURN_ATTENTION_MASK,
368
+ "max_microbatch_segments": BREEZE25_MAX_MICROBATCH_SEGMENTS,
369
  "do_sample": False,
370
  "num_beams": 1,
371
+ "repo_id": BREEZE25_MODEL_ID,
372
+ "revision": BREEZE25_REVISION,
373
+ "shared_weights_sha256": str(breeze_weights_pin.sha256),
374
  }
375
+ breeze_primary = _artifact_pin(
376
+ breeze_weights_pin,
377
  kind="asr",
378
+ logical_name=BREEZE25_PRIMARY_PIN_NAME,
379
  config={
380
+ **shared_asr_config,
381
+ "decoder_profile": BREEZE25_PRIMARY_DECODER_PROFILE,
382
+ "language": BREEZE25_PRIMARY_LANGUAGE,
383
  },
384
  )
385
+ breeze_confirmation = _artifact_pin(
386
+ breeze_weights_pin,
387
  kind="asr",
388
+ logical_name=BREEZE25_CONFIRMATION_PIN_NAME,
389
  config={
390
+ **shared_asr_config,
391
+ "decoder_profile": BREEZE25_CONFIRMATION_DECODER_PROFILE,
392
+ "language": "auto",
393
  },
394
  )
395
  ecapa = _artifact_pin(
 
417
  "device": "cpu",
418
  },
419
  )
420
+ return (
421
+ source_pins,
422
+ model,
423
+ breeze_primary,
424
+ breeze_confirmation,
425
+ ecapa,
426
+ squim,
427
+ )
428
 
429
 
430
  def _trusted_bundle(
 
814
  (
815
  source_pins,
816
  model_pin,
817
+ breeze_primary_pin,
818
+ breeze_confirmation_pin,
819
  ecapa_pin,
820
  squim_pin,
821
  ) = _runtime_pins(runtime, bundle)
 
1029
  )
1030
 
1031
  expected_pins = {
1032
+ breeze_primary_pin.name: breeze_primary_pin,
1033
+ breeze_confirmation_pin.name: breeze_confirmation_pin,
1034
  ecapa_pin.name: ecapa_pin,
1035
  squim_pin.name: squim_pin,
1036
  }
 
1054
  key = pin.name, _sha256_bytes(pcm16_le), sample_rate
1055
  if key not in asr_cache:
1056
  waveform = _pcm_waveform(pcm16_le)
1057
+ if pin == breeze_primary_pin:
1058
+ transcriber = runtime.breeze_primary_transcriber
1059
+ language = BREEZE25_PRIMARY_LANGUAGE
1060
+ elif pin == breeze_confirmation_pin:
1061
+ transcriber = runtime.breeze_confirmation_transcriber
1062
+ language = BREEZE25_CONFIRMATION_LANGUAGE
1063
+ else:
 
1064
  raise GlyphHybridAdapterError(
1065
  "ASR evaluator kind is not pinned"
1066
  )
1067
  transcript = transcriber(
1068
  waveform,
1069
  sample_rate,
1070
+ language=language,
1071
  task=ASR_TASK,
1072
  max_new_tokens=runtime.asr_max_new_tokens,
1073
+ max_verification_segments=(
1074
+ runtime.asr_max_verification_segments
1075
+ ),
1076
  )
1077
  if type(transcript) is not str:
1078
  raise GlyphHybridAdapterError(
 
1150
  return squim_cache[key]
1151
 
1152
  output_pcm = bytes(assembly.pcm16_le)
1153
+ breeze_primary_transcript = asr_evaluator(
1154
+ breeze_primary_pin,
1155
  output_pcm,
1156
  runtime.sample_rate,
1157
  )
1158
+ breeze_confirmation_transcript = asr_evaluator(
1159
+ breeze_confirmation_pin,
1160
  output_pcm,
1161
  runtime.sample_rate,
1162
  )
 
1174
  final_comparison = getattr(final_row, "comparison", None)
1175
  if (
1176
  final_comparison is None
1177
+ or not breeze_primary_transcript
1178
+ or not breeze_confirmation_transcript
1179
  ):
1180
  raise GlyphHybridAdapterError(
1181
  "semantic evaluator output is unavailable"
 
1191
  runtime.gate_policy,
1192
  )
1193
  for scope, asr_pin, transcript in (
1194
+ (
1195
+ "final",
1196
+ breeze_primary_pin,
1197
+ breeze_primary_transcript,
1198
+ ),
1199
  (
1200
  "independent",
1201
+ breeze_confirmation_pin,
1202
+ breeze_confirmation_transcript,
1203
  ),
1204
  ):
1205
  asr_call_id = f"asr-{scope}"
 
1303
  source_pins=source_pins,
1304
  model_pins=(model_pin,),
1305
  evaluator_pins=(
1306
+ breeze_primary_pin,
1307
+ breeze_confirmation_pin,
1308
  ecapa_pin,
1309
  squim_pin,
1310
  ),
glyph_hybrid_evidence.py CHANGED
@@ -40,6 +40,13 @@ MAX_ASR_CALLS = 96
40
  MAX_ECAPA_CALLS = 96
41
  MAX_SQUIM_CALLS = 96
42
  CONTENT_COMMITMENT_KEY_BYTES = 32
 
 
 
 
 
 
 
43
 
44
  _SHA256_RE = re.compile(r"[0-9a-f]{64}\Z", flags=re.ASCII)
45
  _ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:/+-]{0,127}\Z", flags=re.ASCII)
@@ -147,6 +154,15 @@ def _require_text(value: Any, *, field: str, allow_empty: bool = False) -> str:
147
  return value
148
 
149
 
 
 
 
 
 
 
 
 
 
150
  def _require_int(
151
  value: Any,
152
  *,
@@ -2794,17 +2810,42 @@ def rebuild_hybrid_evidence(inputs: EvidenceRebuildInputs) -> HybridEvidence:
2794
  if (
2795
  final_asr.evaluator_pin_name
2796
  == independent_asr.evaluator_pin_name
2797
- or (
2798
- final_asr_pin.sha256,
2799
- final_asr_pin.config_sha256,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2800
  )
2801
- == (
2802
- independent_asr_pin.sha256,
2803
- independent_asr_pin.config_sha256,
 
 
 
 
 
 
2804
  )
 
 
 
 
2805
  ):
2806
  raise GlyphHybridEvidenceError(
2807
- "independent ASR must use a distinct pinned evaluator"
 
2808
  )
2809
  gate_call_ids = {
2810
  call_id
 
40
  MAX_ECAPA_CALLS = 96
41
  MAX_SQUIM_CALLS = 96
42
  CONTENT_COMMITMENT_KEY_BYTES = 32
43
+ BREEZE25_MODEL_ID = "MediaTek-Research/Breeze-ASR-25"
44
+ BREEZE25_REVISION = "cffe7ccb404d025296a00758d0a33468bec3a9d0"
45
+ BREEZE25_WEIGHT_SHA256 = (
46
+ "c5d952b3bc03ea277209aff0ef5b5c4c055d74449ff794c02d8f4e315fdef6b6"
47
+ )
48
+ BREEZE25_PRIMARY_PIN_NAME = "breeze25-primary-zh"
49
+ BREEZE25_CONFIRMATION_PIN_NAME = "breeze25-confirmation-auto"
50
 
51
  _SHA256_RE = re.compile(r"[0-9a-f]{64}\Z", flags=re.ASCII)
52
  _ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:/+-]{0,127}\Z", flags=re.ASCII)
 
154
  return value
155
 
156
 
157
+ def _normalized_hf_uri(value: str) -> str:
158
+ normalized = value.strip().rstrip("/")
159
+ for prefix in ("hf://", "https://huggingface.co/"):
160
+ if normalized.casefold().startswith(prefix):
161
+ normalized = normalized[len(prefix) :]
162
+ break
163
+ return normalized.casefold()
164
+
165
+
166
  def _require_int(
167
  value: Any,
168
  *,
 
2810
  if (
2811
  final_asr.evaluator_pin_name
2812
  == independent_asr.evaluator_pin_name
2813
+ or final_asr_pin.config_sha256
2814
+ == independent_asr_pin.config_sha256
2815
+ ):
2816
+ raise GlyphHybridEvidenceError(
2817
+ "ASR gates must use distinct pinned decoder profiles"
2818
+ )
2819
+ if (
2820
+ final_asr_pin.uri,
2821
+ final_asr_pin.revision,
2822
+ final_asr_pin.sha256,
2823
+ ) != (
2824
+ independent_asr_pin.uri,
2825
+ independent_asr_pin.revision,
2826
+ independent_asr_pin.sha256,
2827
+ ):
2828
+ raise GlyphHybridEvidenceError(
2829
+ "ASR decoder profiles must share the exact pinned model weights"
2830
  )
2831
+ if (
2832
+ _normalized_hf_uri(final_asr_pin.uri)
2833
+ != BREEZE25_MODEL_ID.casefold()
2834
+ or final_asr_pin.revision != BREEZE25_REVISION
2835
+ or final_asr_pin.sha256 != BREEZE25_WEIGHT_SHA256
2836
+ ):
2837
+ raise GlyphHybridEvidenceError(
2838
+ "ASR decoder profiles must use the official pinned Breeze ASR 25 "
2839
+ "weights"
2840
  )
2841
+ if (
2842
+ final_asr.evaluator_pin_name != BREEZE25_PRIMARY_PIN_NAME
2843
+ or independent_asr.evaluator_pin_name
2844
+ != BREEZE25_CONFIRMATION_PIN_NAME
2845
  ):
2846
  raise GlyphHybridEvidenceError(
2847
+ "ASR gates must use the honest Breeze primary and confirmation "
2848
+ "logical pin names"
2849
  )
2850
  gate_call_ids = {
2851
  call_id
latency_timing.py CHANGED
@@ -18,8 +18,7 @@ from typing import Callable, Iterator
18
  LATENCY_STAGE_NAMES = (
19
  "request_total",
20
  "generation",
21
- "turbo_asr",
22
- "large_v3_asr",
23
  "ecapa",
24
  "squim",
25
  "echo",
 
18
  LATENCY_STAGE_NAMES = (
19
  "request_total",
20
  "generation",
21
+ "breeze25_asr",
 
22
  "ecapa",
23
  "squim",
24
  "echo",
production.py CHANGED
@@ -57,7 +57,8 @@ _SPOKEN_DATE_RE = re.compile(
57
  r"(?<![A-Za-z0-9/])(\d{4})([/-])(\d{1,2})\2(\d{1,2})(?![A-Za-z0-9/])"
58
  )
59
  _SPOKEN_ZH_DATE_RE = re.compile(
60
- r"(?<![A-Za-z0-9])(\d{4})年(\d{1,2})月(\d{1,2})(?:日|號)(?![A-Za-z0-9])"
 
61
  )
62
  _SPOKEN_TIME_RE = re.compile(
63
  r"(?<![A-Za-z0-9:])([01]?\d|2[0-3]):([0-5]\d)(?![A-Za-z0-9:])"
@@ -71,6 +72,11 @@ _ASR_BARE_ZH_TIME_RE = re.compile(
71
  r"([0-5]?\d)(?![A-Za-z0-9]|\u5206|\u6beb?\u79d2|\u5fae\u79d2|"
72
  r"\u516c\u65a4|\u5343\u514b|\u516c\u514b|\u6beb\u514b|\u5ea6)"
73
  )
 
 
 
 
 
74
  _ASR_ARABIC_DIGIT_RUN_RE = re.compile(
75
  r"(?<![A-Za-z0-9])\d+(?![A-Za-z0-9])"
76
  )
@@ -192,6 +198,10 @@ _SPOKEN_CURRENCY_SUFFIX_RE = re.compile(
192
  _MODEL_CODE_RE = re.compile(
193
  r"(?<![A-Za-z0-9])([A-Z]{2,8})[ -]*(\d{1,8})(?![A-Za-z0-9.])"
194
  )
 
 
 
 
195
  _UPPERCASE_ACRONYM_RE = re.compile(r"(?<![A-Za-z0-9])([A-Z]{2,8})(?![A-Za-z0-9])")
196
  _ZH_UNIT_READINGS = {
197
  "km/h": "公里每小時",
@@ -663,6 +673,92 @@ def _canonicalize_target_proven_arabic_digits(
663
  return "".join(output)
664
 
665
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  def _canonical_jpy_amount_reading(amount: str) -> str | None:
667
  """Return one validated Mandarin amount reading for a JPY proof span."""
668
 
@@ -2632,6 +2728,10 @@ def normalize_asr_spoken_forms(
2632
  value = value.replace(placeholder, spoken)
2633
  return value
2634
 
 
 
 
 
2635
  target_proven_text = _canonicalize_target_proven_frequency_scales(
2636
  raw_text,
2637
  raw_target,
 
57
  r"(?<![A-Za-z0-9/])(\d{4})([/-])(\d{1,2})\2(\d{1,2})(?![A-Za-z0-9/])"
58
  )
59
  _SPOKEN_ZH_DATE_RE = re.compile(
60
+ r"(?<![A-Za-z0-9])(\d{4})\s*\s*(\d{1,2})\s*月\s*"
61
+ r"(\d{1,2})\s*(?:日|號)(?![A-Za-z0-9])"
62
  )
63
  _SPOKEN_TIME_RE = re.compile(
64
  r"(?<![A-Za-z0-9:])([01]?\d|2[0-3]):([0-5]\d)(?![A-Za-z0-9:])"
 
72
  r"([0-5]?\d)(?![A-Za-z0-9]|\u5206|\u6beb?\u79d2|\u5fae\u79d2|"
73
  r"\u516c\u65a4|\u5343\u514b|\u516c\u514b|\u6beb\u514b|\u5ea6)"
74
  )
75
+ _ASR_FORMATTED_CLOCK_RE = re.compile(
76
+ r"(?<![A-Za-z0-9])([01]?\d|2[0-3])\s*"
77
+ r"[.:\u9ede\u70b9\u6642\u65f6]\s*([0-5]?\d)(?:\s*\u5206)?"
78
+ r"(?![A-Za-z0-9])"
79
+ )
80
  _ASR_ARABIC_DIGIT_RUN_RE = re.compile(
81
  r"(?<![A-Za-z0-9])\d+(?![A-Za-z0-9])"
82
  )
 
198
  _MODEL_CODE_RE = re.compile(
199
  r"(?<![A-Za-z0-9])([A-Z]{2,8})[ -]*(\d{1,8})(?![A-Za-z0-9.])"
200
  )
201
+ _ASR_MODEL_CODE_RE = re.compile(
202
+ r"(?<![A-Za-z0-9])([A-Za-z]{2,8})[ _-]*(\d{1,8})"
203
+ r"(?![A-Za-z0-9.])"
204
+ )
205
  _UPPERCASE_ACRONYM_RE = re.compile(r"(?<![A-Za-z0-9])([A-Z]{2,8})(?![A-Za-z0-9])")
206
  _ZH_UNIT_READINGS = {
207
  "km/h": "公里每小時",
 
673
  return "".join(output)
674
 
675
 
676
+ def _canonicalize_target_proven_structured_asr_forms(
677
+ text: str,
678
+ normalized_target: str,
679
+ ) -> str:
680
+ """Canonicalize Breeze-style clock/model renderings under exact proof.
681
+
682
+ Breeze ASR 25 commonly writes a spoken clock as ``7.45 分`` and emits
683
+ model identifiers in lowercase without their separator (``ax418``).
684
+ Those are legitimate written renderings, but globally interpreting every
685
+ decimal or lowercase alphanumeric run would hide real content errors.
686
+ Each replacement is therefore derived from the candidate text, required
687
+ verbatim in the already-normalized target, matched at the same semantic
688
+ left/right anchors, and consumed at most once.
689
+ """
690
+
691
+ used_proofs: set[tuple[int, int, str]] = set()
692
+
693
+ def replace_matches(
694
+ value: str,
695
+ pattern: re.Pattern[str],
696
+ canonicalizer: Callable[[re.Match[str]], str | None],
697
+ ) -> str:
698
+ matches = tuple(pattern.finditer(value))
699
+ if not matches:
700
+ return value
701
+ output: list[str] = []
702
+ cursor = 0
703
+ for match in matches:
704
+ output.append(value[cursor : match.start()])
705
+ canonical = canonicalizer(match)
706
+ replacement: str | None = None
707
+ if canonical:
708
+ text_context = (
709
+ _semantic_neighbor(value, match.start() - 1, -1),
710
+ _semantic_neighbor(value, match.end(), 1),
711
+ )
712
+ proof_start = 0
713
+ while True:
714
+ proof_start = normalized_target.find(canonical, proof_start)
715
+ if proof_start < 0:
716
+ break
717
+ proof_end = proof_start + len(canonical)
718
+ proof_key = (proof_start, proof_end, canonical)
719
+ target_context = (
720
+ _semantic_neighbor(
721
+ normalized_target,
722
+ proof_start - 1,
723
+ -1,
724
+ ),
725
+ _semantic_neighbor(normalized_target, proof_end, 1),
726
+ )
727
+ if proof_key not in used_proofs and target_context == text_context:
728
+ replacement = canonical
729
+ used_proofs.add(proof_key)
730
+ break
731
+ proof_start += 1
732
+ output.append(replacement if replacement is not None else match.group(0))
733
+ cursor = match.end()
734
+ output.append(value[cursor:])
735
+ return "".join(output)
736
+
737
+ def canonical_clock(match: re.Match[str]) -> str | None:
738
+ try:
739
+ return _zh_clock_time(match.group(1), match.group(2))
740
+ except (TypeError, ValueError):
741
+ return None
742
+
743
+ def canonical_model_code(match: re.Match[str]) -> str | None:
744
+ letters, digits = match.groups()
745
+ try:
746
+ return f"{' '.join(letters.upper())} {_zh_digit_sequence(digits)}"
747
+ except ValueError:
748
+ return None
749
+
750
+ canonicalized = replace_matches(
751
+ text,
752
+ _ASR_FORMATTED_CLOCK_RE,
753
+ canonical_clock,
754
+ )
755
+ return replace_matches(
756
+ canonicalized,
757
+ _ASR_MODEL_CODE_RE,
758
+ canonical_model_code,
759
+ )
760
+
761
+
762
  def _canonical_jpy_amount_reading(amount: str) -> str | None:
763
  """Return one validated Mandarin amount reading for a JPY proof span."""
764
 
 
2728
  value = value.replace(placeholder, spoken)
2729
  return value
2730
 
2731
+ raw_text = _canonicalize_target_proven_structured_asr_forms(
2732
+ raw_text,
2733
+ normalized_target,
2734
+ )
2735
  target_proven_text = _canonicalize_target_proven_frequency_scales(
2736
  raw_text,
2737
  raw_target,
quality_runtime.py CHANGED
@@ -1,6 +1,6 @@
1
  """Fail-closed quality runtime helpers for the BlueMagpie-TTS Space.
2
 
3
- The module deliberately has no import-time model downloads. Both Whisper and
4
  ECAPA are supplied through small injectable boundaries so the online Space can
5
  load the real models lazily while unit tests remain deterministic and offline.
6
  """
@@ -31,20 +31,40 @@ from production import (
31
  )
32
 
33
 
34
- WHISPER_MODEL_ID = "openai/whisper-large-v3-turbo"
35
- WHISPER_REVISION = "41f01f3fe87f28c78e2fbf8b568835947dd65ed9"
36
- VERIFICATION_WHISPER_MODEL_ID = "openai/whisper-large-v3"
37
- VERIFICATION_WHISPER_REVISION = "06f233fe06e710322aca913c1bc4249a0d71fce1"
38
- WHISPER_ATTENTION_IMPLEMENTATION = "eager"
39
- WHISPER_RETURN_ATTENTION_MASK = True
40
- WHISPER_SAMPLE_RATE = 16_000
41
- WHISPER_MAX_SEGMENT_SECONDS = 28.0
42
- WHISPER_HARD_MAX_SEGMENT_SECONDS = 30.0
43
- WHISPER_MIN_SEGMENT_SECONDS = 1.25
44
- WHISPER_MIN_PAUSE_SECONDS = 0.25
45
- WHISPER_MAX_VERIFICATION_SEGMENTS = 12
 
 
 
46
  GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS = 160
47
- WHISPER_MAX_MICROBATCH_SEGMENTS = 6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  SQUIM_OBJECTIVE_ASSET_PATH = "models/squim_objective_dns2020.pth"
49
  SQUIM_OBJECTIVE_WEIGHT_SHA256 = (
50
  "2c54586fea83fb5eb5394d710038ee89f55cab7011a5bf730bebed4c8777e828"
@@ -745,8 +765,8 @@ def active_voiced_duration_seconds(
745
 
746
 
747
  @dataclass(frozen=True)
748
- class WhisperRuntime:
749
- """Loaded processor/model pair for deterministic Whisper transcription."""
750
 
751
  processor: Any
752
  model: Any
@@ -754,14 +774,14 @@ class WhisperRuntime:
754
  dtype: torch.dtype
755
 
756
 
757
- def _load_whisper_runtime(
758
  model_id: str,
759
  revision: str,
760
  *,
761
  device: str | torch.device | None = None,
762
  processor_factory: Any | None = None,
763
  model_factory: Any | None = None,
764
- ) -> WhisperRuntime:
765
  """Load one exact ASR revision through the shared deterministic contract.
766
 
767
  Factory injection exists for offline tests. The default imports
@@ -785,14 +805,14 @@ def _load_whisper_runtime(
785
  model = model_factory.from_pretrained(
786
  model_id,
787
  revision=revision,
788
- attn_implementation=WHISPER_ATTENTION_IMPLEMENTATION,
789
  torch_dtype=dtype,
790
  low_cpu_mem_usage=True,
791
  use_safetensors=True,
792
  )
793
  model = model.to(selected_device)
794
  model.eval()
795
- return WhisperRuntime(
796
  processor=processor,
797
  model=model,
798
  device=selected_device,
@@ -800,49 +820,35 @@ def _load_whisper_runtime(
800
  )
801
 
802
 
803
- def load_pinned_whisper_runtime(
804
- *,
805
- device: str | torch.device | None = None,
806
- processor_factory: Any | None = None,
807
- model_factory: Any | None = None,
808
- ) -> WhisperRuntime:
809
- """Load the pinned turbo ASR used for local candidate screening."""
810
-
811
- return _load_whisper_runtime(
812
- WHISPER_MODEL_ID,
813
- WHISPER_REVISION,
814
- device=device,
815
- processor_factory=processor_factory,
816
- model_factory=model_factory,
817
- )
818
-
819
-
820
- def load_pinned_verification_whisper_runtime(
821
  *,
822
  device: str | torch.device | None = None,
823
  processor_factory: Any | None = None,
824
  model_factory: Any | None = None,
825
- ) -> WhisperRuntime:
826
- """Load the independent full large-v3 ASR used only for whole outputs."""
827
 
828
- return _load_whisper_runtime(
829
- VERIFICATION_WHISPER_MODEL_ID,
830
- VERIFICATION_WHISPER_REVISION,
831
  device=device,
832
  processor_factory=processor_factory,
833
  model_factory=model_factory,
834
  )
835
 
836
 
837
- class LazyWhisperASR:
838
  """Thread-safe one-shot lazy loader with an injectable runtime factory."""
839
 
840
- def __init__(self, runtime_loader: Callable[[], WhisperRuntime] | None = None) -> None:
841
- self._runtime_loader = runtime_loader or load_pinned_whisper_runtime
842
- self._runtime: WhisperRuntime | None = None
 
 
 
843
  self._lock = threading.Lock()
844
 
845
- def get_runtime(self) -> WhisperRuntime:
846
  runtime = self._runtime
847
  if runtime is not None:
848
  return runtime
@@ -852,13 +858,10 @@ class LazyWhisperASR:
852
  return self._runtime
853
 
854
 
855
- _DEFAULT_WHISPER = LazyWhisperASR()
856
- _DEFAULT_VERIFICATION_WHISPER = LazyWhisperASR(
857
- load_pinned_verification_whisper_runtime
858
- )
859
 
860
 
861
- def _qualified_whisper_pause_ranges(
862
  waveform: np.ndarray,
863
  *,
864
  sample_rate: int,
@@ -929,7 +932,7 @@ def _qualified_whisper_pause_ranges(
929
  return tuple(qualified)
930
 
931
 
932
- def _split_short_whisper_audio_at_sustained_pause(
933
  waveform: np.ndarray,
934
  *,
935
  sample_rate: int,
@@ -944,7 +947,7 @@ def _split_short_whisper_audio_at_sustained_pause(
944
  minimum_segment = max(1, int(round(minimum_segment_seconds * rate)))
945
  if waveform.size < 2 * minimum_segment:
946
  return (waveform,)
947
- pause_ranges = _qualified_whisper_pause_ranges(
948
  waveform,
949
  sample_rate=rate,
950
  minimum_pause_seconds=minimum_pause_seconds,
@@ -965,19 +968,19 @@ def _split_short_whisper_audio_at_sustained_pause(
965
  )
966
 
967
 
968
- def _split_whisper_audio(
969
  waveform: np.ndarray,
970
  *,
971
- sample_rate: int = WHISPER_SAMPLE_RATE,
972
- max_segment_seconds: float = WHISPER_MAX_SEGMENT_SECONDS,
973
  boundary_search_seconds: float = 1.5,
974
- max_verification_segments: int = WHISPER_MAX_VERIFICATION_SEGMENTS,
975
  ) -> tuple[np.ndarray, ...]:
976
  """Create bounded verification segments without cutting voiced audio.
977
 
978
  Every qualified pause is retained when doing so keeps all segments below
979
  the target cap. This prevents a long multi-chunk decode from swallowing
980
- several already-separated clauses in one Whisper context. If the natural
981
  pause atoms cannot bound the contexts, the coarse latest-pause fallback is
982
  used; a long voiced span without such a pause still fails closed.
983
  """
@@ -991,31 +994,31 @@ def _split_whisper_audio(
991
  <= GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS
992
  ):
993
  raise ValueError(
994
- "Whisper segment budget must be an exact integer in [1, 160]"
995
  )
996
  if maximum_seconds is None or search_seconds is None or sample_rate <= 0:
997
- raise ValueError("invalid Whisper segmentation settings")
998
  maximum_samples = max(1, int(round(maximum_seconds * sample_rate)))
999
- hard_cap_samples = int(round(WHISPER_HARD_MAX_SEGMENT_SECONDS * sample_rate))
1000
  if maximum_samples > hard_cap_samples:
1001
- raise ValueError("Whisper target segment cap exceeds the 30-second hard limit")
1002
  minimum_segment_samples = max(
1003
  1,
1004
- int(round(WHISPER_MIN_SEGMENT_SECONDS * sample_rate)),
1005
  )
1006
  if waveform.size > hard_cap_samples * max_verification_segments:
1007
  raise ValueError("verification audio exceeds the bounded ASR segment budget")
1008
 
1009
- pause_ranges = _qualified_whisper_pause_ranges(
1010
  waveform,
1011
  sample_rate=sample_rate,
1012
- minimum_pause_seconds=WHISPER_MIN_PAUSE_SECONDS,
1013
  )
1014
- pause_segments = _split_short_whisper_audio_at_sustained_pause(
1015
  waveform,
1016
  sample_rate=sample_rate,
1017
- minimum_pause_seconds=WHISPER_MIN_PAUSE_SECONDS,
1018
- minimum_segment_seconds=WHISPER_MIN_SEGMENT_SECONDS,
1019
  )
1020
  if len(pause_segments) > max_verification_segments:
1021
  raise ValueError("verification audio exceeds the pause segment cap")
@@ -1075,27 +1078,30 @@ def _split_whisper_audio(
1075
  )
1076
  or sum(segment.size for segment in segments) != waveform.size
1077
  ):
1078
- raise ValueError("failed to split audio within Whisper's segment limit")
1079
  return segments
1080
 
1081
 
1082
  @torch.inference_mode()
1083
- def _transcribe_whisper(
1084
  audio: np.ndarray | Sequence[float],
1085
  sample_rate: int,
1086
  *,
1087
- lazy_asr: LazyWhisperASR | None = None,
1088
- runtime: WhisperRuntime | None = None,
1089
- language: str = "zh",
1090
- task: str = "transcribe",
1091
  max_new_tokens: int = 128,
1092
- max_verification_segments: int = WHISPER_MAX_VERIFICATION_SEGMENTS,
1093
  ) -> str:
1094
- """Transcribe ndarray audio using the shared deterministic decoder.
1095
 
1096
  ``runtime`` and ``lazy_asr`` are mutually exclusive injection points. An
1097
  empty decoded string is returned as-is; the semantic verifier will reject
1098
- it rather than accepting an arbitrary TTS fallback.
 
 
 
1099
  """
1100
 
1101
  if runtime is not None and lazy_asr is not None:
@@ -1103,22 +1109,26 @@ def _transcribe_whisper(
1103
  token_limit = int(max_new_tokens)
1104
  if token_limit <= 0:
1105
  raise ValueError("max_new_tokens must be positive")
1106
- waveform = _resample_audio(_mono_audio(audio), int(sample_rate), WHISPER_SAMPLE_RATE)
1107
- segments = _split_whisper_audio(
 
 
 
 
1108
  waveform,
1109
  max_verification_segments=max_verification_segments,
1110
  )
1111
- selected_runtime = runtime or (lazy_asr or _DEFAULT_WHISPER).get_runtime()
1112
  decoded_segments: list[str] = []
1113
- for start in range(0, len(segments), WHISPER_MAX_MICROBATCH_SEGMENTS):
1114
- microbatch = segments[start : start + WHISPER_MAX_MICROBATCH_SEGMENTS]
1115
  processor_input: np.ndarray | list[np.ndarray]
1116
  processor_input = microbatch[0] if len(microbatch) == 1 else list(microbatch)
1117
  processor_output = selected_runtime.processor(
1118
  processor_input,
1119
- sampling_rate=WHISPER_SAMPLE_RATE,
1120
  return_tensors="pt",
1121
- return_attention_mask=WHISPER_RETURN_ATTENTION_MASK,
1122
  )
1123
  features = processor_output.input_features.to(
1124
  device=selected_runtime.device,
@@ -1127,15 +1137,16 @@ def _transcribe_whisper(
1127
  attention_mask = processor_output.attention_mask.to(
1128
  device=selected_runtime.device
1129
  )
1130
- token_ids = selected_runtime.model.generate(
1131
- features,
1132
- attention_mask=attention_mask,
1133
- language=language,
1134
- task=task,
1135
- do_sample=False,
1136
- num_beams=1,
1137
- max_new_tokens=token_limit,
1138
- )
 
1139
  decoded = selected_runtime.processor.batch_decode(
1140
  token_ids,
1141
  skip_special_tokens=True,
@@ -1146,21 +1157,21 @@ def _transcribe_whisper(
1146
  return " ".join(text for text in decoded_segments if text)
1147
 
1148
 
1149
- @timed_latency_stage("turbo_asr")
1150
- def transcribe_whisper(
1151
  audio: np.ndarray | Sequence[float],
1152
  sample_rate: int,
1153
  *,
1154
- lazy_asr: LazyWhisperASR | None = None,
1155
- runtime: WhisperRuntime | None = None,
1156
- language: str = "zh",
1157
- task: str = "transcribe",
1158
  max_new_tokens: int = 128,
1159
- max_verification_segments: int = WHISPER_MAX_VERIFICATION_SEGMENTS,
1160
  ) -> str:
1161
- """Transcribe with the pinned Turbo candidate-screening ASR."""
1162
 
1163
- return _transcribe_whisper(
1164
  audio,
1165
  sample_rate,
1166
  lazy_asr=lazy_asr,
@@ -1172,33 +1183,22 @@ def transcribe_whisper(
1172
  )
1173
 
1174
 
1175
- @timed_latency_stage("large_v3_asr")
1176
- def transcribe_verification_whisper(
1177
- audio: np.ndarray | Sequence[float],
1178
- sample_rate: int,
1179
- *,
1180
- lazy_asr: LazyWhisperASR | None = None,
1181
- runtime: WhisperRuntime | None = None,
1182
- language: str = "zh",
1183
- task: str = "transcribe",
1184
- max_new_tokens: int = 128,
1185
- max_verification_segments: int = WHISPER_MAX_VERIFICATION_SEGMENTS,
1186
- ) -> str:
1187
- """Transcribe with the separately pinned full large-v3 final verifier."""
1188
-
1189
- selected_lazy = lazy_asr
1190
- if runtime is None and selected_lazy is None:
1191
- selected_lazy = _DEFAULT_VERIFICATION_WHISPER
1192
- return _transcribe_whisper(
1193
- audio,
1194
- sample_rate,
1195
- lazy_asr=selected_lazy,
1196
- runtime=runtime,
1197
- language=language,
1198
- task=task,
1199
- max_new_tokens=max_new_tokens,
1200
- max_verification_segments=max_verification_segments,
1201
- )
1202
 
1203
 
1204
  @dataclass(frozen=True)
@@ -1229,7 +1229,7 @@ def prepare_candidate_audio(
1229
  if selected_sample_rate <= 0:
1230
  raise ValueError("sample_rate must be positive")
1231
  waveform = _mono_audio(audio)
1232
- transcript = (transcriber or transcribe_whisper)(
1233
  waveform,
1234
  selected_sample_rate,
1235
  )
@@ -2462,15 +2462,16 @@ def intersect_local_semantic_verification(
2462
  ) -> TrajectoryGateResult:
2463
  """Hard-intersect selected local rows without replacing acoustic evidence.
2464
 
2465
- ``primary_verification`` owns the turbo ASR, speaker, SQUIM, pace and
2466
  boundary-proxy evidence consumed by the coverage selector.
2467
- ``independent_verification`` contains semantic-only large-v3 results for
2468
- the selected network-conditioned rows in the order given by
2469
- ``primary_indices``. A large-v3 rejection is projected onto the
2470
  corresponding primary row with allow-listed semantic reason codes, making
2471
  it impossible for either the strict pool or the boundary-only proxy to
2472
- retain that local candidate. Passing intersections return the original
2473
- primary object unchanged.
 
2474
  """
2475
 
2476
  if not isinstance(primary_verification, TrajectoryGateResult):
@@ -4598,7 +4599,7 @@ def _bind_generation_endpoint_evidence(
4598
  artifacts,
4599
  strict=True,
4600
  )
4601
- if result.comparison.passed is True
4602
  ):
4603
  raise RuntimeError("candidate endpoint evidence is invalid")
4604
  return replace(verification, chunk_artifacts=artifacts)
@@ -5903,7 +5904,7 @@ def run_coverage_adaptive_cascade(
5903
  )
5904
 
5905
  # If every initial local chunk was strict, the initial exact assembly was
5906
- # already rejected by turbo or large-v3. Do not spend another final check
5907
  # on that identical waveform. Boundary-recovered initial paths are not
5908
  # excluded because the exact whole checks have not run for them yet.
5909
  excluded_path_list: list[tuple[int, ...]] = []
 
1
  """Fail-closed quality runtime helpers for the BlueMagpie-TTS Space.
2
 
3
+ The module deliberately has no import-time model downloads. Breeze ASR 25 and
4
  ECAPA are supplied through small injectable boundaries so the online Space can
5
  load the real models lazily while unit tests remain deterministic and offline.
6
  """
 
31
  )
32
 
33
 
34
+ BREEZE25_MODEL_ID = "MediaTek-Research/Breeze-ASR-25"
35
+ BREEZE25_REVISION = "cffe7ccb404d025296a00758d0a33468bec3a9d0"
36
+ BREEZE25_WEIGHT_SHA256 = (
37
+ "c5d952b3bc03ea277209aff0ef5b5c4c055d74449ff794c02d8f4e315fdef6b6"
38
+ )
39
+ BREEZE25_ATTENTION_IMPLEMENTATION = "eager"
40
+ BREEZE25_RETURN_ATTENTION_MASK = True
41
+ BREEZE25_LANGUAGE = "zh"
42
+ BREEZE25_TASK = "transcribe"
43
+ BREEZE25_SAMPLE_RATE = 16_000
44
+ BREEZE25_MAX_SEGMENT_SECONDS = 28.0
45
+ BREEZE25_HARD_MAX_SEGMENT_SECONDS = 30.0
46
+ BREEZE25_MIN_SEGMENT_SECONDS = 1.25
47
+ BREEZE25_MIN_PAUSE_SECONDS = 0.25
48
+ BREEZE25_MAX_VERIFICATION_SEGMENTS = 12
49
  GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS = 160
50
+ BREEZE25_MAX_MICROBATCH_SEGMENTS = 6
51
+
52
+ # Compatibility names remain available while app/evaluator callers migrate.
53
+ # They deliberately resolve to the one pinned Breeze ASR 25 contract and never
54
+ # refer to the retired OpenAI verifier models.
55
+ WHISPER_MODEL_ID = BREEZE25_MODEL_ID
56
+ WHISPER_REVISION = BREEZE25_REVISION
57
+ VERIFICATION_WHISPER_MODEL_ID = BREEZE25_MODEL_ID
58
+ VERIFICATION_WHISPER_REVISION = BREEZE25_REVISION
59
+ WHISPER_ATTENTION_IMPLEMENTATION = BREEZE25_ATTENTION_IMPLEMENTATION
60
+ WHISPER_RETURN_ATTENTION_MASK = BREEZE25_RETURN_ATTENTION_MASK
61
+ WHISPER_SAMPLE_RATE = BREEZE25_SAMPLE_RATE
62
+ WHISPER_MAX_SEGMENT_SECONDS = BREEZE25_MAX_SEGMENT_SECONDS
63
+ WHISPER_HARD_MAX_SEGMENT_SECONDS = BREEZE25_HARD_MAX_SEGMENT_SECONDS
64
+ WHISPER_MIN_SEGMENT_SECONDS = BREEZE25_MIN_SEGMENT_SECONDS
65
+ WHISPER_MIN_PAUSE_SECONDS = BREEZE25_MIN_PAUSE_SECONDS
66
+ WHISPER_MAX_VERIFICATION_SEGMENTS = BREEZE25_MAX_VERIFICATION_SEGMENTS
67
+ WHISPER_MAX_MICROBATCH_SEGMENTS = BREEZE25_MAX_MICROBATCH_SEGMENTS
68
  SQUIM_OBJECTIVE_ASSET_PATH = "models/squim_objective_dns2020.pth"
69
  SQUIM_OBJECTIVE_WEIGHT_SHA256 = (
70
  "2c54586fea83fb5eb5394d710038ee89f55cab7011a5bf730bebed4c8777e828"
 
765
 
766
 
767
  @dataclass(frozen=True)
768
+ class Breeze25Runtime:
769
+ """Loaded processor/model pair for deterministic Breeze ASR 25."""
770
 
771
  processor: Any
772
  model: Any
 
774
  dtype: torch.dtype
775
 
776
 
777
+ def _load_breeze25_runtime(
778
  model_id: str,
779
  revision: str,
780
  *,
781
  device: str | torch.device | None = None,
782
  processor_factory: Any | None = None,
783
  model_factory: Any | None = None,
784
+ ) -> Breeze25Runtime:
785
  """Load one exact ASR revision through the shared deterministic contract.
786
 
787
  Factory injection exists for offline tests. The default imports
 
805
  model = model_factory.from_pretrained(
806
  model_id,
807
  revision=revision,
808
+ attn_implementation=BREEZE25_ATTENTION_IMPLEMENTATION,
809
  torch_dtype=dtype,
810
  low_cpu_mem_usage=True,
811
  use_safetensors=True,
812
  )
813
  model = model.to(selected_device)
814
  model.eval()
815
+ return Breeze25Runtime(
816
  processor=processor,
817
  model=model,
818
  device=selected_device,
 
820
  )
821
 
822
 
823
+ def load_pinned_breeze25_runtime(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
824
  *,
825
  device: str | torch.device | None = None,
826
  processor_factory: Any | None = None,
827
  model_factory: Any | None = None,
828
+ ) -> Breeze25Runtime:
829
+ """Load the sole pinned content verifier used by every semantic gate."""
830
 
831
+ return _load_breeze25_runtime(
832
+ BREEZE25_MODEL_ID,
833
+ BREEZE25_REVISION,
834
  device=device,
835
  processor_factory=processor_factory,
836
  model_factory=model_factory,
837
  )
838
 
839
 
840
+ class LazyBreeze25ASR:
841
  """Thread-safe one-shot lazy loader with an injectable runtime factory."""
842
 
843
+ def __init__(
844
+ self,
845
+ runtime_loader: Callable[[], Breeze25Runtime] | None = None,
846
+ ) -> None:
847
+ self._runtime_loader = runtime_loader or load_pinned_breeze25_runtime
848
+ self._runtime: Breeze25Runtime | None = None
849
  self._lock = threading.Lock()
850
 
851
+ def get_runtime(self) -> Breeze25Runtime:
852
  runtime = self._runtime
853
  if runtime is not None:
854
  return runtime
 
858
  return self._runtime
859
 
860
 
861
+ _DEFAULT_BREEZE25 = LazyBreeze25ASR()
 
 
 
862
 
863
 
864
+ def _qualified_breeze25_pause_ranges(
865
  waveform: np.ndarray,
866
  *,
867
  sample_rate: int,
 
932
  return tuple(qualified)
933
 
934
 
935
+ def _split_short_breeze25_audio_at_sustained_pause(
936
  waveform: np.ndarray,
937
  *,
938
  sample_rate: int,
 
947
  minimum_segment = max(1, int(round(minimum_segment_seconds * rate)))
948
  if waveform.size < 2 * minimum_segment:
949
  return (waveform,)
950
+ pause_ranges = _qualified_breeze25_pause_ranges(
951
  waveform,
952
  sample_rate=rate,
953
  minimum_pause_seconds=minimum_pause_seconds,
 
968
  )
969
 
970
 
971
+ def _split_breeze25_audio(
972
  waveform: np.ndarray,
973
  *,
974
+ sample_rate: int = BREEZE25_SAMPLE_RATE,
975
+ max_segment_seconds: float = BREEZE25_MAX_SEGMENT_SECONDS,
976
  boundary_search_seconds: float = 1.5,
977
+ max_verification_segments: int = BREEZE25_MAX_VERIFICATION_SEGMENTS,
978
  ) -> tuple[np.ndarray, ...]:
979
  """Create bounded verification segments without cutting voiced audio.
980
 
981
  Every qualified pause is retained when doing so keeps all segments below
982
  the target cap. This prevents a long multi-chunk decode from swallowing
983
+ several already-separated clauses in one Breeze25 context. If the natural
984
  pause atoms cannot bound the contexts, the coarse latest-pause fallback is
985
  used; a long voiced span without such a pause still fails closed.
986
  """
 
994
  <= GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS
995
  ):
996
  raise ValueError(
997
+ "Breeze25 segment budget must be an exact integer in [1, 160]"
998
  )
999
  if maximum_seconds is None or search_seconds is None or sample_rate <= 0:
1000
+ raise ValueError("invalid Breeze25 segmentation settings")
1001
  maximum_samples = max(1, int(round(maximum_seconds * sample_rate)))
1002
+ hard_cap_samples = int(round(BREEZE25_HARD_MAX_SEGMENT_SECONDS * sample_rate))
1003
  if maximum_samples > hard_cap_samples:
1004
+ raise ValueError("Breeze25 target segment cap exceeds the 30-second hard limit")
1005
  minimum_segment_samples = max(
1006
  1,
1007
+ int(round(BREEZE25_MIN_SEGMENT_SECONDS * sample_rate)),
1008
  )
1009
  if waveform.size > hard_cap_samples * max_verification_segments:
1010
  raise ValueError("verification audio exceeds the bounded ASR segment budget")
1011
 
1012
+ pause_ranges = _qualified_breeze25_pause_ranges(
1013
  waveform,
1014
  sample_rate=sample_rate,
1015
+ minimum_pause_seconds=BREEZE25_MIN_PAUSE_SECONDS,
1016
  )
1017
+ pause_segments = _split_short_breeze25_audio_at_sustained_pause(
1018
  waveform,
1019
  sample_rate=sample_rate,
1020
+ minimum_pause_seconds=BREEZE25_MIN_PAUSE_SECONDS,
1021
+ minimum_segment_seconds=BREEZE25_MIN_SEGMENT_SECONDS,
1022
  )
1023
  if len(pause_segments) > max_verification_segments:
1024
  raise ValueError("verification audio exceeds the pause segment cap")
 
1078
  )
1079
  or sum(segment.size for segment in segments) != waveform.size
1080
  ):
1081
+ raise ValueError("failed to split audio within Breeze25's segment limit")
1082
  return segments
1083
 
1084
 
1085
  @torch.inference_mode()
1086
+ def _transcribe_breeze25(
1087
  audio: np.ndarray | Sequence[float],
1088
  sample_rate: int,
1089
  *,
1090
+ lazy_asr: LazyBreeze25ASR | None = None,
1091
+ runtime: Breeze25Runtime | None = None,
1092
+ language: str | None = BREEZE25_LANGUAGE,
1093
+ task: str = BREEZE25_TASK,
1094
  max_new_tokens: int = 128,
1095
+ max_verification_segments: int = BREEZE25_MAX_VERIFICATION_SEGMENTS,
1096
  ) -> str:
1097
+ """Transcribe ndarray audio using the sole pinned Breeze25 decoder.
1098
 
1099
  ``runtime`` and ``lazy_asr`` are mutually exclusive injection points. An
1100
  empty decoded string is returned as-is; the semantic verifier will reject
1101
+ it rather than accepting an arbitrary TTS fallback. Production fixes the
1102
+ shared mixed-language embedding with ``language="zh"``. Passing ``None``
1103
+ is supported only as an explicit evaluation injection and omits the
1104
+ language argument so the model performs its own language detection.
1105
  """
1106
 
1107
  if runtime is not None and lazy_asr is not None:
 
1109
  token_limit = int(max_new_tokens)
1110
  if token_limit <= 0:
1111
  raise ValueError("max_new_tokens must be positive")
1112
+ waveform = _resample_audio(
1113
+ _mono_audio(audio),
1114
+ int(sample_rate),
1115
+ BREEZE25_SAMPLE_RATE,
1116
+ )
1117
+ segments = _split_breeze25_audio(
1118
  waveform,
1119
  max_verification_segments=max_verification_segments,
1120
  )
1121
+ selected_runtime = runtime or (lazy_asr or _DEFAULT_BREEZE25).get_runtime()
1122
  decoded_segments: list[str] = []
1123
+ for start in range(0, len(segments), BREEZE25_MAX_MICROBATCH_SEGMENTS):
1124
+ microbatch = segments[start : start + BREEZE25_MAX_MICROBATCH_SEGMENTS]
1125
  processor_input: np.ndarray | list[np.ndarray]
1126
  processor_input = microbatch[0] if len(microbatch) == 1 else list(microbatch)
1127
  processor_output = selected_runtime.processor(
1128
  processor_input,
1129
+ sampling_rate=BREEZE25_SAMPLE_RATE,
1130
  return_tensors="pt",
1131
+ return_attention_mask=BREEZE25_RETURN_ATTENTION_MASK,
1132
  )
1133
  features = processor_output.input_features.to(
1134
  device=selected_runtime.device,
 
1137
  attention_mask = processor_output.attention_mask.to(
1138
  device=selected_runtime.device
1139
  )
1140
+ generation_kwargs = {
1141
+ "attention_mask": attention_mask,
1142
+ "task": task,
1143
+ "do_sample": False,
1144
+ "num_beams": 1,
1145
+ "max_new_tokens": token_limit,
1146
+ }
1147
+ if language is not None:
1148
+ generation_kwargs["language"] = language
1149
+ token_ids = selected_runtime.model.generate(features, **generation_kwargs)
1150
  decoded = selected_runtime.processor.batch_decode(
1151
  token_ids,
1152
  skip_special_tokens=True,
 
1157
  return " ".join(text for text in decoded_segments if text)
1158
 
1159
 
1160
+ @timed_latency_stage("breeze25_asr")
1161
+ def transcribe_breeze25(
1162
  audio: np.ndarray | Sequence[float],
1163
  sample_rate: int,
1164
  *,
1165
+ lazy_asr: LazyBreeze25ASR | None = None,
1166
+ runtime: Breeze25Runtime | None = None,
1167
+ language: str | None = BREEZE25_LANGUAGE,
1168
+ task: str = BREEZE25_TASK,
1169
  max_new_tokens: int = 128,
1170
+ max_verification_segments: int = BREEZE25_MAX_VERIFICATION_SEGMENTS,
1171
  ) -> str:
1172
+ """Transcribe with the sole pinned Breeze ASR 25 content verifier."""
1173
 
1174
+ return _transcribe_breeze25(
1175
  audio,
1176
  sample_rate,
1177
  lazy_asr=lazy_asr,
 
1183
  )
1184
 
1185
 
1186
+ # Compatibility aliases are direct references, not decorated wrappers. All
1187
+ # callers therefore share one lazy model and each decode is timed exactly once.
1188
+ WhisperRuntime = Breeze25Runtime
1189
+ LazyWhisperASR = LazyBreeze25ASR
1190
+ load_pinned_whisper_runtime = load_pinned_breeze25_runtime
1191
+ load_pinned_verification_whisper_runtime = load_pinned_breeze25_runtime
1192
+ _DEFAULT_WHISPER = _DEFAULT_BREEZE25
1193
+ _DEFAULT_VERIFICATION_WHISPER = _DEFAULT_BREEZE25
1194
+ _qualified_whisper_pause_ranges = _qualified_breeze25_pause_ranges
1195
+ _split_short_whisper_audio_at_sustained_pause = (
1196
+ _split_short_breeze25_audio_at_sustained_pause
1197
+ )
1198
+ _split_whisper_audio = _split_breeze25_audio
1199
+ _transcribe_whisper = _transcribe_breeze25
1200
+ transcribe_whisper = transcribe_breeze25
1201
+ transcribe_verification_whisper = transcribe_breeze25
 
 
 
 
 
 
 
 
 
 
 
1202
 
1203
 
1204
  @dataclass(frozen=True)
 
1229
  if selected_sample_rate <= 0:
1230
  raise ValueError("sample_rate must be positive")
1231
  waveform = _mono_audio(audio)
1232
+ transcript = (transcriber or transcribe_breeze25)(
1233
  waveform,
1234
  selected_sample_rate,
1235
  )
 
2462
  ) -> TrajectoryGateResult:
2463
  """Hard-intersect selected local rows without replacing acoustic evidence.
2464
 
2465
+ ``primary_verification`` owns the Breeze25, speaker, SQUIM, pace and
2466
  boundary-proxy evidence consumed by the coverage selector.
2467
+ ``independent_verification`` contains a semantic-only projection for the
2468
+ selected network-conditioned rows in the order given by
2469
+ ``primary_indices``. A secondary rejection is projected onto the
2470
  corresponding primary row with allow-listed semantic reason codes, making
2471
  it impossible for either the strict pool or the boundary-only proxy to
2472
+ retain that local candidate. Production reuses the exact Breeze25
2473
+ transcript cache, so this projection does not decode twice. Passing
2474
+ intersections return the original primary object unchanged.
2475
  """
2476
 
2477
  if not isinstance(primary_verification, TrajectoryGateResult):
 
4599
  artifacts,
4600
  strict=True,
4601
  )
4602
+ if result.passed is True
4603
  ):
4604
  raise RuntimeError("candidate endpoint evidence is invalid")
4605
  return replace(verification, chunk_artifacts=artifacts)
 
5904
  )
5905
 
5906
  # If every initial local chunk was strict, the initial exact assembly was
5907
+ # already rejected by Breeze25. Do not spend another final check
5908
  # on that identical waveform. Boundary-recovered initial paths are not
5909
  # excluded because the exact whole checks have not run for them yet.
5910
  excluded_path_list: list[tuple[int, ...]] = []
tests/test_coverage_adaptive.py CHANGED
@@ -637,7 +637,7 @@ def test_single_exact_fast_tier_never_uses_local_proxy():
637
  assert result.seed == 11
638
 
639
 
640
- def test_single_exact_fast_tier_requires_independent_whole_asr_evidence():
641
  chunk = ("完整內容",)
642
  generated = []
643
 
@@ -766,6 +766,73 @@ def test_preferred_refill_selects_prior_natural_endpoint_over_forced_candidate()
766
  ]
767
 
768
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
769
  def test_fast_exact_refill_cannot_select_relaxed_endpoint_waiver_candidate():
770
  chunk = ("完整內容",)
771
  generated = []
@@ -2464,7 +2531,7 @@ def test_final_verifier_exception_invalid_evidence_and_all_reject_fail_closed():
2464
  run_coverage_adaptive_cascade(
2465
  **common,
2466
  sequence_final_verifier=lambda *args: (_ for _ in ()).throw(
2467
- RuntimeError("large-v3 OOM")
2468
  ),
2469
  )
2470
  with pytest.raises(RuntimeError, match="invalid result"):
 
637
  assert result.seed == 11
638
 
639
 
640
+ def test_single_exact_fast_tier_requires_whole_semantic_projection_evidence():
641
  chunk = ("完整內容",)
642
  generated = []
643
 
 
766
  ]
767
 
768
 
769
+ def test_endpoint_binding_keeps_preflight_rejection_and_continues_to_refill():
770
+ chunk = ("完整內容",)
771
+ generated = []
772
+
773
+ def verifier(trajectory, candidate_chunks, seed):
774
+ if seed == 10:
775
+ return verify_trajectory(
776
+ [
777
+ CandidateObservation(
778
+ target_text=candidate_chunks[0],
779
+ transcript_text=candidate_chunks[0],
780
+ audio_duration_seconds=0.5,
781
+ pace_cps=8.0,
782
+ )
783
+ ],
784
+ chunk_artifacts=(ChunkCandidateArtifact(),),
785
+ max_pace_cps=4.3,
786
+ )
787
+ return _local_verification(candidate_chunks)
788
+
789
+ def generation_evidence(candidate_index, seed, chunk_indices, candidate_chunks):
790
+ return CandidateGenerationEvidence(
791
+ chunk_indices=chunk_indices,
792
+ chunk_text_units=(4,),
793
+ scheduled_cfg=generation_cfg_for_candidate_offset(candidate_index),
794
+ effective_cfgs=(
795
+ generation_cfg_for_candidate_offset(candidate_index),
796
+ ),
797
+ floor_reasons=((),),
798
+ chunk_stop_reasons=("stop_threshold",),
799
+ chunk_endpoint_energy_ratios=(0.10,),
800
+ chunk_generated_steps=(4,),
801
+ chunk_hard_stop_steps=(5,),
802
+ )
803
+
804
+ result = run_coverage_adaptive_cascade(
805
+ chunk,
806
+ 10,
807
+ lambda candidate_chunks, seed: (
808
+ generated.append(seed) or (f"audio-{seed}",)
809
+ ),
810
+ verifier,
811
+ verifier,
812
+ sequence_final_verifier=lambda *args: pytest.fail(
813
+ "passing refill must return before sequence search"
814
+ ),
815
+ generation_evidence_factory=generation_evidence,
816
+ max_generated_chunks=5,
817
+ max_generated_text_units=100,
818
+ preferred_min_speaker_similarity=0.70,
819
+ preferred_max_boundary_speaker_drop=0.10,
820
+ require_endpoint_evidence=True,
821
+ )
822
+
823
+ assert generated == [10, 11]
824
+ assert result.seed == 11
825
+ assert result.diagnostics.attempts[0].trajectory_passed is False
826
+ assert (
827
+ "chunk_0:pace_too_fast"
828
+ in result.diagnostics.attempts[0].trajectory_rejection_reasons
829
+ )
830
+ assert (
831
+ result.diagnostics.attempts[0].chunk_stop_reasons
832
+ == ("stop_threshold",)
833
+ )
834
+
835
+
836
  def test_fast_exact_refill_cannot_select_relaxed_endpoint_waiver_candidate():
837
  chunk = ("完整內容",)
838
  generated = []
 
2531
  run_coverage_adaptive_cascade(
2532
  **common,
2533
  sequence_final_verifier=lambda *args: (_ for _ in ()).throw(
2534
+ RuntimeError("Breeze25 verifier OOM")
2535
  ),
2536
  )
2537
  with pytest.raises(RuntimeError, match="invalid result"):
tests/test_glyph_app_integration.py CHANGED
@@ -374,9 +374,9 @@ def test_pure_asset_integration_makes_zero_model_calls_and_still_emits_schema7()
374
  "WholeWaveformVerificationCache": object,
375
  "_verify_trajectory_audio": lambda *args, **kwargs: final,
376
  "QUALITY_FINAL_ASR_MAX_NEW_TOKENS": 440,
377
- "transcribe_whisper": object(),
378
- "_transcribe_glyph_hybrid_turbo": object(),
379
- "_transcribe_glyph_hybrid_independent": object(),
380
  "_require_glyph_final_verification": lambda value: None,
381
  "_verify_independent_whole_audio": lambda *args, **kwargs: final,
382
  "FinalOutputRejectedError": RuntimeError,
@@ -801,22 +801,26 @@ def test_hybrid_assembly_has_no_post_asset_global_audio_transform():
801
  assert "pcm16_audio_output(assembly.sample_rate, waveform)" in source
802
 
803
 
804
- def test_hybrid_only_whisper_wrappers_pin_the_160_segment_override():
805
- turbo = _function_source("_transcribe_glyph_hybrid_turbo")
806
- independent = _function_source("_transcribe_glyph_hybrid_independent")
 
 
807
  hybrid = _function_source("_synthesize_glyph_hybrid")
808
 
809
  assert (
810
- "max_verification_segments="
811
  "GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS"
812
- ) in turbo
813
  assert (
814
- "max_verification_segments="
815
  "GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS"
816
- ) in independent
817
- for source in (turbo, independent):
 
818
  assert "language=language" in source
819
  assert "task=task" in source
820
  assert "max_new_tokens=max_new_tokens" in source
821
- assert "transcriber=_transcribe_glyph_hybrid_turbo" in hybrid
822
- assert "transcriber=_transcribe_glyph_hybrid_independent" in hybrid
 
 
374
  "WholeWaveformVerificationCache": object,
375
  "_verify_trajectory_audio": lambda *args, **kwargs: final,
376
  "QUALITY_FINAL_ASR_MAX_NEW_TOKENS": 440,
377
+ "transcribe_breeze25": object(),
378
+ "_transcribe_glyph_hybrid_breeze_primary": object(),
379
+ "_transcribe_glyph_hybrid_breeze_confirmation": object(),
380
  "_require_glyph_final_verification": lambda value: None,
381
  "_verify_independent_whole_audio": lambda *args, **kwargs: final,
382
  "FinalOutputRejectedError": RuntimeError,
 
801
  assert "pcm16_audio_output(assembly.sample_rate, waveform)" in source
802
 
803
 
804
+ def test_hybrid_only_breeze25_wrappers_pin_the_160_segment_override():
805
+ primary = _function_source("_transcribe_glyph_hybrid_breeze_primary")
806
+ confirmation = _function_source(
807
+ "_transcribe_glyph_hybrid_breeze_confirmation"
808
+ )
809
  hybrid = _function_source("_synthesize_glyph_hybrid")
810
 
811
  assert (
812
+ "max_verification_segments: int = "
813
  "GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS"
814
+ ) in primary
815
  assert (
816
+ "max_verification_segments: int = "
817
  "GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS"
818
+ ) in confirmation
819
+ for source in (primary, confirmation):
820
+ assert "transcribe_breeze25(" in source
821
  assert "language=language" in source
822
  assert "task=task" in source
823
  assert "max_new_tokens=max_new_tokens" in source
824
+ assert "max_verification_segments=max_verification_segments" in source
825
+ assert "transcriber=_transcribe_glyph_hybrid_breeze_primary" in hybrid
826
+ assert "transcriber=_transcribe_glyph_hybrid_breeze_confirmation" in hybrid
tests/test_glyph_assets.py CHANGED
@@ -76,6 +76,17 @@ def _pin(name: str, index: int) -> dict[str, object]:
76
  }
77
 
78
 
 
 
 
 
 
 
 
 
 
 
 
79
  def _entry_for_file(
80
  spec: ga.GlyphSpec,
81
  *,
@@ -188,8 +199,7 @@ def bundle_fixture(tmp_path: Path) -> BundleFixture:
188
  "source_pins": [_pin("asset-builder", 1)],
189
  "model_pins": [_pin("bluemagpie-checkpoint", 2)],
190
  "evaluator_pins": [
191
- _pin("whisper-turbo", 3),
192
- _pin("whisper-large-v3", 4),
193
  _pin("ecapa", 5),
194
  _pin("squim", 6),
195
  ],
@@ -221,6 +231,8 @@ def test_loads_exact_immutable_32_entry_manifest(bundle_fixture: BundleFixture):
221
  with pytest.raises(TypeError):
222
  bundle.join_profile["speed"] = 0.9
223
  decoded = bundle.assets[0].decoded_pcm16()
 
 
224
  assert not decoded.flags.writeable
225
  with pytest.raises(ValueError):
226
  decoded[0] = 1
 
76
  }
77
 
78
 
79
+ def _breeze25_pin() -> dict[str, object]:
80
+ return {
81
+ "name": "breeze-asr-25",
82
+ "uri": "hf://MediaTek-Research/Breeze-ASR-25",
83
+ "revision": "cffe7ccb404d025296a00758d0a33468bec3a9d0",
84
+ "sha256": (
85
+ "c5d952b3bc03ea277209aff0ef5b5c4c055d74449ff794c02d8f4e315fdef6b6"
86
+ ),
87
+ }
88
+
89
+
90
  def _entry_for_file(
91
  spec: ga.GlyphSpec,
92
  *,
 
199
  "source_pins": [_pin("asset-builder", 1)],
200
  "model_pins": [_pin("bluemagpie-checkpoint", 2)],
201
  "evaluator_pins": [
202
+ _breeze25_pin(),
 
203
  _pin("ecapa", 5),
204
  _pin("squim", 6),
205
  ],
 
231
  with pytest.raises(TypeError):
232
  bundle.join_profile["speed"] = 0.9
233
  decoded = bundle.assets[0].decoded_pcm16()
234
+ assert bundle.assets[0].endpoint.breeze_primary_cer == 0.0
235
+ assert bundle.assets[0].endpoint.breeze_confirmation_cer == 0.0
236
  assert not decoded.flags.writeable
237
  with pytest.raises(ValueError):
238
  decoded[0] = 1
tests/test_glyph_hybrid_adapter.py CHANGED
@@ -27,10 +27,6 @@ from glyph_hybrid_evidence import (
27
  )
28
  from quality_runtime import (
29
  SQUIM_OBJECTIVE_WEIGHT_SHA256,
30
- VERIFICATION_WHISPER_MODEL_ID,
31
- VERIFICATION_WHISPER_REVISION,
32
- WHISPER_MODEL_ID,
33
- WHISPER_REVISION,
34
  )
35
 
36
 
@@ -171,11 +167,11 @@ def _bundle(anchor_sha256: str, asset_pcm: bytes):
171
  _pin("bluemagpie-checkpoint", MODEL_REPO_ID, MODEL_REVISION),
172
  ),
173
  evaluator_pins=(
174
- _pin("whisper-turbo", WHISPER_MODEL_ID, WHISPER_REVISION),
175
  _pin(
176
- "whisper-large-v3",
177
- VERIFICATION_WHISPER_MODEL_ID,
178
- VERIFICATION_WHISPER_REVISION,
 
179
  ),
180
  _pin("ecapa", ECAPA_REPO_ID, ECAPA_REVISION),
181
  _pin(
@@ -208,14 +204,23 @@ def _runtime(
208
  transcript: str,
209
  counters: dict[str, int],
210
  ) -> GlyphAdapterRuntime:
 
 
211
  def transcriber(audio, sample_rate, **kwargs):
212
  assert sample_rate == SAMPLE_RATE
213
- assert kwargs["language"] == "zh"
 
 
 
214
  assert kwargs["task"] == "transcribe"
215
  assert kwargs["max_new_tokens"] == 160
 
 
216
  counters["asr"] += 1
217
  return transcript
218
 
 
 
219
  def speaker_measure(
220
  audio,
221
  sample_rate,
@@ -272,8 +277,8 @@ def _runtime(
272
  "assembly",
273
  counters["assembly"] + 1,
274
  ),
275
- turbo_transcriber=transcriber,
276
- verification_transcriber=transcriber,
277
  speaker_measure=speaker_measure,
278
  squim_measure=squim_measure,
279
  )
@@ -452,6 +457,10 @@ def test_adapter_replays_generation_and_reruns_pinned_evaluators(
452
  "squim": 1,
453
  "assembly": 1,
454
  }
 
 
 
 
455
  assert payload["request_proof"]["content_commitment_scheme"] == (
456
  "hmac-sha256-v1"
457
  )
@@ -479,6 +488,26 @@ def test_adapter_replays_generation_and_reruns_pinned_evaluators(
479
  payload["request_proof"]["independent_gate"]["asr_call_id"]
480
  ]["evaluator_pin_name"]
481
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
  asset_segment = next(
483
  row for row in segments.values() if row["kind"] == "asset"
484
  )
 
27
  )
28
  from quality_runtime import (
29
  SQUIM_OBJECTIVE_WEIGHT_SHA256,
 
 
 
 
30
  )
31
 
32
 
 
167
  _pin("bluemagpie-checkpoint", MODEL_REPO_ID, MODEL_REVISION),
168
  ),
169
  evaluator_pins=(
 
170
  _pin(
171
+ "breeze-asr-25",
172
+ adapter_module.BREEZE25_MODEL_ID,
173
+ adapter_module.BREEZE25_REVISION,
174
+ adapter_module.BREEZE25_WEIGHT_SHA256,
175
  ),
176
  _pin("ecapa", ECAPA_REPO_ID, ECAPA_REVISION),
177
  _pin(
 
204
  transcript: str,
205
  counters: dict[str, int],
206
  ) -> GlyphAdapterRuntime:
207
+ asr_languages: list[str | None] = []
208
+
209
  def transcriber(audio, sample_rate, **kwargs):
210
  assert sample_rate == SAMPLE_RATE
211
+ assert kwargs["language"] in {
212
+ adapter_module.BREEZE25_PRIMARY_LANGUAGE,
213
+ adapter_module.BREEZE25_CONFIRMATION_LANGUAGE,
214
+ }
215
  assert kwargs["task"] == "transcribe"
216
  assert kwargs["max_new_tokens"] == 160
217
+ assert kwargs["max_verification_segments"] == 160
218
+ asr_languages.append(kwargs["language"])
219
  counters["asr"] += 1
220
  return transcript
221
 
222
+ transcriber.languages = asr_languages
223
+
224
  def speaker_measure(
225
  audio,
226
  sample_rate,
 
277
  "assembly",
278
  counters["assembly"] + 1,
279
  ),
280
+ breeze_primary_transcriber=transcriber,
281
+ breeze_confirmation_transcriber=transcriber,
282
  speaker_measure=speaker_measure,
283
  squim_measure=squim_measure,
284
  )
 
457
  "squim": 1,
458
  "assembly": 1,
459
  }
460
+ assert case.runtime.breeze_primary_transcriber.languages == [
461
+ adapter_module.BREEZE25_PRIMARY_LANGUAGE,
462
+ adapter_module.BREEZE25_CONFIRMATION_LANGUAGE,
463
+ ]
464
  assert payload["request_proof"]["content_commitment_scheme"] == (
465
  "hmac-sha256-v1"
466
  )
 
488
  payload["request_proof"]["independent_gate"]["asr_call_id"]
489
  ]["evaluator_pin_name"]
490
  )
491
+ evaluator_pins = {
492
+ pin["name"]: pin
493
+ for pin in payload["renderer_contract"]["evaluator_pins"]
494
+ }
495
+ primary_pin = evaluator_pins[
496
+ adapter_module.BREEZE25_PRIMARY_PIN_NAME
497
+ ]
498
+ confirmation_pin = evaluator_pins[
499
+ adapter_module.BREEZE25_CONFIRMATION_PIN_NAME
500
+ ]
501
+ assert (
502
+ primary_pin["uri"],
503
+ primary_pin["revision"],
504
+ primary_pin["sha256"],
505
+ ) == (
506
+ confirmation_pin["uri"],
507
+ confirmation_pin["revision"],
508
+ confirmation_pin["sha256"],
509
+ )
510
+ assert primary_pin["config_sha256"] != confirmation_pin["config_sha256"]
511
  asset_segment = next(
512
  row for row in segments.values() if row["kind"] == "asset"
513
  )
tests/test_glyph_hybrid_evidence.py CHANGED
@@ -75,6 +75,13 @@ CONTENT_COMMITMENT_KEY = hashlib.sha256(
75
  ).digest()
76
  SPEAKER_ANCHOR_SHA256 = _sha("speaker-anchor")
77
  GENERATION_POLICY_SHA256 = _sha("quality-primary")
 
 
 
 
 
 
 
78
 
79
 
80
  def test_schema7_default_gate_policy_is_the_frozen_per_return_contract():
@@ -241,6 +248,20 @@ def _pin(name: str) -> ArtifactPin:
241
  )
242
 
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  def _inputs() -> EvidenceRebuildInputs:
245
  raw = "甲a乙"
246
  canonical = "甲第一個英文字母乙"
@@ -295,8 +316,14 @@ def _inputs() -> EvidenceRebuildInputs:
295
  source_pins=(_pin("source"),),
296
  model_pins=(_pin("tts-model"),),
297
  evaluator_pins=(
298
- _pin("asr-large-v3"),
299
- _pin("asr-large-v2"),
 
 
 
 
 
 
300
  _pin("ecapa"),
301
  _pin("squim"),
302
  ),
@@ -393,7 +420,7 @@ def _inputs() -> EvidenceRebuildInputs:
393
  TrustedAsrCall(
394
  call_id="asr-final",
395
  scope="final",
396
- evaluator_pin_name="asr-large-v3",
397
  input_pcm16_le=output,
398
  target_text=canonical,
399
  transcript_text=canonical,
@@ -420,7 +447,7 @@ def _inputs() -> EvidenceRebuildInputs:
420
  TrustedAsrCall(
421
  call_id="asr-independent",
422
  scope="independent",
423
- evaluator_pin_name="asr-large-v2",
424
  input_pcm16_le=output,
425
  target_text=canonical,
426
  transcript_text=canonical,
@@ -966,7 +993,7 @@ def test_evaluator_calls_must_use_exact_pcm_pins_scope_and_subject_attribution()
966
  local_asr = TrustedAsrCall(
967
  call_id="asr-local-wrong-target",
968
  scope="local-primary",
969
- evaluator_pin_name="asr-large-v3",
970
  input_pcm16_le=model.output_pcm16_le,
971
  target_text="錯",
972
  transcript_text="甲",
@@ -1167,17 +1194,17 @@ def test_endpoint_and_active_speech_are_recomputed_from_exact_pcm():
1167
  )
1168
 
1169
 
1170
- def test_independent_asr_anchor_and_generation_policy_are_frozen():
1171
  inputs = _inputs()
1172
  independent_asr = inputs.calls[6]
1173
  assert isinstance(independent_asr, TrustedAsrCall)
1174
  same_asr = replace(
1175
  independent_asr,
1176
- evaluator_pin_name="asr-large-v3",
1177
  )
1178
  with pytest.raises(
1179
  GlyphHybridEvidenceError,
1180
- match="distinct pinned evaluator",
1181
  ):
1182
  rebuild_hybrid_evidence(
1183
  replace(
@@ -1190,6 +1217,70 @@ def test_independent_asr_anchor_and_generation_policy_are_frozen():
1190
  )
1191
  )
1192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1193
  ecapa = inputs.calls[4]
1194
  assert isinstance(ecapa, TrustedEcapaCall)
1195
  wrong_anchor = replace(
 
75
  ).digest()
76
  SPEAKER_ANCHOR_SHA256 = _sha("speaker-anchor")
77
  GENERATION_POLICY_SHA256 = _sha("quality-primary")
78
+ BREEZE25_MODEL_ID = "MediaTek-Research/Breeze-ASR-25"
79
+ BREEZE25_REVISION = "cffe7ccb404d025296a00758d0a33468bec3a9d0"
80
+ BREEZE25_WEIGHT_SHA256 = (
81
+ "c5d952b3bc03ea277209aff0ef5b5c4c055d74449ff794c02d8f4e315fdef6b6"
82
+ )
83
+ BREEZE25_PRIMARY_PIN_NAME = "breeze25-primary-zh"
84
+ BREEZE25_CONFIRMATION_PIN_NAME = "breeze25-confirmation-auto"
85
 
86
 
87
  def test_schema7_default_gate_policy_is_the_frozen_per_return_contract():
 
248
  )
249
 
250
 
251
+ def _breeze25_profile_pin(
252
+ name: str,
253
+ decoder_profile: str,
254
+ ) -> ArtifactPin:
255
+ return ArtifactPin(
256
+ name=name,
257
+ kind="asr",
258
+ uri=f"hf://{BREEZE25_MODEL_ID}",
259
+ revision=BREEZE25_REVISION,
260
+ sha256=BREEZE25_WEIGHT_SHA256,
261
+ config_sha256=_sha(f"breeze25:{decoder_profile}"),
262
+ )
263
+
264
+
265
  def _inputs() -> EvidenceRebuildInputs:
266
  raw = "甲a乙"
267
  canonical = "甲第一個英文字母乙"
 
316
  source_pins=(_pin("source"),),
317
  model_pins=(_pin("tts-model"),),
318
  evaluator_pins=(
319
+ _breeze25_profile_pin(
320
+ BREEZE25_PRIMARY_PIN_NAME,
321
+ "fixed-zh",
322
+ ),
323
+ _breeze25_profile_pin(
324
+ BREEZE25_CONFIRMATION_PIN_NAME,
325
+ "auto-language",
326
+ ),
327
  _pin("ecapa"),
328
  _pin("squim"),
329
  ),
 
420
  TrustedAsrCall(
421
  call_id="asr-final",
422
  scope="final",
423
+ evaluator_pin_name=BREEZE25_PRIMARY_PIN_NAME,
424
  input_pcm16_le=output,
425
  target_text=canonical,
426
  transcript_text=canonical,
 
447
  TrustedAsrCall(
448
  call_id="asr-independent",
449
  scope="independent",
450
+ evaluator_pin_name=BREEZE25_CONFIRMATION_PIN_NAME,
451
  input_pcm16_le=output,
452
  target_text=canonical,
453
  transcript_text=canonical,
 
993
  local_asr = TrustedAsrCall(
994
  call_id="asr-local-wrong-target",
995
  scope="local-primary",
996
+ evaluator_pin_name=BREEZE25_PRIMARY_PIN_NAME,
997
  input_pcm16_le=model.output_pcm16_le,
998
  target_text="錯",
999
  transcript_text="甲",
 
1194
  )
1195
 
1196
 
1197
+ def test_independent_asr_profile_anchor_and_generation_policy_are_frozen():
1198
  inputs = _inputs()
1199
  independent_asr = inputs.calls[6]
1200
  assert isinstance(independent_asr, TrustedAsrCall)
1201
  same_asr = replace(
1202
  independent_asr,
1203
+ evaluator_pin_name=BREEZE25_PRIMARY_PIN_NAME,
1204
  )
1205
  with pytest.raises(
1206
  GlyphHybridEvidenceError,
1207
+ match="distinct pinned decoder profiles",
1208
  ):
1209
  rebuild_hybrid_evidence(
1210
  replace(
 
1217
  )
1218
  )
1219
 
1220
+ different_weights_pin = replace(
1221
+ inputs.contract.evaluator_pins[1],
1222
+ sha256=_sha("different-breeze25-weights"),
1223
+ )
1224
+ with pytest.raises(
1225
+ GlyphHybridEvidenceError,
1226
+ match="share the exact pinned model weights",
1227
+ ):
1228
+ rebuild_hybrid_evidence(
1229
+ replace(
1230
+ inputs,
1231
+ contract=replace(
1232
+ inputs.contract,
1233
+ evaluator_pins=(
1234
+ inputs.contract.evaluator_pins[0],
1235
+ different_weights_pin,
1236
+ *inputs.contract.evaluator_pins[2:],
1237
+ ),
1238
+ ),
1239
+ )
1240
+ )
1241
+
1242
+ foreign_weights_pins = tuple(
1243
+ replace(
1244
+ pin,
1245
+ uri="hf://untrusted/other-asr",
1246
+ revision="foreign-revision",
1247
+ sha256=_sha("foreign-shared-weights"),
1248
+ )
1249
+ if pin.kind == "asr"
1250
+ else pin
1251
+ for pin in inputs.contract.evaluator_pins
1252
+ )
1253
+ with pytest.raises(
1254
+ GlyphHybridEvidenceError,
1255
+ match="official pinned Breeze ASR 25 weights",
1256
+ ):
1257
+ rebuild_hybrid_evidence(
1258
+ replace(
1259
+ inputs,
1260
+ contract=replace(
1261
+ inputs.contract,
1262
+ evaluator_pins=foreign_weights_pins,
1263
+ ),
1264
+ )
1265
+ )
1266
+
1267
+ swapped_calls = list(inputs.calls)
1268
+ swapped_calls[3] = replace(
1269
+ inputs.calls[3],
1270
+ evaluator_pin_name=BREEZE25_CONFIRMATION_PIN_NAME,
1271
+ )
1272
+ swapped_calls[6] = replace(
1273
+ inputs.calls[6],
1274
+ evaluator_pin_name=BREEZE25_PRIMARY_PIN_NAME,
1275
+ )
1276
+ with pytest.raises(
1277
+ GlyphHybridEvidenceError,
1278
+ match="honest Breeze primary and confirmation logical pin names",
1279
+ ):
1280
+ rebuild_hybrid_evidence(
1281
+ replace(inputs, calls=tuple(swapped_calls))
1282
+ )
1283
+
1284
  ecapa = inputs.calls[4]
1285
  assert isinstance(ecapa, TrustedEcapaCall)
1286
  wrong_anchor = replace(
tests/test_inference_cleanup.py CHANGED
@@ -215,7 +215,7 @@ def test_trajectory_verifier_skips_acoustic_models_after_semantic_preflight_fail
215
  "np": np,
216
  "CandidateObservation": CandidateObservation,
217
  "ChunkCandidateArtifact": ChunkCandidateArtifact,
218
- "transcribe_whisper": lambda *_args, **_kwargs: "",
219
  "prepare_candidate_audio": lambda *_args, **_kwargs: SimpleNamespace(
220
  waveform=waveform,
221
  transcript_text="錯誤內容",
@@ -264,6 +264,7 @@ def test_trajectory_verifier_skips_acoustic_models_after_semantic_preflight_fail
264
 
265
  def test_trajectory_verifier_skips_acoustics_above_conservative_pace_preflight():
266
  acoustic_calls = []
 
267
  waveform = np.ones(2 * 48_000, dtype=np.float32)
268
  target = "甲乙丙丁戊己庚辛壬癸"
269
 
@@ -271,17 +272,18 @@ def test_trajectory_verifier_skips_acoustics_above_conservative_pace_preflight()
271
  acoustic_calls.append((args, kwargs))
272
  raise AssertionError("acoustic model should not run")
273
 
 
 
 
 
274
  verify_audio = _isolated_app_function(
275
  "_verify_trajectory_audio",
276
  {
277
  "np": np,
278
  "CandidateObservation": CandidateObservation,
279
  "ChunkCandidateArtifact": ChunkCandidateArtifact,
280
- "transcribe_whisper": lambda *_args, **_kwargs: "",
281
- "prepare_candidate_audio": lambda *_args, **_kwargs: SimpleNamespace(
282
- waveform=waveform,
283
- transcript_text=target,
284
- ),
285
  "active_voiced_duration_seconds": lambda *_args, **_kwargs: 2.0,
286
  "count_speech_units": count_speech_units,
287
  "verify_candidate": verify_candidate,
@@ -318,8 +320,10 @@ def test_trajectory_verifier_skips_acoustics_above_conservative_pace_preflight()
318
 
319
  assert not verification.passed
320
  assert verification.candidate_results[0].rejection_reasons == (
 
321
  "pace_too_fast",
322
  )
 
323
  assert acoustic_calls == []
324
 
325
 
@@ -350,7 +354,7 @@ def test_borderline_preflight_still_runs_acoustics_then_enforces_final_pace_gate
350
  "np": np,
351
  "CandidateObservation": CandidateObservation,
352
  "ChunkCandidateArtifact": ChunkCandidateArtifact,
353
- "transcribe_whisper": lambda *_args, **_kwargs: "",
354
  "prepare_candidate_audio": lambda *_args, **_kwargs: SimpleNamespace(
355
  waveform=waveform,
356
  transcript_text=target,
 
215
  "np": np,
216
  "CandidateObservation": CandidateObservation,
217
  "ChunkCandidateArtifact": ChunkCandidateArtifact,
218
+ "transcribe_breeze25": lambda *_args, **_kwargs: "",
219
  "prepare_candidate_audio": lambda *_args, **_kwargs: SimpleNamespace(
220
  waveform=waveform,
221
  transcript_text="錯誤內容",
 
264
 
265
  def test_trajectory_verifier_skips_acoustics_above_conservative_pace_preflight():
266
  acoustic_calls = []
267
+ asr_calls = []
268
  waveform = np.ones(2 * 48_000, dtype=np.float32)
269
  target = "甲乙丙丁戊己庚辛壬癸"
270
 
 
272
  acoustic_calls.append((args, kwargs))
273
  raise AssertionError("acoustic model should not run")
274
 
275
+ def forbidden_asr(*args, **kwargs):
276
+ asr_calls.append((args, kwargs))
277
+ raise AssertionError("Breeze25 should not run after hard pace rejection")
278
+
279
  verify_audio = _isolated_app_function(
280
  "_verify_trajectory_audio",
281
  {
282
  "np": np,
283
  "CandidateObservation": CandidateObservation,
284
  "ChunkCandidateArtifact": ChunkCandidateArtifact,
285
+ "transcribe_breeze25": lambda *_args, **_kwargs: "",
286
+ "prepare_candidate_audio": forbidden_asr,
 
 
 
287
  "active_voiced_duration_seconds": lambda *_args, **_kwargs: 2.0,
288
  "count_speech_units": count_speech_units,
289
  "verify_candidate": verify_candidate,
 
320
 
321
  assert not verification.passed
322
  assert verification.candidate_results[0].rejection_reasons == (
323
+ "semantic_gate",
324
  "pace_too_fast",
325
  )
326
+ assert asr_calls == []
327
  assert acoustic_calls == []
328
 
329
 
 
354
  "np": np,
355
  "CandidateObservation": CandidateObservation,
356
  "ChunkCandidateArtifact": ChunkCandidateArtifact,
357
+ "transcribe_breeze25": lambda *_args, **_kwargs: "",
358
  "prepare_candidate_audio": lambda *_args, **_kwargs: SimpleNamespace(
359
  waveform=waveform,
360
  transcript_text=target,
tests/test_latency_timing.py CHANGED
@@ -8,6 +8,8 @@ import torch
8
 
9
  import latency_timing
10
  from quality_runtime import (
 
 
11
  WhisperRuntime,
12
  transcribe_verification_whisper,
13
  transcribe_whisper,
@@ -37,7 +39,7 @@ def test_request_timer_emits_fixed_content_free_cumulative_schema(monkeypatch):
37
  with latency_timing.latency_request(emit=emitted.append) as totals:
38
  with latency_timing.latency_stage("generation"):
39
  pass
40
- with latency_timing.latency_stage("turbo_asr"):
41
  pass
42
 
43
  assert len(emitted) == 1
@@ -51,10 +53,8 @@ def test_request_timer_emits_fixed_content_free_cumulative_schema(monkeypatch):
51
  assert fields["request_total_calls"] == "1"
52
  assert fields["generation_seconds"] == "2.500000"
53
  assert fields["generation_calls"] == "1"
54
- assert fields["turbo_asr_seconds"] == "1.000000"
55
- assert fields["turbo_asr_calls"] == "1"
56
- assert fields["large_v3_asr_seconds"] == "0.000000"
57
- assert fields["large_v3_asr_calls"] == "0"
58
  assert totals.active_depths == {}
59
 
60
 
@@ -112,8 +112,11 @@ class _Model:
112
  return torch.ones((features.shape[0], 2), dtype=torch.long)
113
 
114
 
115
- def test_dual_asr_wrappers_accumulate_under_distinct_stage_names():
116
- runtime = WhisperRuntime(
 
 
 
117
  processor=_Processor(),
118
  model=_Model(),
119
  device=torch.device("cpu"),
@@ -134,10 +137,10 @@ def test_dual_asr_wrappers_accumulate_under_distinct_stage_names():
134
  )
135
 
136
  fields = _fields(emitted[0])
137
- assert fields["turbo_asr_calls"] == "1"
138
- assert fields["large_v3_asr_calls"] == "1"
139
- assert float(fields["turbo_asr_seconds"]) >= 0.0
140
- assert float(fields["large_v3_asr_seconds"]) >= 0.0
141
 
142
 
143
  def _decorator_stage(node):
@@ -182,11 +185,12 @@ def test_space_wires_every_requested_latency_stage_without_gate_changes():
182
  )
183
  assert generate_source is not None
184
  assert 'with latency_stage("generation"):' in generate_source
185
- assert _decorator_stage(quality_functions["transcribe_whisper"]) == "turbo_asr"
186
  assert (
187
- _decorator_stage(quality_functions["transcribe_verification_whisper"])
188
- == "large_v3_asr"
189
  )
 
 
190
  assert _decorator_stage(quality_functions["speaker_evidence_from_audio"]) == "ecapa"
191
  assert (
192
  _decorator_stage(quality_functions["release_speaker_evidence_from_audio"])
 
8
 
9
  import latency_timing
10
  from quality_runtime import (
11
+ Breeze25Runtime,
12
+ transcribe_breeze25,
13
  WhisperRuntime,
14
  transcribe_verification_whisper,
15
  transcribe_whisper,
 
39
  with latency_timing.latency_request(emit=emitted.append) as totals:
40
  with latency_timing.latency_stage("generation"):
41
  pass
42
+ with latency_timing.latency_stage("breeze25_asr"):
43
  pass
44
 
45
  assert len(emitted) == 1
 
53
  assert fields["request_total_calls"] == "1"
54
  assert fields["generation_seconds"] == "2.500000"
55
  assert fields["generation_calls"] == "1"
56
+ assert fields["breeze25_asr_seconds"] == "1.000000"
57
+ assert fields["breeze25_asr_calls"] == "1"
 
 
58
  assert totals.active_depths == {}
59
 
60
 
 
112
  return torch.ones((features.shape[0], 2), dtype=torch.long)
113
 
114
 
115
+ def test_compatibility_asr_aliases_share_one_stage_without_nested_timing():
116
+ assert WhisperRuntime is Breeze25Runtime
117
+ assert transcribe_whisper is transcribe_breeze25
118
+ assert transcribe_verification_whisper is transcribe_breeze25
119
+ runtime = Breeze25Runtime(
120
  processor=_Processor(),
121
  model=_Model(),
122
  device=torch.device("cpu"),
 
137
  )
138
 
139
  fields = _fields(emitted[0])
140
+ assert fields["breeze25_asr_calls"] == "2"
141
+ assert float(fields["breeze25_asr_seconds"]) >= 0.0
142
+ assert "turbo_asr_calls" not in fields
143
+ assert "large_v3_asr_calls" not in fields
144
 
145
 
146
  def _decorator_stage(node):
 
185
  )
186
  assert generate_source is not None
187
  assert 'with latency_stage("generation"):' in generate_source
 
188
  assert (
189
+ _decorator_stage(quality_functions["transcribe_breeze25"])
190
+ == "breeze25_asr"
191
  )
192
+ assert "transcribe_whisper" not in quality_functions
193
+ assert "transcribe_verification_whisper" not in quality_functions
194
  assert _decorator_stage(quality_functions["speaker_evidence_from_audio"]) == "ecapa"
195
  assert (
196
  _decorator_stage(quality_functions["release_speaker_evidence_from_audio"])
tests/test_production.py CHANGED
@@ -3461,6 +3461,76 @@ def test_asr_comparison_canonicalizes_target_proven_bare_clock_time_only():
3461
  )
3462
 
3463
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3464
  def test_asr_numeric_runs_require_exact_target_proven_cardinal_values():
3465
  target = "原價是 NT$1,250,折扣後是 NT$999。"
3466
  transcript = "原價是新台幣1250,折扣後是新台幣999。"
 
3461
  )
3462
 
3463
 
3464
+ def test_breeze_asr_spaced_date_and_dot_clock_are_exact_target_proven_forms():
3465
+ target = "維護時段是二零二六年八月九日晚上七點四十五分。"
3466
+ transcript = "維護時段是 2026 年 8 月 9 日晚上 7.45 分"
3467
+
3468
+ comparison = compare_asr_text(target, transcript)
3469
+
3470
+ assert comparison.passed
3471
+ assert comparison.cer == 0.0
3472
+ assert comparison.prefix_cer == 0.0
3473
+ assert comparison.suffix_cer == 0.0
3474
+ assert comparison.extra_tail_units == 0
3475
+
3476
+
3477
+ def test_breeze_asr_target_proven_dot_clock_may_omit_written_minute_unit():
3478
+ target = "海龜調查預定在二零三三年四月二十七日凌晨四點五十五分出發。"
3479
+ transcript = "海龜調查預定在 2033 年 4 月 27 日凌晨 4.55 出發"
3480
+
3481
+ comparison = compare_asr_text(target, transcript)
3482
+
3483
+ assert comparison.passed
3484
+ assert comparison.cer == 0.0
3485
+ assert comparison.prefix_cer == 0.0
3486
+ assert comparison.suffix_cer == 0.0
3487
+
3488
+
3489
+ @pytest.mark.parametrize(
3490
+ "transcript",
3491
+ (
3492
+ "維護時段是 2026 年 8 月 8 日晚上 7.45 分",
3493
+ "維護時段是 2026 年 8 月 9 日晚上 7.44 分",
3494
+ "維護時段是 2027 年 8 月 9 日晚上 7.45 分",
3495
+ ),
3496
+ )
3497
+ def test_breeze_asr_structured_date_clock_forms_keep_wrong_values_visible(
3498
+ transcript,
3499
+ ):
3500
+ target = "維護時段是二零二六年八月九日晚上七點四十五分。"
3501
+
3502
+ comparison = compare_asr_text(target, transcript)
3503
+
3504
+ assert comparison.edit_distance > 0
3505
+ assert comparison.target_text != comparison.transcript_text
3506
+
3507
+
3508
+ def test_breeze_asr_lowercase_compact_model_code_requires_exact_target_proof():
3509
+ target = "請先安裝韌體 v3.7.2,再核對模組代碼 AX-418。"
3510
+ transcript = "請先安裝韌體版本 3.7.2再核對模組代碼 ax418"
3511
+
3512
+ comparison = compare_asr_text(target, transcript)
3513
+
3514
+ assert comparison.passed
3515
+ assert comparison.cer == 0.0
3516
+ assert comparison.prefix_cer == 0.0
3517
+ assert comparison.suffix_cer == 0.0
3518
+
3519
+
3520
+ @pytest.mark.parametrize(
3521
+ "transcript",
3522
+ (
3523
+ "請先安裝韌體版本 3.7.2再核對模組代碼 ax419",
3524
+ "請先安裝韌體版本 3.7.2再核對模組代碼 bx418",
3525
+ "請先安裝韌體版本 3.7.2再核對模組代碼 ax4188",
3526
+ ),
3527
+ )
3528
+ def test_breeze_asr_model_code_proof_rejects_wrong_or_extra_atoms(transcript):
3529
+ target = "請先安裝韌體 v3.7.2,再核對模組代碼 AX-418。"
3530
+
3531
+ assert not compare_asr_text(target, transcript).passed
3532
+
3533
+
3534
  def test_asr_numeric_runs_require_exact_target_proven_cardinal_values():
3535
  target = "原價是 NT$1,250,折扣後是 NT$999。"
3536
  transcript = "原價是新台幣1250,折扣後是新台幣999。"
tests/test_quality_runtime.py CHANGED
@@ -7,6 +7,7 @@ import pytest
7
  import soundfile as sf
8
  import torch
9
 
 
10
  from production import (
11
  count_network_endpoint_duration_units,
12
  count_speech_units,
@@ -16,6 +17,13 @@ from production import (
16
  from quality_runtime import (
17
  ADAPTIVE_CASCADE_STAGE_LIMITS,
18
  BASE_GENERATION_POLICY,
 
 
 
 
 
 
 
19
  COMPLETION_HEADROOM_GENERATION_POLICY,
20
  CASCADE_EVIDENCE_LOG_PREFIX,
21
  CASCADE_EVIDENCE_SCHEMA_VERSION,
@@ -38,8 +46,10 @@ from quality_runtime import (
38
  CandidateVerification,
39
  CascadeResult,
40
  ChunkCandidateArtifact,
 
41
  FinalOutputRejectedError,
42
  LazySquimObjective,
 
43
  LazyWhisperASR,
44
  LocalIndependentGateEvidence,
45
  NoQualifiedCandidateError,
@@ -67,6 +77,7 @@ from quality_runtime import (
67
  intersect_local_semantic_verification,
68
  local_candidate_has_coverage_eligibility,
69
  load_pinned_whisper_runtime,
 
70
  load_pinned_verification_whisper_runtime,
71
  load_pinned_squim_objective_runtime,
72
  prepare_candidate_audio,
@@ -83,6 +94,7 @@ from quality_runtime import (
83
  squim_objective_evidence_from_audio,
84
  trim_release_speaker_activity,
85
  transcribe_whisper,
 
86
  transcribe_verification_whisper,
87
  trajectory_gate_evidence,
88
  verify_candidate,
@@ -607,13 +619,13 @@ def _joined_verification(target, transcript):
607
  )
608
 
609
 
610
- def test_pinned_whisper_loader_uses_exact_revision_without_global_download():
611
  processor = _FakeProcessor()
612
  model = _FakeWhisperModel()
613
  processor_factory = _Factory(processor)
614
  model_factory = _Factory(model)
615
 
616
- runtime = load_pinned_whisper_runtime(
617
  device="cpu",
618
  processor_factory=processor_factory,
619
  model_factory=model_factory,
@@ -625,19 +637,64 @@ def test_pinned_whisper_loader_uses_exact_revision_without_global_download():
625
  assert runtime.dtype == torch.float32
626
  assert model.evaluated
627
  assert processor_factory.calls == [
628
- ((WHISPER_MODEL_ID,), {"revision": WHISPER_REVISION})
629
  ]
630
  model_args, model_kwargs = model_factory.calls[0]
631
- assert model_args == (WHISPER_MODEL_ID,)
632
- assert model_kwargs["revision"] == WHISPER_REVISION
633
- assert model_kwargs["attn_implementation"] == WHISPER_ATTENTION_IMPLEMENTATION
634
  assert model_kwargs["torch_dtype"] == torch.float32
635
  assert model_kwargs["use_safetensors"] is True
636
- assert WHISPER_ATTENTION_IMPLEMENTATION == "eager"
637
- assert WHISPER_RETURN_ATTENTION_MASK is True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
 
639
 
640
- def test_pinned_verification_whisper_loader_is_separate_and_exact():
641
  processor = _FakeProcessor()
642
  model = _FakeWhisperModel()
643
  processor_factory = _Factory(processor)
@@ -665,16 +722,16 @@ def test_pinned_verification_whisper_loader_is_separate_and_exact():
665
  assert model_kwargs["use_safetensors"] is True
666
 
667
 
668
- def test_lazy_whisper_runtime_loads_once_and_transcription_is_deterministic():
669
  processor = _FakeProcessor()
670
  model = _FakeWhisperModel()
671
- runtime = WhisperRuntime(processor, model, torch.device("cpu"), torch.float32)
672
  loads = []
673
- lazy_asr = LazyWhisperASR(lambda: loads.append("load") or runtime)
674
  audio_8khz = _tone(sample_rate=8_000, seconds=0.5)
675
 
676
- first = transcribe_whisper(audio_8khz, 8_000, lazy_asr=lazy_asr)
677
- second = transcribe_whisper(audio_8khz, 8_000, lazy_asr=lazy_asr)
678
 
679
  assert first == second == "合成內容完整。"
680
  assert loads == ["load"]
@@ -689,20 +746,40 @@ def test_lazy_whisper_runtime_loads_once_and_transcription_is_deterministic():
689
  assert torch.equal(attention_mask, processor.attention_masks[0])
690
  assert attention_mask.device == runtime.device
691
  assert generation_kwargs == {
692
- "language": "zh",
693
  "task": "transcribe",
694
  "do_sample": False,
695
  "num_beams": 1,
696
  "max_new_tokens": 128,
 
697
  }
698
 
699
 
700
- def test_verification_transcriber_uses_injected_full_runtime_without_turbo_state():
701
  processor = _FakeProcessor()
702
  model = _FakeWhisperModel()
703
- runtime = WhisperRuntime(processor, model, torch.device("cpu"), torch.float32)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
704
  loads = []
705
- lazy_asr = LazyWhisperASR(lambda: loads.append("verification") or runtime)
706
 
707
  transcript = transcribe_verification_whisper(
708
  _tone(seconds=0.5),
 
7
  import soundfile as sf
8
  import torch
9
 
10
+ import quality_runtime
11
  from production import (
12
  count_network_endpoint_duration_units,
13
  count_speech_units,
 
17
  from quality_runtime import (
18
  ADAPTIVE_CASCADE_STAGE_LIMITS,
19
  BASE_GENERATION_POLICY,
20
+ BREEZE25_ATTENTION_IMPLEMENTATION,
21
+ BREEZE25_LANGUAGE,
22
+ BREEZE25_MODEL_ID,
23
+ BREEZE25_RETURN_ATTENTION_MASK,
24
+ BREEZE25_REVISION,
25
+ BREEZE25_TASK,
26
+ BREEZE25_WEIGHT_SHA256,
27
  COMPLETION_HEADROOM_GENERATION_POLICY,
28
  CASCADE_EVIDENCE_LOG_PREFIX,
29
  CASCADE_EVIDENCE_SCHEMA_VERSION,
 
46
  CandidateVerification,
47
  CascadeResult,
48
  ChunkCandidateArtifact,
49
+ Breeze25Runtime,
50
  FinalOutputRejectedError,
51
  LazySquimObjective,
52
+ LazyBreeze25ASR,
53
  LazyWhisperASR,
54
  LocalIndependentGateEvidence,
55
  NoQualifiedCandidateError,
 
77
  intersect_local_semantic_verification,
78
  local_candidate_has_coverage_eligibility,
79
  load_pinned_whisper_runtime,
80
+ load_pinned_breeze25_runtime,
81
  load_pinned_verification_whisper_runtime,
82
  load_pinned_squim_objective_runtime,
83
  prepare_candidate_audio,
 
94
  squim_objective_evidence_from_audio,
95
  trim_release_speaker_activity,
96
  transcribe_whisper,
97
+ transcribe_breeze25,
98
  transcribe_verification_whisper,
99
  trajectory_gate_evidence,
100
  verify_candidate,
 
619
  )
620
 
621
 
622
+ def test_pinned_breeze25_loader_uses_exact_revision_without_global_download():
623
  processor = _FakeProcessor()
624
  model = _FakeWhisperModel()
625
  processor_factory = _Factory(processor)
626
  model_factory = _Factory(model)
627
 
628
+ runtime = load_pinned_breeze25_runtime(
629
  device="cpu",
630
  processor_factory=processor_factory,
631
  model_factory=model_factory,
 
637
  assert runtime.dtype == torch.float32
638
  assert model.evaluated
639
  assert processor_factory.calls == [
640
+ ((BREEZE25_MODEL_ID,), {"revision": BREEZE25_REVISION})
641
  ]
642
  model_args, model_kwargs = model_factory.calls[0]
643
+ assert model_args == (BREEZE25_MODEL_ID,)
644
+ assert model_kwargs["revision"] == BREEZE25_REVISION
645
+ assert model_kwargs["attn_implementation"] == BREEZE25_ATTENTION_IMPLEMENTATION
646
  assert model_kwargs["torch_dtype"] == torch.float32
647
  assert model_kwargs["use_safetensors"] is True
648
+ assert BREEZE25_MODEL_ID == "MediaTek-Research/Breeze-ASR-25"
649
+ assert BREEZE25_REVISION == "cffe7ccb404d025296a00758d0a33468bec3a9d0"
650
+ assert BREEZE25_WEIGHT_SHA256 == (
651
+ "c5d952b3bc03ea277209aff0ef5b5c4c055d74449ff794c02d8f4e315fdef6b6"
652
+ )
653
+ assert BREEZE25_ATTENTION_IMPLEMENTATION == "eager"
654
+ assert BREEZE25_RETURN_ATTENTION_MASK is True
655
+ assert BREEZE25_LANGUAGE == "zh"
656
+ assert BREEZE25_TASK == "transcribe"
657
+
658
+
659
+ def test_pinned_breeze25_loader_uses_fp16_on_explicit_cuda_device():
660
+ processor = _FakeProcessor()
661
+ model = _FakeWhisperModel()
662
+ processor_factory = _Factory(processor)
663
+ model_factory = _Factory(model)
664
+
665
+ runtime = load_pinned_breeze25_runtime(
666
+ device="cuda",
667
+ processor_factory=processor_factory,
668
+ model_factory=model_factory,
669
+ )
670
+
671
+ assert runtime.device == torch.device("cuda")
672
+ assert runtime.dtype == torch.float16
673
+ assert model.device == torch.device("cuda")
674
+ assert model_factory.calls[0][1]["torch_dtype"] == torch.float16
675
+
676
+
677
+ def test_legacy_whisper_loader_names_alias_the_single_breeze25_contract():
678
+ assert WhisperRuntime is Breeze25Runtime
679
+ assert LazyWhisperASR is LazyBreeze25ASR
680
+ assert load_pinned_whisper_runtime is load_pinned_breeze25_runtime
681
+ assert load_pinned_verification_whisper_runtime is load_pinned_breeze25_runtime
682
+ assert transcribe_whisper is transcribe_breeze25
683
+ assert transcribe_verification_whisper is transcribe_breeze25
684
+ assert WHISPER_MODEL_ID == BREEZE25_MODEL_ID
685
+ assert WHISPER_REVISION == BREEZE25_REVISION
686
+ assert VERIFICATION_WHISPER_MODEL_ID == BREEZE25_MODEL_ID
687
+ assert VERIFICATION_WHISPER_REVISION == BREEZE25_REVISION
688
+ assert WHISPER_ATTENTION_IMPLEMENTATION == BREEZE25_ATTENTION_IMPLEMENTATION
689
+ assert WHISPER_RETURN_ATTENTION_MASK is BREEZE25_RETURN_ATTENTION_MASK
690
+ assert quality_runtime._DEFAULT_WHISPER is quality_runtime._DEFAULT_BREEZE25
691
+ assert (
692
+ quality_runtime._DEFAULT_VERIFICATION_WHISPER
693
+ is quality_runtime._DEFAULT_BREEZE25
694
+ )
695
 
696
 
697
+ def test_legacy_verification_loader_cannot_select_a_second_model():
698
  processor = _FakeProcessor()
699
  model = _FakeWhisperModel()
700
  processor_factory = _Factory(processor)
 
722
  assert model_kwargs["use_safetensors"] is True
723
 
724
 
725
+ def test_lazy_breeze25_runtime_loads_once_and_transcription_is_deterministic():
726
  processor = _FakeProcessor()
727
  model = _FakeWhisperModel()
728
+ runtime = Breeze25Runtime(processor, model, torch.device("cpu"), torch.float32)
729
  loads = []
730
+ lazy_asr = LazyBreeze25ASR(lambda: loads.append("load") or runtime)
731
  audio_8khz = _tone(sample_rate=8_000, seconds=0.5)
732
 
733
+ first = transcribe_breeze25(audio_8khz, 8_000, lazy_asr=lazy_asr)
734
+ second = transcribe_breeze25(audio_8khz, 8_000, lazy_asr=lazy_asr)
735
 
736
  assert first == second == "合成內容完整。"
737
  assert loads == ["load"]
 
746
  assert torch.equal(attention_mask, processor.attention_masks[0])
747
  assert attention_mask.device == runtime.device
748
  assert generation_kwargs == {
 
749
  "task": "transcribe",
750
  "do_sample": False,
751
  "num_beams": 1,
752
  "max_new_tokens": 128,
753
+ "language": "zh",
754
  }
755
 
756
 
757
+ def test_breeze25_auto_language_ab_injection_omits_language_generate_argument():
758
  processor = _FakeProcessor()
759
  model = _FakeWhisperModel()
760
+ runtime = Breeze25Runtime(processor, model, torch.device("cpu"), torch.float32)
761
+
762
+ transcript = transcribe_breeze25(
763
+ _tone(seconds=0.5),
764
+ 16_000,
765
+ runtime=runtime,
766
+ language=None,
767
+ )
768
+
769
+ assert transcript == "合成內容完整。"
770
+ generation_kwargs = model.generate_calls[0][1]
771
+ assert "language" not in generation_kwargs
772
+ assert generation_kwargs["task"] == "transcribe"
773
+ assert generation_kwargs["do_sample"] is False
774
+ assert generation_kwargs["num_beams"] == 1
775
+
776
+
777
+ def test_legacy_verification_transcriber_uses_injected_shared_runtime():
778
+ processor = _FakeProcessor()
779
+ model = _FakeWhisperModel()
780
+ runtime = Breeze25Runtime(processor, model, torch.device("cpu"), torch.float32)
781
  loads = []
782
+ lazy_asr = LazyBreeze25ASR(lambda: loads.append("verification") or runtime)
783
 
784
  transcript = transcribe_verification_whisper(
785
  _tone(seconds=0.5),
tests/test_release_pins.py CHANGED
@@ -346,26 +346,32 @@ def test_barbet_runtime_dependency_is_commit_pinned():
346
  ]
347
 
348
 
349
- def test_quality_asr_is_revision_pinned():
350
  quality_path = ROOT / "quality_runtime.py"
351
  quality_source = quality_path.read_text(encoding="utf-8")
352
  constants = _string_constants(quality_path)
353
  app_source = (ROOT / "app.py").read_text(encoding="utf-8")
354
 
355
- assert constants["WHISPER_MODEL_ID"] == "openai/whisper-large-v3-turbo"
356
- assert constants["WHISPER_REVISION"] == "41f01f3fe87f28c78e2fbf8b568835947dd65ed9"
357
- assert constants["VERIFICATION_WHISPER_MODEL_ID"] == "openai/whisper-large-v3"
358
  assert (
359
- constants["VERIFICATION_WHISPER_REVISION"]
360
- == "06f233fe06e710322aca913c1bc4249a0d71fce1"
361
  )
362
- assert constants["WHISPER_ATTENTION_IMPLEMENTATION"] == "eager"
363
- assert "WHISPER_RETURN_ATTENTION_MASK = True" in quality_source
364
- assert "snapshot_download(WHISPER_MODEL_ID, revision=WHISPER_REVISION)" in app_source
365
- assert "snapshot_download(\n VERIFICATION_WHISPER_MODEL_ID," in app_source
366
- assert "revision=VERIFICATION_WHISPER_REVISION" in app_source
367
- assert "load_pinned_verification_whisper_runtime" in quality_source
368
- assert "transcribe_verification_whisper" in quality_source
 
 
 
 
 
 
 
 
369
 
370
 
371
  def test_quality_runtime_dependencies_are_version_pinned():
@@ -737,7 +743,7 @@ def test_network_endpoint_headroom_is_isolated_from_public_unit_contracts():
737
  ) == 1
738
 
739
 
740
- def test_network_local_dual_asr_capability_is_range_bound_for_initial_and_refill():
741
  app_source = (ROOT / "app.py").read_text(encoding="utf-8")
742
  quality_source = (ROOT / "quality_runtime.py").read_text(encoding="utf-8")
743
  app_tree = ast.parse(app_source)
@@ -779,11 +785,12 @@ def test_network_local_dual_asr_capability_is_range_bound_for_initial_and_refill
779
  )
780
  )
781
  assert "proof_rows[index] for index in selected_indices" in helper_source
782
- assert "transcriber=transcribe_verification_whisper" in helper_source
 
783
  assert "semantic_only=True" in helper_source
784
  assert "network_fragment_proofs=independent_proof_rows" in helper_source
785
  assert "intersect_local_semantic_verification(" in helper_source
786
- assert "[BlueMagpie] network local independent " in helper_source
787
  assert "proof_count=" in helper_source
788
  assert "transcript_text" not in helper_source
789
  for caller_source in (initial_source, refill_source):
@@ -798,6 +805,7 @@ def test_network_local_dual_asr_capability_is_range_bound_for_initial_and_refill
798
  assert "network_fragment_proofs=proof_rows" in caller_source
799
  assert "proof_rows," in caller_source[intersection_index:]
800
  assert "candidate_seed=" in caller_source[intersection_index:]
 
801
  assert "semantic_reasons = [\"semantic_gate\"]" in intersection_source
802
  assert "\"network_protected_span_mismatch\"" in intersection_source
803
  assert "chunk_artifacts=primary_verification.chunk_artifacts" in (
@@ -812,7 +820,7 @@ def test_network_local_dual_asr_capability_is_range_bound_for_initial_and_refill
812
  assert '"independent_local_results"' in quality_source
813
 
814
 
815
- def test_network_local_dual_asr_runtime_reuses_exact_proof_rows_and_logs_no_text(
816
  capsys,
817
  ):
818
  source = (ROOT / "app.py").read_text(encoding="utf-8")
@@ -869,7 +877,7 @@ def test_network_local_dual_asr_runtime_reuses_exact_proof_rows_and_logs_no_text
869
  "_verify_trajectory_audio": fake_verify,
870
  "QUALITY_FINAL_ASR_MAX_NEW_TOKENS": 440,
871
  "SEQUENCE_FALLBACK_MAX_LOCAL_BOUNDARY_SPEAKER_DROP": 0.15,
872
- "transcribe_verification_whisper": verification_transcriber,
873
  "intersect_local_semantic_verification": fake_intersect,
874
  "local_candidate_has_coverage_eligibility": (
875
  lambda result, **_kwargs: result is primary_results[1]
@@ -885,6 +893,7 @@ def test_network_local_dual_asr_runtime_reuses_exact_proof_rows_and_logs_no_text
885
  "anchor",
886
  proof_rows,
887
  candidate_seed=123,
 
888
  )
889
 
890
  assert result[0] == "combined"
@@ -1045,10 +1054,7 @@ def test_app_reverifies_the_post_join_speed_adjusted_whole_waveform():
1045
  return_index = synthesize_source.index("return SR, waveform")
1046
  assert assemble_index < verify_index < require_index < return_index
1047
  assert "text," in synthesize_source[verify_index:require_index]
1048
- assert "RELEASE_LARGE_V3_VERIFICATION_PROFILE" in synthesize_source[
1049
- verify_index:require_index
1050
- ]
1051
- assert "RELEASE_TURBO_VERIFICATION_PROFILE" in synthesize_source[
1052
  verify_index:require_index
1053
  ]
1054
  assert " 1.0," in release_verify_source
@@ -1249,7 +1255,7 @@ def test_whole_candidate_qualification_uses_the_exact_return_assembler_after_loc
1249
  "joined_verification = _verify_release_whole_audio("
1250
  )
1251
  assert local_index < local_fail_index < assemble_index < joined_index
1252
- assert "RELEASE_TURBO_VERIFICATION_PROFILE" in qualify_source[joined_index:]
1253
  assert "qualify_trajectory_with_joined_output(" in qualify_source[joined_index:]
1254
  assert "waveform = _assemble_trajectory_audio(" in synthesize_source
1255
  assert "cascade.trajectory" in synthesize_source
@@ -1257,7 +1263,7 @@ def test_whole_candidate_qualification_uses_the_exact_return_assembler_after_loc
1257
  assert "require_verified_final_output(final_verification)" in synthesize_source
1258
 
1259
 
1260
- def test_space_hard_intersects_dual_asr_on_whole_and_proven_network_locals():
1261
  source = (ROOT / "app.py").read_text(encoding="utf-8")
1262
  tree = ast.parse(source)
1263
  functions = {
@@ -1300,18 +1306,34 @@ def test_space_hard_intersects_dual_asr_on_whole_and_proven_network_locals():
1300
  synthesize_source,
1301
  )
1302
  )
1303
- assert "transcriber=transcribe_whisper" in verify_source
1304
  assert "speaker_gate_enabled=not semantic_only" in verify_source
1305
- assert "transcriber=transcribe_verification_whisper" in independent_source
1306
  assert "semantic_only=True" in independent_source
1307
  assert "cache.verify(" in independent_source
1308
- assert "VERIFICATION_ASR_PROFILE" in independent_source
1309
  assert "local_verification = _verify_trajectory_audio(" in refill_source
1310
  assert "_verify_independent_whole_audio" not in refill_source
1311
- assert "transcriber=transcribe_verification_whisper" in network_local_source
1312
  assert "semantic_only=True" in network_local_source
1313
  assert "network_fragment_proofs=independent_proof_rows" in network_local_source
1314
  assert "intersect_local_semantic_verification(" in network_local_source
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1315
 
1316
  local_index = qualify_source.index("local_verification = _verify_trajectory_audio(")
1317
  local_fail_index = qualify_source.index("if not local_verification.passed:")
@@ -1321,8 +1343,8 @@ def test_space_hard_intersects_dual_asr_on_whole_and_proven_network_locals():
1321
  )
1322
  turbo_fail_index = qualify_source.index("if not qualified.passed:")
1323
  independent_index = qualify_source.index("_verify_independent_whole_audio(")
1324
- dual_index = qualify_source.index(
1325
- "dual_qualified = qualify_trajectory_with_joined_output("
1326
  )
1327
  assert (
1328
  local_index
@@ -1331,7 +1353,7 @@ def test_space_hard_intersects_dual_asr_on_whole_and_proven_network_locals():
1331
  < turbo_joined_index
1332
  < turbo_fail_index
1333
  < independent_index
1334
- < dual_index
1335
  )
1336
 
1337
  sequence_assemble = sequence_source.index("_assemble_trajectory_audio(")
@@ -1762,16 +1784,13 @@ def test_assembled_h06_email_separator_uses_400ms_proven_network_pause():
1762
  )
1763
 
1764
 
1765
- def test_space_locks_waveform_only_bounded_whisper_segmentation():
1766
  source = (ROOT / "quality_runtime.py").read_text(encoding="utf-8")
1767
- readme = (ROOT / "README.md").read_text(encoding="utf-8")
1768
 
1769
- assert "WHISPER_MAX_SEGMENT_SECONDS = 28.0" in source
1770
- assert "WHISPER_HARD_MAX_SEGMENT_SECONDS = 30.0" in source
1771
- assert "WHISPER_MIN_SEGMENT_SECONDS = 1.25" in source
1772
- assert "WHISPER_MIN_PAUSE_SECONDS = 0.25" in source
1773
- assert "WHISPER_MAX_VERIFICATION_SEGMENTS = 12" in source
1774
  assert "GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS = 160" in source
1775
- assert "WHISPER_MAX_MICROBATCH_SEGMENTS = 6" in source
1776
- assert "target-dependent fallback" in readme
1777
- assert "每次驗證最多 12 段" in readme
 
346
  ]
347
 
348
 
349
+ def test_breeze25_content_asr_is_revision_and_weight_pinned_once():
350
  quality_path = ROOT / "quality_runtime.py"
351
  quality_source = quality_path.read_text(encoding="utf-8")
352
  constants = _string_constants(quality_path)
353
  app_source = (ROOT / "app.py").read_text(encoding="utf-8")
354
 
355
+ assert constants["BREEZE25_MODEL_ID"] == "MediaTek-Research/Breeze-ASR-25"
 
 
356
  assert (
357
+ constants["BREEZE25_REVISION"]
358
+ == "cffe7ccb404d025296a00758d0a33468bec3a9d0"
359
  )
360
+ assert (
361
+ constants["BREEZE25_WEIGHT_SHA256"]
362
+ == "c5d952b3bc03ea277209aff0ef5b5c4c055d74449ff794c02d8f4e315fdef6b6"
363
+ )
364
+ assert "load_pinned_breeze25_runtime" in quality_source
365
+ assert "transcribe_breeze25" in quality_source
366
+ assert app_source.count("snapshot_download(\n BREEZE25_MODEL_ID,") == 1
367
+ assert "snapshot_download(WHISPER_MODEL_ID" not in app_source
368
+ assert "snapshot_download(\n VERIFICATION_WHISPER_MODEL_ID," not in app_source
369
+ allow_patterns = _literal_constants(ROOT / "app.py")[
370
+ "BREEZE25_SNAPSHOT_ALLOW_PATTERNS"
371
+ ]
372
+ assert "model.safetensors" in allow_patterns
373
+ assert "optimizer.bin" not in allow_patterns
374
+ assert not any(pattern.startswith("whisper-github/") for pattern in allow_patterns)
375
 
376
 
377
  def test_quality_runtime_dependencies_are_version_pinned():
 
743
  ) == 1
744
 
745
 
746
+ def test_network_local_breeze25_projection_is_range_bound_for_initial_and_refill():
747
  app_source = (ROOT / "app.py").read_text(encoding="utf-8")
748
  quality_source = (ROOT / "quality_runtime.py").read_text(encoding="utf-8")
749
  app_tree = ast.parse(app_source)
 
785
  )
786
  )
787
  assert "proof_rows[index] for index in selected_indices" in helper_source
788
+ assert "transcriber=transcribe_breeze25" in helper_source
789
+ assert "transcriber=transcriber" in helper_source
790
  assert "semantic_only=True" in helper_source
791
  assert "network_fragment_proofs=independent_proof_rows" in helper_source
792
  assert "intersect_local_semantic_verification(" in helper_source
793
+ assert "[BlueMagpie] network local Breeze25 projection " in helper_source
794
  assert "proof_count=" in helper_source
795
  assert "transcript_text" not in helper_source
796
  for caller_source in (initial_source, refill_source):
 
805
  assert "network_fragment_proofs=proof_rows" in caller_source
806
  assert "proof_rows," in caller_source[intersection_index:]
807
  assert "candidate_seed=" in caller_source[intersection_index:]
808
+ assert "transcriber=" in caller_source[intersection_index:]
809
  assert "semantic_reasons = [\"semantic_gate\"]" in intersection_source
810
  assert "\"network_protected_span_mismatch\"" in intersection_source
811
  assert "chunk_artifacts=primary_verification.chunk_artifacts" in (
 
820
  assert '"independent_local_results"' in quality_source
821
 
822
 
823
+ def test_network_local_breeze25_projection_reuses_exact_proof_rows_and_logs_no_text(
824
  capsys,
825
  ):
826
  source = (ROOT / "app.py").read_text(encoding="utf-8")
 
877
  "_verify_trajectory_audio": fake_verify,
878
  "QUALITY_FINAL_ASR_MAX_NEW_TOKENS": 440,
879
  "SEQUENCE_FALLBACK_MAX_LOCAL_BOUNDARY_SPEAKER_DROP": 0.15,
880
+ "transcribe_breeze25": verification_transcriber,
881
  "intersect_local_semantic_verification": fake_intersect,
882
  "local_candidate_has_coverage_eligibility": (
883
  lambda result, **_kwargs: result is primary_results[1]
 
893
  "anchor",
894
  proof_rows,
895
  candidate_seed=123,
896
+ transcriber=verification_transcriber,
897
  )
898
 
899
  assert result[0] == "combined"
 
1054
  return_index = synthesize_source.index("return SR, waveform")
1055
  assert assemble_index < verify_index < require_index < return_index
1056
  assert "text," in synthesize_source[verify_index:require_index]
1057
+ assert "BREEZE25_RELEASE_VERIFICATION_PROFILE" in synthesize_source[
 
 
 
1058
  verify_index:require_index
1059
  ]
1060
  assert " 1.0," in release_verify_source
 
1255
  "joined_verification = _verify_release_whole_audio("
1256
  )
1257
  assert local_index < local_fail_index < assemble_index < joined_index
1258
+ assert "BREEZE25_RELEASE_VERIFICATION_PROFILE" in qualify_source[joined_index:]
1259
  assert "qualify_trajectory_with_joined_output(" in qualify_source[joined_index:]
1260
  assert "waveform = _assemble_trajectory_audio(" in synthesize_source
1261
  assert "cascade.trajectory" in synthesize_source
 
1263
  assert "require_verified_final_output(final_verification)" in synthesize_source
1264
 
1265
 
1266
+ def test_space_uses_one_cached_breeze25_transcriber_for_every_semantic_gate():
1267
  source = (ROOT / "app.py").read_text(encoding="utf-8")
1268
  tree = ast.parse(source)
1269
  functions = {
 
1306
  synthesize_source,
1307
  )
1308
  )
1309
+ assert "transcriber=transcribe_breeze25" in verify_source
1310
  assert "speaker_gate_enabled=not semantic_only" in verify_source
1311
+ assert "transcriber=transcribe_breeze25" in independent_source
1312
  assert "semantic_only=True" in independent_source
1313
  assert "cache.verify(" in independent_source
1314
+ assert "BREEZE25_SEMANTIC_VERIFICATION_PROFILE" in independent_source
1315
  assert "local_verification = _verify_trajectory_audio(" in refill_source
1316
  assert "_verify_independent_whole_audio" not in refill_source
1317
+ assert "transcriber=transcribe_breeze25" in network_local_source
1318
  assert "semantic_only=True" in network_local_source
1319
  assert "network_fragment_proofs=independent_proof_rows" in network_local_source
1320
  assert "intersect_local_semantic_verification(" in network_local_source
1321
+ assert "BREEZE25_TRANSCRIPT_PROFILE" in synthesize_source
1322
+ assert synthesize_source.count(
1323
+ "cached_breeze25_transcriber = _cached_request_transcriber("
1324
+ ) == 1
1325
+ assert "transcribe_breeze25," in synthesize_source
1326
+ assert "cached_turbo_transcriber" not in synthesize_source
1327
+ assert "cached_large_v3_transcriber" not in synthesize_source
1328
+ assert "transcribe_whisper" not in synthesize_source
1329
+ assert "transcribe_verification_whisper" not in synthesize_source
1330
+ assert synthesize_source.count(
1331
+ "release_transcriber=cached_breeze25_transcriber"
1332
+ ) == 3
1333
+ assert synthesize_source.count(
1334
+ "independent_transcriber=cached_breeze25_transcriber"
1335
+ ) == 3
1336
+ assert "transcriber=cached_breeze25_transcriber" in synthesize_source
1337
 
1338
  local_index = qualify_source.index("local_verification = _verify_trajectory_audio(")
1339
  local_fail_index = qualify_source.index("if not local_verification.passed:")
 
1343
  )
1344
  turbo_fail_index = qualify_source.index("if not qualified.passed:")
1345
  independent_index = qualify_source.index("_verify_independent_whole_audio(")
1346
+ semantic_projection_index = qualify_source.index(
1347
+ "semantic_qualified = qualify_trajectory_with_joined_output("
1348
  )
1349
  assert (
1350
  local_index
 
1353
  < turbo_joined_index
1354
  < turbo_fail_index
1355
  < independent_index
1356
+ < semantic_projection_index
1357
  )
1358
 
1359
  sequence_assemble = sequence_source.index("_assemble_trajectory_audio(")
 
1784
  )
1785
 
1786
 
1787
+ def test_space_locks_waveform_only_bounded_breeze25_segmentation():
1788
  source = (ROOT / "quality_runtime.py").read_text(encoding="utf-8")
 
1789
 
1790
+ assert "BREEZE25_MAX_SEGMENT_SECONDS = 28.0" in source
1791
+ assert "BREEZE25_HARD_MAX_SEGMENT_SECONDS = 30.0" in source
1792
+ assert "BREEZE25_MIN_SEGMENT_SECONDS = 1.25" in source
1793
+ assert "BREEZE25_MIN_PAUSE_SECONDS = 0.25" in source
1794
+ assert "BREEZE25_MAX_VERIFICATION_SEGMENTS = 12" in source
1795
  assert "GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS = 160" in source
1796
+ assert "BREEZE25_MAX_MICROBATCH_SEGMENTS = 6" in source
 
 
tests/test_silence_segmentation_security.py CHANGED
@@ -7,12 +7,13 @@ import pytest
7
  import torch
8
 
9
  from quality_runtime import (
 
 
10
  GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS,
11
  WHISPER_MAX_VERIFICATION_SEGMENTS,
12
- WhisperRuntime,
13
- _split_short_whisper_audio_at_sustained_pause,
14
- _split_whisper_audio,
15
- transcribe_whisper,
16
  )
17
 
18
 
@@ -58,10 +59,10 @@ def test_pause_threshold_is_inclusive_at_250_ms():
58
  (_constant(2.0), np.zeros(pause_samples, dtype=np.float32), _constant(2.0))
59
  )
60
 
61
- below = _split_short_whisper_audio_at_sustained_pause(
62
  waveform(249), sample_rate=SAMPLE_RATE
63
  )
64
- boundary = _split_short_whisper_audio_at_sustained_pause(
65
  waveform(250), sample_rate=SAMPLE_RATE
66
  )
67
 
@@ -74,8 +75,8 @@ def test_segmentation_is_deterministic_lossless_and_contiguous():
74
  (_constant(2.0), _pause(0.25), _constant(2.0, 0.15))
75
  )
76
 
77
- first = _split_whisper_audio(waveform, sample_rate=SAMPLE_RATE)
78
- second = _split_whisper_audio(waveform.copy(), sample_rate=SAMPLE_RATE)
79
 
80
  assert [segment.size for segment in first] == [segment.size for segment in second]
81
  assert all(np.array_equal(left, right) for left, right in zip(first, second))
@@ -85,7 +86,7 @@ def test_segmentation_is_deterministic_lossless_and_contiguous():
85
  def test_pure_silence_is_not_turned_into_meaningless_asr_batch_segments():
86
  waveform = np.zeros(MAXIMUM_SEGMENT_SAMPLES, dtype=np.float32)
87
 
88
- segments = _split_whisper_audio(waveform, sample_rate=SAMPLE_RATE)
89
 
90
  assert len(segments) == 1
91
 
@@ -95,7 +96,7 @@ def test_peak_transient_does_not_reclassify_continuous_low_energy_speech_as_paus
95
  waveform = (0.01 * np.sin(2.0 * np.pi * 220.0 * time)).astype(np.float32)
96
  waveform[2_990:3_010] = 0.9
97
 
98
- segments = _split_whisper_audio(waveform, sample_rate=SAMPLE_RATE)
99
 
100
  assert len(segments) == 1
101
 
@@ -105,7 +106,7 @@ def test_every_nonempty_verifier_segment_respects_minimum_and_target_cap():
105
  (_constant(27.65), _pause(0.30), _constant(0.20))
106
  )
107
 
108
- segments = _split_whisper_audio(waveform, sample_rate=SAMPLE_RATE)
109
 
110
  assert np.array_equal(np.concatenate(segments), waveform)
111
  assert len(segments) == 1
@@ -125,7 +126,7 @@ def test_adversarial_short_pause_density_fails_closed_above_segment_cap():
125
  )
126
 
127
  with pytest.raises(ValueError, match="pause segment cap"):
128
- _split_whisper_audio(waveform, sample_rate=SAMPLE_RATE)
129
 
130
 
131
  def test_long_nine_pause_atoms_remain_independent_verified_contexts():
@@ -136,7 +137,7 @@ def test_long_nine_pause_atoms_remain_independent_verified_contexts():
136
  cursor += round(duration * SAMPLE_RATE)
137
  waveform[cursor - 150 : cursor + 150] = 0.0
138
 
139
- segments = _split_whisper_audio(waveform, sample_rate=SAMPLE_RATE)
140
 
141
  assert len(segments) == len(expected_seconds)
142
  assert np.array_equal(np.concatenate(segments), waveform)
@@ -154,7 +155,7 @@ def test_h11_shorter_atomic_layout_preserves_every_proven_pause_atom():
154
  cursor += round(duration * SAMPLE_RATE)
155
  waveform[cursor - 150 : cursor + 150] = 0.0
156
 
157
- segments = _split_whisper_audio(waveform, sample_rate=SAMPLE_RATE)
158
 
159
  assert len(segments) == len(atomic_seconds)
160
  assert np.array_equal(np.concatenate(segments), waveform)
@@ -170,7 +171,7 @@ def test_h06_network_seam_from_app_contract_splits_exactly_two_segments():
170
  (_constant(4.51), _pause(network_pause_seconds), _constant(2.55))
171
  )
172
 
173
- segments = _split_whisper_audio(waveform, sample_rate=SAMPLE_RATE)
174
 
175
  assert network_pause_seconds == 0.40
176
  assert len(segments) == 2
@@ -181,7 +182,7 @@ def test_long_continuous_voiced_audio_without_qualified_pause_fails_closed():
181
  waveform = _constant(61.0)
182
 
183
  with pytest.raises(ValueError, match="no qualified 250ms pause"):
184
- _split_whisper_audio(waveform, sample_rate=SAMPLE_RATE)
185
 
186
 
187
  def test_long_coarse_split_allows_exactly_twelve_segments_and_rejects_thirteen():
@@ -197,11 +198,11 @@ def test_long_coarse_split_allows_exactly_twelve_segments_and_rejects_thirteen()
197
  )
198
  )
199
 
200
- twelve = _split_whisper_audio(waveform(12), sample_rate=SAMPLE_RATE)
201
 
202
  assert len(twelve) == MAXIMUM_VERIFIER_SEGMENTS
203
  with pytest.raises(ValueError, match="pause segment cap"):
204
- _split_whisper_audio(waveform(13), sample_rate=SAMPLE_RATE)
205
 
206
 
207
  class _RecordingProcessor:
@@ -244,14 +245,14 @@ def test_verification_asr_uses_bounded_microbatches_not_one_unbounded_batch():
244
  )
245
  )
246
  processor = _RecordingProcessor()
247
- runtime = WhisperRuntime(
248
  processor,
249
  _FakeModel(),
250
  torch.device("cpu"),
251
  torch.float32,
252
  )
253
 
254
- transcribe_whisper(waveform, sample_rate, runtime=runtime)
255
 
256
  batch_sizes = [len(audio) if isinstance(audio, list) else 1 for audio in processor.calls]
257
  assert batch_sizes == [6, 1]
@@ -270,14 +271,14 @@ def test_long_nine_atom_regression_uses_two_bounded_pause_atom_batches():
270
  cursor - round(0.15 * sample_rate) : cursor + round(0.15 * sample_rate)
271
  ] = 0.0
272
  processor = _RecordingProcessor()
273
- runtime = WhisperRuntime(
274
  processor,
275
  _FakeModel(),
276
  torch.device("cpu"),
277
  torch.float32,
278
  )
279
 
280
- transcript = transcribe_whisper(waveform, sample_rate, runtime=runtime)
281
 
282
  assert [
283
  len(audio) if isinstance(audio, list) else 1
@@ -299,21 +300,22 @@ def test_glyph_override_expands_only_the_explicit_bounded_segment_budget():
299
  )
300
 
301
  with pytest.raises(ValueError, match="pause segment cap"):
302
- _split_whisper_audio(waveform, sample_rate=SAMPLE_RATE)
303
 
304
- segments = _split_whisper_audio(
305
  waveform,
306
  sample_rate=SAMPLE_RATE,
307
  max_verification_segments=13,
308
  )
309
 
310
- assert WHISPER_MAX_VERIFICATION_SEGMENTS == 12
 
311
  assert GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS == 160
312
  assert len(segments) == 13
313
  assert sum(segment.size for segment in segments) == waveform.size
314
  for invalid in (True, 0, 161):
315
  with pytest.raises(ValueError, match=r"integer in \[1, 160\]"):
316
- _split_whisper_audio(
317
  waveform,
318
  sample_rate=SAMPLE_RATE,
319
  max_verification_segments=invalid,
 
7
  import torch
8
 
9
  from quality_runtime import (
10
+ BREEZE25_MAX_VERIFICATION_SEGMENTS,
11
+ Breeze25Runtime,
12
  GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS,
13
  WHISPER_MAX_VERIFICATION_SEGMENTS,
14
+ _split_breeze25_audio,
15
+ _split_short_breeze25_audio_at_sustained_pause,
16
+ transcribe_breeze25,
 
17
  )
18
 
19
 
 
59
  (_constant(2.0), np.zeros(pause_samples, dtype=np.float32), _constant(2.0))
60
  )
61
 
62
+ below = _split_short_breeze25_audio_at_sustained_pause(
63
  waveform(249), sample_rate=SAMPLE_RATE
64
  )
65
+ boundary = _split_short_breeze25_audio_at_sustained_pause(
66
  waveform(250), sample_rate=SAMPLE_RATE
67
  )
68
 
 
75
  (_constant(2.0), _pause(0.25), _constant(2.0, 0.15))
76
  )
77
 
78
+ first = _split_breeze25_audio(waveform, sample_rate=SAMPLE_RATE)
79
+ second = _split_breeze25_audio(waveform.copy(), sample_rate=SAMPLE_RATE)
80
 
81
  assert [segment.size for segment in first] == [segment.size for segment in second]
82
  assert all(np.array_equal(left, right) for left, right in zip(first, second))
 
86
  def test_pure_silence_is_not_turned_into_meaningless_asr_batch_segments():
87
  waveform = np.zeros(MAXIMUM_SEGMENT_SAMPLES, dtype=np.float32)
88
 
89
+ segments = _split_breeze25_audio(waveform, sample_rate=SAMPLE_RATE)
90
 
91
  assert len(segments) == 1
92
 
 
96
  waveform = (0.01 * np.sin(2.0 * np.pi * 220.0 * time)).astype(np.float32)
97
  waveform[2_990:3_010] = 0.9
98
 
99
+ segments = _split_breeze25_audio(waveform, sample_rate=SAMPLE_RATE)
100
 
101
  assert len(segments) == 1
102
 
 
106
  (_constant(27.65), _pause(0.30), _constant(0.20))
107
  )
108
 
109
+ segments = _split_breeze25_audio(waveform, sample_rate=SAMPLE_RATE)
110
 
111
  assert np.array_equal(np.concatenate(segments), waveform)
112
  assert len(segments) == 1
 
126
  )
127
 
128
  with pytest.raises(ValueError, match="pause segment cap"):
129
+ _split_breeze25_audio(waveform, sample_rate=SAMPLE_RATE)
130
 
131
 
132
  def test_long_nine_pause_atoms_remain_independent_verified_contexts():
 
137
  cursor += round(duration * SAMPLE_RATE)
138
  waveform[cursor - 150 : cursor + 150] = 0.0
139
 
140
+ segments = _split_breeze25_audio(waveform, sample_rate=SAMPLE_RATE)
141
 
142
  assert len(segments) == len(expected_seconds)
143
  assert np.array_equal(np.concatenate(segments), waveform)
 
155
  cursor += round(duration * SAMPLE_RATE)
156
  waveform[cursor - 150 : cursor + 150] = 0.0
157
 
158
+ segments = _split_breeze25_audio(waveform, sample_rate=SAMPLE_RATE)
159
 
160
  assert len(segments) == len(atomic_seconds)
161
  assert np.array_equal(np.concatenate(segments), waveform)
 
171
  (_constant(4.51), _pause(network_pause_seconds), _constant(2.55))
172
  )
173
 
174
+ segments = _split_breeze25_audio(waveform, sample_rate=SAMPLE_RATE)
175
 
176
  assert network_pause_seconds == 0.40
177
  assert len(segments) == 2
 
182
  waveform = _constant(61.0)
183
 
184
  with pytest.raises(ValueError, match="no qualified 250ms pause"):
185
+ _split_breeze25_audio(waveform, sample_rate=SAMPLE_RATE)
186
 
187
 
188
  def test_long_coarse_split_allows_exactly_twelve_segments_and_rejects_thirteen():
 
198
  )
199
  )
200
 
201
+ twelve = _split_breeze25_audio(waveform(12), sample_rate=SAMPLE_RATE)
202
 
203
  assert len(twelve) == MAXIMUM_VERIFIER_SEGMENTS
204
  with pytest.raises(ValueError, match="pause segment cap"):
205
+ _split_breeze25_audio(waveform(13), sample_rate=SAMPLE_RATE)
206
 
207
 
208
  class _RecordingProcessor:
 
245
  )
246
  )
247
  processor = _RecordingProcessor()
248
+ runtime = Breeze25Runtime(
249
  processor,
250
  _FakeModel(),
251
  torch.device("cpu"),
252
  torch.float32,
253
  )
254
 
255
+ transcribe_breeze25(waveform, sample_rate, runtime=runtime)
256
 
257
  batch_sizes = [len(audio) if isinstance(audio, list) else 1 for audio in processor.calls]
258
  assert batch_sizes == [6, 1]
 
271
  cursor - round(0.15 * sample_rate) : cursor + round(0.15 * sample_rate)
272
  ] = 0.0
273
  processor = _RecordingProcessor()
274
+ runtime = Breeze25Runtime(
275
  processor,
276
  _FakeModel(),
277
  torch.device("cpu"),
278
  torch.float32,
279
  )
280
 
281
+ transcript = transcribe_breeze25(waveform, sample_rate, runtime=runtime)
282
 
283
  assert [
284
  len(audio) if isinstance(audio, list) else 1
 
300
  )
301
 
302
  with pytest.raises(ValueError, match="pause segment cap"):
303
+ _split_breeze25_audio(waveform, sample_rate=SAMPLE_RATE)
304
 
305
+ segments = _split_breeze25_audio(
306
  waveform,
307
  sample_rate=SAMPLE_RATE,
308
  max_verification_segments=13,
309
  )
310
 
311
+ assert BREEZE25_MAX_VERIFICATION_SEGMENTS == 12
312
+ assert WHISPER_MAX_VERIFICATION_SEGMENTS == BREEZE25_MAX_VERIFICATION_SEGMENTS
313
  assert GLYPH_HYBRID_MAX_VERIFICATION_SEGMENTS == 160
314
  assert len(segments) == 13
315
  assert sum(segment.size for segment in segments) == waveform.size
316
  for invalid in (True, 0, 161):
317
  with pytest.raises(ValueError, match=r"integer in \[1, 160\]"):
318
+ _split_breeze25_audio(
319
  waveform,
320
  sample_rate=SAMPLE_RATE,
321
  max_verification_segments=invalid,