1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//! Streaming and long-form window geometry (samples @16 kHz).
//!
//! Free of ONNX sessions so the constants and backend-aware window selection
//! are unit-testable without a loaded model. Called from [`super::engine::Engine`]
//! for streaming slide/stride and file-transcription chunking.
use WindowSpec;
/// Default max streaming encoder window before sliding (samples @16kHz, 2.5s).
/// Configurable at serve time via `--stream-max-window-secs` (see
/// [`stream_max_window_samples`]); the engine stores the resolved value.
/// Re-decoding the whole window each stride gives the offline Conformer left
/// context; this cap bounds the per-stride encoder cost. With the 1.5s retained
/// left context and the 0.8s stride, a 2.5s window keeps the steady-state
/// re-encode overlap near ~3x (vs ~6.25x at a 5s window) — roughly half the
/// streaming encoder work. Short utterances stay on par with batch (ordered-WER
/// `streaming_quality` tests); phrases longer than the window can degrade
/// (stream-vs-file gap measured in docs/benchmarks.md) — raising the window is
/// the mitigation, at a linear encoder-cost increase per stride.
///
/// Hitting the cap **commits a stable prefix** and slides; it does **not** emit
/// a speech-final `final` (that would mean "utterance complete" to assistants).
pub const STREAM_MAX_WINDOW_SAMPLES: usize = 16000 * 5 / 2;
/// Bounds for the configurable streaming window (seconds). The floor keeps the
/// window larger than the retained left context plus one decode stride (a
/// smaller cap would slide almost immediately and re-commit degenerate tails);
/// the ceiling matches the Conformer's useful-context limit used for file
/// chunking (see `CHUNK_THRESHOLD_SAMPLES`).
pub const MIN_STREAM_WINDOW_SECS: f64 = 2.4;
pub const MAX_STREAM_WINDOW_SECS: f64 = 30.0;
/// Resolve a user-requested streaming window length (seconds) to samples
/// @16kHz, clamped to [`MIN_STREAM_WINDOW_SECS`]..=[`MAX_STREAM_WINDOW_SECS`].
/// Non-finite input falls back to the default. Pure so the clamping policy is
/// unit-testable without a loaded model.
pub
/// Left-context audio retained across a streaming finalize/slide (samples @16kHz,
/// ~1.5s) so the next window keeps acoustic context instead of restarting cold.
pub const STREAM_LEFT_CONTEXT_SAMPLES: usize = 16000 * 3 / 2;
/// Decode stride: re-run the encoder only after this much NEW audio has
/// accumulated (samples @16kHz, 0.8s) instead of on every ~100ms chunk.
/// Re-decoding the window is the dominant streaming cost, so the stride keeps
/// the engine real-time; `finish_stream` decodes the sub-stride remainder at EOF.
pub const STREAM_DECODE_STRIDE_SAMPLES: usize = 16000 * 4 / 5;
/// Commit horizon for stable-prefix slides (seconds): words decoded from the
/// last stretch of the window are not committed even when consecutive
/// hypotheses agree on them — near the buffer edge a word may still be
/// mid-formation, decoded from incomplete audio (the edge truncates it, and
/// two consecutive truncated decodes agree with each other). 1.0 s covers the
/// observed edge-truncation window.
pub const STREAM_COMMIT_HORIZON_SECS: f64 = 1.0;
/// Consecutive cap hits with zero hypothesis agreement after which the whole
/// live tail is committed anyway, so a pathological stream cannot grow the
/// retained buffer (and its per-chunk encoder cost) without bound.
pub const STREAM_CAP_STREAK_MAX: usize = 3;
/// File-transcription chunking threshold (samples @16kHz, 30s). Inputs at or
/// below this length take the single-pass path unchanged; longer inputs are
/// split into overlapping windows so the encoder's peak activation memory is
/// bounded by the chunk size, not the file length. The Conformer encoder only
/// carries ~20–30s of useful context, so chunking above this costs no accuracy
/// in the common case. (A higher single-pass ceiling for CTC was tried for
/// stretch RTF on ~40s clips; measured wall time was worse than 24s windows —
/// larger activation tensors thrash CPU caches — so both head families share
/// this 30s ceiling.)
pub const CHUNK_THRESHOLD_SAMPLES: usize = 16000 * 30;
/// Long-form decode window on ort / CoreML-EP / CUDA (samples @16kHz, 24s).
/// Bounds per-chunk encoder activation memory; the ANE path uses a longer
/// window via [`chunk_window_samples`].
pub const CHUNK_WINDOW_SAMPLES_ORT: usize = 16000 * 24;
/// Long-form decode window on the ANE encoder (samples @16kHz, 30s). Full chunks
/// fill ANE bucket 3000 at ~99.97% (vs ~80% fill at 24s), recovering pad-up
/// waste. Peak activation is free on-device; ort keeps the shorter window.
pub const CHUNK_WINDOW_SAMPLES_ANE: usize = 16000 * 30;
/// Overlap retained between consecutive long-form windows (samples @16kHz, 2s),
/// so a word straddling a seam is decoded fully in at least one chunk. The
/// stitch step de-dups words in the overlap region (see [`super::token_format::stitch_chunk_words`]).
pub const CHUNK_OVERLAP_SAMPLES: usize = 16000 * 2;
/// Select the long-form chunk window length for the active encoder backend.
///
/// ANE uses 30s so each full chunk nearly fills bucket 3000; every other
/// backend keeps 24s to bound peak encoder activation memory on CPU/EP paths.
/// Pure so the selection is unit-tested without a loaded model.
pub
/// Long-form window geometry for the active encoder backend: the single-pass
/// ceiling, the backend's window length, and the fixed inter-window overlap.
/// Free-standing (like [`chunk_window_samples`]) so the geometry is unit-tested
/// without a loaded model. `ctc` is accepted for call-site uniformity (CTC and
/// RNN-T share the same 30s ceiling after measurement).
pub